diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index dae954a0970..92a51eb84a4 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -16,7 +16,7 @@ The Magento 2 development team will review all issues and contributions submitte 4. PRs which include bug fixes must be accompanied with a step-by-step description of how to reproduce the bug. 3. PRs which include new logic or new features must be submitted along with: * Unit/integration test coverage -* Proposed [documentation](http://devdocs.magento.com) updates. Documentation contributions can be submitted via the [devdocs GitHub](https://github.com/magento/devdocs). +* Proposed [documentation](https://devdocs.magento.com) updates. Documentation contributions can be submitted via the [devdocs GitHub](https://github.com/magento/devdocs). 4. For larger features or changes, please [open an issue](https://github.com/magento/magento2/issues) to discuss the proposed changes prior to development. This may prevent duplicate or unnecessary effort and allow other contributors to provide input. 5. All automated tests must pass (all builds on [Travis CI](https://travis-ci.org/magento/magento2) must be green). @@ -27,7 +27,7 @@ If you are a new GitHub user, we recommend that you create your own [free github 1. Search current [listed issues](https://github.com/magento/magento2/issues) (open or closed) for similar proposals of intended contribution before starting work on a new contribution. 2. Review the [Contributor License Agreement](https://magento.com/legaldocuments/mca) if this is your first time contributing. 3. Create and test your work. -4. Fork the Magento 2 repository according to the [Fork A Repository instructions](http://devdocs.magento.com/guides/v2.2/contributor-guide/contributing.html#fork) and when you are ready to send us a pull request – follow the [Create A Pull Request instructions](http://devdocs.magento.com/guides/v2.2/contributor-guide/contributing.html#pull_request). +4. Fork the Magento 2 repository according to the [Fork A Repository instructions](https://devdocs.magento.com/guides/v2.2/contributor-guide/contributing.html#fork) and when you are ready to send us a pull request – follow the [Create A Pull Request instructions](https://devdocs.magento.com/guides/v2.2/contributor-guide/contributing.html#pull_request). 5. Once your contribution is received the Magento 2 development team will review the contribution and collaborate with you as needed. ## Code of Conduct diff --git a/CHANGELOG.md b/CHANGELOG.md index b86c7b79a0c..322cd10d0ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ To get detailed information about changes in Magento 2.3.0, see the [Release Not 2.1.0 ============= -To get detailed information about changes in Magento 2.1.0, please visit [Magento Community Edition (CE) Release Notes](http://devdocs.magento.com/guides/v2.1/release-notes/ReleaseNotes2.1.0CE.html "Magento Community Edition (CE) Release Notes") +To get detailed information about changes in Magento 2.1.0, please visit [Magento Community Edition (CE) Release Notes](https://devdocs.magento.com/guides/v2.1/release-notes/ReleaseNotes2.1.0CE.html "Magento Community Edition (CE) Release Notes") 2.0.0 ============= @@ -1025,7 +1025,7 @@ Tests: * Improved backend menu keyboard accessibility * Accessibility improvements: WAI-ARIA in a product item on a category page and related products * Checkout flow code can work with a separate DB storage - * Unit tests moved to module directories + * Unit tests moved to module directories * Addressed naming inconsistencies in REST routes * Added Advanced Developer workflow for frontend developers * Setup diff --git a/app/code/Magento/AdminNotification/view/adminhtml/layout/default.xml b/app/code/Magento/AdminNotification/view/adminhtml/layout/default.xml index aa8ba23d0ee..eed6b53f343 100644 --- a/app/code/Magento/AdminNotification/view/adminhtml/layout/default.xml +++ b/app/code/Magento/AdminNotification/view/adminhtml/layout/default.xml @@ -8,7 +8,7 @@ - + dataHelper->wrapGatewayError($this->getResponse()->getXResponseReasonText()); $order = $this->getOrderFromResponse(); - $this->paymentFailures->handle((int)$order->getQuoteId(), $errorMessage); + $this->paymentFailures->handle((int)$order->getQuoteId(), (string)$errorMessage); throw new \Magento\Framework\Exception\LocalizedException($errorMessage); default: throw new \Magento\Framework\Exception\LocalizedException( diff --git a/app/code/Magento/Authorizenet/Model/Directpost/Request.php b/app/code/Magento/Authorizenet/Model/Directpost/Request.php index fc78d836b60..357385e5c8c 100644 --- a/app/code/Magento/Authorizenet/Model/Directpost/Request.php +++ b/app/code/Magento/Authorizenet/Model/Directpost/Request.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Authorizenet\Model\Directpost; @@ -10,6 +11,7 @@ /** * Authorize.net request model for DirectPost model + * @deprecated 2.3.1 Authorize.net is removing all support for this payment method */ class Request extends AuthorizenetRequest { @@ -20,6 +22,7 @@ class Request extends AuthorizenetRequest /** * Return merchant transaction key. + * * Needed to generate sign. * * @return string @@ -31,6 +34,7 @@ protected function _getTransactionKey() /** * Set merchant transaction key. + * * Needed to generate sign. * * @param string $transKey @@ -162,6 +166,7 @@ public function setDataFromOrder( /** * Set sign hash into the request object. + * * All needed fields should be placed in the object fist. * * @return $this diff --git a/app/code/Magento/Authorizenet/Model/Directpost/Request/Factory.php b/app/code/Magento/Authorizenet/Model/Directpost/Request/Factory.php index 2cdd02d7f84..6036935f57b 100644 --- a/app/code/Magento/Authorizenet/Model/Directpost/Request/Factory.php +++ b/app/code/Magento/Authorizenet/Model/Directpost/Request/Factory.php @@ -3,12 +3,15 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Authorizenet\Model\Directpost\Request; use Magento\Authorizenet\Model\Request\Factory as AuthorizenetRequestFactory; /** * Factory class for @see \Magento\Authorizenet\Model\Directpost\Request + * @deprecated 2.3.1 Authorize.net is removing all support for this payment method */ class Factory extends AuthorizenetRequestFactory { diff --git a/app/code/Magento/Authorizenet/Model/Directpost/Response.php b/app/code/Magento/Authorizenet/Model/Directpost/Response.php index dc62c1e990d..1c713a159c3 100644 --- a/app/code/Magento/Authorizenet/Model/Directpost/Response.php +++ b/app/code/Magento/Authorizenet/Model/Directpost/Response.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Authorizenet\Model\Directpost; use Magento\Authorizenet\Model\Response as AuthorizenetResponse; @@ -10,6 +12,7 @@ /** * Authorize.net response model for DirectPost model + * @deprecated 2.3.1 Authorize.net is removing all support for this payment method */ class Response extends AuthorizenetResponse { diff --git a/app/code/Magento/Authorizenet/Model/Directpost/Response/Factory.php b/app/code/Magento/Authorizenet/Model/Directpost/Response/Factory.php index c2a24ef386a..4fda5ac62b4 100644 --- a/app/code/Magento/Authorizenet/Model/Directpost/Response/Factory.php +++ b/app/code/Magento/Authorizenet/Model/Directpost/Response/Factory.php @@ -3,12 +3,15 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Authorizenet\Model\Directpost\Response; use Magento\Authorizenet\Model\Response\Factory as AuthorizenetResponseFactory; /** * Factory class for @see \Magento\Authorizenet\Model\Directpost\Response + * @deprecated 2.3.1 Authorize.net is removing all support for this payment method */ class Factory extends AuthorizenetResponseFactory { diff --git a/app/code/Magento/Authorizenet/Model/Directpost/Session.php b/app/code/Magento/Authorizenet/Model/Directpost/Session.php index 7ddedac1613..26c5ff0cb7e 100644 --- a/app/code/Magento/Authorizenet/Model/Directpost/Session.php +++ b/app/code/Magento/Authorizenet/Model/Directpost/Session.php @@ -3,12 +3,17 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Authorizenet\Model\Directpost; use Magento\Framework\Session\SessionManager; /** * Authorize.net DirectPost session model + * + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) + * @deprecated 2.3.1 Authorize.net is removing all support for this payment method */ class Session extends SessionManager { diff --git a/app/code/Magento/Authorizenet/Model/Request.php b/app/code/Magento/Authorizenet/Model/Request.php index dc52f84baec..552439fc8bb 100644 --- a/app/code/Magento/Authorizenet/Model/Request.php +++ b/app/code/Magento/Authorizenet/Model/Request.php @@ -3,12 +3,15 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Authorizenet\Model; use Magento\Framework\DataObject; /** * Request object + * @deprecated 2.3.1 Authorize.net is removing all support for this payment method */ class Request extends DataObject { diff --git a/app/code/Magento/Authorizenet/Model/Request/Factory.php b/app/code/Magento/Authorizenet/Model/Request/Factory.php index e60bbd0c88e..a7a636280e2 100644 --- a/app/code/Magento/Authorizenet/Model/Request/Factory.php +++ b/app/code/Magento/Authorizenet/Model/Request/Factory.php @@ -3,10 +3,13 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Authorizenet\Model\Request; /** * Factory class for @see \Magento\Authorizenet\Model\Request + * @deprecated 2.3.1 Authorize.net is removing all support for this payment method */ class Factory { diff --git a/app/code/Magento/Authorizenet/Model/ResourceModel/Debug.php b/app/code/Magento/Authorizenet/Model/ResourceModel/Debug.php index ee6ec6783bb..2c21d0e2e28 100644 --- a/app/code/Magento/Authorizenet/Model/ResourceModel/Debug.php +++ b/app/code/Magento/Authorizenet/Model/ResourceModel/Debug.php @@ -3,10 +3,13 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Authorizenet\Model\ResourceModel; /** * Resource Authorize.net debug model + * @deprecated 2.3.1 Authorize.net is removing all support for this payment method */ class Debug extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb { diff --git a/app/code/Magento/Authorizenet/Model/ResourceModel/Debug/Collection.php b/app/code/Magento/Authorizenet/Model/ResourceModel/Debug/Collection.php index 095ac5a91dd..b84ee1e72a2 100644 --- a/app/code/Magento/Authorizenet/Model/ResourceModel/Debug/Collection.php +++ b/app/code/Magento/Authorizenet/Model/ResourceModel/Debug/Collection.php @@ -3,10 +3,13 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Authorizenet\Model\ResourceModel\Debug; /** * Resource Authorize.net debug collection model + * @deprecated 2.3.1 Authorize.net is removing all support for this payment method */ class Collection extends \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection { diff --git a/app/code/Magento/Authorizenet/Model/Response.php b/app/code/Magento/Authorizenet/Model/Response.php index 52b43c251dc..c552663a153 100644 --- a/app/code/Magento/Authorizenet/Model/Response.php +++ b/app/code/Magento/Authorizenet/Model/Response.php @@ -3,12 +3,15 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Authorizenet\Model; use Magento\Framework\DataObject; /** * Response object + * @deprecated 2.3.1 Authorize.net is removing all support for this payment method */ class Response extends DataObject { diff --git a/app/code/Magento/Authorizenet/Model/Response/Factory.php b/app/code/Magento/Authorizenet/Model/Response/Factory.php index 74bf8953471..45780955660 100644 --- a/app/code/Magento/Authorizenet/Model/Response/Factory.php +++ b/app/code/Magento/Authorizenet/Model/Response/Factory.php @@ -3,10 +3,13 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Authorizenet\Model\Response; /** * Factory class for @see \Magento\Authorizenet\Model\Response + * @deprecated 2.3.1 Authorize.net is removing all support for this payment method */ class Factory { diff --git a/app/code/Magento/Authorizenet/Model/Source/Cctype.php b/app/code/Magento/Authorizenet/Model/Source/Cctype.php index 0a5fe8ab9b3..ffb35847224 100644 --- a/app/code/Magento/Authorizenet/Model/Source/Cctype.php +++ b/app/code/Magento/Authorizenet/Model/Source/Cctype.php @@ -3,16 +3,21 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Authorizenet\Model\Source; use Magento\Payment\Model\Source\Cctype as PaymentCctype; /** * Authorize.net Payment CC Types Source Model + * @deprecated 2.3.1 Authorize.net is removing all support for this payment method */ class Cctype extends PaymentCctype { /** + * Return all supported credit card types + * * @return string[] */ public function getAllowedTypes() diff --git a/app/code/Magento/Authorizenet/Model/Source/PaymentAction.php b/app/code/Magento/Authorizenet/Model/Source/PaymentAction.php index 9943e1001da..c6e57557f65 100644 --- a/app/code/Magento/Authorizenet/Model/Source/PaymentAction.php +++ b/app/code/Magento/Authorizenet/Model/Source/PaymentAction.php @@ -3,18 +3,20 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Authorizenet\Model\Source; use Magento\Framework\Option\ArrayInterface; /** - * * Authorize.net Payment Action Dropdown source + * @deprecated 2.3.1 Authorize.net is removing all support for this payment method */ class PaymentAction implements ArrayInterface { /** - * {@inheritdoc} + * @inheritdoc */ public function toOptionArray() { diff --git a/app/code/Magento/Authorizenet/Model/TransactionService.php b/app/code/Magento/Authorizenet/Model/TransactionService.php index 693a5b890fa..af0b02e94cf 100644 --- a/app/code/Magento/Authorizenet/Model/TransactionService.php +++ b/app/code/Magento/Authorizenet/Model/TransactionService.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Authorizenet\Model; @@ -15,7 +16,7 @@ /** * Class TransactionService - * @package Magento\Authorizenet\Model + * @deprecated 2.3.1 Authorize.net is removing all support for this payment method */ class TransactionService { @@ -74,6 +75,7 @@ public function __construct( /** * Get transaction information + * * @param \Magento\Authorizenet\Model\Authorizenet $context * @param string $transactionId * @return \Magento\Framework\Simplexml\Element @@ -142,6 +144,7 @@ protected function loadTransactionDetails(Authorizenet $context, $transactionId) /** * Create request body to get transaction details + * * @param string $login * @param string $transactionKey * @param string $transactionId diff --git a/app/code/Magento/Authorizenet/Observer/AddFieldsToResponseObserver.php b/app/code/Magento/Authorizenet/Observer/AddFieldsToResponseObserver.php index 03846dddfde..bdd10437927 100644 --- a/app/code/Magento/Authorizenet/Observer/AddFieldsToResponseObserver.php +++ b/app/code/Magento/Authorizenet/Observer/AddFieldsToResponseObserver.php @@ -3,11 +3,19 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Authorizenet\Observer; use Magento\Framework\Event\ObserverInterface; use Magento\Sales\Model\Order; +/** + * Class AddFieldsToResponseObserver + * + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) + * @deprecated 2.3.1 Authorize.net is removing all support for this payment method + */ class AddFieldsToResponseObserver implements ObserverInterface { /** diff --git a/app/code/Magento/Authorizenet/Observer/SaveOrderAfterSubmitObserver.php b/app/code/Magento/Authorizenet/Observer/SaveOrderAfterSubmitObserver.php index 8426d004c20..45f0adfa96f 100644 --- a/app/code/Magento/Authorizenet/Observer/SaveOrderAfterSubmitObserver.php +++ b/app/code/Magento/Authorizenet/Observer/SaveOrderAfterSubmitObserver.php @@ -3,11 +3,17 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Authorizenet\Observer; use Magento\Framework\Event\ObserverInterface; use Magento\Sales\Model\Order; +/** + * Class SaveOrderAfterSubmitObserver + * @deprecated 2.3.1 Authorize.net is removing all support for this payment method + */ class SaveOrderAfterSubmitObserver implements ObserverInterface { /** diff --git a/app/code/Magento/Authorizenet/Observer/UpdateAllEditIncrementsObserver.php b/app/code/Magento/Authorizenet/Observer/UpdateAllEditIncrementsObserver.php index 3e62fe2278d..d6cc51eb63c 100644 --- a/app/code/Magento/Authorizenet/Observer/UpdateAllEditIncrementsObserver.php +++ b/app/code/Magento/Authorizenet/Observer/UpdateAllEditIncrementsObserver.php @@ -3,11 +3,17 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Authorizenet\Observer; use Magento\Framework\Event\ObserverInterface; use Magento\Sales\Model\Order; +/** + * Class UpdateAllEditIncrementsObserver + * @deprecated 2.3.1 Authorize.net is removing all support for this payment method + */ class UpdateAllEditIncrementsObserver implements ObserverInterface { /** diff --git a/app/code/Magento/Authorizenet/etc/adminhtml/system.xml b/app/code/Magento/Authorizenet/etc/adminhtml/system.xml index 1319fa102d0..28bf6945c8b 100644 --- a/app/code/Magento/Authorizenet/etc/adminhtml/system.xml +++ b/app/code/Magento/Authorizenet/etc/adminhtml/system.xml @@ -9,7 +9,7 @@
- + Magento\Config\Model\Config\Source\Yesno diff --git a/app/code/Magento/Authorizenet/etc/config.xml b/app/code/Magento/Authorizenet/etc/config.xml index 3a192646b6f..02dca74023e 100644 --- a/app/code/Magento/Authorizenet/etc/config.xml +++ b/app/code/Magento/Authorizenet/etc/config.xml @@ -19,7 +19,7 @@ processing authorize 1 - Credit Card Direct Post (Authorize.net) + Credit Card Direct Post (Authorize.Net) 0 @@ -33,6 +33,7 @@ https://apitest.authorize.net/xml/v1/request.api https://api2.authorize.net/xml/v1/request.api x_card_type,x_account_number,x_avs_code,x_auth_code,x_response_reason_text,x_cvv2_resp_code + authorizenet diff --git a/app/code/Magento/Authorizenet/etc/payment.xml b/app/code/Magento/Authorizenet/etc/payment.xml new file mode 100644 index 00000000000..1d2cac374d8 --- /dev/null +++ b/app/code/Magento/Authorizenet/etc/payment.xml @@ -0,0 +1,15 @@ + + + + + + + + + diff --git a/app/code/Magento/Authorizenet/i18n/en_US.csv b/app/code/Magento/Authorizenet/i18n/en_US.csv index 6228d5102b1..d724bd960d3 100644 --- a/app/code/Magento/Authorizenet/i18n/en_US.csv +++ b/app/code/Magento/Authorizenet/i18n/en_US.csv @@ -45,7 +45,7 @@ void,void "Fraud Filters","Fraud Filters" "Place Order","Place Order" "Sorry, but something went wrong. Please contact the seller.","Sorry, but something went wrong. Please contact the seller." -"Authorize.net Direct Post","Authorize.net Direct Post" +"Authorize.Net Direct Post (Deprecated)","Authorize.Net Direct Post (Deprecated)" Enabled,Enabled "Payment Action","Payment Action" Title,Title diff --git a/app/code/Magento/AuthorizenetAcceptjs/Block/Form.php b/app/code/Magento/AuthorizenetAcceptjs/Block/Form.php new file mode 100644 index 00000000000..9f10b2df40e --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Block/Form.php @@ -0,0 +1,62 @@ +config = $config; + $this->sessionQuote = $sessionQuote; + } + + /** + * Check if cvv validation is available + * + * @return boolean + */ + public function isCvvEnabled(): bool + { + return $this->config->isCvvEnabled($this->sessionQuote->getStoreId()); + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Block/Info.php b/app/code/Magento/AuthorizenetAcceptjs/Block/Info.php new file mode 100644 index 00000000000..ea476eaa557 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Block/Info.php @@ -0,0 +1,31 @@ +config = $config; + $this->json = $json; + } + + /** + * Retrieves the config that should be used by the block + * + * @return string + */ + public function getPaymentConfig(): string + { + $payment = $this->config->getConfig()['payment']; + $config = $payment[$this->getMethodCode()]; + $config['code'] = $this->getMethodCode(); + + return $this->json->serialize($config); + } + + /** + * Returns the method code for this payment method + * + * @return string + */ + public function getMethodCode(): string + { + return Config::METHOD; + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Command/AcceptPaymentStrategyCommand.php b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Command/AcceptPaymentStrategyCommand.php new file mode 100644 index 00000000000..a72435644d2 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Command/AcceptPaymentStrategyCommand.php @@ -0,0 +1,74 @@ +commandPool = $commandPool; + $this->subjectReader = $subjectReader; + } + + /** + * @inheritdoc + */ + public function execute(array $commandSubject): void + { + if ($this->shouldAcceptInGateway($commandSubject)) { + $this->commandPool->get(self::ACCEPT_FDS) + ->execute($commandSubject); + } + } + + /** + * Determines if the transaction needs to be accepted in the gateway + * + * @param array $commandSubject + * @return bool + * @throws CommandException + */ + private function shouldAcceptInGateway(array $commandSubject): bool + { + $details = $this->commandPool->get('get_transaction_details') + ->execute($commandSubject) + ->get(); + + return in_array($details['transaction']['transactionStatus'], self::NEEDS_APPROVAL_STATUSES); + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Command/CaptureStrategyCommand.php b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Command/CaptureStrategyCommand.php new file mode 100644 index 00000000000..a4d895d4daa --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Command/CaptureStrategyCommand.php @@ -0,0 +1,140 @@ +commandPool = $commandPool; + $this->transactionRepository = $repository; + $this->filterBuilder = $filterBuilder; + $this->searchCriteriaBuilder = $searchCriteriaBuilder; + $this->subjectReader = $subjectReader; + } + + /** + * @inheritdoc + */ + public function execute(array $commandSubject): void + { + /** @var PaymentDataObjectInterface $paymentDO */ + $paymentDO = $this->subjectReader->readPayment($commandSubject); + + $command = $this->getCommand($paymentDO); + $this->commandPool->get($command) + ->execute($commandSubject); + } + + /** + * Get execution command name. + * + * @param PaymentDataObjectInterface $paymentDO + * @return string + */ + private function getCommand(PaymentDataObjectInterface $paymentDO): string + { + $payment = $paymentDO->getPayment(); + ContextHelper::assertOrderPayment($payment); + + // If auth transaction does not exist then execute authorize&capture command + $captureExists = $this->captureTransactionExists($payment); + if (!$payment->getAuthorizationTransaction() && !$captureExists) { + return self::SALE; + } + + return self::CAPTURE; + } + + /** + * Check if capture transaction already exists + * + * @param OrderPaymentInterface $payment + * @return bool + */ + private function captureTransactionExists(OrderPaymentInterface $payment): bool + { + $this->searchCriteriaBuilder->addFilters( + [ + $this->filterBuilder + ->setField('payment_id') + ->setValue($payment->getId()) + ->create(), + ] + ); + + $this->searchCriteriaBuilder->addFilters( + [ + $this->filterBuilder + ->setField('txn_type') + ->setValue(TransactionInterface::TYPE_CAPTURE) + ->create(), + ] + ); + + $searchCriteria = $this->searchCriteriaBuilder->create(); + $count = $this->transactionRepository->getList($searchCriteria) + ->getTotalCount(); + + return $count > 0; + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Command/FetchTransactionInfoCommand.php b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Command/FetchTransactionInfoCommand.php new file mode 100644 index 00000000000..bb9e7c26a45 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Command/FetchTransactionInfoCommand.php @@ -0,0 +1,87 @@ +commandPool = $commandPool; + $this->subjectReader = $subjectReader; + $this->config = $config; + $this->handler = $handler; + } + + /** + * @inheritdoc + */ + public function execute(array $commandSubject): array + { + $paymentDO = $this->subjectReader->readPayment($commandSubject); + $order = $paymentDO->getOrder(); + + $command = $this->commandPool->get('get_transaction_details'); + $result = $command->execute($commandSubject); + $response = $result->get(); + + if ($this->handler) { + $this->handler->handle($commandSubject, $response); + } + + $additionalInformationKeys = $this->config->getTransactionInfoSyncKeys($order->getStoreId()); + $rawDetails = []; + foreach ($additionalInformationKeys as $key) { + if (isset($response['transaction'][$key])) { + $rawDetails[$key] = $response['transaction'][$key]; + } + } + + return $rawDetails; + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Command/GatewayQueryCommand.php b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Command/GatewayQueryCommand.php new file mode 100644 index 00000000000..f8975ef38ee --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Command/GatewayQueryCommand.php @@ -0,0 +1,100 @@ +requestBuilder = $requestBuilder; + $this->transferFactory = $transferFactory; + $this->client = $client; + $this->validator = $validator; + $this->logger = $logger; + } + + /** + * @inheritdoc + * + * @throws Exception + */ + public function execute(array $commandSubject): ResultInterface + { + $transferO = $this->transferFactory->create( + $this->requestBuilder->build($commandSubject) + ); + + try { + $response = $this->client->placeRequest($transferO); + } catch (Exception $e) { + $this->logger->critical($e); + + throw new CommandException(__('There was an error while trying to process the request.')); + } + + $result = $this->validator->validate( + array_merge($commandSubject, ['response' => $response]) + ); + if (!$result->isValid()) { + throw new CommandException(__('There was an error while trying to process the request.')); + } + + return new ArrayResult($response); + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Command/RefundTransactionStrategyCommand.php b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Command/RefundTransactionStrategyCommand.php new file mode 100644 index 00000000000..53a1f13fa87 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Command/RefundTransactionStrategyCommand.php @@ -0,0 +1,77 @@ +commandPool = $commandPool; + $this->subjectReader = $subjectReader; + } + + /** + * @inheritdoc + */ + public function execute(array $commandSubject): void + { + $command = $this->getCommand($commandSubject); + + $this->commandPool->get($command) + ->execute($commandSubject); + } + + /** + * Determines the command that should be used based on the status of the transaction + * + * @param array $commandSubject + * @return string + * @throws CommandException + */ + private function getCommand(array $commandSubject): string + { + $details = $this->commandPool->get('get_transaction_details') + ->execute($commandSubject) + ->get(); + + if ($details['transaction']['transactionStatus'] === 'capturedPendingSettlement') { + return self::VOID; + } elseif ($details['transaction']['transactionStatus'] !== 'settledSuccessfully') { + throw new CommandException(__('This transaction cannot be refunded with its current status.')); + } + + return self::REFUND; + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Config.php b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Config.php new file mode 100644 index 00000000000..2a28945d983 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Config.php @@ -0,0 +1,199 @@ +getValue(Config::KEY_LOGIN_ID, $storeId); + } + + /** + * Gets the current environment + * + * @param int|null $storeId + * @return string + */ + public function getEnvironment($storeId = null): string + { + return $this->getValue(Config::KEY_ENVIRONMENT, $storeId); + } + + /** + * Gets the transaction key + * + * @param int|null $storeId + * @return string + */ + public function getTransactionKey($storeId = null): ?string + { + return $this->getValue(Config::KEY_TRANSACTION_KEY, $storeId); + } + + /** + * Gets the API endpoint URL + * + * @param int|null $storeId + * @return string + */ + public function getApiUrl($storeId = null): string + { + $environment = $this->getValue(Config::KEY_ENVIRONMENT, $storeId); + + return $environment === Environment::ENVIRONMENT_SANDBOX + ? self::ENDPOINT_URL_SANDBOX + : self::ENDPOINT_URL_PRODUCTION; + } + + /** + * Gets the configured signature key + * + * @param int|null $storeId + * @return string + */ + public function getTransactionSignatureKey($storeId = null): ?string + { + return $this->getValue(Config::KEY_SIGNATURE_KEY, $storeId); + } + + /** + * Gets the configured legacy transaction hash + * + * @param int|null $storeId + * @return string + */ + public function getLegacyTransactionHash($storeId = null): ?string + { + return $this->getValue(Config::KEY_LEGACY_TRANSACTION_HASH, $storeId); + } + + /** + * Gets the configured payment action + * + * @param int|null $storeId + * @return string + */ + public function getPaymentAction($storeId = null): ?string + { + return $this->getValue(Config::KEY_PAYMENT_ACTION, $storeId); + } + + /** + * Gets the configured client key + * + * @param int|null $storeId + * @return string + */ + public function getClientKey($storeId = null): ?string + { + return $this->getValue(Config::KEY_CLIENT_KEY, $storeId); + } + + /** + * Should authorize.net email the customer their receipt. + * + * @param int|null $storeId + * @return bool + */ + public function shouldEmailCustomer($storeId = null): bool + { + return (bool)$this->getValue(Config::KEY_SHOULD_EMAIL_CUSTOMER, $storeId); + } + + /** + * Should the cvv field be shown + * + * @param int|null $storeId + * @return bool + */ + public function isCvvEnabled($storeId = null): bool + { + return (bool)$this->getValue(Config::KEY_CVV_ENABLED, $storeId); + } + + /** + * Retrieves the solution id for the given store based on environment + * + * @param int|null $storeId + * @return string + */ + public function getSolutionId($storeId = null): ?string + { + $environment = $this->getValue(Config::KEY_ENVIRONMENT, $storeId); + + return $environment === Environment::ENVIRONMENT_SANDBOX + ? self::SOLUTION_ID_SANDBOX + : self::SOLUTION_ID_PRODUCTION; + } + + /** + * Returns the keys to be pulled from the transaction and displayed + * + * @param int|null $storeId + * @return string[] + */ + public function getAdditionalInfoKeys($storeId = null): array + { + return explode(',', $this->getValue(Config::KEY_ADDITIONAL_INFO_KEYS, $storeId) ?? ''); + } + + /** + * Returns the keys to be pulled from the transaction and displayed when syncing the transaction + * + * @param int|null $storeId + * @return string[] + */ + public function getTransactionInfoSyncKeys($storeId = null): array + { + return explode(',', $this->getValue(Config::KEY_TRANSACTION_SYNC_KEYS, $storeId) ?? ''); + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Http/Client.php b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Http/Client.php new file mode 100644 index 00000000000..1b2efbb8572 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Http/Client.php @@ -0,0 +1,126 @@ +httpClientFactory = $httpClientFactory; + $this->config = $config; + $this->paymentLogger = $paymentLogger; + $this->logger = $logger; + $this->json = $json; + } + + /** + * Places request to gateway. Returns result as ENV array + * + * @param TransferInterface $transferObject + * @return array + * @throws \Magento\Payment\Gateway\Http\ClientException + */ + public function placeRequest(TransferInterface $transferObject) + { + $request = $transferObject->getBody(); + $log = [ + 'request' => $request, + ]; + $client = $this->httpClientFactory->create(); + $url = $this->config->getApiUrl(); + + $type = $request['payload_type']; + unset($request['payload_type']); + $request = [$type => $request]; + + try { + $client->setUri($url); + $client->setConfig(['maxredirects' => 0, 'timeout' => 30]); + $client->setRawData($this->json->serialize($request), 'application/json'); + $client->setMethod(ZendClient::POST); + + $responseBody = $client->request() + ->getBody(); + + // Strip BOM because Authorize.net sends it in the response + if ($responseBody && substr($responseBody, 0, 3) === pack('CCC', 0xef, 0xbb, 0xbf)) { + $responseBody = substr($responseBody, 3); + } + + $log['response'] = $responseBody; + + try { + $data = $this->json->unserialize($responseBody); + } catch (InvalidArgumentException $e) { + throw new \Exception('Invalid JSON was returned by the gateway'); + } + + return $data; + } catch (\Exception $e) { + $this->logger->critical($e); + + throw new ClientException( + __('Something went wrong in the payment gateway.') + ); + } finally { + $this->paymentLogger->debug($log); + } + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Http/Payload/Filter/RemoveFieldsFilter.php b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Http/Payload/Filter/RemoveFieldsFilter.php new file mode 100644 index 00000000000..a23397c0918 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Http/Payload/Filter/RemoveFieldsFilter.php @@ -0,0 +1,42 @@ +fields = $fields; + } + + /** + * @inheritdoc + */ + public function filter(array $data): array + { + foreach ($this->fields as $field) { + unset($data[$field]); + } + + return $data; + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Http/Payload/FilterInterface.php b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Http/Payload/FilterInterface.php new file mode 100644 index 00000000000..35e563eacb0 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Http/Payload/FilterInterface.php @@ -0,0 +1,23 @@ +transferBuilder = $transferBuilder; + $this->payloadFilters = $payloadFilters; + } + + /** + * Builds gateway transfer object + * + * @param array $request + * @return TransferInterface + */ + public function create(array $request) + { + foreach ($this->payloadFilters as $filter) { + $request = $filter->filter($request); + } + + return $this->transferBuilder + ->setBody($request) + ->build(); + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/AcceptFdsDataBuilder.php b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/AcceptFdsDataBuilder.php new file mode 100644 index 00000000000..6883d63397b --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/AcceptFdsDataBuilder.php @@ -0,0 +1,65 @@ +subjectReader = $subjectReader; + } + + /** + * @inheritdoc + */ + public function build(array $buildSubject): array + { + $paymentDO = $this->subjectReader->readPayment($buildSubject); + $payment = $paymentDO->getPayment(); + $data = []; + + if ($payment instanceof Payment) { + $authorizationTransaction = $payment->getAuthorizationTransaction(); + + if (empty($authorizationTransaction)) { + $transactionId = $payment->getLastTransId(); + } else { + $transactionId = $authorizationTransaction->getParentTxnId(); + + if (empty($transactionId)) { + $transactionId = $authorizationTransaction->getTxnId(); + } + } + + $data = [ + 'heldTransactionRequest' => [ + 'action' => 'approve', + 'refTransId' => $transactionId, + ] + ]; + } + + return $data; + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/AddressDataBuilder.php b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/AddressDataBuilder.php new file mode 100644 index 00000000000..e9c42e86444 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/AddressDataBuilder.php @@ -0,0 +1,77 @@ +subjectReader = $subjectReader; + } + + /** + * @inheritdoc + */ + public function build(array $buildSubject): array + { + $paymentDO = $this->subjectReader->readPayment($buildSubject); + $order = $paymentDO->getOrder(); + $billingAddress = $order->getBillingAddress(); + $shippingAddress = $order->getShippingAddress(); + $result = [ + 'transactionRequest' => [] + ]; + + if ($billingAddress) { + $result['transactionRequest']['billTo'] = [ + 'firstName' => $billingAddress->getFirstname(), + 'lastName' => $billingAddress->getLastname(), + 'company' => $billingAddress->getCompany() ?? '', + 'address' => $billingAddress->getStreetLine1(), + 'city' => $billingAddress->getCity(), + 'state' => $billingAddress->getRegionCode(), + 'zip' => $billingAddress->getPostcode(), + 'country' => $billingAddress->getCountryId() + ]; + } + + if ($shippingAddress) { + $result['transactionRequest']['shipTo'] = [ + 'firstName' => $shippingAddress->getFirstname(), + 'lastName' => $shippingAddress->getLastname(), + 'company' => $shippingAddress->getCompany() ?? '', + 'address' => $shippingAddress->getStreetLine1(), + 'city' => $shippingAddress->getCity(), + 'state' => $shippingAddress->getRegionCode(), + 'zip' => $shippingAddress->getPostcode(), + 'country' => $shippingAddress->getCountryId() + ]; + } + + if ($order->getRemoteIp()) { + $result['transactionRequest']['customerIP'] = $order->getRemoteIp(); + } + + return $result; + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/AmountDataBuilder.php b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/AmountDataBuilder.php new file mode 100644 index 00000000000..601c329fe4f --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/AmountDataBuilder.php @@ -0,0 +1,46 @@ +subjectReader = $subjectReader; + } + + /** + * @inheritdoc + */ + public function build(array $buildSubject): array + { + return [ + 'transactionRequest' => [ + 'amount' => $this->formatPrice($this->subjectReader->readAmount($buildSubject)), + ] + ]; + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/AuthenticationDataBuilder.php b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/AuthenticationDataBuilder.php new file mode 100644 index 00000000000..2387ab0ab89 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/AuthenticationDataBuilder.php @@ -0,0 +1,59 @@ +subjectReader = $subjectReader; + $this->config = $config; + } + + /** + * Adds authentication information to the request + * + * @param array $buildSubject + * @return array + */ + public function build(array $buildSubject): array + { + $storeId = $this->subjectReader->readStoreId($buildSubject); + + return [ + 'merchantAuthentication' => [ + 'name' => $this->config->getLoginId($storeId), + 'transactionKey' => $this->config->getTransactionKey($storeId) + ] + ]; + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/AuthorizeDataBuilder.php b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/AuthorizeDataBuilder.php new file mode 100644 index 00000000000..226175f74d5 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/AuthorizeDataBuilder.php @@ -0,0 +1,69 @@ +subjectReader = $subjectReader; + $this->passthroughData = $passthroughData; + } + + /** + * @inheritdoc + */ + public function build(array $buildSubject): array + { + $paymentDO = $this->subjectReader->readPayment($buildSubject); + $payment = $paymentDO->getPayment(); + $data = []; + + if ($payment instanceof Payment) { + $data = [ + 'transactionRequest' => [ + 'transactionType' => self::REQUEST_AUTH_ONLY, + ] + ]; + + $this->passthroughData->setData( + 'transactionType', + $data['transactionRequest']['transactionType'] + ); + } + + return $data; + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/CaptureDataBuilder.php b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/CaptureDataBuilder.php new file mode 100644 index 00000000000..0b17d10fb0d --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/CaptureDataBuilder.php @@ -0,0 +1,73 @@ +subjectReader = $subjectReader; + $this->passthroughData = $passthroughData; + } + + /** + * @inheritdoc + */ + public function build(array $buildSubject): array + { + $paymentDO = $this->subjectReader->readPayment($buildSubject); + $payment = $paymentDO->getPayment(); + $data = []; + + if ($payment instanceof Payment) { + $authTransaction = $payment->getAuthorizationTransaction(); + $refId = $authTransaction->getAdditionalInformation('real_transaction_id'); + + $data = [ + 'transactionRequest' => [ + 'transactionType' => self::REQUEST_TYPE_PRIOR_AUTH_CAPTURE, + 'refTransId' => $refId + ] + ]; + + $this->passthroughData->setData( + 'transactionType', + $data['transactionRequest']['transactionType'] + ); + } + + return $data; + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/CustomSettingsBuilder.php b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/CustomSettingsBuilder.php new file mode 100644 index 00000000000..e5b4472c098 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/CustomSettingsBuilder.php @@ -0,0 +1,62 @@ +subjectReader = $subjectReader; + $this->config = $config; + } + + /** + * @inheritdoc + */ + public function build(array $buildSubject): array + { + $result = []; + + if ($this->config->shouldEmailCustomer($this->subjectReader->readStoreId($buildSubject))) { + $result['transactionRequest'] = [ + 'transactionSettings' => [ + 'setting' => [ + [ + 'settingName' => 'emailCustomer', + 'settingValue' => 'true' + ] + ] + ] + ]; + } + + return $result; + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/CustomerDataBuilder.php b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/CustomerDataBuilder.php new file mode 100644 index 00000000000..7cd0426e93d --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/CustomerDataBuilder.php @@ -0,0 +1,52 @@ +subjectReader = $subjectReader; + } + + /** + * @inheritdoc + */ + public function build(array $buildSubject): array + { + $paymentDO = $this->subjectReader->readPayment($buildSubject); + $order = $paymentDO->getOrder(); + $billingAddress = $order->getBillingAddress(); + $result = [ + 'transactionRequest' => [ + 'customer' => [ + 'id' => $order->getCustomerId(), + 'email' => $billingAddress->getEmail() + ] + ] + ]; + + return $result; + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/OrderDataBuilder.php b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/OrderDataBuilder.php new file mode 100644 index 00000000000..b0e33c9ca96 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/OrderDataBuilder.php @@ -0,0 +1,48 @@ +subjectReader = $subjectReader; + } + + /** + * @inheritdoc + */ + public function build(array $buildSubject): array + { + $paymentDO = $this->subjectReader->readPayment($buildSubject); + $order = $paymentDO->getOrder(); + + return [ + 'transactionRequest' => [ + 'order' => [ + 'invoiceNumber' => $order->getOrderIncrementId() + ] + ] + ]; + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/PassthroughDataBuilder.php b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/PassthroughDataBuilder.php new file mode 100644 index 00000000000..0301d08ad42 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/PassthroughDataBuilder.php @@ -0,0 +1,58 @@ +passthroughData = $passthroughData; + } + + /** + * @inheritdoc + */ + public function build(array $buildSubject): array + { + $fields = []; + + foreach ($this->passthroughData->getData() as $key => $value) { + $fields[] = [ + 'name' => $key, + 'value' => $value + ]; + } + + if (!empty($fields)) { + return [ + 'transactionRequest' => [ + 'userFields' => [ + 'userField' => $fields + ] + ] + ]; + } + + return []; + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/PaymentDataBuilder.php b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/PaymentDataBuilder.php new file mode 100644 index 00000000000..1ad73f62366 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/PaymentDataBuilder.php @@ -0,0 +1,56 @@ +subjectReader = $subjectReader; + } + + /** + * @inheritdoc + */ + public function build(array $buildSubject): array + { + $paymentDO = $this->subjectReader->readPayment($buildSubject); + $payment = $paymentDO->getPayment(); + $data = []; + + if ($payment instanceof Payment) { + $dataDescriptor = $payment->getAdditionalInformation('opaqueDataDescriptor'); + $dataValue = $payment->getAdditionalInformation('opaqueDataValue'); + + $data['transactionRequest']['payment'] = [ + 'opaqueData' => [ + 'dataDescriptor' => $dataDescriptor, + 'dataValue' => $dataValue + ] + ]; + } + + return $data; + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/PoDataBuilder.php b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/PoDataBuilder.php new file mode 100644 index 00000000000..ad8f8c2b05d --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/PoDataBuilder.php @@ -0,0 +1,52 @@ +subjectReader = $subjectReader; + } + + /** + * @inheritdoc + */ + public function build(array $buildSubject): array + { + $paymentDO = $this->subjectReader->readPayment($buildSubject); + $payment = $paymentDO->getPayment(); + $data = []; + + if ($payment instanceof Payment) { + $data = [ + 'transactionRequest' => [ + 'poNumber' => $payment->getPoNumber() + ] + ]; + } + + return $data; + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/RefundPaymentDataBuilder.php b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/RefundPaymentDataBuilder.php new file mode 100644 index 00000000000..96f3e67720f --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/RefundPaymentDataBuilder.php @@ -0,0 +1,58 @@ +subjectReader = $subjectReader; + } + + /** + * @inheritdoc + * @throws \Exception + */ + public function build(array $buildSubject): array + { + $paymentDO = $this->subjectReader->readPayment($buildSubject); + $payment = $paymentDO->getPayment(); + $data = []; + + if ($payment instanceof Payment) { + $data = [ + 'transactionRequest' => [ + 'payment' => [ + 'creditCard' => [ + 'cardNumber' => $payment->getAdditionalInformation('ccLast4'), + 'expirationDate' => 'XXXX' + ] + ] + ] + ]; + } + + return $data; + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/RefundReferenceTransactionDataBuilder.php b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/RefundReferenceTransactionDataBuilder.php new file mode 100644 index 00000000000..b8cb5f858d0 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/RefundReferenceTransactionDataBuilder.php @@ -0,0 +1,53 @@ +subjectReader = $subjectReader; + } + + /** + * @inheritdoc + */ + public function build(array $buildSubject): array + { + $paymentDO = $this->subjectReader->readPayment($buildSubject); + $payment = $paymentDO->getPayment(); + $data = []; + + if ($payment instanceof Payment) { + $transactionId = $payment->getAuthorizationTransaction()->getParentTxnId(); + $data = [ + 'transactionRequest' => [ + 'refTransId' => $transactionId + ] + ]; + } + + return $data; + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/RefundTransactionTypeDataBuilder.php b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/RefundTransactionTypeDataBuilder.php new file mode 100644 index 00000000000..752be05f6b5 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/RefundTransactionTypeDataBuilder.php @@ -0,0 +1,31 @@ + [ + 'transactionType' => self::REQUEST_TYPE_REFUND + ] + ]; + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/RequestTypeBuilder.php b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/RequestTypeBuilder.php new file mode 100644 index 00000000000..16c3f9556de --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/RequestTypeBuilder.php @@ -0,0 +1,45 @@ +type = $type; + } + + /** + * Adds the type of the request to the build subject + * + * @param array $buildSubject + * @return array + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function build(array $buildSubject): array + { + return [ + 'payload_type' => $this->type + ]; + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/SaleDataBuilder.php b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/SaleDataBuilder.php new file mode 100644 index 00000000000..6ec27b10561 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/SaleDataBuilder.php @@ -0,0 +1,69 @@ +subjectReader = $subjectReader; + $this->passthroughData = $passthroughData; + } + + /** + * @inheritdoc + */ + public function build(array $buildSubject): array + { + $paymentDO = $this->subjectReader->readPayment($buildSubject); + $payment = $paymentDO->getPayment(); + $data = []; + + if ($payment instanceof Payment) { + $data = [ + 'transactionRequest' => [ + 'transactionType' => self::REQUEST_AUTH_AND_CAPTURE, + ] + ]; + + $this->passthroughData->setData( + 'transactionType', + $data['transactionRequest']['transactionType'] + ); + } + + return $data; + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/ShippingDataBuilder.php b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/ShippingDataBuilder.php new file mode 100644 index 00000000000..390714579f0 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/ShippingDataBuilder.php @@ -0,0 +1,56 @@ +subjectReader = $subjectReader; + } + + /** + * @inheritdoc + */ + public function build(array $buildSubject): array + { + $paymentDO = $this->subjectReader->readPayment($buildSubject); + $payment = $paymentDO->getPayment(); + $order = $paymentDO->getOrder(); + $data = []; + + if ($payment instanceof Payment && $order instanceof Order) { + $data = [ + 'transactionRequest' => [ + 'shipping' => [ + 'amount' => $order->getBaseShippingAmount() + ] + ] + ]; + } + + return $data; + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/SolutionDataBuilder.php b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/SolutionDataBuilder.php new file mode 100644 index 00000000000..0c89a0116de --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/SolutionDataBuilder.php @@ -0,0 +1,53 @@ +config = $config; + $this->subjectReader = $subjectReader; + } + + /** + * @inheritdoc + */ + public function build(array $buildSubject): array + { + return [ + 'transactionRequest' => [ + 'solution' => [ + 'id' => $this->config->getSolutionId($this->subjectReader->readStoreId($buildSubject)), + ] + ] + ]; + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/StoreConfigBuilder.php b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/StoreConfigBuilder.php new file mode 100644 index 00000000000..f44b1e5de9a --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/StoreConfigBuilder.php @@ -0,0 +1,43 @@ +subjectReader = $subjectReader; + } + + /** + * @inheritdoc + */ + public function build(array $buildSubject): array + { + $paymentDO = $this->subjectReader->readPayment($buildSubject); + $order = $paymentDO->getOrder(); + + return [ + 'store_id' => $order->getStoreId() + ]; + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/TransactionDetailsDataBuilder.php b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/TransactionDetailsDataBuilder.php new file mode 100644 index 00000000000..e3a17e96368 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/TransactionDetailsDataBuilder.php @@ -0,0 +1,69 @@ +subjectReader = $subjectReader; + } + + /** + * @inheritdoc + */ + public function build(array $buildSubject): array + { + $data = []; + + if (!empty($buildSubject['transactionId'])) { + $data = [ + 'transId' => $buildSubject['transactionId'] + ]; + } else { + $paymentDO = $this->subjectReader->readPayment($buildSubject); + $payment = $paymentDO->getPayment(); + + if ($payment instanceof Payment) { + $authorizationTransaction = $payment->getAuthorizationTransaction(); + + if (empty($authorizationTransaction)) { + $transactionId = $payment->getLastTransId(); + } else { + $transactionId = $authorizationTransaction->getParentTxnId(); + + if (empty($transactionId)) { + $transactionId = $authorizationTransaction->getTxnId(); + } + } + + $data = [ + 'transId' => $transactionId + ]; + } + } + + return $data; + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/VoidDataBuilder.php b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/VoidDataBuilder.php new file mode 100644 index 00000000000..ef0cb96774e --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Request/VoidDataBuilder.php @@ -0,0 +1,60 @@ +subjectReader = $subjectReader; + } + + /** + * @inheritdoc + */ + public function build(array $buildSubject): array + { + $paymentDO = $this->subjectReader->readPayment($buildSubject); + $payment = $paymentDO->getPayment(); + $transactionData = []; + + if ($payment instanceof Payment) { + $authorizationTransaction = $payment->getAuthorizationTransaction(); + $refId = $authorizationTransaction->getAdditionalInformation('real_transaction_id'); + if (empty($refId)) { + $refId = $authorizationTransaction->getParentTxnId(); + } + + $transactionData['transactionRequest'] = [ + 'transactionType' => self::REQUEST_TYPE_VOID, + 'refTransId' => $refId + ]; + } + + return $transactionData; + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Response/CloseParentTransactionHandler.php b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Response/CloseParentTransactionHandler.php new file mode 100644 index 00000000000..30b1ce88b08 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Response/CloseParentTransactionHandler.php @@ -0,0 +1,45 @@ +subjectReader = $subjectReader; + } + + /** + * @inheritdoc + */ + public function handle(array $handlingSubject, array $response): void + { + $paymentDO = $this->subjectReader->readPayment($handlingSubject); + $payment = $paymentDO->getPayment(); + + if ($payment instanceof Payment) { + $payment->setShouldCloseParentTransaction(true); + } + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Response/CloseTransactionHandler.php b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Response/CloseTransactionHandler.php new file mode 100644 index 00000000000..f0dff200e80 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Response/CloseTransactionHandler.php @@ -0,0 +1,46 @@ +subjectReader = $subjectReader; + } + + /** + * @inheritdoc + */ + public function handle(array $handlingSubject, array $response): void + { + $paymentDO = $this->subjectReader->readPayment($handlingSubject); + $payment = $paymentDO->getPayment(); + + if ($payment instanceof Payment) { + $payment->setIsTransactionClosed(true); + $payment->setShouldCloseParentTransaction(true); + } + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Response/PaymentResponseHandler.php b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Response/PaymentResponseHandler.php new file mode 100644 index 00000000000..16e8fbabb21 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Response/PaymentResponseHandler.php @@ -0,0 +1,55 @@ +subjectReader = $subjectReader; + } + + /** + * @inheritdoc + */ + public function handle(array $handlingSubject, array $response): void + { + $paymentDO = $this->subjectReader->readPayment($handlingSubject); + $payment = $paymentDO->getPayment(); + $transactionResponse = $response['transactionResponse']; + + if ($payment instanceof Payment) { + $payment->setCcLast4($payment->getAdditionalInformation('ccLast4')); + $payment->setCcAvsStatus($transactionResponse['avsResultCode']); + $payment->setIsTransactionClosed(false); + + if ($transactionResponse['responseCode'] == self::RESPONSE_CODE_HELD) { + $payment->setIsTransactionPending(true) + ->setIsFraudDetected(true); + } + } + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Response/PaymentReviewStatusHandler.php b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Response/PaymentReviewStatusHandler.php new file mode 100644 index 00000000000..9f7c6287366 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Response/PaymentReviewStatusHandler.php @@ -0,0 +1,63 @@ +subjectReader = $subjectReader; + } + + /** + * @inheritdoc + */ + public function handle(array $handlingSubject, array $response): void + { + $paymentDO = $this->subjectReader->readPayment($handlingSubject); + $payment = $paymentDO->getPayment(); + + if ($payment instanceof Payment) { + $paymentDO = $this->subjectReader->readPayment($handlingSubject); + $payment = $paymentDO->getPayment(); + + $status = $response['transaction']['transactionStatus']; + // This data is only used when updating the order payment via Get Payment Update + if (!in_array($status, self::REVIEW_PENDING_STATUSES)) { + $denied = in_array($status, self::REVIEW_DECLINED_STATUSES); + $payment->setData('is_transaction_denied', $denied); + $payment->setData('is_transaction_approved', !$denied); + } + } + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Response/TransactionDetailsResponseHandler.php b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Response/TransactionDetailsResponseHandler.php new file mode 100644 index 00000000000..0dab6414521 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Response/TransactionDetailsResponseHandler.php @@ -0,0 +1,65 @@ +subjectReader = $subjectReader; + $this->config = $config; + } + + /** + * @inheritdoc + */ + public function handle(array $handlingSubject, array $response): void + { + $storeId = $this->subjectReader->readStoreId($handlingSubject); + $paymentDO = $this->subjectReader->readPayment($handlingSubject); + $payment = $paymentDO->getPayment(); + $transactionResponse = $response['transactionResponse']; + + if ($payment instanceof Payment) { + // Add the keys that should show in the transaction details interface + $additionalInformationKeys = $this->config->getAdditionalInfoKeys($storeId); + $rawDetails = []; + foreach ($additionalInformationKeys as $paymentInfoKey) { + if (isset($transactionResponse[$paymentInfoKey])) { + $rawDetails[$paymentInfoKey] = $transactionResponse[$paymentInfoKey]; + $payment->setAdditionalInformation($paymentInfoKey, $transactionResponse[$paymentInfoKey]); + } + } + $payment->setTransactionAdditionalInfo(Payment\Transaction::RAW_DETAILS, $rawDetails); + } + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Response/TransactionIdHandler.php b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Response/TransactionIdHandler.php new file mode 100644 index 00000000000..bf5257f95da --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Response/TransactionIdHandler.php @@ -0,0 +1,54 @@ +subjectReader = $subjectReader; + } + + /** + * @inheritdoc + */ + public function handle(array $handlingSubject, array $response): void + { + $paymentDO = $this->subjectReader->readPayment($handlingSubject); + $payment = $paymentDO->getPayment(); + $transactionResponse = $response['transactionResponse']; + + if ($payment instanceof Payment) { + if (!$payment->getParentTransactionId() + || $transactionResponse['transId'] != $payment->getParentTransactionId() + ) { + $payment->setTransactionId($transactionResponse['transId']); + } + $payment->setTransactionAdditionalInfo( + 'real_transaction_id', + $transactionResponse['transId'] + ); + } + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Response/VoidResponseHandler.php b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Response/VoidResponseHandler.php new file mode 100644 index 00000000000..06b16b37278 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Response/VoidResponseHandler.php @@ -0,0 +1,49 @@ +subjectReader = $subjectReader; + } + + /** + * @inheritdoc + */ + public function handle(array $handlingSubject, array $response): void + { + $paymentDO = $this->subjectReader->readPayment($handlingSubject); + $payment = $paymentDO->getPayment(); + $transactionId = $response['transactionResponse']['transId']; + + if ($payment instanceof Payment) { + $payment->setIsTransactionClosed(true); + $payment->setShouldCloseParentTransaction(true); + $payment->setTransactionAdditionalInfo('real_transaction_id', $transactionId); + } + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Gateway/SubjectReader.php b/app/code/Magento/AuthorizenetAcceptjs/Gateway/SubjectReader.php new file mode 100644 index 00000000000..855d48e2796 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Gateway/SubjectReader.php @@ -0,0 +1,96 @@ +readPayment($subject) + ->getOrder() + ->getStoreId(); + } catch (\InvalidArgumentException $e) { + // No store id is current set + } + } + + return $storeId ? (int)$storeId : null; + } + + /** + * Reads amount from subject + * + * @param array $subject + * @return string + */ + public function readAmount(array $subject): string + { + return (string)Helper\SubjectReader::readAmount($subject); + } + + /** + * Reads response from subject + * + * @param array $subject + * @return array + */ + public function readResponse(array $subject): ?array + { + return Helper\SubjectReader::readResponse($subject); + } + + /** + * Reads login id from subject + * + * @param array $subject + * @return string|null + */ + public function readLoginId(array $subject): ?string + { + return $subject['merchantAuthentication']['name'] ?? null; + } + + /** + * Reads transaction key from subject + * + * @param array $subject + * @return string|null + */ + public function readTransactionKey(array $subject): ?string + { + return $subject['merchantAuthentication']['transactionKey'] ?? null; + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Validator/GeneralResponseValidator.php b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Validator/GeneralResponseValidator.php new file mode 100644 index 00000000000..7ad4647b421 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Validator/GeneralResponseValidator.php @@ -0,0 +1,79 @@ +resultFactory = $resultFactory; + $this->subjectReader = $subjectReader; + } + + /** + * @inheritdoc + */ + public function validate(array $validationSubject): ResultInterface + { + $response = $this->subjectReader->readResponse($validationSubject); + $isValid = (isset($response['messages']['resultCode']) + && $response['messages']['resultCode'] === self::RESULT_CODE_SUCCESS); + $errorCodes = []; + $errorMessages = []; + + if (!$isValid) { + if (isset($response['messages']['message']['code'])) { + $errorCodes[] = $response['messages']['message']['code']; + $errorMessages[] = $response['messages']['message']['text']; + } elseif (isset($response['messages']['message'])) { + foreach ($response['messages']['message'] as $message) { + $errorCodes[] = $message['code']; + $errorMessages[] = $message['text']; + } + } elseif (isset($response['errors']['error'])) { + foreach ($response['errors']['error'] as $message) { + $errorCodes[] = $message['errorCode']; + $errorMessages[] = $message['errorText']; + } + } + } + + return $this->createResult($isValid, $errorMessages, $errorCodes); + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Validator/TransactionHashValidator.php b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Validator/TransactionHashValidator.php new file mode 100644 index 00000000000..0d1c2ad033d --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Validator/TransactionHashValidator.php @@ -0,0 +1,197 @@ +subjectReader = $subjectReader; + $this->config = $config; + } + + /** + * Validates the transaction hash matches the configured hash + * + * @param array $validationSubject + * @return ResultInterface + */ + public function validate(array $validationSubject): ResultInterface + { + $response = $this->subjectReader->readResponse($validationSubject); + $storeId = $this->subjectReader->readStoreId($validationSubject); + + if (!empty($response['transactionResponse']['transHashSha2'])) { + return $this->validateHash( + $validationSubject, + $this->config->getTransactionSignatureKey($storeId), + 'transHashSha2', + 'generateSha512Hash' + ); + } elseif (!empty($response['transactionResponse']['transHash'])) { + return $this->validateHash( + $validationSubject, + $this->config->getLegacyTransactionHash($storeId), + 'transHash', + 'generateMd5Hash' + ); + } + + return $this->createResult( + false, + [ + __('The authenticity of the gateway response could not be verified.') + ], + [self::ERROR_TRANSACTION_HASH] + ); + } + + /** + * Validates the response again the legacy MD5 spec + * + * @param array $validationSubject + * @param string $storedHash + * @param string $hashField + * @param string $generateFunction + * @return ResultInterface + */ + private function validateHash( + array $validationSubject, + string $storedHash, + string $hashField, + string $generateFunction + ): ResultInterface { + $storeId = $this->subjectReader->readStoreId($validationSubject); + $response = $this->subjectReader->readResponse($validationSubject); + $transactionResponse = $response['transactionResponse']; + + /* + * Authorize.net is inconsistent with how they hash and heuristically trying to detect whether or not they used + * the amount to calculate the hash is risky because their responses are incorrect in some cases. + * Refund uses the amount when referencing a transaction but will use 0 when refunding without a reference. + * Non-refund reference transactions such as (void/capture) don't use the amount. Authorize/auth&capture + * transactions will use amount but if there is an AVS error the response will indicate the transaction was a + * reference transaction so this can't be heuristically detected by looking at combinations of refTransID + * and transId (yes they also mixed the letter casing for "id"). Their documentation doesn't talk about this + * and to make this even better, none of their official SDKs support the new hash field to compare + * implementations. Therefore the only way to safely validate this hash without failing for even more + * unexpected corner cases we simply need to validate with and without the amount. + */ + try { + $amount = $this->subjectReader->readAmount($validationSubject); + } catch (\InvalidArgumentException $e) { + $amount = 0; + } + + $hash = $this->{$generateFunction}( + $storedHash, + $this->config->getLoginId($storeId), + sprintf('%.2F', $amount), + $transactionResponse['transId'] ?? '' + ); + $valid = Security::compareStrings($hash, $transactionResponse[$hashField]); + + if (!$valid && $amount > 0) { + $hash = $this->{$generateFunction}( + $storedHash, + $this->config->getLoginId($storeId), + '0.00', + $transactionResponse['transId'] ?? '' + ); + $valid = Security::compareStrings($hash, $transactionResponse[$hashField]); + } + + if ($valid) { + return $this->createResult(true); + } + + return $this->createResult( + false, + [ + __('The authenticity of the gateway response could not be verified.') + ], + [self::ERROR_TRANSACTION_HASH] + ); + } + + /** + * Generates a Md5 hash to compare against AuthNet's. + * + * @param string $merchantMd5 + * @param string $merchantApiLogin + * @param string $amount + * @param string $transactionId + * @return string + * @SuppressWarnings(PHPMD.UnusedPrivateMethod) + */ + private function generateMd5Hash( + $merchantMd5, + $merchantApiLogin, + $amount, + $transactionId + ) { + return strtoupper(md5($merchantMd5 . $merchantApiLogin . $transactionId . $amount)); + } + + /** + * Generates a SHA-512 hash to compare against AuthNet's. + * + * @param string $merchantKey + * @param string $merchantApiLogin + * @param string $amount + * @param string $transactionId + * @return string + * @SuppressWarnings(PHPMD.UnusedPrivateMethod) + */ + private function generateSha512Hash( + $merchantKey, + $merchantApiLogin, + $amount, + $transactionId + ) { + $message = '^' . $merchantApiLogin . '^' . $transactionId . '^' . $amount . '^'; + + return strtoupper(hash_hmac('sha512', $message, pack('H*', $merchantKey))); + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Validator/TransactionResponseValidator.php b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Validator/TransactionResponseValidator.php new file mode 100644 index 00000000000..93b5f2bb62a --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Validator/TransactionResponseValidator.php @@ -0,0 +1,99 @@ +subjectReader = $subjectReader; + } + + /** + * @inheritdoc + */ + public function validate(array $validationSubject): ResultInterface + { + $response = $this->subjectReader->readResponse($validationSubject); + $transactionResponse = $response['transactionResponse']; + + if ($this->isResponseCodeAnError($transactionResponse)) { + $errorCodes = []; + $errorMessages = []; + + if (isset($transactionResponse['messages']['message']['code'])) { + $errorCodes[] = $transactionResponse['messages']['message']['code']; + $errorMessages[] = $transactionResponse['messages']['message']['text']; + } elseif ($transactionResponse['messages']['message']) { + foreach ($transactionResponse['messages']['message'] as $message) { + $errorCodes[] = $message['code']; + $errorMessages[] = $message['description']; + } + } elseif (isset($transactionResponse['errors'])) { + foreach ($transactionResponse['errors'] as $message) { + $errorCodes[] = $message['errorCode']; + $errorMessages[] = $message['errorCode']; + } + } + + return $this->createResult(false, $errorMessages, $errorCodes); + } + + return $this->createResult(true); + } + + /** + * Determines if the response code is actually an error + * + * @param array $transactionResponse + * @return bool + */ + private function isResponseCodeAnError(array $transactionResponse): bool + { + $code = $transactionResponse['messages']['message']['code'] + ?? $transactionResponse['messages']['message'][0]['code'] + ?? $transactionResponse['errors'][0]['errorCode'] + ?? null; + + return in_array($transactionResponse['responseCode'], [self::RESPONSE_CODE_APPROVED, self::RESPONSE_CODE_HELD]) + && $code + && !in_array( + $code, + [ + self::RESPONSE_REASON_CODE_APPROVED, + self::RESPONSE_REASON_CODE_PENDING_REVIEW, + self::RESPONSE_REASON_CODE_PENDING_REVIEW_AUTHORIZED + ] + ); + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/LICENSE.txt b/app/code/Magento/AuthorizenetAcceptjs/LICENSE.txt new file mode 100644 index 00000000000..49525fd99da --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/LICENSE.txt @@ -0,0 +1,48 @@ + +Open Software License ("OSL") v. 3.0 + +This Open Software License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + +Licensed under the Open Software License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + 1. to reproduce the Original Work in copies, either alone or as part of a collective work; + + 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, with the proviso that copies of Original Work or Derivative Works that You distribute or communicate shall be licensed under this Open Software License; + + 4. to perform the Original Work publicly; and + + 5. to display the Original Work publicly. + + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including 'fair use' or 'fair dealing'). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + + 16. Modification of This License. This License is Copyright (C) 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Open Software License" or "OSL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under " or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. \ No newline at end of file diff --git a/app/code/Magento/AuthorizenetAcceptjs/LICENSE_AFL.txt b/app/code/Magento/AuthorizenetAcceptjs/LICENSE_AFL.txt new file mode 100644 index 00000000000..f39d641b18a --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/LICENSE_AFL.txt @@ -0,0 +1,48 @@ + +Academic Free License ("AFL") v. 3.0 + +This Academic Free License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + +Licensed under the Academic Free License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + 1. to reproduce the Original Work in copies, either alone or as part of a collective work; + + 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, under any license of your choice that does not contradict the terms and conditions, including Licensor's reserved rights and remedies, in this Academic Free License; + + 4. to perform the Original Work publicly; and + + 5. to display the Original Work publicly. + + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including "fair use" or "fair dealing"). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + + 16. Modification of This License. This License is Copyright © 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Academic Free License" or "AFL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under " or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. diff --git a/app/code/Magento/AuthorizenetAcceptjs/Model/Adminhtml/Source/Cctype.php b/app/code/Magento/AuthorizenetAcceptjs/Model/Adminhtml/Source/Cctype.php new file mode 100644 index 00000000000..046907ebb88 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Model/Adminhtml/Source/Cctype.php @@ -0,0 +1,25 @@ + self::ENVIRONMENT_SANDBOX, + 'label' => 'Sandbox', + ], + [ + 'value' => self::ENVIRONMENT_PRODUCTION, + 'label' => 'Production' + ] + ]; + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Model/Adminhtml/Source/PaymentAction.php b/app/code/Magento/AuthorizenetAcceptjs/Model/Adminhtml/Source/PaymentAction.php new file mode 100644 index 00000000000..907a1b2a51b --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Model/Adminhtml/Source/PaymentAction.php @@ -0,0 +1,32 @@ + 'authorize', + 'label' => __('Authorize Only'), + ], + [ + 'value' => 'authorize_capture', + 'label' => __('Authorize and Capture') + ] + ]; + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Model/PassthroughDataObject.php b/app/code/Magento/AuthorizenetAcceptjs/Model/PassthroughDataObject.php new file mode 100644 index 00000000000..b49ef7e6225 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Model/PassthroughDataObject.php @@ -0,0 +1,19 @@ +config = $config; + $this->cart = $cart; + } + + /** + * Retrieve assoc array of checkout configuration + * + * @return array + */ + public function getConfig() + { + $storeId = $this->cart->getStoreId(); + + return [ + 'payment' => [ + Config::METHOD => [ + 'clientKey' => $this->config->getClientKey($storeId), + 'apiLoginID' => $this->config->getLoginId($storeId), + 'environment' => $this->config->getEnvironment($storeId), + 'useCvv' => $this->config->isCvvEnabled($storeId), + ] + ] + ]; + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Observer/DataAssignObserver.php b/app/code/Magento/AuthorizenetAcceptjs/Observer/DataAssignObserver.php new file mode 100644 index 00000000000..c7490ad0c80 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Observer/DataAssignObserver.php @@ -0,0 +1,52 @@ +readDataArgument($observer); + + $additionalData = $data->getData(PaymentInterface::KEY_ADDITIONAL_DATA); + if (!is_array($additionalData)) { + return; + } + + $paymentInfo = $this->readPaymentModelArgument($observer); + + foreach ($this->additionalInformationList as $additionalInformationKey) { + if (isset($additionalData[$additionalInformationKey])) { + $paymentInfo->setAdditionalInformation( + $additionalInformationKey, + $additionalData[$additionalInformationKey] + ); + } + } + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/README.md b/app/code/Magento/AuthorizenetAcceptjs/README.md new file mode 100644 index 00000000000..b066f8a2d75 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/README.md @@ -0,0 +1 @@ +The Magento_AuthorizenetAcceptjs module implements the integration with the Authorize.Net payment gateway and makes the latter available as a payment method in Magento. diff --git a/app/code/Magento/AuthorizenetAcceptjs/Setup/Patch/Data/CopyCurrentConfig.php b/app/code/Magento/AuthorizenetAcceptjs/Setup/Patch/Data/CopyCurrentConfig.php new file mode 100644 index 00000000000..0675bd94b62 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Setup/Patch/Data/CopyCurrentConfig.php @@ -0,0 +1,230 @@ +scopeConfig = $scopeConfig; + $this->resourceConfig = $resourceConfig; + $this->encryptor = $encryptor; + $this->moduleDataSetup = $moduleDataSetup; + $this->storeManager = $storeManager; + } + + /** + * @inheritdoc + */ + public function apply(): void + { + $this->moduleDataSetup->startSetup(); + $this->migrateDefaultValues(); + $this->migrateWebsiteValues(); + $this->moduleDataSetup->endSetup(); + } + + /** + * Migrate configuration values from DirectPost to Accept.js on default scope + * + * @return void + */ + private function migrateDefaultValues(): void + { + foreach ($this->configFieldsToMigrate as $field) { + $configValue = $this->getOldConfigValue($field); + + if (!empty($configValue)) { + $this->saveNewConfigValue($field, $configValue); + } + } + + foreach ($this->encryptedConfigFieldsToMigrate as $field) { + $configValue = $this->getOldConfigValue($field); + + if (!empty($configValue)) { + $this->saveNewConfigValue( + $field, + $configValue, + ScopeConfigInterface::SCOPE_TYPE_DEFAULT, + 0, + true + ); + } + } + } + + /** + * Migrate configuration values from DirectPost to Accept.js on all website scopes + * + * @return void + */ + private function migrateWebsiteValues(): void + { + foreach ($this->storeManager->getWebsites() as $website) { + $websiteID = (int) $website->getId(); + + foreach ($this->configFieldsToMigrate as $field) { + $configValue = $this->getOldConfigValue($field, ScopeInterface::SCOPE_WEBSITES, $websiteID); + + if (!empty($configValue)) { + $this->saveNewConfigValue($field, $configValue, ScopeInterface::SCOPE_WEBSITES, $websiteID); + } + } + + foreach ($this->encryptedConfigFieldsToMigrate as $field) { + $configValue = $this->getOldConfigValue($field, ScopeInterface::SCOPE_WEBSITES, $websiteID); + + if (!empty($configValue)) { + $this->saveNewConfigValue($field, $configValue, ScopeInterface::SCOPE_WEBSITES, $websiteID, true); + } + } + } + } + + /** + * Get old configuration value from the DirectPost module's configuration on the store scope + * + * @param string $field + * @param string $scope + * @param int $scopeID + * @return mixed + */ + private function getOldConfigValue( + string $field, + string $scope = ScopeConfigInterface::SCOPE_TYPE_DEFAULT, + int $scopeID = null + ) { + return $this->scopeConfig->getValue( + sprintf(self::PAYMENT_PATH_FORMAT, self::DIRECTPOST_PATH, $field), + $scope, + $scopeID + ); + } + + /** + * Save configuration value for AcceptJS + * + * @param string $field + * @param mixed $value + * @param string $scope + * @param int $scopeID + * @param bool $isEncrypted + * @return void + */ + private function saveNewConfigValue( + string $field, + $value, + string $scope = ScopeConfigInterface::SCOPE_TYPE_DEFAULT, + int $scopeID = 0, + bool $isEncrypted = false + ): void { + $value = $isEncrypted ? $this->encryptor->encrypt($value) : $value; + + $this->resourceConfig->saveConfig( + sprintf(self::PAYMENT_PATH_FORMAT, self::ACCEPTJS_PATH, $field), + $value, + $scope, + $scopeID + ); + } + + /** + * @inheritdoc + */ + public static function getDependencies() + { + return []; + } + + /** + * @inheritdoc + */ + public function getAliases() + { + return []; + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/ActionGroup/ConfigureAuthorizenetAcceptjsActionGroup.xml b/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/ActionGroup/ConfigureAuthorizenetAcceptjsActionGroup.xml new file mode 100644 index 00000000000..e9a194435e3 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/ActionGroup/ConfigureAuthorizenetAcceptjsActionGroup.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/ActionGroup/FillPaymentInformationActionGroup.xml b/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/ActionGroup/FillPaymentInformationActionGroup.xml new file mode 100644 index 00000000000..d06bf996a1f --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/ActionGroup/FillPaymentInformationActionGroup.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/ActionGroup/ViewAndValidateOrderActionGroup.xml b/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/ActionGroup/ViewAndValidateOrderActionGroup.xml new file mode 100644 index 00000000000..ba0e49d1876 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/ActionGroup/ViewAndValidateOrderActionGroup.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/Data/AuthorizenetAcceptjsOrderValidationData.xml b/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/Data/AuthorizenetAcceptjsOrderValidationData.xml new file mode 100644 index 00000000000..59d4be98d45 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/Data/AuthorizenetAcceptjsOrderValidationData.xml @@ -0,0 +1,18 @@ + + + + + + $24.68 + $128.00 + Processing + Capture + No + + diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/LICENSE.txt b/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/LICENSE.txt new file mode 100644 index 00000000000..49525fd99da --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/LICENSE.txt @@ -0,0 +1,48 @@ + +Open Software License ("OSL") v. 3.0 + +This Open Software License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + +Licensed under the Open Software License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + 1. to reproduce the Original Work in copies, either alone or as part of a collective work; + + 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, with the proviso that copies of Original Work or Derivative Works that You distribute or communicate shall be licensed under this Open Software License; + + 4. to perform the Original Work publicly; and + + 5. to display the Original Work publicly. + + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including 'fair use' or 'fair dealing'). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + + 16. Modification of This License. This License is Copyright (C) 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Open Software License" or "OSL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under " or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. \ No newline at end of file diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/LICENSE_AFL.txt b/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/LICENSE_AFL.txt new file mode 100644 index 00000000000..f39d641b18a --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/LICENSE_AFL.txt @@ -0,0 +1,48 @@ + +Academic Free License ("AFL") v. 3.0 + +This Academic Free License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + +Licensed under the Academic Free License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + 1. to reproduce the Original Work in copies, either alone or as part of a collective work; + + 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, under any license of your choice that does not contradict the terms and conditions, including Licensor's reserved rights and remedies, in this Academic Free License; + + 4. to perform the Original Work publicly; and + + 5. to display the Original Work publicly. + + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including "fair use" or "fair dealing"). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + + 16. Modification of This License. This License is Copyright © 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Academic Free License" or "AFL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under " or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/README.md b/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/README.md new file mode 100644 index 00000000000..aba235e2cfa --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/README.md @@ -0,0 +1,3 @@ +# AuthorizenetAcceptjs Functional Tests + +The Functional Test Module for **Magento AuthorizenetAcceptjs** module. diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/Section/AdminMenuSection.xml b/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/Section/AdminMenuSection.xml new file mode 100644 index 00000000000..defb91339ea --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/Section/AdminMenuSection.xml @@ -0,0 +1,25 @@ + + + + +
+ + + + + + + + + + + + +
+
diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/Section/AuthorizenetAcceptjsConfigurationSection.xml b/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/Section/AuthorizenetAcceptjsConfigurationSection.xml new file mode 100644 index 00000000000..31be865ea26 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/Section/AuthorizenetAcceptjsConfigurationSection.xml @@ -0,0 +1,24 @@ + + + + +
+ + + + + + + + + + + +
+
diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/Section/AuthorizenetCheckoutSection.xml b/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/Section/AuthorizenetCheckoutSection.xml new file mode 100644 index 00000000000..5d97842de37 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/Section/AuthorizenetCheckoutSection.xml @@ -0,0 +1,19 @@ + + + + +
+ + + + + + +
+
diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/Section/ConfigurationMainActionsSection.xml b/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/Section/ConfigurationMainActionsSection.xml new file mode 100644 index 00000000000..344330c4bc0 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/Section/ConfigurationMainActionsSection.xml @@ -0,0 +1,14 @@ + + + + +
+ +
+
diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/Section/GuestAuthorizenetCheckoutSection.xml b/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/Section/GuestAuthorizenetCheckoutSection.xml new file mode 100644 index 00000000000..b5f2ecf6411 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/Section/GuestAuthorizenetCheckoutSection.xml @@ -0,0 +1,22 @@ + + + + +
+ + + + + + + + + +
+
diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/Section/OrdersGridSection.xml b/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/Section/OrdersGridSection.xml new file mode 100644 index 00000000000..7ae3dd0ffee --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/Section/OrdersGridSection.xml @@ -0,0 +1,14 @@ + + + + +
+ +
+
diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/Section/StoresConfigurationListSection.xml b/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/Section/StoresConfigurationListSection.xml new file mode 100644 index 00000000000..f9f1bef38d1 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/Section/StoresConfigurationListSection.xml @@ -0,0 +1,15 @@ + + + + +
+ + +
+
diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/Section/StoresSubmenuSection.xml b/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/Section/StoresSubmenuSection.xml new file mode 100644 index 00000000000..e54f9808fd4 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/Section/StoresSubmenuSection.xml @@ -0,0 +1,14 @@ + + + + +
+ +
+
diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/Section/ViewOrderSection.xml b/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/Section/ViewOrderSection.xml new file mode 100644 index 00000000000..608067d7d31 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/Section/ViewOrderSection.xml @@ -0,0 +1,25 @@ + + + + +
+ + + + + + + + + + + + +
+
diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/Test/FullCaptureAuthorizenetAcceptjsTest.xml b/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/Test/FullCaptureAuthorizenetAcceptjsTest.xml new file mode 100644 index 00000000000..42a78291436 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/Test/FullCaptureAuthorizenetAcceptjsTest.xml @@ -0,0 +1,74 @@ + + + + + + + + + <description value="Capture an order placed using Authorize.net Accept.js"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-12255"/> + <skip> + <issueId value="DEVOPS-4604"/> + </skip> + <group value="AuthorizenetAcceptjs"/> + <group value="ThirdPartyPayments"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + + <createData stepKey="createCustomer" entity="Simple_US_Customer"/> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="_defaultProduct" stepKey="createProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + + <!--Configure Auth.net--> + <actionGroup ref="ConfigureAuthorizenetAcceptjs" stepKey="configureAuthorizenetAcceptjs"> + <argument name="paymentAction" value="Authorize Only"/> + </actionGroup> + + </before> + <after> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> + <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> + <actionGroup ref="DisableAuthorizenetAcceptjs" stepKey="DisableAuthorizenetAcceptjs"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Storefront Login--> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginStorefront"> + <argument name="Customer" value="$$createCustomer$$"/> + </actionGroup> + + <!--Add product to cart--> + <amOnPage url="$$createProduct.name$$.html" stepKey="goToProductPage"/> + <waitForPageLoad stepKey="waitForProductPage"/> + <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="addProductToCart"/> + <waitForPageLoad stepKey="waitForCartToFill"/> + + <!--Checkout steps--> + <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="GoToCheckoutFromMinicartActionGroup"/> + <waitForPageLoad stepKey="waitForCheckoutLoad"/> + <actionGroup ref="CheckoutSelectFlatRateShippingMethodActionGroup" stepKey="selectFlatRateShipping"/> + <click selector="{{CheckoutShippingMethodsSection.next}}" stepKey="submitShippingSelection"/> + <waitForPageLoad stepKey="waitForShippingToFinish"/> + <actionGroup ref="FillPaymentInformation" stepKey="fillPaymentInfo"/> + + <!--View and validate order--> + <actionGroup ref="ViewAndValidateOrderActionGroup" stepKey="viewAndValidateOrder"> + <argument name="amount" value="{{AuthorizenetAcceptjsOrderValidationData.twoSimpleProductsOrderAmount}}"/> + <argument name="status" value="{{AuthorizenetAcceptjsOrderValidationData.processingStatusProcessing}}"/> + <argument name="captureStatus" value="{{AuthorizenetAcceptjsOrderValidationData.captureStatusCapture}}"/> + <argument name="closedStatus" value="{{AuthorizenetAcceptjsOrderValidationData.closedStatusNo}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/Test/GuestCheckoutVirtualProductAuthorizenetAcceptjsTest.xml b/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/Test/GuestCheckoutVirtualProductAuthorizenetAcceptjsTest.xml new file mode 100644 index 00000000000..95c24369052 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Mftf/Test/GuestCheckoutVirtualProductAuthorizenetAcceptjsTest.xml @@ -0,0 +1,82 @@ +<?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="GuestCheckoutVirtualProductAuthorizenetAcceptjsTest"> + <annotations> + <stories value="Authorize.net Accept.js"/> + <title value="Guest Checkout of Virtual Product using Authorize.net Accept.js"/> + <description value="Checkout a virtual product with a guest using Authorize.net Accept.js"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-12712"/> + <skip> + <issueId value="DEVOPS-4604"/> + </skip> + <group value="AuthorizenetAcceptjs"/> + <group value="ThirdPartyPayments"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + + <!-- Create virtual product --> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="navigateToProductIndex"/> + <waitForPageLoad stepKey="waitForProductIndexPage"/> + <actionGroup ref="goToCreateProductPage" stepKey="goToCreateProduct"> + <argument name="product" value="defaultVirtualProduct"/> + </actionGroup> + <actionGroup ref="fillMainProductFormNoWeight" stepKey="fillProductForm"> + <argument name="product" value="defaultVirtualProduct"/> + </actionGroup> + <actionGroup ref="saveProductForm" stepKey="saveProductForm"/> + + <!--Configure Auth.net--> + <actionGroup ref="ConfigureAuthorizenetAcceptjs" stepKey="configureAuthorizenetAcceptjs"> + <argument name="paymentAction" value="Authorize and Capture"/> + </actionGroup> + + </before> + <after> + <actionGroup ref="DisableAuthorizenetAcceptjs" stepKey="DisableAuthorizenetAcceptjs"/> + <!-- Delete virtual product --> + <actionGroup ref="deleteProductUsingProductGrid" stepKey="deleteProduct"> + <argument name="product" value="defaultVirtualProduct"/> + </actionGroup> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Add product to cart twice--> + <amOnPage url="{{defaultVirtualProduct.sku}}.html" stepKey="goToProductPage"/> + <waitForPageLoad stepKey="waitForProductPage"/> + <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="addProductToCart"/> + <waitForPageLoad stepKey="waitForCartToFill"/> + <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="addProductToCartAgain"/> + <waitForPageLoad stepKey="waitForCartToFillAgain"/> + + <!--Checkout steps--> + <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="GoToCheckoutFromMinicartActionGroup"/> + <waitForPageLoad stepKey="waitForCheckoutLoad"/> + + <fillField selector="{{CheckoutShippingSection.email}}" userInput="{{Simple_US_Customer.email}}" stepKey="enterEmail"/> + <click stepKey="clickOnAuthorizenetToggle" selector="{{AuthorizenetCheckoutSection.selectAuthorizenet}}"/> + <waitForPageLoad stepKey="waitForBillingInfoLoad"/> + <actionGroup ref="GuestCheckoutAuthorizenetFillBillingAddress" stepKey="fillAddressForm"> + <argument name="customer" value="Simple_US_Customer"/> + <argument name="customerAddress" value="CustomerAddressSimple"/> + </actionGroup> + <actionGroup ref="FillPaymentInformation" stepKey="fillPaymentInfo"/> + + <!--View and validate order--> + <actionGroup ref="ViewAndValidateOrderActionGroupNoSubmit" stepKey="viewAndValidateOrder"> + <argument name="amount" value="{{AuthorizenetAcceptjsOrderValidationData.virtualProductOrderAmount}}"/> + <argument name="status" value="{{AuthorizenetAcceptjsOrderValidationData.processingStatusProcessing}}"/> + <argument name="captureStatus" value="{{AuthorizenetAcceptjsOrderValidationData.captureStatusCapture}}"/> + <argument name="closedStatus" value="{{AuthorizenetAcceptjsOrderValidationData.closedStatusNo}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Block/FormTest.php b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Block/FormTest.php new file mode 100644 index 00000000000..020b651aaaf --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Block/FormTest.php @@ -0,0 +1,59 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Test\Unit\Block; + +use Magento\AuthorizenetAcceptjs\Block\Form; +use PHPUnit\Framework\MockObject\Builder\InvocationMocker; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; +use Magento\AuthorizenetAcceptjs\Gateway\Config; +use Magento\Backend\Model\Session\Quote; +use Magento\Framework\View\Element\Template\Context; +use Magento\Payment\Model\Config as PaymentConfig; + +class FormTest extends TestCase +{ + /** + * @var Form + */ + private $block; + + /** + * @var Config|MockObject|InvocationMocker + */ + private $configMock; + + protected function setUp() + { + $contextMock = $this->createMock(Context::class); + $this->configMock = $this->createMock(Config::class); + $quoteMock = $this->getMockBuilder(Quote::class) + ->disableOriginalConstructor() + ->setMethods(['getStoreId']) + ->getMock(); + $quoteMock->method('getStoreId') + ->willReturn('123'); + $paymentConfig = $this->createMock(PaymentConfig::class); + + $this->block = new Form( + $contextMock, + $paymentConfig, + $this->configMock, + $quoteMock + ); + } + + public function testIsCvvEnabled() + { + $this->configMock->method('isCvvEnabled') + ->with('123') + ->willReturn(true); + $this->assertTrue($this->block->isCvvEnabled()); + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Block/InfoTest.php b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Block/InfoTest.php new file mode 100644 index 00000000000..70dfb140e15 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Block/InfoTest.php @@ -0,0 +1,80 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Test\Unit\Block; + +use Magento\AuthorizenetAcceptjs\Block\Info; +use Magento\AuthorizenetAcceptjs\Gateway\Config; +use Magento\Framework\Phrase; +use Magento\Framework\Phrase\RendererInterface; +use Magento\Framework\View\Element\Template\Context; +use Magento\Payment\Gateway\ConfigInterface; +use Magento\Payment\Model\InfoInterface; +use PHPUnit\Framework\MockObject\Builder\InvocationMocker; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class InfoTest extends TestCase +{ + public function testLabelsAreTranslated() + { + /** @var Context|MockObject|InvocationMocker $contextMock */ + $contextMock = $this->createMock(Context::class); + /** @var Config|MockObject|InvocationMocker $configMock */ + $configMock = $this->createMock(ConfigInterface::class); + $block = new Info($contextMock, $configMock); + /** @var InfoInterface|MockObject|InvocationMocker $payment */ + $payment = $this->createMock(InfoInterface::class); + /** @var RendererInterface|MockObject|InvocationMocker $translationRenderer */ + $translationRenderer = $this->createMock(RendererInterface::class); + + // only foo should be used + $configMock->method('getValue') + ->willReturnMap([ + ['paymentInfoKeys', null, 'foo'], + ['privateInfoKeys', null, ''] + ]); + + // Give more info to ensure only foo is translated + $payment->method('getAdditionalInformation') + ->willReturnCallback(function ($name = null) { + $info = [ + 'foo' => 'bar', + 'baz' => 'bash' + ]; + + if (empty($name)) { + return $info; + } + + return $info[$name]; + }); + + // Foo should be translated to Super Cool String + $translationRenderer->method('render') + ->with(['foo'], []) + ->willReturn('Super Cool String'); + + $previousRenderer = Phrase::getRenderer(); + Phrase::setRenderer($translationRenderer); + + try { + $block->setData('info', $payment); + + $info = $block->getSpecificInformation(); + } finally { + // No matter what, restore the renderer + Phrase::setRenderer($previousRenderer); + } + + // Assert the label was correctly translated + $this->assertSame($info['Super Cool String'], 'bar'); + $this->assertArrayNotHasKey('foo', $info); + $this->assertArrayNotHasKey('baz', $info); + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Block/PaymentTest.php b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Block/PaymentTest.php new file mode 100644 index 00000000000..11ae27f9d2e --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Block/PaymentTest.php @@ -0,0 +1,53 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Test\Unit\Block; + +use Magento\AuthorizenetAcceptjs\Block\Payment; +use Magento\AuthorizenetAcceptjs\Model\Ui\ConfigProvider; +use Magento\Framework\Serialize\Serializer\Json; +use Magento\Framework\View\Element\Template\Context; +use PHPUnit\Framework\MockObject\Builder\InvocationMocker; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class PaymentTest extends TestCase +{ + /** + * @var ConfigProvider|MockObject|InvocationMocker + */ + private $configMock; + + /** + * @var Payment + */ + private $block; + + protected function setUp() + { + $contextMock = $this->createMock(Context::class); + $this->configMock = $this->createMock(ConfigProvider::class); + $this->block = new Payment($contextMock, $this->configMock, new Json()); + } + + public function testConfigIsCreated() + { + $this->configMock->method('getConfig') + ->willReturn([ + 'payment' => [ + 'authorizenet_acceptjs' => [ + 'foo' => 'bar' + ] + ] + ]); + + $result = $this->block->getPaymentConfig(); + $expected = '{"foo":"bar","code":"authorizenet_acceptjs"}'; + $this->assertEquals($expected, $result); + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Command/AcceptPaymentStrategyCommandTest.php b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Command/AcceptPaymentStrategyCommandTest.php new file mode 100644 index 00000000000..316fef54433 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Command/AcceptPaymentStrategyCommandTest.php @@ -0,0 +1,131 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Test\Unit\Gateway\Command; + +use Magento\AuthorizenetAcceptjs\Gateway\Command\AcceptPaymentStrategyCommand; +use Magento\AuthorizenetAcceptjs\Gateway\Command\RefundTransactionStrategyCommand; +use Magento\AuthorizenetAcceptjs\Gateway\SubjectReader; +use Magento\Payment\Gateway\Command\CommandPoolInterface; +use Magento\Payment\Gateway\Command\ResultInterface; +use Magento\Payment\Gateway\CommandInterface; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class AcceptPaymentStrategyCommandTest extends TestCase +{ + /** + * @var CommandInterface|MockObject + */ + private $commandMock; + + /** + * @var CommandInterface|MockObject + */ + private $transactionDetailsCommandMock; + + /** + * @var CommandPoolInterface|MockObject + */ + private $commandPoolMock; + + /** + * @var RefundTransactionStrategyCommand + */ + private $command; + + /** + * @var ResultInterface|MockObject + */ + private $transactionResultMock; + + protected function setUp() + { + $this->transactionDetailsCommandMock = $this->createMock(CommandInterface::class); + $this->commandMock = $this->createMock(CommandInterface::class); + $this->transactionResultMock = $this->createMock(ResultInterface::class); + $this->commandPoolMock = $this->createMock(CommandPoolInterface::class); + $this->command = new AcceptPaymentStrategyCommand( + $this->commandPoolMock, + new SubjectReader() + ); + } + + /** + * @param string $status + * @dataProvider inReviewStatusesProvider + */ + public function testCommandWillAcceptInTheGatewayWhenInFDSReview(string $status) + { + // Assert command is executed + $this->commandMock->expects($this->once()) + ->method('execute'); + + $this->commandPoolMock->method('get') + ->willReturnMap([ + ['get_transaction_details', $this->transactionDetailsCommandMock], + ['accept_fds', $this->commandMock] + ]); + + $this->transactionResultMock->method('get') + ->willReturn([ + 'transaction' => [ + 'transactionStatus' => $status + ] + ]); + + $buildSubject = [ + 'foo' => '123' + ]; + + $this->transactionDetailsCommandMock->expects($this->once()) + ->method('execute') + ->with($buildSubject) + ->willReturn($this->transactionResultMock); + + $this->command->execute($buildSubject); + } + + public function testCommandWillDoNothingWhenTransactionHasAlreadyBeenAuthorized() + { + // Assert command is never executed + $this->commandMock->expects($this->never()) + ->method('execute'); + + $this->commandPoolMock->method('get') + ->willReturnMap([ + ['get_transaction_details', $this->transactionDetailsCommandMock], + ]); + + $this->transactionResultMock->method('get') + ->willReturn([ + 'transaction' => [ + 'transactionStatus' => 'anythingelseisfine' + ] + ]); + + $buildSubject = [ + 'foo' => '123' + ]; + + $this->transactionDetailsCommandMock->expects($this->once()) + ->method('execute') + ->with($buildSubject) + ->willReturn($this->transactionResultMock); + + $this->command->execute($buildSubject); + } + + public function inReviewStatusesProvider() + { + return [ + ['FDSPendingReview'], + ['FDSAuthorizedPendingReview'] + ]; + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Command/CaptureStrategyCommandTest.php b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Command/CaptureStrategyCommandTest.php new file mode 100644 index 00000000000..4cbded97647 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Command/CaptureStrategyCommandTest.php @@ -0,0 +1,181 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Test\Unit\Gateway\Command; + +use Magento\AuthorizenetAcceptjs\Gateway\Command\CaptureStrategyCommand; +use Magento\AuthorizenetAcceptjs\Gateway\SubjectReader; +use Magento\Framework\Api\FilterBuilder; +use Magento\Framework\Api\Search\SearchCriteria; +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Payment\Gateway\Command\CommandPoolInterface; +use Magento\Payment\Gateway\Command\GatewayCommand; +use Magento\Payment\Gateway\Data\PaymentDataObject; +use Magento\Sales\Api\Data\TransactionSearchResultInterface; +use Magento\Sales\Api\TransactionRepositoryInterface; +use Magento\Sales\Model\Order\Payment; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class CaptureStrategyCommandTest extends TestCase +{ + /** + * @var CaptureStrategyCommand + */ + private $strategyCommand; + + /** + * @var CommandPoolInterface|MockObject + */ + private $commandPoolMock; + + /** + * @var TransactionRepositoryInterface|MockObject + */ + private $transactionRepositoryMock; + + /** + * @var FilterBuilder|MockObject + */ + private $filterBuilderMock; + + /** + * @var SearchCriteriaBuilder|MockObject + */ + private $searchCriteriaBuilderMock; + + /** + * @var Payment|MockObject + */ + private $paymentMock; + + /** + * @var PaymentDataObject|MockObject + */ + private $paymentDOMock; + + /** + * @var GatewayCommand|MockObject + */ + private $commandMock; + + /** + * @var TransactionSearchResultInterface|MockObject + */ + private $transactionsResult; + + protected function setUp() + { + // Simple mocks + $this->paymentDOMock = $this->createMock(PaymentDataObject::class); + $this->paymentMock = $this->createMock(Payment::class); + $this->paymentDOMock->method('getPayment') + ->willReturn($this->paymentMock); + $this->commandMock = $this->createMock(GatewayCommand::class); + $this->commandPoolMock = $this->createMock(CommandPoolInterface::class); + $this->searchCriteriaBuilderMock = $this->createMock(SearchCriteriaBuilder::class); + $this->transactionRepositoryMock = $this->createMock(TransactionRepositoryInterface::class); + + // The search criteria builder should return the criteria with the specified filters + $this->filterBuilderMock = $this->createMock(FilterBuilder::class); + // We aren't coupling the implementation to the test. The test only cares how the result is processed + $this->filterBuilderMock->method('setField') + ->willReturnSelf(); + $this->filterBuilderMock->method('setValue') + ->willReturnSelf(); + $searchCriteria = new SearchCriteria(); + $this->searchCriteriaBuilderMock->method('addFilters') + ->willReturnSelf(); + $this->searchCriteriaBuilderMock->method('create') + ->willReturn($searchCriteria); + // The transaction result can be customized per test to simulate different scenarios + $this->transactionsResult = $this->createMock(TransactionSearchResultInterface::class); + $this->transactionRepositoryMock->method('getList') + ->with($searchCriteria) + ->willReturn($this->transactionsResult); + + $this->strategyCommand = new CaptureStrategyCommand( + $this->commandPoolMock, + $this->transactionRepositoryMock, + $this->filterBuilderMock, + $this->searchCriteriaBuilderMock, + new SubjectReader() + ); + } + + public function testExecuteWillAuthorizeWhenNotAuthorizedAndNotCaptured() + { + $subject = ['payment' => $this->paymentDOMock]; + + // Hasn't been authorized + $this->paymentMock->method('getAuthorizationTransaction') + ->willReturn(false); + // Hasn't been captured + $this->transactionsResult->method('getTotalCount') + ->willReturn(0); + // Assert authorize command was used + $this->commandPoolMock->expects($this->once()) + ->method('get') + ->with('sale') + ->willReturn($this->commandMock); + // Assert execute was called and with correct data + $this->commandMock->expects($this->once()) + ->method('execute') + ->with($subject); + + $this->strategyCommand->execute($subject); + // Assertions are performed via mock expects above + } + + public function testExecuteWillAuthorizeAndCaptureWhenAlreadyCaptured() + { + $subject = ['payment' => $this->paymentDOMock]; + + // Already authorized + $this->paymentMock->method('getAuthorizationTransaction') + ->willReturn(true); + // And already captured + $this->transactionsResult->method('getTotalCount') + ->willReturn(1); + // Assert authorize command was used + $this->commandPoolMock->expects($this->once()) + ->method('get') + ->with('settle') + ->willReturn($this->commandMock); + // Assert execute was called and with correct data + $this->commandMock->expects($this->once()) + ->method('execute') + ->with($subject); + + $this->strategyCommand->execute($subject); + // Assertions are performed via mock expects above + } + + public function testExecuteWillCaptureWhenAlreadyAuthorizedButNotCaptured() + { + $subject = ['payment' => $this->paymentDOMock]; + + // Was already authorized + $this->paymentMock->method('getAuthorizationTransaction') + ->willReturn(true); + // But, hasn't been captured + $this->transactionsResult->method('getTotalCount') + ->willReturn(0); + // Assert authorize command was used + $this->commandPoolMock->expects($this->once()) + ->method('get') + ->with('settle') + ->willReturn($this->commandMock); + // Assert execute was called and with correct data + $this->commandMock->expects($this->once()) + ->method('execute') + ->with($subject); + + $this->strategyCommand->execute($subject); + // Assertions are performed via mock expects above + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Command/FetchTransactionInfoCommandTest.php b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Command/FetchTransactionInfoCommandTest.php new file mode 100644 index 00000000000..757500c7e50 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Command/FetchTransactionInfoCommandTest.php @@ -0,0 +1,132 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Test\Unit\Gateway\Command; + +use Magento\AuthorizenetAcceptjs\Gateway\Command\FetchTransactionInfoCommand; +use Magento\AuthorizenetAcceptjs\Gateway\Config; +use Magento\AuthorizenetAcceptjs\Gateway\SubjectReader; +use Magento\Payment\Gateway\Command\CommandPoolInterface; +use Magento\Payment\Gateway\Command\ResultInterface; +use Magento\Payment\Gateway\CommandInterface; +use Magento\Payment\Gateway\Data\PaymentDataObject; +use Magento\Payment\Gateway\Response\HandlerInterface; +use Magento\Sales\Model\Order; +use Magento\Sales\Model\Order\Payment; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class FetchTransactionInfoCommandTest extends TestCase +{ + /** + * @var CommandInterface|MockObject + */ + private $transactionDetailsCommandMock; + + /** + * @var CommandPoolInterface|MockObject + */ + private $commandPoolMock; + + /** + * @var FetchTransactionInfoCommand + */ + private $command; + + /** + * @var ResultInterface|MockObject + */ + private $transactionResultMock; + + /** + * @var PaymentDataObject|MockObject + */ + private $paymentDOMock; + + /** + * @var Payment|MockObject + */ + private $paymentMock; + + /** + * @var Config + */ + private $configMock; + + /** + * @var HandlerInterface + */ + private $handlerMock; + + protected function setUp() + { + $this->paymentDOMock = $this->createMock(PaymentDataObject::class); + $this->paymentMock = $this->createMock(Payment::class); + $this->paymentDOMock->method('getPayment') + ->willReturn($this->paymentMock); + $this->configMock = $this->createMock(Config::class); + $this->configMock->method('getTransactionInfoSyncKeys') + ->willReturn(['foo', 'bar']); + $orderMock = $this->createMock(Order::class); + $this->paymentDOMock->method('getOrder') + ->willReturn($orderMock); + $this->transactionDetailsCommandMock = $this->createMock(CommandInterface::class); + $this->transactionResultMock = $this->createMock(ResultInterface::class); + $this->commandPoolMock = $this->createMock(CommandPoolInterface::class); + $this->handlerMock = $this->createMock(HandlerInterface::class); + $this->command = new FetchTransactionInfoCommand( + $this->commandPoolMock, + new SubjectReader(), + $this->configMock, + $this->handlerMock + ); + } + + public function testCommandWillMarkTransactionAsApprovedWhenNotVoid() + { + $response = [ + 'transaction' => [ + 'transactionStatus' => 'authorizedPendingCapture', + 'foo' => 'abc', + 'bar' => 'cba', + 'dontreturnme' => 'justdont' + ] + ]; + + $this->commandPoolMock->method('get') + ->willReturnMap([ + ['get_transaction_details', $this->transactionDetailsCommandMock], + ]); + + $this->transactionResultMock->method('get') + ->willReturn($response); + + $buildSubject = [ + 'payment' => $this->paymentDOMock + ]; + + $this->transactionDetailsCommandMock->expects($this->once()) + ->method('execute') + ->with($buildSubject) + ->willReturn($this->transactionResultMock); + + $this->handlerMock->expects($this->once()) + ->method('handle') + ->with($buildSubject, $response) + ->willReturn($this->transactionResultMock); + + $result = $this->command->execute($buildSubject); + + $expected = [ + 'foo' => 'abc', + 'bar' => 'cba' + ]; + + $this->assertSame($expected, $result); + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Command/GatewayQueryCommandTest.php b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Command/GatewayQueryCommandTest.php new file mode 100644 index 00000000000..e37db349363 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Command/GatewayQueryCommandTest.php @@ -0,0 +1,196 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Test\Unit\Gateway\Command; + +use Magento\AuthorizenetAcceptjs\Gateway\Command\GatewayQueryCommand; +use Magento\Payment\Gateway\Command\Result\ArrayResult; +use Magento\Payment\Gateway\Http\ClientInterface; +use Magento\Payment\Gateway\Http\TransferFactoryInterface; +use Magento\Payment\Gateway\Http\TransferInterface; +use Magento\Payment\Gateway\Request\BuilderInterface; +use Magento\Payment\Gateway\Validator\Result; +use Magento\Payment\Gateway\Validator\ValidatorInterface; +use PHPUnit\Framework\MockObject\Builder\InvocationMocker; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; +use Psr\Log\LoggerInterface; + +class GatewayQueryCommandTest extends TestCase +{ + /** + * @var GatewayQueryCommand + */ + private $command; + + /** + * @var BuilderInterface|MockObject|InvocationMocker + */ + private $requestBuilderMock; + + /** + * @var TransferFactoryInterface|MockObject|InvocationMocker + */ + private $transferFactoryMock; + + /** + * @var ClientInterface|MockObject|InvocationMocker + */ + private $clientMock; + + /** + * @var LoggerInterface|MockObject|InvocationMocker + */ + private $loggerMock; + + /** + * @var ValidatorInterface|MockObject|InvocationMocker + */ + private $validatorMock; + + /** + * @var TransferInterface|MockObject|InvocationMocker + */ + private $transferMock; + + protected function setUp() + { + $this->requestBuilderMock = $this->createMock(BuilderInterface::class); + $this->transferFactoryMock = $this->createMock(TransferFactoryInterface::class); + $this->transferMock = $this->createMock(TransferInterface::class); + $this->clientMock = $this->createMock(ClientInterface::class); + $this->loggerMock = $this->createMock(LoggerInterface::class); + $this->validatorMock = $this->createMock(ValidatorInterface::class); + + $this->command = new GatewayQueryCommand( + $this->requestBuilderMock, + $this->transferFactoryMock, + $this->clientMock, + $this->loggerMock, + $this->validatorMock + ); + } + + public function testNormalExecution() + { + $buildSubject = [ + 'foo' => '123' + ]; + + $request = [ + 'bar' => '321' + ]; + + $response = [ + 'transaction' => [ + 'transactionType' => 'foo', + 'transactionStatus' => 'bar', + 'responseCode' => 'baz' + ] + ]; + + $validationSubject = $buildSubject; + $validationSubject['response'] = $response; + + $this->requestBuilderMock->method('build') + ->with($buildSubject) + ->willReturn($request); + + $this->transferFactoryMock->method('create') + ->with($request) + ->willReturn($this->transferMock); + + $this->clientMock->method('placeRequest') + ->with($this->transferMock) + ->willReturn($response); + + $this->validatorMock->method('validate') + ->with($validationSubject) + ->willReturn(new Result(true)); + + $result = $this->command->execute($buildSubject); + + $this->assertInstanceOf(ArrayResult::class, $result); + $this->assertEquals($response, $result->get()); + } + + /** + * @expectedExceptionMessage There was an error while trying to process the request. + * @expectedException \Magento\Payment\Gateway\Command\CommandException + */ + public function testExceptionIsThrownAndLoggedWhenRequestFails() + { + $buildSubject = [ + 'foo' => '123' + ]; + + $request = [ + 'bar' => '321' + ]; + + $this->requestBuilderMock->method('build') + ->with($buildSubject) + ->willReturn($request); + + $this->transferFactoryMock->method('create') + ->with($request) + ->willReturn($this->transferMock); + + $e = new \Exception('foobar'); + + $this->clientMock->method('placeRequest') + ->with($this->transferMock) + ->willThrowException($e); + + // assert the exception is logged + $this->loggerMock->expects($this->once()) + ->method('critical') + ->with($e); + + $this->command->execute($buildSubject); + } + /** + * @expectedExceptionMessage There was an error while trying to process the request. + * @expectedException \Magento\Payment\Gateway\Command\CommandException + */ + public function testExceptionIsThrownWhenResponseIsInvalid() + { + $buildSubject = [ + 'foo' => '123' + ]; + + $request = [ + 'bar' => '321' + ]; + + $response = [ + 'baz' => '456' + ]; + + $validationSubject = $buildSubject; + $validationSubject['response'] = $response; + + $this->requestBuilderMock->method('build') + ->with($buildSubject) + ->willReturn($request); + + $this->transferFactoryMock->method('create') + ->with($request) + ->willReturn($this->transferMock); + + $this->clientMock->method('placeRequest') + ->with($this->transferMock) + ->willReturn($response); + + $this->validatorMock->method('validate') + ->with($validationSubject) + ->willReturn(new Result(false)); + + $this->command->execute($buildSubject); + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Command/RefundTransactionStrategyCommandTest.php b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Command/RefundTransactionStrategyCommandTest.php new file mode 100644 index 00000000000..df6d89d7bc5 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Command/RefundTransactionStrategyCommandTest.php @@ -0,0 +1,153 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Test\Unit\Gateway\Command; + +use Magento\AuthorizenetAcceptjs\Gateway\Command\RefundTransactionStrategyCommand; +use Magento\AuthorizenetAcceptjs\Gateway\SubjectReader; +use Magento\Payment\Gateway\Command\CommandPoolInterface; +use Magento\Payment\Gateway\Command\ResultInterface; +use Magento\Payment\Gateway\CommandInterface; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class RefundTransactionStrategyCommandTest extends TestCase +{ + /** + * @var CommandInterface|MockObject + */ + private $commandMock; + + /** + * @var CommandInterface|MockObject + */ + private $transactionDetailsCommandMock; + + /** + * @var CommandPoolInterface|MockObject + */ + private $commandPoolMock; + + /** + * @var RefundTransactionStrategyCommand + */ + private $command; + + /** + * @var ResultInterface|MockObject + */ + private $transactionResultMock; + + protected function setUp() + { + $this->transactionDetailsCommandMock = $this->createMock(CommandInterface::class); + $this->commandMock = $this->createMock(CommandInterface::class); + $this->transactionResultMock = $this->createMock(ResultInterface::class); + $this->commandPoolMock = $this->createMock(CommandPoolInterface::class); + $this->command = new RefundTransactionStrategyCommand( + $this->commandPoolMock, + new SubjectReader() + ); + } + + public function testCommandWillVoidWhenTransactionIsPendingSettlement() + { + // Assert command is executed + $this->commandMock->expects($this->once()) + ->method('execute'); + + $this->commandPoolMock->method('get') + ->willReturnMap([ + ['get_transaction_details', $this->transactionDetailsCommandMock], + ['void', $this->commandMock] + ]); + + $this->transactionResultMock->method('get') + ->willReturn([ + 'transaction' => [ + 'transactionStatus' => 'capturedPendingSettlement' + ] + ]); + + $buildSubject = [ + 'foo' => '123' + ]; + + $this->transactionDetailsCommandMock->expects($this->once()) + ->method('execute') + ->with($buildSubject) + ->willReturn($this->transactionResultMock); + + $this->command->execute($buildSubject); + } + + public function testCommandWillRefundWhenTransactionIsSettled() + { + // Assert command is executed + $this->commandMock->expects($this->once()) + ->method('execute'); + + $this->commandPoolMock->method('get') + ->willReturnMap([ + ['get_transaction_details', $this->transactionDetailsCommandMock], + ['refund_settled', $this->commandMock] + ]); + + $this->transactionResultMock->method('get') + ->willReturn([ + 'transaction' => [ + 'transactionStatus' => 'settledSuccessfully' + ] + ]); + + $buildSubject = [ + 'foo' => '123' + ]; + + $this->transactionDetailsCommandMock->expects($this->once()) + ->method('execute') + ->with($buildSubject) + ->willReturn($this->transactionResultMock); + + $this->command->execute($buildSubject); + } + + /** + * @expectedException \Magento\Payment\Gateway\Command\CommandException + * @expectedExceptionMessage This transaction cannot be refunded with its current status. + */ + public function testCommandWillThrowExceptionWhenTransactionIsInInvalidState() + { + // Assert command is never executed + $this->commandMock->expects($this->never()) + ->method('execute'); + + $this->commandPoolMock->method('get') + ->willReturnMap([ + ['get_transaction_details', $this->transactionDetailsCommandMock], + ]); + + $this->transactionResultMock->method('get') + ->willReturn([ + 'transaction' => [ + 'transactionStatus' => 'somethingIsWrong' + ] + ]); + + $buildSubject = [ + 'foo' => '123' + ]; + + $this->transactionDetailsCommandMock->expects($this->once()) + ->method('execute') + ->with($buildSubject) + ->willReturn($this->transactionResultMock); + + $this->command->execute($buildSubject); + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/ConfigTest.php b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/ConfigTest.php new file mode 100644 index 00000000000..da2b953d843 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/ConfigTest.php @@ -0,0 +1,126 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Test\Unit\Gateway; + +use Magento\AuthorizenetAcceptjs\Gateway\Config; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Store\Model\ScopeInterface; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class ConfigTest extends TestCase +{ + /** + * @var Config + */ + private $model; + + /** + * @var ScopeConfigInterface|MockObject + */ + private $scopeConfigMock; + + protected function setUp() + { + $this->scopeConfigMock = $this->createMock(ScopeConfigInterface::class); + + $objectManager = new ObjectManager($this); + $this->model = $objectManager->getObject( + Config::class, + [ + 'scopeConfig' => $this->scopeConfigMock, + 'methodCode' => Config::METHOD, + ] + ); + } + + /** + * @param $getterName + * @param $configField + * @param $configValue + * @param $expectedValue + * @dataProvider configMapProvider + */ + public function testConfigGetters($getterName, $configField, $configValue, $expectedValue) + { + $this->scopeConfigMock->method('getValue') + ->with($this->getPath($configField), ScopeInterface::SCOPE_STORE, 123) + ->willReturn($configValue); + $this->assertEquals($expectedValue, $this->model->{$getterName}(123)); + } + + /** + * @dataProvider environmentUrlProvider + * @param $environment + * @param $expectedUrl + */ + public function testGetApiUrl($environment, $expectedUrl) + { + $this->scopeConfigMock->method('getValue') + ->with($this->getPath('environment'), ScopeInterface::SCOPE_STORE, 123) + ->willReturn($environment); + $this->assertEquals($expectedUrl, $this->model->getApiUrl(123)); + } + + /** + * @dataProvider environmentSolutionProvider + * @param $environment + * @param $expectedSolution + */ + public function testGetSolutionIdSandbox($environment, $expectedSolution) + { + $this->scopeConfigMock->method('getValue') + ->with($this->getPath('environment'), ScopeInterface::SCOPE_STORE, 123) + ->willReturn($environment); + $this->assertEquals($expectedSolution, $this->model->getSolutionId(123)); + } + + public function configMapProvider() + { + return [ + ['getLoginId', 'login', 'username', 'username'], + ['getEnvironment', 'environment', 'production', 'production'], + ['getClientKey', 'public_client_key', 'abc', 'abc'], + ['getTransactionKey', 'trans_key', 'password', 'password'], + ['getLegacyTransactionHash', 'trans_md5', 'abc123', 'abc123'], + ['getTransactionSignatureKey', 'trans_signature_key', 'abc123', 'abc123'], + ['getPaymentAction', 'payment_action', 'authorize', 'authorize'], + ['shouldEmailCustomer', 'email_customer', true, true], + ['isCvvEnabled', 'cvv_enabled', true, true], + ['getAdditionalInfoKeys', 'paymentInfoKeys', 'a,b,c', ['a', 'b', 'c']], + ['getTransactionInfoSyncKeys', 'transactionSyncKeys', 'a,b,c', ['a', 'b', 'c']], + ]; + } + public function environmentUrlProvider() + { + return [ + ['sandbox', 'https://apitest.authorize.net/xml/v1/request.api'], + ['production', 'https://api.authorize.net/xml/v1/request.api'], + ]; + } + + public function environmentSolutionProvider() + { + return [ + ['sandbox', 'AAA102993'], + ['production', 'AAA175350'], + ]; + } + + /** + * Return config path + * + * @param string $field + * @return string + */ + private function getPath($field) + { + return sprintf(Config::DEFAULT_PATH_PATTERN, Config::METHOD, $field); + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Http/ClientTest.php b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Http/ClientTest.php new file mode 100644 index 00000000000..4086195ff4c --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Http/ClientTest.php @@ -0,0 +1,218 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Test\Unit\Gateway\Http; + +use Magento\AuthorizenetAcceptjs\Gateway\Http\Client; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\HTTP\ZendClientFactory; +use Magento\Framework\Serialize\Serializer\Json; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Payment\Gateway\Http\TransferInterface; +use Magento\Payment\Model\Method\Logger; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; +use Psr\Log\LoggerInterface; +use Zend_Http_Client; +use Zend_Http_Response; + +class ClientTest extends TestCase +{ + /** + * @var ObjectManager + */ + private $objectManager; + + /** + * @var Logger + */ + private $paymentLogger; + + /** + * @var ZendClientFactory + */ + private $httpClientFactory; + + /** + * @var Zend_Http_Client + */ + private $httpClient; + + /** + * @var Zend_Http_Response + */ + private $httpResponse; + + /** + * @var LoggerInterface + */ + private $logger; + + protected function setUp() + { + $this->objectManager = new ObjectManager($this); + $this->paymentLogger = $this->createMock(Logger::class); + $this->httpClientFactory = $this->createMock(ZendClientFactory::class); + $this->httpClient = $this->createMock(Zend_Http_Client::class); + $this->httpResponse = $this->createMock(Zend_Http_Response::class); + $this->httpClientFactory->method('create')->will($this->returnValue($this->httpClient)); + $this->httpClient->method('request') + ->willReturn($this->httpResponse); + /** @var MockObject $logger */ + $this->logger = $this->createMock(LoggerInterface::class); + } + + public function testCanSendRequest() + { + // Assert the raw data was set on the client + $this->httpClient->expects($this->once()) + ->method('setRawData') + ->with( + '{"doSomeThing":{"foobar":"baz"}}', + 'application/json' + ); + + $request = [ + 'payload_type' => 'doSomeThing', + 'foobar' => 'baz' + ]; + // Authorize.net returns a BOM and refuses to fix it + $response = pack('CCC', 0xef, 0xbb, 0xbf) . '{"foo":{"bar":"baz"}}'; + + $this->httpResponse->method('getBody') + ->willReturn($response); + + // Assert the logger was given the data + $this->paymentLogger->expects($this->once()) + ->method('debug') + ->with(['request' => $request, 'response' => '{"foo":{"bar":"baz"}}']); + + /** + * @var $apiClient Client + */ + $apiClient = $this->objectManager->getObject(Client::class, [ + 'httpClientFactory' => $this->httpClientFactory, + 'paymentLogger' => $this->paymentLogger, + 'json' => new Json() + ]); + + $result = $apiClient->placeRequest($this->getTransferObjectMock($request)); + + $this->assertSame('baz', $result['foo']['bar']); + } + + /** + * @expectedException \Magento\Framework\Exception\LocalizedException + * @expectedExceptionMessage Something went wrong in the payment gateway. + */ + public function testExceptionIsThrownWhenEmptyResponseIsReceived() + { + // Assert the client has the raw data set + $this->httpClient->expects($this->once()) + ->method('setRawData') + ->with( + '{"doSomeThing":{"foobar":"baz"}}', + 'application/json' + ); + + $this->httpResponse->method('getBody') + ->willReturn(''); + + // Assert the exception is given to the logger + $this->logger->expects($this->once()) + ->method('critical') + ->with($this->callback(function ($e) { + return $e instanceof \Exception + && $e->getMessage() === 'Invalid JSON was returned by the gateway'; + })); + + $request = [ + 'payload_type' => 'doSomeThing', + 'foobar' => 'baz' + ]; + + // Assert the logger was given the data + $this->paymentLogger->expects($this->once()) + ->method('debug') + ->with(['request' => $request, 'response' => '']); + + /** + * @var $apiClient Client + */ + $apiClient = $this->objectManager->getObject(Client::class, [ + 'httpClientFactory' => $this->httpClientFactory, + 'paymentLogger' => $this->paymentLogger, + 'logger' => $this->logger, + 'json' => new Json() + ]); + + $apiClient->placeRequest($this->getTransferObjectMock($request)); + } + + /** + * @expectedException \Magento\Framework\Exception\LocalizedException + * @expectedExceptionMessage Something went wrong in the payment gateway. + */ + public function testExceptionIsThrownWhenInvalidResponseIsReceived() + { + // Assert the client was given the raw data + $this->httpClient->expects($this->once()) + ->method('setRawData') + ->with( + '{"doSomeThing":{"foobar":"baz"}}', + 'application/json' + ); + + $this->httpResponse->method('getBody') + ->willReturn('bad'); + + $request = [ + 'payload_type' => 'doSomeThing', + 'foobar' => 'baz' + ]; + + // Assert the logger was given the data + $this->paymentLogger->expects($this->once()) + ->method('debug') + ->with(['request' => $request, 'response' => 'bad']); + + // Assert the exception was given to the logger + $this->logger->expects($this->once()) + ->method('critical') + ->with($this->callback(function ($e) { + return $e instanceof \Exception + && $e->getMessage() === 'Invalid JSON was returned by the gateway'; + })); + + /** + * @var $apiClient Client + */ + $apiClient = $this->objectManager->getObject(Client::class, [ + 'httpClientFactory' => $this->httpClientFactory, + 'paymentLogger' => $this->paymentLogger, + 'logger' => $this->logger, + 'json' => new Json() + ]); + + $apiClient->placeRequest($this->getTransferObjectMock($request)); + } + + /** + * Creates mock object for TransferInterface. + * + * @return TransferInterface|MockObject + */ + private function getTransferObjectMock(array $data) + { + $transferObjectMock = $this->createMock(TransferInterface::class); + $transferObjectMock->method('getBody') + ->willReturn($data); + + return $transferObjectMock; + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Http/Payload/Filter/RemoveFieldsFilterTest.php b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Http/Payload/Filter/RemoveFieldsFilterTest.php new file mode 100644 index 00000000000..bcc6279f5b1 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Http/Payload/Filter/RemoveFieldsFilterTest.php @@ -0,0 +1,36 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Test\Unit\Gateway\Http\Payload\Filter; + +use Magento\AuthorizenetAcceptjs\Gateway\Http\Payload\Filter\RemoveFieldsFilter; +use PHPUnit\Framework\TestCase; + +class RemoveFieldsFilterTest extends TestCase +{ + public function testFilterRemovesFields() + { + $filter = new RemoveFieldsFilter(['foo', 'bar']); + + $actual = $filter->filter([ + 'some' => 123, + 'data' => 321, + 'foo' => 'to', + 'filter' => ['blah'], + 'bar' => 'fields from' + ]); + + $expected = [ + 'some' => 123, + 'data' => 321, + 'filter' => ['blah'], + ]; + + $this->assertEquals($expected, $actual); + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Http/TransferFactoryTest.php b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Http/TransferFactoryTest.php new file mode 100644 index 00000000000..954fd9782bd --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Http/TransferFactoryTest.php @@ -0,0 +1,73 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Test\Unit\Gateway\Http; + +use Magento\AuthorizenetAcceptjs\Gateway\Http\Payload\Filter\RemoveFieldsFilter; +use Magento\AuthorizenetAcceptjs\Gateway\Http\TransferFactory; +use Magento\Payment\Gateway\Http\TransferBuilder; +use Magento\Payment\Gateway\Http\TransferInterface; +use Magento\AuthorizenetAcceptjs\Gateway\Http\Payload\FilterInterface; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class TransferFactoryTest extends TestCase +{ + /** + * @var TransferFactory + */ + private $transferFactory; + + /** + * @var TransferFactory + */ + private $transferMock; + + /** + * @var TransferBuilder|MockObject + */ + private $transferBuilder; + + /** + * @var FilterInterface|MockObject + */ + private $filterMock; + + protected function setUp() + { + $this->transferBuilder = $this->createMock(TransferBuilder::class); + $this->transferMock = $this->createMock(TransferInterface::class); + $this->filterMock = $this->createMock(RemoveFieldsFilter::class); + + $this->transferFactory = new TransferFactory( + $this->transferBuilder, + [$this->filterMock] + ); + } + + public function testCreate() + { + $request = ['data1', 'data2']; + + // Assert the filter was created + $this->filterMock->expects($this->once()) + ->method('filter') + ->with($request) + ->willReturn($request); + + // Assert the body of the transfer was set + $this->transferBuilder->expects($this->once()) + ->method('setBody') + ->with($request) + ->willReturnSelf(); + + $this->transferBuilder->method('build') + ->willReturn($this->transferMock); + + $this->assertEquals($this->transferMock, $this->transferFactory->create($request)); + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/AcceptFdsDataBuilderTest.php b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/AcceptFdsDataBuilderTest.php new file mode 100644 index 00000000000..00bb7ee84f9 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/AcceptFdsDataBuilderTest.php @@ -0,0 +1,70 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Test\Unit\Gateway\Request; + +use Magento\AuthorizenetAcceptjs\Gateway\Request\AcceptFdsDataBuilder; +use Magento\AuthorizenetAcceptjs\Gateway\SubjectReader; +use Magento\Sales\Model\Order\Payment\Transaction; +use PHPUnit\Framework\TestCase; +use Magento\Payment\Gateway\Data\PaymentDataObjectInterface; +use Magento\Sales\Model\Order; +use Magento\Sales\Model\Order\Payment; +use PHPUnit\Framework\MockObject\MockObject; + +class AcceptFdsDataBuilderTest extends TestCase +{ + /** + * @var AcceptFdsDataBuilder + */ + private $builder; + + /** + * @var Payment|MockObject + */ + private $paymentMock; + + /** + * @var Payment|MockObject + */ + private $paymentDOMock; + + protected function setUp() + { + $this->paymentDOMock = $this->createMock(PaymentDataObjectInterface::class); + $this->paymentMock = $this->createMock(Payment::class); + $this->orderMock = $this->createMock(Order::class); + $this->paymentDOMock->method('getPayment') + ->willReturn($this->paymentMock); + + $this->builder = new AcceptFdsDataBuilder(new SubjectReader()); + } + + public function testBuild() + { + $transactionMock = $this->createMock(Transaction::class); + + $this->paymentMock->method('getAuthorizationTransaction') + ->willReturn($transactionMock); + + $transactionMock->method('getTxnId') + ->willReturn('foo'); + + $expected = [ + 'heldTransactionRequest' => [ + 'action' => 'approve', + 'refTransId' => 'foo' + ] + ]; + + $buildSubject = [ + 'payment' => $this->paymentDOMock, + ]; + + $this->assertEquals($expected, $this->builder->build($buildSubject)); + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/AddressDataBuilderTest.php b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/AddressDataBuilderTest.php new file mode 100644 index 00000000000..6ddb30a64af --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/AddressDataBuilderTest.php @@ -0,0 +1,129 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Test\Unit\Gateway\Request; + +use Magento\AuthorizenetAcceptjs\Gateway\Request\AddressDataBuilder; +use Magento\AuthorizenetAcceptjs\Gateway\SubjectReader; +use Magento\Payment\Gateway\Data\AddressAdapterInterface; +use Magento\Payment\Gateway\Data\OrderAdapterInterface; +use Magento\Payment\Gateway\Data\PaymentDataObjectInterface; +use Magento\Sales\Model\Order\Payment; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class AddressDataBuilderTest extends TestCase +{ + /** + * @var AddressDataBuilder + */ + private $builder; + + /** + * @var Payment|MockObject + */ + private $paymentMock; + + /** + * @var PaymentDataObjectInterface|MockObject + */ + private $paymentDOMock; + + /** + * @var OrderAdapterInterface|MockObject + */ + private $orderMock; + + private $mockAddressData = [ + 'firstName' => [ + 'method' => 'getFirstname', + 'sampleData' => 'John' + ], + 'lastName' => [ + 'method' => 'getLastname', + 'sampleData' => 'Doe' + ], + 'company' => [ + 'method' => 'getCompany', + 'sampleData' => 'Magento' + ], + 'address' => [ + 'method' => 'getStreetLine1', + 'sampleData' => '11501 Domain Dr' + ], + 'city' => [ + 'method' => 'getCity', + 'sampleData' => 'Austin' + ], + 'state' => [ + 'method' => 'getRegionCode', + 'sampleData' => 'TX' + ], + 'zip' => [ + 'method' => 'getPostcode', + 'sampleData' => '78758' + ], + 'country' => [ + 'method' => 'getCountryId', + 'sampleData' => 'US' + ], + ]; + + protected function setUp() + { + $this->paymentDOMock = $this->createMock(PaymentDataObjectInterface::class); + $this->paymentMock = $this->createMock(Payment::class); + $this->paymentDOMock->method('getPayment') + ->willReturn($this->paymentMock); + $this->orderMock = $this->createMock(OrderAdapterInterface::class); + $this->paymentDOMock->method('getOrder') + ->willReturn($this->orderMock); + + $this->builder = new AddressDataBuilder(new SubjectReader()); + } + + public function testBuildWithBothAddresses() + { + $billingAddress = $this->createAddressMock('billing'); + $shippingAddress = $this->createAddressMock('shipping'); + $this->orderMock->method('getBillingAddress') + ->willReturn($billingAddress); + $this->orderMock->method('getShippingAddress') + ->willReturn($shippingAddress); + $this->orderMock->method('getRemoteIp') + ->willReturn('abc'); + + $buildSubject = [ + 'payment' => $this->paymentDOMock + ]; + + $result = $this->builder->build($buildSubject); + + $this->validateAddressData($result['transactionRequest']['billTo'], 'billing'); + $this->validateAddressData($result['transactionRequest']['shipTo'], 'shipping'); + $this->assertEquals('abc', $result['transactionRequest']['customerIP']); + } + + private function validateAddressData($responseData, $addressPrefix) + { + foreach ($this->mockAddressData as $fieldValue => $field) { + $this->assertEquals($addressPrefix . $field['sampleData'], $responseData[$fieldValue]); + } + } + + private function createAddressMock($prefix) + { + $addressAdapterMock = $this->createMock(AddressAdapterInterface::class); + + foreach ($this->mockAddressData as $field) { + $addressAdapterMock->method($field['method']) + ->willReturn($prefix . $field['sampleData']); + } + + return $addressAdapterMock; + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/AmountDataBuilderTest.php b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/AmountDataBuilderTest.php new file mode 100644 index 00000000000..9da0139302a --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/AmountDataBuilderTest.php @@ -0,0 +1,42 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Test\Unit\Gateway\Request; + +use Magento\AuthorizenetAcceptjs\Gateway\Request\AmountDataBuilder; +use Magento\AuthorizenetAcceptjs\Gateway\SubjectReader; +use PHPUnit\Framework\TestCase; + +class AmountDataBuilderTest extends TestCase +{ + /** + * @var AmountDataBuilder + */ + private $builder; + + protected function setUp() + { + $this->builder = new AmountDataBuilder( + new SubjectReader() + ); + } + + public function testBuild() + { + $expected = [ + 'transactionRequest' => [ + 'amount' => '123.45', + ] + ]; + + $buildSubject = [ + 'amount' => 123.45 + ]; + + $this->assertEquals($expected, $this->builder->build($buildSubject)); + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/AuthenticationDataBuilderTest.php b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/AuthenticationDataBuilderTest.php new file mode 100644 index 00000000000..e9588e51b0f --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/AuthenticationDataBuilderTest.php @@ -0,0 +1,78 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Test\Unit\Gateway\Request; + +use Magento\AuthorizenetAcceptjs\Gateway\Config; +use Magento\AuthorizenetAcceptjs\Gateway\Request\AuthenticationDataBuilder; +use Magento\AuthorizenetAcceptjs\Gateway\SubjectReader; +use Magento\Payment\Gateway\Data\PaymentDataObjectInterface; +use Magento\Sales\Model\Order\Payment; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class AuthenticationDataBuilderTest extends TestCase +{ + /** + * @var AuthenticationDataBuilder + */ + private $builder; + + /** + * @var Payment|MockObject + */ + private $paymentMock; + + /** + * @var Payment|MockObject + */ + private $paymentDOMock; + + /** + * @var SubjectReader|MockObject + */ + private $subjectReaderMock; + + /** + * @var Config|MockObject + */ + private $configMock; + + protected function setUp() + { + $this->configMock = $this->createMock(Config::class); + $this->paymentDOMock = $this->createMock(PaymentDataObjectInterface::class); + $this->paymentMock = $this->createMock(Payment::class); + /** @var MockObject|SubjectReader subjectReaderMock */ + $this->subjectReaderMock = $this->createMock(SubjectReader::class); + + $this->builder = new AuthenticationDataBuilder($this->subjectReaderMock, $this->configMock); + } + + public function testBuild() + { + $this->configMock->method('getLoginId') + ->willReturn('myloginid'); + $this->configMock->method('getTransactionKey') + ->willReturn('mytransactionkey'); + + $expected = [ + 'merchantAuthentication' => [ + 'name' => 'myloginid', + 'transactionKey' => 'mytransactionkey' + ] + ]; + + $buildSubject = []; + + $this->subjectReaderMock->method('readStoreId') + ->with($buildSubject) + ->willReturn(123); + + $this->assertEquals($expected, $this->builder->build($buildSubject)); + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/AuthorizationDataBuilderTest.php b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/AuthorizationDataBuilderTest.php new file mode 100644 index 00000000000..438d681a2b5 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/AuthorizationDataBuilderTest.php @@ -0,0 +1,70 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Test\Unit\Gateway\Request; + +use Magento\AuthorizenetAcceptjs\Gateway\Request\AuthorizeDataBuilder; +use Magento\AuthorizenetAcceptjs\Gateway\SubjectReader; +use Magento\AuthorizenetAcceptjs\Model\PassthroughDataObject; +use Magento\Payment\Gateway\Data\PaymentDataObjectInterface; +use Magento\Sales\Model\Order\Payment; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class AuthorizationDataBuilderTest extends TestCase +{ + /** + * @var AuthorizeDataBuilder + */ + private $builder; + + /** + * @var Payment|MockObject + */ + private $paymentMock; + + /** + * @var Payment|MockObject + */ + private $paymentDOMock; + + /** + * @var PassthroughDataObject + */ + private $passthroughData; + + protected function setUp() + { + $this->paymentDOMock = $this->createMock(PaymentDataObjectInterface::class); + $this->paymentMock = $this->createMock(Payment::class); + $this->paymentDOMock->method('getPayment') + ->willReturn($this->paymentMock); + $this->passthroughData = new PassthroughDataObject(); + + $this->builder = new AuthorizeDataBuilder( + new SubjectReader(), + $this->passthroughData + ); + } + + public function testBuildWillAddTransactionType() + { + $expected = [ + 'transactionRequest' => [ + 'transactionType' => 'authOnlyTransaction' + ] + ]; + + $buildSubject = [ + 'store_id' => 123, + 'payment' => $this->paymentDOMock, + ]; + + $this->assertEquals($expected, $this->builder->build($buildSubject)); + $this->assertEquals('authOnlyTransaction', $this->passthroughData->getData('transactionType')); + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/CaptureDataBuilderTest.php b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/CaptureDataBuilderTest.php new file mode 100644 index 00000000000..537a685f1ff --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/CaptureDataBuilderTest.php @@ -0,0 +1,77 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Test\Unit\Gateway\Request; + +use Magento\AuthorizenetAcceptjs\Gateway\Request\CaptureDataBuilder; +use Magento\AuthorizenetAcceptjs\Gateway\SubjectReader; +use Magento\AuthorizenetAcceptjs\Model\PassthroughDataObject; +use Magento\Payment\Gateway\Data\PaymentDataObjectInterface; +use Magento\Sales\Model\Order\Payment; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class CaptureDataBuilderTest extends TestCase +{ + /** + * @var CaptureDataBuilder + */ + private $builder; + + /** + * @var Payment|MockObject + */ + private $paymentMock; + + /** + * @var Payment|MockObject + */ + private $paymentDOMock; + + /** + * @var PassthroughDataObject + */ + private $passthroughData; + + protected function setUp() + { + $this->paymentDOMock = $this->createMock(PaymentDataObjectInterface::class); + $this->paymentMock = $this->createMock(Payment::class); + $this->paymentDOMock->method('getPayment') + ->willReturn($this->paymentMock); + $this->passthroughData = new PassthroughDataObject(); + + $this->builder = new CaptureDataBuilder( + new SubjectReader(), + $this->passthroughData + ); + } + + public function testBuildWillCaptureWhenAuthorizeTransactionExists() + { + $transactionMock = $this->createMock(Payment\Transaction::class); + $transactionMock->method('getAdditionalInformation') + ->with('real_transaction_id') + ->willReturn('prevtrans'); + $this->paymentMock->method('getAuthorizationTransaction') + ->willReturn($transactionMock); + + $expected = [ + 'transactionRequest' => [ + 'transactionType' => 'priorAuthCaptureTransaction', + 'refTransId' => 'prevtrans' + ] + ]; + + $buildSubject = [ + 'payment' => $this->paymentDOMock, + ]; + + $this->assertEquals($expected, $this->builder->build($buildSubject)); + $this->assertEquals('priorAuthCaptureTransaction', $this->passthroughData->getData('transactionType')); + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/CustomSettingsBuilderTest.php b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/CustomSettingsBuilderTest.php new file mode 100644 index 00000000000..be7dd7eca17 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/CustomSettingsBuilderTest.php @@ -0,0 +1,74 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Test\Unit\Gateway\Request; + +use Magento\AuthorizenetAcceptjs\Gateway\Config; +use Magento\AuthorizenetAcceptjs\Gateway\Request\CustomSettingsBuilder; +use Magento\AuthorizenetAcceptjs\Gateway\SubjectReader; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class CustomSettingsBuilderTest extends TestCase +{ + /** + * @var CustomSettingsBuilder + */ + private $builder; + + /** + * @var SubjectReader|MockObject + */ + private $subjectReaderMock; + + /** + * @var Config|MockObject + */ + private $configMock; + + protected function setUp() + { + $this->configMock = $this->createMock(Config::class); + /** @var MockObject|SubjectReader subjectReaderMock */ + $this->subjectReaderMock = $this->createMock(SubjectReader::class); + $this->subjectReaderMock->method('readStoreId') + ->willReturn('123'); + + $this->builder = new CustomSettingsBuilder($this->subjectReaderMock, $this->configMock); + } + + public function testBuildWithEmailCustomerDisabled() + { + $this->configMock->method('shouldEmailCustomer') + ->with('123') + ->willReturn(false); + + $this->assertEquals([], $this->builder->build([])); + } + + public function testBuildWithEmailCustomerEnabled() + { + $this->configMock->method('shouldEmailCustomer') + ->with('123') + ->willReturn(true); + + $expected = [ + 'transactionRequest' => [ + 'transactionSettings' => [ + 'setting' => [ + [ + 'settingName' => 'emailCustomer', + 'settingValue' => 'true' + ] + ] + ] + ] + ]; + + $this->assertEquals($expected, $this->builder->build([])); + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/CustomerDataBuilderTest.php b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/CustomerDataBuilderTest.php new file mode 100644 index 00000000000..7c9116cad54 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/CustomerDataBuilderTest.php @@ -0,0 +1,79 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Test\Unit\Gateway\Request; + +use Magento\AuthorizenetAcceptjs\Gateway\Request\CustomerDataBuilder; +use Magento\AuthorizenetAcceptjs\Gateway\SubjectReader; +use Magento\Payment\Gateway\Data\AddressAdapterInterface; +use Magento\Payment\Gateway\Data\OrderAdapterInterface; +use Magento\Payment\Gateway\Data\PaymentDataObjectInterface; +use Magento\Sales\Model\Order\Payment; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class CustomerDataBuilderTest extends TestCase +{ + /** + * @var CustomerDataBuilder + */ + private $builder; + + /** + * @var Payment|MockObject + */ + private $paymentMock; + + /** + * @var Payment|MockObject + */ + private $paymentDOMock; + + /** + * @var OrderAdapterInterface|MockObject + */ + private $orderMock; + + protected function setUp() + { + $this->paymentDOMock = $this->createMock(PaymentDataObjectInterface::class); + $this->paymentMock = $this->createMock(Payment::class); + $this->paymentDOMock->method('getPayment') + ->willReturn($this->paymentMock); + $this->orderMock = $this->createMock(OrderAdapterInterface::class); + $this->paymentDOMock->method('getOrder') + ->willReturn($this->orderMock); + + $this->builder = new CustomerDataBuilder(new SubjectReader()); + } + + public function testBuild() + { + $addressAdapterMock = $this->createMock(AddressAdapterInterface::class); + $addressAdapterMock->method('getEmail') + ->willReturn('foo@bar.com'); + $this->orderMock->method('getBillingAddress') + ->willReturn($addressAdapterMock); + $this->orderMock->method('getCustomerId') + ->willReturn('123'); + + $expected = [ + 'transactionRequest' => [ + 'customer' => [ + 'id' => '123', + 'email' => 'foo@bar.com' + ] + ] + ]; + + $buildSubject = [ + 'payment' => $this->paymentDOMock, + ]; + + $this->assertEquals($expected, $this->builder->build($buildSubject)); + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/OrderDataBuilderTest.php b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/OrderDataBuilderTest.php new file mode 100644 index 00000000000..d66421d48ca --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/OrderDataBuilderTest.php @@ -0,0 +1,74 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Test\Unit\Gateway\Request; + +use Magento\AuthorizenetAcceptjs\Gateway\Request\OrderDataBuilder; +use Magento\AuthorizenetAcceptjs\Gateway\SubjectReader; +use Magento\Payment\Gateway\Data\PaymentDataObjectInterface; +use Magento\Payment\Model\InfoInterface; +use Magento\Payment\Gateway\Data\OrderAdapterInterface; +use Magento\Sales\Model\Order\Payment; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class OrderDataBuilderTest extends TestCase +{ + /** + * @var OrderDataBuilder + */ + private $builder; + + /** + * @var InfoInterface|MockObject + */ + private $paymentMock; + + /** + * @var PaymentDataObjectInterface|MockObject + */ + private $paymentDOMock; + + /** + * @var OrderAdapterInterface|MockObject + */ + private $orderMock; + + protected function setUp() + { + $this->paymentDOMock = $this->createMock(PaymentDataObjectInterface::class); + $this->paymentMock = $this->createMock(Payment::class); + $this->paymentDOMock->method('getPayment') + ->willReturn($this->paymentMock); + $this->orderMock = $this->createMock(OrderAdapterInterface::class); + $this->paymentDOMock->method('getOrder') + ->willReturn($this->orderMock); + + $this->builder = new OrderDataBuilder(new SubjectReader()); + } + + public function testBuild() + { + $this->orderMock->method('getOrderIncrementId') + ->willReturn('10000015'); + + $expected = [ + 'transactionRequest' => [ + 'order' => [ + 'invoiceNumber' => '10000015' + ] + ] + ]; + + $buildSubject = [ + 'payment' => $this->paymentDOMock, + 'order' => $this->orderMock, + ]; + + $this->assertEquals($expected, $this->builder->build($buildSubject)); + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/PassthroughDataBuilderTest.php b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/PassthroughDataBuilderTest.php new file mode 100644 index 00000000000..f4c5f56efe8 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/PassthroughDataBuilderTest.php @@ -0,0 +1,52 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Test\Unit\Gateway\Request; + +use Magento\AuthorizenetAcceptjs\Gateway\Request\PassthroughDataBuilder; +use Magento\AuthorizenetAcceptjs\Model\PassthroughDataObject; +use PHPUnit\Framework\TestCase; + +class PassthroughDataBuilderTest extends TestCase +{ + public function testBuild() + { + $passthroughData = new PassthroughDataObject([ + 'foo' => 'bar', + 'baz' => 'bash' + ]); + $builder = new PassthroughDataBuilder($passthroughData); + + $expected = [ + 'transactionRequest' => [ + 'userFields' => [ + 'userField' => [ + [ + 'name' => 'foo', + 'value' => 'bar' + ], + [ + 'name' => 'baz', + 'value' => 'bash' + ], + ] + ] + ] + ]; + + $this->assertEquals($expected, $builder->build([])); + } + + public function testBuildWithNoData() + { + $passthroughData = new PassthroughDataObject(); + $builder = new PassthroughDataBuilder($passthroughData); + $expected = []; + + $this->assertEquals($expected, $builder->build([])); + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/PaymentDataBuilderTest.php b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/PaymentDataBuilderTest.php new file mode 100644 index 00000000000..cf3842b8947 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/PaymentDataBuilderTest.php @@ -0,0 +1,72 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Test\Unit\Gateway\Request; + +use Magento\AuthorizenetAcceptjs\Gateway\Request\PaymentDataBuilder; +use Magento\AuthorizenetAcceptjs\Gateway\SubjectReader; +use Magento\Payment\Gateway\Data\PaymentDataObjectInterface; +use Magento\Sales\Model\Order; +use Magento\Sales\Model\Order\Payment; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class PaymentDataBuilderTest extends TestCase +{ + /** + * @var PaymentDataBuilder + */ + private $builder; + + /** + * @var Payment|MockObject + */ + private $paymentMock; + + /** + * @var Payment|MockObject + */ + private $paymentDOMock; + + protected function setUp() + { + $this->paymentDOMock = $this->createMock(PaymentDataObjectInterface::class); + $this->paymentMock = $this->createMock(Payment::class); + $this->orderMock = $this->createMock(Order::class); + $this->paymentDOMock->method('getPayment') + ->willReturn($this->paymentMock); + + $this->builder = new PaymentDataBuilder(new SubjectReader()); + } + + public function testBuild() + { + $this->paymentMock->method('getAdditionalInformation') + ->willReturnMap([ + ['opaqueDataDescriptor', 'foo'], + ['opaqueDataValue', 'bar'] + ]); + + $expected = [ + 'transactionRequest' => [ + 'payment' => [ + 'opaqueData' => [ + 'dataDescriptor' => 'foo', + 'dataValue' => 'bar' + ] + ] + ] + ]; + + $buildSubject = [ + 'payment' => $this->paymentDOMock, + 'amount' => 123.45 + ]; + + $this->assertEquals($expected, $this->builder->build($buildSubject)); + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/PoDataBuilderTest.php b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/PoDataBuilderTest.php new file mode 100644 index 00000000000..97b51c1e180 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/PoDataBuilderTest.php @@ -0,0 +1,61 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Test\Unit\Gateway\Request; + +use Magento\AuthorizenetAcceptjs\Gateway\Request\PoDataBuilder; +use Magento\AuthorizenetAcceptjs\Gateway\SubjectReader; +use Magento\Payment\Gateway\Data\PaymentDataObjectInterface; +use Magento\Payment\Model\InfoInterface; +use Magento\Sales\Model\Order\Payment; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class PoDataBuilderTest extends TestCase +{ + /** + * @var PoDataBuilder + */ + private $builder; + + /** + * @var InfoInterface|MockObject + */ + private $paymentMock; + + /** + * @var PaymentDataObjectInterface|MockObject + */ + private $paymentDOMock; + + protected function setUp() + { + $this->paymentDOMock = $this->createMock(PaymentDataObjectInterface::class); + $this->paymentMock = $this->createMock(Payment::class); + $this->paymentDOMock->method('getPayment') + ->willReturn($this->paymentMock); + + $this->builder = new PoDataBuilder(new SubjectReader()); + } + + public function testBuild() + { + $this->paymentMock->method('getPoNumber') + ->willReturn('abc'); + + $expected = [ + 'transactionRequest' => [ + 'poNumber' => 'abc' + ] + ]; + + $buildSubject = [ + 'payment' => $this->paymentDOMock, + ]; + $this->assertEquals($expected, $this->builder->build($buildSubject)); + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/RefundPaymentDataBuilderTest.php b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/RefundPaymentDataBuilderTest.php new file mode 100644 index 00000000000..c1879b3df83 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/RefundPaymentDataBuilderTest.php @@ -0,0 +1,70 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Test\Unit\Gateway\Request; + +use Magento\AuthorizenetAcceptjs\Gateway\Request\RefundPaymentDataBuilder; +use Magento\AuthorizenetAcceptjs\Gateway\SubjectReader; +use Magento\Payment\Gateway\Data\PaymentDataObjectInterface; +use Magento\Sales\Model\Order\Payment; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class RefundPaymentDataBuilderTest extends TestCase +{ + /** + * @var RefundPaymentDataBuilder + */ + private $builder; + + /** + * @var Payment|MockObject + */ + private $paymentMock; + + /** + * @var Payment|MockObject + */ + private $paymentDOMock; + + protected function setUp() + { + $this->paymentDOMock = $this->createMock(PaymentDataObjectInterface::class); + $this->paymentMock = $this->createMock(Payment::class); + $this->paymentDOMock->method('getPayment') + ->willReturn($this->paymentMock); + + $this->builder = new RefundPaymentDataBuilder( + new SubjectReader() + ); + } + + public function testBuild() + { + $this->paymentMock->method('getAdditionalInformation') + ->with('ccLast4') + ->willReturn('1111'); + + $expected = [ + 'transactionRequest' => [ + 'payment' => [ + 'creditCard' => [ + 'cardNumber' => '1111', + 'expirationDate' => 'XXXX' + ] + ] + ] + ]; + + $buildSubject = [ + 'payment' => $this->paymentDOMock, + 'amount' => 123.45 + ]; + + $this->assertEquals($expected, $this->builder->build($buildSubject)); + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/RefundReferenceTransactionDataBuilderTest.php b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/RefundReferenceTransactionDataBuilderTest.php new file mode 100644 index 00000000000..cf1803005ac --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/RefundReferenceTransactionDataBuilderTest.php @@ -0,0 +1,69 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Test\Unit\Gateway\Request; + +use Magento\AuthorizenetAcceptjs\Gateway\Request\RefundReferenceTransactionDataBuilder; +use Magento\AuthorizenetAcceptjs\Gateway\SubjectReader; +use Magento\Sales\Model\Order\Payment\Transaction; +use PHPUnit\Framework\TestCase; +use Magento\Payment\Gateway\Data\PaymentDataObjectInterface; +use Magento\Sales\Model\Order; +use Magento\Sales\Model\Order\Payment; +use PHPUnit\Framework\MockObject\MockObject; + +class RefundReferenceTransactionDataBuilderTest extends TestCase +{ + /** + * @var RefundReferenceTransactionDataBuilder + */ + private $builder; + + /** + * @var Payment|MockObject + */ + private $paymentMock; + + /** + * @var Payment|MockObject + */ + private $paymentDOMock; + + protected function setUp() + { + $this->paymentDOMock = $this->createMock(PaymentDataObjectInterface::class); + $this->paymentMock = $this->createMock(Payment::class); + $this->orderMock = $this->createMock(Order::class); + $this->paymentDOMock->method('getPayment') + ->willReturn($this->paymentMock); + + $this->builder = new RefundReferenceTransactionDataBuilder(new SubjectReader()); + } + + public function testBuild() + { + $transactionMock = $this->createMock(Transaction::class); + + $this->paymentMock->method('getAuthorizationTransaction') + ->willReturn($transactionMock); + + $transactionMock->method('getParentTxnId') + ->willReturn('foo'); + + $expected = [ + 'transactionRequest' => [ + 'refTransId' => 'foo' + ] + ]; + + $buildSubject = [ + 'payment' => $this->paymentDOMock, + ]; + + $this->assertEquals($expected, $this->builder->build($buildSubject)); + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/RefundTransactionTypeDataBuilderTest.php b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/RefundTransactionTypeDataBuilderTest.php new file mode 100644 index 00000000000..4e0f5f75fb9 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/RefundTransactionTypeDataBuilderTest.php @@ -0,0 +1,30 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Test\Unit\Gateway\Request; + +use Magento\AuthorizenetAcceptjs\Gateway\Request\RefundTransactionTypeDataBuilder; +use PHPUnit\Framework\TestCase; + +class RefundTransactionTypeDataBuilderTest extends TestCase +{ + private const REQUEST_TYPE_REFUND = 'refundTransaction'; + + public function testBuild() + { + $builder = new RefundTransactionTypeDataBuilder(); + + $expected = [ + 'transactionRequest' => [ + 'transactionType' => self::REQUEST_TYPE_REFUND + ] + ]; + + $this->assertEquals($expected, $builder->build([])); + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/RequestTypeBuilderTest.php b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/RequestTypeBuilderTest.php new file mode 100644 index 00000000000..cb03dfc3dac --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/RequestTypeBuilderTest.php @@ -0,0 +1,36 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Test\Unit\Gateway\Request; + +use Magento\AuthorizenetAcceptjs\Gateway\Request\AuthenticationDataBuilder; +use Magento\AuthorizenetAcceptjs\Gateway\Request\RequestTypeBuilder; +use PHPUnit\Framework\TestCase; + +class RequestTypeBuilderTest extends TestCase +{ + /** + * @var AuthenticationDataBuilder + */ + private $builder; + + protected function setUp() + { + $this->builder = new RequestTypeBuilder('foo'); + } + + public function testBuild() + { + $expected = [ + 'payload_type' => 'foo' + ]; + + $buildSubject = []; + $this->assertEquals($expected, $this->builder->build($buildSubject)); + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/SaleDataBuilderTest.php b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/SaleDataBuilderTest.php new file mode 100644 index 00000000000..407b9bc85a2 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/SaleDataBuilderTest.php @@ -0,0 +1,70 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Test\Unit\Gateway\Request; + +use Magento\AuthorizenetAcceptjs\Gateway\Request\SaleDataBuilder; +use Magento\AuthorizenetAcceptjs\Gateway\SubjectReader; +use Magento\AuthorizenetAcceptjs\Model\PassthroughDataObject; +use Magento\Payment\Gateway\Data\PaymentDataObjectInterface; +use Magento\Sales\Model\Order\Payment; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class SaleDataBuilderTest extends TestCase +{ + /** + * @var SaleDataBuilder + */ + private $builder; + + /** + * @var Payment|MockObject + */ + private $paymentMock; + + /** + * @var Payment|MockObject + */ + private $paymentDOMock; + + /** + * @var PassthroughDataObject + */ + private $passthroughData; + + protected function setUp() + { + $this->paymentDOMock = $this->createMock(PaymentDataObjectInterface::class); + $this->paymentMock = $this->createMock(Payment::class); + $this->paymentDOMock->method('getPayment') + ->willReturn($this->paymentMock); + $this->passthroughData = new PassthroughDataObject(); + + $this->builder = new SaleDataBuilder( + new SubjectReader(), + $this->passthroughData + ); + } + + public function testBuildWillAddTransactionType() + { + $expected = [ + 'transactionRequest' => [ + 'transactionType' => 'authCaptureTransaction' + ] + ]; + + $buildSubject = [ + 'store_id' => 123, + 'payment' => $this->paymentDOMock, + ]; + + $this->assertEquals($expected, $this->builder->build($buildSubject)); + $this->assertEquals('authCaptureTransaction', $this->passthroughData->getData('transactionType')); + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/ShippingDataBuilderTest.php b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/ShippingDataBuilderTest.php new file mode 100644 index 00000000000..d6525e610a2 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/ShippingDataBuilderTest.php @@ -0,0 +1,75 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Test\Unit\Gateway\Request; + +use Magento\AuthorizenetAcceptjs\Gateway\Request\ShippingDataBuilder; +use Magento\AuthorizenetAcceptjs\Gateway\SubjectReader; +use Magento\Payment\Gateway\Data\PaymentDataObjectInterface; +use Magento\Sales\Model\Order; +use Magento\Sales\Model\Order\Payment; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class ShippingDataBuilderTest extends TestCase +{ + /** + * @var v + */ + private $builder; + + /** + * @var Payment|MockObject + */ + private $paymentMock; + + /** + * @var Payment|MockObject + */ + private $paymentDOMock; + + /** + * @var Order + */ + private $orderMock; + + protected function setUp() + { + $this->paymentDOMock = $this->createMock(PaymentDataObjectInterface::class); + $this->paymentMock = $this->createMock(Payment::class); + $this->orderMock = $this->createMock(Order::class); + $this->paymentDOMock->method('getPayment') + ->willReturn($this->paymentMock); + $this->paymentDOMock->method('getOrder') + ->willReturn($this->orderMock); + + $this->builder = new ShippingDataBuilder( + new SubjectReader() + ); + } + + public function testBuild() + { + $this->orderMock->method('getBaseShippingAmount') + ->willReturn('43.12'); + + $expected = [ + 'transactionRequest' => [ + 'shipping' => [ + 'amount' => '43.12' + ] + ] + ]; + + $buildSubject = [ + 'payment' => $this->paymentDOMock, + 'order' => $this->orderMock, + ]; + + $this->assertEquals($expected, $this->builder->build($buildSubject)); + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/SolutionDataBuilderTest.php b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/SolutionDataBuilderTest.php new file mode 100644 index 00000000000..1b06546c2ea --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/SolutionDataBuilderTest.php @@ -0,0 +1,75 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Test\Unit\Gateway\Request; + +use Magento\AuthorizenetAcceptjs\Gateway\Config; +use Magento\AuthorizenetAcceptjs\Gateway\Request\SolutionDataBuilder; +use Magento\AuthorizenetAcceptjs\Gateway\SubjectReader; +use Magento\Payment\Gateway\Data\PaymentDataObjectInterface; +use Magento\Sales\Model\Order\Payment; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class SolutionDataBuilderTest extends TestCase +{ + /** + * @var SolutionDataBuilder + */ + private $builder; + + /** + * @var Payment|MockObject + */ + private $paymentMock; + + /** + * @var Payment|MockObject + */ + private $paymentDOMock; + + /** + * @var SubjectReader|MockObject + */ + private $subjectReaderMock; + + /** + * @var Config|MockObject + */ + private $configMock; + + protected function setUp() + { + $this->configMock = $this->createMock(Config::class); + $this->paymentDOMock = $this->createMock(PaymentDataObjectInterface::class); + $this->paymentMock = $this->createMock(Payment::class); + /** @var MockObject|SubjectReader subjectReaderMock */ + $this->subjectReaderMock = $this->createMock(SubjectReader::class); + + $this->builder = new SolutionDataBuilder($this->subjectReaderMock, $this->configMock); + } + + public function testBuild() + { + $this->subjectReaderMock->method('readStoreId') + ->willReturn('123'); + $this->configMock->method('getSolutionId') + ->with('123') + ->willReturn('solutionid'); + + $expected = [ + 'transactionRequest' => [ + 'solution' => [ + 'id' => 'solutionid', + ] + ] + ]; + + $buildSubject = []; + $this->assertEquals($expected, $this->builder->build($buildSubject)); + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/StoreConfigBuilderTest.php b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/StoreConfigBuilderTest.php new file mode 100644 index 00000000000..2ed0cb13ed6 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/StoreConfigBuilderTest.php @@ -0,0 +1,68 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Test\Unit\Gateway\Request; + +use Magento\AuthorizenetAcceptjs\Gateway\Request\StoreConfigBuilder; +use Magento\AuthorizenetAcceptjs\Gateway\SubjectReader; +use Magento\Payment\Gateway\Data\PaymentDataObjectInterface; +use Magento\Payment\Model\InfoInterface; +use Magento\Payment\Gateway\Data\OrderAdapterInterface; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class StoreConfigBuilderTest extends TestCase +{ + /** + * @var StoreConfigBuilder + */ + private $builder; + + /** + * @var InfoInterface|MockObject + */ + private $paymentMock; + + /** + * @var PaymentDataObjectInterface|MockObject + */ + private $paymentDOMock; + + /** + * @var OrderAdapterInterface|MockObject + */ + private $orderMock; + + protected function setUp() + { + $this->paymentDOMock = $this->createMock(PaymentDataObjectInterface::class); + $this->paymentMock = $this->createMock(InfoInterface::class); + $this->paymentDOMock->method('getPayment') + ->willReturn($this->paymentMock); + $this->orderMock = $this->createMock(OrderAdapterInterface::class); + $this->paymentDOMock->method('getOrder') + ->willReturn($this->orderMock); + + $this->builder = new StoreConfigBuilder(new SubjectReader()); + } + + public function testBuild() + { + $this->orderMock->method('getStoreID') + ->willReturn(123); + + $expected = [ + 'store_id' => 123 + ]; + + $buildSubject = [ + 'payment' => $this->paymentDOMock, + ]; + $this->assertEquals($expected, $this->builder->build($buildSubject)); + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/TransactionDetailsDataBuilderTest.php b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/TransactionDetailsDataBuilderTest.php new file mode 100644 index 00000000000..03c036c0271 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/TransactionDetailsDataBuilderTest.php @@ -0,0 +1,89 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Test\Unit\Gateway\Request; + +use Magento\AuthorizenetAcceptjs\Gateway\Request\TransactionDetailsDataBuilder; +use Magento\AuthorizenetAcceptjs\Gateway\SubjectReader; +use Magento\Sales\Model\Order\Payment\Transaction; +use PHPUnit\Framework\TestCase; +use Magento\Payment\Gateway\Data\PaymentDataObjectInterface; +use Magento\Sales\Model\Order; +use Magento\Sales\Model\Order\Payment; +use PHPUnit\Framework\MockObject\MockObject; + +class TransactionDetailsDataBuilderTest extends TestCase +{ + /** + * @var TransactionDetailsDataBuilder + */ + private $builder; + + /** + * @var Payment|MockObject + */ + private $paymentMock; + + /** + * @var Payment|MockObject + */ + private $paymentDOMock; + + protected function setUp() + { + $this->paymentDOMock = $this->createMock(PaymentDataObjectInterface::class); + $this->paymentMock = $this->createMock(Payment::class); + $this->orderMock = $this->createMock(Order::class); + $this->paymentDOMock->method('getPayment') + ->willReturn($this->paymentMock); + + $this->builder = new TransactionDetailsDataBuilder(new SubjectReader()); + } + + public function testBuild() + { + $transactionMock = $this->createMock(Transaction::class); + + $this->paymentMock->method('getAuthorizationTransaction') + ->willReturn($transactionMock); + + $transactionMock->method('getParentTxnId') + ->willReturn('foo'); + + $expected = [ + 'transId' => 'foo' + ]; + + $buildSubject = [ + 'payment' => $this->paymentDOMock, + ]; + + $this->assertEquals($expected, $this->builder->build($buildSubject)); + } + + public function testBuildWithIncludedTransactionId() + { + $transactionMock = $this->createMock(Transaction::class); + + $this->paymentMock->expects($this->never()) + ->method('getAuthorizationTransaction'); + + $transactionMock->expects($this->never()) + ->method('getParentTxnId'); + + $expected = [ + 'transId' => 'foo' + ]; + + $buildSubject = [ + 'payment' => $this->paymentDOMock, + 'transactionId' => 'foo' + ]; + + $this->assertEquals($expected, $this->builder->build($buildSubject)); + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/VoidDataBuilderTest.php b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/VoidDataBuilderTest.php new file mode 100644 index 00000000000..84460a1c744 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Request/VoidDataBuilderTest.php @@ -0,0 +1,68 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Test\Unit\Gateway\Request; + +use Magento\AuthorizenetAcceptjs\Gateway\Request\VoidDataBuilder; +use Magento\AuthorizenetAcceptjs\Gateway\SubjectReader; +use Magento\Payment\Gateway\Data\PaymentDataObjectInterface; +use Magento\Sales\Model\Order\Payment; +use Magento\Sales\Model\Order\Payment\Transaction; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class VoidDataBuilderTest extends TestCase +{ + private const REQUEST_TYPE_VOID = 'voidTransaction'; + + /** + * @var VoidDataBuilder + */ + private $builder; + + /** + * @var Payment|MockObject + */ + private $paymentMock; + + /** + * @var Payment|MockObject + */ + private $paymentDOMock; + + protected function setUp() + { + $this->paymentDOMock = $this->createMock(PaymentDataObjectInterface::class); + $this->paymentMock = $this->createMock(Payment::class); + $this->paymentDOMock->method('getPayment') + ->willReturn($this->paymentMock); + + $this->builder = new VoidDataBuilder(new SubjectReader()); + } + + public function testBuild() + { + $transactionMock = $this->createMock(Transaction::class); + $this->paymentMock->method('getAuthorizationTransaction') + ->willReturn($transactionMock); + $transactionMock->method('getParentTxnId') + ->willReturn('myref'); + + $buildSubject = [ + 'payment' => $this->paymentDOMock + ]; + + $expected = [ + 'transactionRequest' => [ + 'transactionType' => self::REQUEST_TYPE_VOID, + 'refTransId' => 'myref', + ] + ]; + $this->assertEquals($expected, $this->builder->build($buildSubject)); + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Response/CloseParentTransactionHandlerTest.php b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Response/CloseParentTransactionHandlerTest.php new file mode 100644 index 00000000000..e9929c631eb --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Response/CloseParentTransactionHandlerTest.php @@ -0,0 +1,62 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Test\Unit\Gateway\Response; + +use Magento\AuthorizenetAcceptjs\Gateway\Response\CloseParentTransactionHandler; +use Magento\AuthorizenetAcceptjs\Gateway\SubjectReader; +use Magento\Payment\Gateway\Data\PaymentDataObjectInterface; +use Magento\Payment\Model\InfoInterface; +use Magento\Sales\Model\Order\Payment; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class CloseParentTransactionHandlerTest extends TestCase +{ + /** + * @var CloseParentTransactionHandler + */ + private $handler; + + /** + * @var InfoInterface|MockObject + */ + private $paymentMock; + + /** + * @var PaymentDataObjectInterface|MockObject + */ + private $paymentDOMock; + + protected function setUp() + { + $this->paymentDOMock = $this->createMock(PaymentDataObjectInterface::class); + $this->paymentMock = $this->createMock(Payment::class); + $this->paymentDOMock->method('getPayment') + ->willReturn($this->paymentMock); + + $this->handler = new CloseParentTransactionHandler(new SubjectReader()); + } + + public function testHandleClosesTransactionByDefault() + { + $subject = [ + 'payment' => $this->paymentDOMock + ]; + $response = [ + 'transactionResponse' => [] + ]; + + // Assert the parent transaction i closed + $this->paymentMock->expects($this->once()) + ->method('setShouldCloseParentTransaction') + ->with(true); + + $this->handler->handle($subject, $response); + // Assertions are via mock expects above + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Response/CloseTransactionHandlerTest.php b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Response/CloseTransactionHandlerTest.php new file mode 100644 index 00000000000..a7093f0dac8 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Response/CloseTransactionHandlerTest.php @@ -0,0 +1,62 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Test\Unit\Gateway\Response; + +use Magento\AuthorizenetAcceptjs\Gateway\Response\CloseTransactionHandler; +use Magento\AuthorizenetAcceptjs\Gateway\SubjectReader; +use Magento\Payment\Gateway\Data\PaymentDataObjectInterface; +use Magento\Payment\Model\InfoInterface; +use Magento\Sales\Model\Order\Payment; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class CloseTransactionHandlerTest extends TestCase +{ + /** + * @var CloseTransactionHandler + */ + private $handler; + + /** + * @var InfoInterface|MockObject + */ + private $paymentMock; + + /** + * @var PaymentDataObjectInterface|MockObject + */ + private $paymentDOMock; + + protected function setUp() + { + $this->paymentDOMock = $this->createMock(PaymentDataObjectInterface::class); + $this->paymentMock = $this->createMock(Payment::class); + $this->paymentDOMock->method('getPayment') + ->willReturn($this->paymentMock); + + $this->handler = new CloseTransactionHandler(new SubjectReader()); + } + + public function testHandleClosesTransactionByDefault() + { + $subject = [ + 'payment' => $this->paymentDOMock + ]; + $response = [ + 'transactionResponse' => [] + ]; + + // Assert the transaction is closed + $this->paymentMock->expects($this->once()) + ->method('setIsTransactionClosed') + ->with(true); + + $this->handler->handle($subject, $response); + // Assertions are via mock expects above + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Response/PaymentResponseHandlerTest.php b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Response/PaymentResponseHandlerTest.php new file mode 100644 index 00000000000..d051c7d2910 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Response/PaymentResponseHandlerTest.php @@ -0,0 +1,112 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Test\Unit\Gateway\Response; + +use Magento\AuthorizenetAcceptjs\Gateway\Response\PaymentResponseHandler; +use Magento\AuthorizenetAcceptjs\Gateway\SubjectReader; +use Magento\Payment\Gateway\Data\PaymentDataObjectInterface; +use Magento\Sales\Model\Order\Payment; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class PaymentResponseHandlerTest extends TestCase +{ + private const RESPONSE_CODE_APPROVED = 1; + private const RESPONSE_CODE_HELD = 4; + + /** + * @var PaymentResponseHandler + */ + private $builder; + + /** + * @var Payment|MockObject + */ + private $paymentMock; + + /** + * @var Payment|MockObject + */ + private $paymentDOMock; + + protected function setUp() + { + $this->paymentDOMock = $this->createMock(PaymentDataObjectInterface::class); + $this->paymentMock = $this->createMock(Payment::class); + $this->paymentDOMock->method('getPayment') + ->willReturn($this->paymentMock); + + $this->builder = new PaymentResponseHandler(new SubjectReader()); + } + + public function testHandleDefaultResponse() + { + $this->paymentMock->method('getAdditionalInformation') + ->with('ccLast4') + ->willReturn('1234'); + // Assert the avs code is saved + $this->paymentMock->expects($this->once()) + ->method('setCcAvsStatus') + ->with('avshurray'); + $this->paymentMock->expects($this->once()) + ->method('setCcLast4') + ->with('1234'); + $this->paymentMock->expects($this->once()) + ->method('setIsTransactionClosed') + ->with(false); + + $response = [ + 'transactionResponse' => [ + 'avsResultCode' => 'avshurray', + 'responseCode' => self::RESPONSE_CODE_APPROVED, + ] + ]; + $subject = [ + 'payment' => $this->paymentDOMock + ]; + + $this->builder->handle($subject, $response); + // Assertions are part of mocking above + } + + public function testHandleHeldResponse() + { + // Assert the avs code is saved + $this->paymentMock->expects($this->once()) + ->method('setCcAvsStatus') + ->with('avshurray'); + $this->paymentMock->expects($this->once()) + ->method('setIsTransactionClosed') + ->with(false); + // opaque data wasn't provided + $this->paymentMock->expects($this->never()) + ->method('setAdditionalInformation'); + // Assert the payment is flagged for review + $this->paymentMock->expects($this->once()) + ->method('setIsTransactionPending') + ->with(true) + ->willReturnSelf(); + $this->paymentMock->expects($this->once()) + ->method('setIsFraudDetected') + ->with(true); + + $response = [ + 'transactionResponse' => [ + 'avsResultCode' => 'avshurray', + 'responseCode' => self::RESPONSE_CODE_HELD, + ] + ]; + $subject = [ + 'payment' => $this->paymentDOMock + ]; + + $this->builder->handle($subject, $response); + // Assertions are part of mocking above + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Response/PaymentReviewStatusHandlerTest.php b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Response/PaymentReviewStatusHandlerTest.php new file mode 100644 index 00000000000..a52a1b317fb --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Response/PaymentReviewStatusHandlerTest.php @@ -0,0 +1,130 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Test\Unit\Gateway\Response; + +use Magento\AuthorizenetAcceptjs\Gateway\Response\PaymentReviewStatusHandler; +use Magento\AuthorizenetAcceptjs\Gateway\SubjectReader; +use Magento\Payment\Gateway\Data\PaymentDataObjectInterface; +use Magento\Payment\Model\InfoInterface; +use Magento\Sales\Model\Order\Payment; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class PaymentReviewStatusHandlerTest extends TestCase +{ + /** + * @var PaymentReviewStatusHandler + */ + private $handler; + + /** + * @var InfoInterface|MockObject + */ + private $paymentMock; + + /** + * @var PaymentDataObjectInterface|MockObject + */ + private $paymentDOMock; + + protected function setUp() + { + $this->paymentDOMock = $this->createMock(PaymentDataObjectInterface::class); + $this->paymentMock = $this->createMock(Payment::class); + $this->paymentDOMock->method('getPayment') + ->willReturn($this->paymentMock); + + $this->handler = new PaymentReviewStatusHandler(new SubjectReader()); + } + + public function testApprovesPayment() + { + $subject = [ + 'payment' => $this->paymentDOMock + ]; + $response = [ + 'transaction' => [ + 'transactionStatus' => 'approvedOrSomething', + ] + ]; + + // Assert payment is handled correctly + $this->paymentMock->expects($this->exactly(2)) + ->method('setData') + ->withConsecutive( + ['is_transaction_denied', false], + ['is_transaction_approved', true] + ); + + $this->handler->handle($subject, $response); + // Assertions are via mock expects above + } + + /** + * @param string $status + * @dataProvider declinedTransactionStatusesProvider + */ + public function testDeniesPayment(string $status) + { + $subject = [ + 'payment' => $this->paymentDOMock + ]; + $response = [ + 'transaction' => [ + 'transactionStatus' => $status, + ] + ]; + + // Assert payment is handled correctly + $this->paymentMock->expects($this->exactly(2)) + ->method('setData') + ->withConsecutive( + ['is_transaction_denied', true], + ['is_transaction_approved', false] + ); + $this->handler->handle($subject, $response); + } + + /** + * @param string $status + * @dataProvider pendingTransactionStatusesProvider + */ + public function testDoesNothingWhenPending(string $status) + { + $subject = [ + 'payment' => $this->paymentDOMock + ]; + $response = [ + 'transaction' => [ + 'transactionStatus' => $status, + ] + ]; + + // Assert payment is handled correctly + $this->paymentMock->expects($this->never()) + ->method('setData'); + + $this->handler->handle($subject, $response); + } + + public function pendingTransactionStatusesProvider() + { + return [ + ['FDSPendingReview'], + ['FDSAuthorizedPendingReview'] + ]; + } + + public function declinedTransactionStatusesProvider() + { + return [ + ['void'], + ['declined'] + ]; + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Response/TransactionDetailsResponseHandlerTest.php b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Response/TransactionDetailsResponseHandlerTest.php new file mode 100644 index 00000000000..016e3a1e953 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Response/TransactionDetailsResponseHandlerTest.php @@ -0,0 +1,82 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Test\Unit\Gateway\Response; + +use Magento\AuthorizenetAcceptjs\Gateway\Config; +use Magento\AuthorizenetAcceptjs\Gateway\Response\TransactionDetailsResponseHandler; +use Magento\AuthorizenetAcceptjs\Gateway\SubjectReader; +use Magento\Payment\Gateway\Data\PaymentDataObjectInterface; +use Magento\Payment\Model\InfoInterface; +use Magento\Sales\Model\Order\Payment; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class TransactionDetailsResponseHandlerTest extends TestCase +{ + /** + * @var TransactionDetailsResponseHandler + */ + private $handler; + + /** + * @var InfoInterface|MockObject + */ + private $paymentMock; + + /** + * @var PaymentDataObjectInterface|MockObject + */ + private $paymentDOMock; + + /** + * @var Config|MockObject + */ + private $configMock; + + protected function setUp() + { + $this->paymentDOMock = $this->createMock(PaymentDataObjectInterface::class); + $this->paymentMock = $this->createMock(Payment::class); + $this->configMock = $this->createMock(Config::class); + $this->paymentDOMock->method('getPayment') + ->willReturn($this->paymentMock); + + $this->handler = new TransactionDetailsResponseHandler(new SubjectReader(), $this->configMock); + } + + public function testHandle() + { + $subject = [ + 'payment' => $this->paymentDOMock, + 'store_id' => 123, + ]; + $response = [ + 'transactionResponse' => [ + 'dontsaveme' => 'dontdoti', + 'abc' => 'foobar', + ] + ]; + + // Assert the information comes from the right store config + $this->configMock->method('getAdditionalInfoKeys') + ->with(123) + ->willReturn(['abc']); + + // Assert the payment has the most recent information always set on it + $this->paymentMock->expects($this->once()) + ->method('setAdditionalInformation') + ->with('abc', 'foobar'); + // Assert the transaction has the raw details from the transaction + $this->paymentMock->expects($this->once()) + ->method('setTransactionAdditionalInfo') + ->with('raw_details_info', ['abc' => 'foobar']); + + $this->handler->handle($subject, $response); + // Assertions are via mock expects above + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Response/TransactionIdHandlerTest.php b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Response/TransactionIdHandlerTest.php new file mode 100644 index 00000000000..710f9959184 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Response/TransactionIdHandlerTest.php @@ -0,0 +1,92 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Test\Unit\Gateway\Response; + +use Magento\AuthorizenetAcceptjs\Gateway\Response\TransactionIdHandler; +use Magento\AuthorizenetAcceptjs\Gateway\SubjectReader; +use Magento\Payment\Gateway\Data\PaymentDataObjectInterface; +use Magento\Sales\Model\Order\Payment; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class TransactionIdHandlerTest extends TestCase +{ + /** + * @var TransactionIdHandler + */ + private $builder; + + /** + * @var Payment|MockObject + */ + private $paymentMock; + + /** + * @var Payment|MockObject + */ + private $paymentDOMock; + + protected function setUp() + { + $this->paymentDOMock = $this->createMock(PaymentDataObjectInterface::class); + $this->paymentMock = $this->createMock(Payment::class); + $this->paymentDOMock->method('getPayment') + ->willReturn($this->paymentMock); + + $this->builder = new TransactionIdHandler(new SubjectReader()); + } + + public function testHandleDefaultResponse() + { + $this->paymentMock->method('getParentTransactionId') + ->willReturn(null); + // Assert the id is set + $this->paymentMock->expects($this->once()) + ->method('setTransactionId') + ->with('thetransid'); + // Assert the id is set in the additional info for later + $this->paymentMock->expects($this->once()) + ->method('setTransactionAdditionalInfo') + ->with('real_transaction_id', 'thetransid'); + + $response = [ + 'transactionResponse' => [ + 'transId' => 'thetransid', + ] + ]; + $subject = [ + 'payment' => $this->paymentDOMock + ]; + + $this->builder->handle($subject, $response); + // Assertions are part of mocking above + } + + public function testHandleDifferenceInTransactionId() + { + $this->paymentMock->method('getParentTransactionId') + ->willReturn('somethingElse'); + // Assert the id is set + $this->paymentMock->expects($this->once()) + ->method('setTransactionId') + ->with('thetransid'); + + $response = [ + 'transactionResponse' => [ + 'transId' => 'thetransid', + ] + ]; + $subject = [ + 'payment' => $this->paymentDOMock + ]; + + $this->builder->handle($subject, $response); + // Assertions are part of mocking above + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Response/VoidResponseHandlerTest.php b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Response/VoidResponseHandlerTest.php new file mode 100644 index 00000000000..f99da2b2ec9 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Response/VoidResponseHandlerTest.php @@ -0,0 +1,72 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Test\Unit\Gateway\Response; + +use Magento\AuthorizenetAcceptjs\Gateway\Response\VoidResponseHandler; +use Magento\AuthorizenetAcceptjs\Gateway\SubjectReader; +use Magento\Payment\Gateway\Data\PaymentDataObjectInterface; +use Magento\Payment\Model\InfoInterface; +use Magento\Sales\Model\Order\Payment; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class VoidResponseHandlerTest extends TestCase +{ + /** + * @var VoidResponseHandler + */ + private $handler; + + /** + * @var InfoInterface|MockObject + */ + private $paymentMock; + + /** + * @var PaymentDataObjectInterface|MockObject + */ + private $paymentDOMock; + + protected function setUp() + { + $this->paymentDOMock = $this->createMock(PaymentDataObjectInterface::class); + $this->paymentMock = $this->createMock(Payment::class); + $this->paymentDOMock->method('getPayment') + ->willReturn($this->paymentMock); + + $this->handler = new VoidResponseHandler(new SubjectReader()); + } + + public function testHandle() + { + $subject = [ + 'payment' => $this->paymentDOMock + ]; + $response = [ + 'transactionResponse' => [ + 'transId' => 'abc123', + ] + ]; + + // Assert the transaction is closed + $this->paymentMock->expects($this->once()) + ->method('setIsTransactionClosed') + ->with(true); + // Assert the parent transaction is closed + $this->paymentMock->expects($this->once()) + ->method('setShouldCloseParentTransaction') + ->with(true); + // Assert the authorize.net transaction id is saved + $this->paymentMock->expects($this->once()) + ->method('setTransactionAdditionalInfo') + ->with('real_transaction_id', 'abc123'); + + $this->handler->handle($subject, $response); + // Assertions are via mock expects above + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/SubjectReaderTest.php b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/SubjectReaderTest.php new file mode 100644 index 00000000000..42219024bad --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/SubjectReaderTest.php @@ -0,0 +1,119 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Test\Unit\Gateway; + +use Magento\AuthorizenetAcceptjs\Gateway\SubjectReader; +use Magento\Payment\Gateway\Data\OrderAdapterInterface; +use Magento\Payment\Gateway\Data\PaymentDataObjectInterface; +use PHPUnit\Framework\TestCase; + +class SubjectReaderTest extends TestCase +{ + /** + * @var SubjectReader + */ + private $subjectReader; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->subjectReader = new SubjectReader(); + } + + public function testReadPayment(): void + { + $paymentDO = $this->createMock(PaymentDataObjectInterface::class); + + $this->assertSame($paymentDO, $this->subjectReader->readPayment(['payment' => $paymentDO])); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Payment data object should be provided + */ + public function testReadPaymentThrowsExceptionWhenNotAPaymentObject(): void + { + $this->subjectReader->readPayment(['payment' => 'nope']); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Payment data object should be provided + */ + public function testReadPaymentThrowsExceptionWhenNotSet(): void + { + $this->subjectReader->readPayment([]); + } + + public function testReadResponse(): void + { + $expected = ['foo' => 'bar']; + + $this->assertSame($expected, $this->subjectReader->readResponse(['response' => $expected])); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Response does not exist + */ + public function testReadResponseThrowsExceptionWhenNotAvailable(): void + { + $this->subjectReader->readResponse([]); + } + + public function testReadStoreId(): void + { + $this->assertEquals(123, $this->subjectReader->readStoreId(['store_id' => '123'])); + } + + public function testReadStoreIdFromOrder(): void + { + $paymentDOMock = $this->createMock(PaymentDataObjectInterface::class); + $orderMock = $this->createMock(OrderAdapterInterface::class); + $paymentDOMock->method('getOrder') + ->willReturn($orderMock); + $orderMock->method('getStoreID') + ->willReturn('123'); + + $result = $this->subjectReader->readStoreId([ + 'payment' => $paymentDOMock + ]); + + $this->assertEquals(123, $result); + } + + public function testReadLoginId(): void + { + $this->assertEquals('abc', $this->subjectReader->readLoginId([ + 'merchantAuthentication' => ['name' => 'abc'] + ])); + } + + public function testReadTransactionKey(): void + { + $this->assertEquals('abc', $this->subjectReader->readTransactionKey([ + 'merchantAuthentication' => ['transactionKey' => 'abc'] + ])); + } + + public function testReadAmount(): void + { + $this->assertSame('123.12', $this->subjectReader->readAmount(['amount' => 123.12])); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Amount should be provided + */ + public function testReadAmountThrowsExceptionWhenNotAvailable(): void + { + $this->subjectReader->readAmount([]); + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Validator/GeneralResponseValidatorTest.php b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Validator/GeneralResponseValidatorTest.php new file mode 100644 index 00000000000..347cd071acc --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Validator/GeneralResponseValidatorTest.php @@ -0,0 +1,161 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Test\Unit\Gateway\Validator; + +use Magento\AuthorizenetAcceptjs\Gateway\SubjectReader; +use Magento\AuthorizenetAcceptjs\Gateway\Validator\GeneralResponseValidator; +use Magento\Payment\Gateway\Validator\ResultInterface; +use Magento\Payment\Gateway\Validator\ResultInterfaceFactory; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class GeneralResponseValidatorTest extends TestCase +{ + /** + * @var ResultInterfaceFactory|MockObject + */ + private $resultFactoryMock; + + /** + * @var GeneralResponseValidator + */ + private $validator; + + protected function setUp() + { + $this->resultFactoryMock = $this->createMock(ResultInterfaceFactory::class); + $this->validator = new GeneralResponseValidator($this->resultFactoryMock, new SubjectReader()); + } + + public function testValidateParsesSuccess() + { + $args = []; + + $this->resultFactoryMock->method('create') + ->with($this->callback(function ($a) use (&$args) { + // Spy on method call + $args = $a; + + return true; + })) + ->willReturn($this->createMock(ResultInterface::class)); + + $this->validator->validate([ + 'response' => [ + 'messages' => [ + 'resultCode' => 'Ok', + 'message' => [ + [ + 'code' => 'foo', + 'text' => 'bar' + ] + ] + ] + ] + ]); + + $this->assertTrue($args['isValid']); + $this->assertEmpty($args['errorCodes']); + $this->assertEmpty($args['failsDescription']); + } + + public function testValidateParsesErrors() + { + $args = []; + + $this->resultFactoryMock->method('create') + ->with($this->callback(function ($a) use (&$args) { + // Spy on method call + $args = $a; + + return true; + })) + ->willReturn($this->createMock(ResultInterface::class)); + + $this->validator->validate([ + 'response' => [ + 'errors' => [ + 'resultCode' => 'Error', + 'error' => [ + [ + 'errorCode' => 'foo', + 'errorText' => 'bar' + ] + ] + ] + ] + ]); + + $this->assertFalse($args['isValid']); + $this->assertSame(['foo'], $args['errorCodes']); + $this->assertSame(['bar'], $args['failsDescription']); + } + + public function testValidateParsesMessages() + { + $args = []; + + $this->resultFactoryMock->method('create') + ->with($this->callback(function ($a) use (&$args) { + // Spy on method call + $args = $a; + + return true; + })) + ->willReturn($this->createMock(ResultInterface::class)); + + $this->validator->validate([ + 'response' => [ + 'messages' => [ + 'resultCode' => 'Error', + 'message' => [ + [ + 'code' => 'foo', + 'text' => 'bar' + ] + ] + ] + ] + ]); + + $this->assertFalse($args['isValid']); + $this->assertSame(['foo'], $args['errorCodes']); + $this->assertSame(['bar'], $args['failsDescription']); + } + + public function testValidateParsesErrorsWhenOnlyOneIsReturned() + { + $args = []; + + $this->resultFactoryMock->method('create') + ->with($this->callback(function ($a) use (&$args) { + // Spy on method call + $args = $a; + + return true; + })) + ->willReturn($this->createMock(ResultInterface::class)); + + $this->validator->validate([ + 'response' => [ + 'messages' => [ + 'resultCode' => 'Error', + 'message' => [ + 'code' => 'foo', + 'text' => 'bar' + ] + ] + ] + ]); + + $this->assertFalse($args['isValid']); + $this->assertSame(['foo'], $args['errorCodes']); + $this->assertSame(['bar'], $args['failsDescription']); + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Validator/TransactionHashValidatorTest.php b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Validator/TransactionHashValidatorTest.php new file mode 100644 index 00000000000..fb3f9d0520d --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Validator/TransactionHashValidatorTest.php @@ -0,0 +1,280 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Test\Unit\Gateway\Validator; + +use Magento\AuthorizenetAcceptjs\Gateway\Config; +use Magento\AuthorizenetAcceptjs\Gateway\SubjectReader; +use Magento\AuthorizenetAcceptjs\Gateway\Validator\TransactionHashValidator; +use Magento\Payment\Gateway\Validator\ResultInterface; +use Magento\Payment\Gateway\Validator\ResultInterfaceFactory; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class TransactionHashValidatorTest extends TestCase +{ + /** + * @var ResultInterfaceFactory|MockObject + */ + private $resultFactoryMock; + + /** + * @var TransactionHashValidator + */ + private $validator; + + /** + * @var Config|MockObject + */ + private $configMock; + + /** + * @var ResultInterface + */ + private $resultMock; + + protected function setUp() + { + $this->resultFactoryMock = $this->createMock(ResultInterfaceFactory::class); + $this->configMock = $this->createMock(Config::class); + $this->resultMock = $this->createMock(ResultInterface::class); + + $this->validator = new TransactionHashValidator( + $this->resultFactoryMock, + new SubjectReader(), + $this->configMock + ); + } + + /** + * @param $response + * @param $isValid + * @param $errorCodes + * @param $errorDescriptions + * @dataProvider sha512ResponseProvider + */ + public function testValidateSha512HashScenarios( + $response, + $isValid, + $errorCodes, + $errorDescriptions + ) { + $args = []; + + $this->resultFactoryMock->method('create') + ->with($this->callback(function ($a) use (&$args) { + // Spy on method call + $args = $a; + + return true; + })) + ->willReturn($this->resultMock); + + $this->configMock->method('getTransactionSignatureKey') + ->willReturn('abc'); + $this->configMock->method('getLoginId') + ->willReturn('username'); + + $this->validator->validate($response); + + $this->assertSame($isValid, $args['isValid']); + $this->assertEquals($errorCodes, $args['errorCodes']); + $this->assertEquals($errorDescriptions, $args['failsDescription']); + } + + /** + * @param $response + * @param $isValid + * @param $errorCodes + * @param $errorDescriptions + * @dataProvider md5ResponseProvider + */ + public function testValidateMd5HashScenarios( + $response, + $isValid, + $errorCodes, + $errorDescriptions + ) { + $args = []; + + $this->resultFactoryMock->method('create') + ->with($this->callback(function ($a) use (&$args) { + // Spy on method call + $args = $a; + + return true; + })) + ->willReturn($this->resultMock); + + $this->configMock->method('getLegacyTransactionHash') + ->willReturn('abc'); + $this->configMock->method('getLoginId') + ->willReturn('username'); + + $this->validator->validate($response); + + $this->assertSame($isValid, $args['isValid']); + $this->assertEquals($errorCodes, $args['errorCodes']); + $this->assertEquals($errorDescriptions, $args['failsDescription']); + } + + public function md5ResponseProvider() + { + return [ + [ + [ + 'response' => [ + 'transactionResponse' => [ + 'transId' => '123', + 'transHash' => 'C8675D9F7BE7BE4A04C18EA1B6F7B6FD' + ] + ] + ], + true, + [], + [] + ], + [ + [ + 'response' => [ + 'transactionResponse' => [ + 'transId' => '123', + 'transHash' => 'C8675D9F7BE7BE4A04C18EA1B6F7B6FD' + ] + ] + ], + true, + [], + [] + ], + [ + [ + 'amount' => '123.00', + 'response' => [ + 'transactionResponse' => [ + 'transHash' => 'bad' + ] + ] + ], + false, + ['ETHV'], + ['The authenticity of the gateway response could not be verified.'] + ], + [ + [ + 'amount' => '123.00', + 'response' => [ + 'transactionResponse' => [ + 'refTransID' => '123', + 'transId' => '123', + 'transHash' => 'C8675D9F7BE7BE4A04C18EA1B6F7B6FD' + ] + ] + ], + true, + [], + [] + ], + ]; + } + + public function sha512ResponseProvider() + { + return [ + [ + [ + 'response' => [ + 'transactionResponse' => [ + 'transId' => '123', + 'refTransID' => '123', + 'transHashSha2' => 'CC0FF465A081D98FFC6E502C40B2DCC7655ACF591F859135B6E66558D' + . '41E3A2C654D5A2ACF4749104F3133711175C232C32676F79F70211C2984B21A33D30DEE' + ] + ] + ], + true, + [], + [] + ], + [ + [ + 'response' => [ + 'transactionResponse' => [ + 'transId' => '0', + 'refTransID' => '123', + 'transHashSha2' => '563D42F4A5189F74334088EF6A02E84F320CD8C005FB0DC436EF96084D' + . 'FAC0C76DE081DFC58A3BF825465C63B7F38E4D463025EAC44597A68C024CBBCE7A3159' + ] + ] + ], + true, + [], + [] + ], + [ + [ + 'amount' => '123.00', + 'response' => [ + 'transactionResponse' => [ + 'transId' => '0', + 'transHashSha2' => 'DEE5309078D9F7A68BA4F706FB3E58618D3991A6A5E4C39DCF9C49E693' + . '673C38BD6BB15C235263C549A6B5F0B6D7019EC729E0C275C9FEA37FB91F8B612D0A5D' + ] + ] + ], + true, + [], + [] + ], + [ + [ + 'amount' => '123.00', + 'response' => [ + 'transactionResponse' => [ + 'transId' => '123', + 'transHashSha2' => '1DBD16DED0DA02F52A22A9AD71A49F70BD2ECD42437552889912DD5CE' + . 'CBA0E09A5E8E6221DA74D98A46E5F77F7774B6D9C39CADF3E9A33D85870A6958DA7C8B2' + ] + ] + ], + true, + [], + [] + ], + [ + [ + 'amount' => '123.00', + 'response' => [ + 'transactionResponse' => [ + 'transId' => '123', + 'refTransID' => '0', + 'transHashSha2' => '1DBD16DED0DA02F52A22A9AD71A49F70BD2ECD42437552889912DD5CE' + . 'CBA0E09A5E8E6221DA74D98A46E5F77F7774B6D9C39CADF3E9A33D85870A6958DA7C8B2' + ] + ] + ], + true, + [], + [] + ], + [ + [ + 'amount' => '123.00', + 'response' => [ + 'transactionResponse' => [ + 'transHashSha2' => 'bad' + ] + ] + ], + false, + ['ETHV'], + ['The authenticity of the gateway response could not be verified.'] + ], + ]; + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Validator/TransactionResponseValidatorTest.php b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Validator/TransactionResponseValidatorTest.php new file mode 100644 index 00000000000..cef7883bd5d --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Validator/TransactionResponseValidatorTest.php @@ -0,0 +1,213 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Test\Unit\Gateway\Validator; + +use Magento\AuthorizenetAcceptjs\Gateway\SubjectReader; +use Magento\AuthorizenetAcceptjs\Gateway\Validator\TransactionResponseValidator; +use Magento\Payment\Gateway\Validator\ResultInterface; +use Magento\Payment\Gateway\Validator\ResultInterfaceFactory; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class TransactionResponseValidatorTest extends TestCase +{ + private const RESPONSE_CODE_APPROVED = 1; + private const RESPONSE_CODE_HELD = 4; + private const RESPONSE_REASON_CODE_APPROVED = 1; + private const RESPONSE_REASON_CODE_PENDING_REVIEW_AUTHORIZED = 252; + private const RESPONSE_REASON_CODE_PENDING_REVIEW = 253; + + /** + * @var ResultInterfaceFactory|MockObject + */ + private $resultFactoryMock; + + /** + * @var TransactionResponseValidator + */ + private $validator; + + /** + * @var ResultInterface + */ + private $resultMock; + + protected function setUp() + { + $this->resultFactoryMock = $this->createMock(ResultInterfaceFactory::class); + $this->resultMock = $this->createMock(ResultInterface::class); + + $this->validator = new TransactionResponseValidator( + $this->resultFactoryMock, + new SubjectReader() + ); + } + + /** + * @param $transactionResponse + * @param $isValid + * @param $errorCodes + * @param $errorMessages + * @dataProvider scenarioProvider + */ + public function testValidateScenarios($transactionResponse, $isValid, $errorCodes, $errorMessages) + { + $args = []; + + $this->resultFactoryMock->method('create') + ->with($this->callback(function ($a) use (&$args) { + // Spy on method call + $args = $a; + + return true; + })) + ->willReturn($this->resultMock); + + $this->validator->validate([ + 'response' => [ + 'transactionResponse' => $transactionResponse + ] + ]); + + $this->assertEquals($isValid, $args['isValid']); + $this->assertEquals($errorCodes, $args['errorCodes']); + $this->assertEquals($errorMessages, $args['failsDescription']); + } + + /** + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function scenarioProvider() + { + return [ + // This validator only cares about successful edge cases so test for default behavior + [ + [ + 'responseCode' => 'foo', + ], + true, + [], + [] + ], + + // Test for acceptable reason codes + [ + [ + 'responseCode' => self::RESPONSE_CODE_APPROVED, + 'messages' => [ + 'message' => [ + 'code' => self::RESPONSE_REASON_CODE_APPROVED, + ] + ] + ], + true, + [], + [] + ], + [ + [ + 'responseCode' => self::RESPONSE_CODE_APPROVED, + 'messages' => [ + 'message' => [ + 'code' => self::RESPONSE_REASON_CODE_PENDING_REVIEW, + ] + ] + ], + true, + [], + [] + ], + [ + [ + 'responseCode' => self::RESPONSE_CODE_APPROVED, + 'messages' => [ + 'message' => [ + 'code' => self::RESPONSE_REASON_CODE_PENDING_REVIEW_AUTHORIZED, + ] + ] + ], + true, + [], + [] + ], + [ + [ + 'responseCode' => self::RESPONSE_CODE_HELD, + 'messages' => [ + 'message' => [ + 'code' => self::RESPONSE_REASON_CODE_APPROVED, + ] + ] + ], + true, + [], + [] + ], + [ + [ + 'responseCode' => self::RESPONSE_CODE_HELD, + 'messages' => [ + 'message' => [ + 'code' => self::RESPONSE_REASON_CODE_PENDING_REVIEW, + ] + ] + ], + true, + [], + [] + ], + [ + [ + 'responseCode' => self::RESPONSE_CODE_HELD, + 'messages' => [ + 'message' => [ + 'code' => self::RESPONSE_REASON_CODE_PENDING_REVIEW_AUTHORIZED, + ] + ] + ], + true, + [], + [] + ], + + // Test for reason codes that aren't acceptable + [ + [ + 'responseCode' => self::RESPONSE_CODE_APPROVED, + 'messages' => [ + 'message' => [ + [ + 'description' => 'bar', + 'code' => 'foo', + ] + ] + ] + ], + false, + ['foo'], + ['bar'] + ], + [ + [ + 'responseCode' => self::RESPONSE_CODE_APPROVED, + 'messages' => [ + 'message' => [ + // Alternate, non-array sytax + 'text' => 'bar', + 'code' => 'foo', + ] + ] + ], + false, + ['foo'], + ['bar'] + ], + ]; + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Model/Ui/ConfigProviderTest.php b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Model/Ui/ConfigProviderTest.php new file mode 100644 index 00000000000..dea4557fd58 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Model/Ui/ConfigProviderTest.php @@ -0,0 +1,76 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Test\Unit\Model\Ui; + +use Magento\AuthorizenetAcceptjs\Gateway\Config; +use Magento\AuthorizenetAcceptjs\Model\Ui\ConfigProvider; +use Magento\Quote\Api\Data\CartInterface; +use PHPUnit\Framework\MockObject\Builder\InvocationMocker; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class ConfigProviderTest extends TestCase +{ + /** + * @var CartInterface|MockObject|InvocationMocker + */ + private $cart; + + /** + * @var Config|MockObject|InvocationMocker + */ + private $config; + + /** + * @var ConfigProvider + */ + private $provider; + + protected function setUp() + { + $this->cart = $this->createMock(CartInterface::class); + $this->config = $this->createMock(Config::class); + $this->provider = new ConfigProvider($this->config, $this->cart); + } + + public function testProviderRetrievesValues() + { + $this->cart->method('getStoreId') + ->willReturn('123'); + + $this->config->method('getClientKey') + ->with('123') + ->willReturn('foo'); + + $this->config->method('getLoginId') + ->with('123') + ->willReturn('bar'); + + $this->config->method('getEnvironment') + ->with('123') + ->willReturn('baz'); + + $this->config->method('isCvvEnabled') + ->with('123') + ->willReturn(false); + + $expected = [ + 'payment' => [ + Config::METHOD => [ + 'clientKey' => 'foo', + 'apiLoginID' => 'bar', + 'environment' => 'baz', + 'useCvv' => false, + ] + ] + ]; + + $this->assertEquals($expected, $this->provider->getConfig()); + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Observer/DataAssignObserverTest.php b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Observer/DataAssignObserverTest.php new file mode 100644 index 00000000000..ebb95263f54 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Observer/DataAssignObserverTest.php @@ -0,0 +1,79 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Test\Unit\Observer; + +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\AuthorizenetAcceptjs\Observer\DataAssignObserver; +use Magento\Quote\Api\Data\PaymentInterface; +use PHPUnit\Framework\TestCase; + +class DataAssignObserverTest extends TestCase +{ + public function testExecuteSetsProperData() + { + $additionalInfo = [ + 'opaqueDataDescriptor' => 'foo', + 'opaqueDataValue' => 'bar', + 'ccLast4' => '1234' + ]; + + $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->at(0)) + ->method('setAdditionalInformation') + ->with('opaqueDataDescriptor', 'foo'); + $paymentInfoModel->expects($this->at(1)) + ->method('setAdditionalInformation') + ->with('opaqueDataValue', 'bar'); + $paymentInfoModel->expects($this->at(2)) + ->method('setAdditionalInformation') + ->with('ccLast4', '1234'); + + $observer = new DataAssignObserver(); + $observer->execute($observerContainer); + } + + public function testDoestSetDataWhenEmpty() + { + $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(); + $observer->execute($observerContainer); + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Setup/Patch/Data/CopyCurrentConfigTest.php b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Setup/Patch/Data/CopyCurrentConfigTest.php new file mode 100644 index 00000000000..5ac8a6ca9b3 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Setup/Patch/Data/CopyCurrentConfigTest.php @@ -0,0 +1,149 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Test\Unit\Setup\Patch\Data; + +use Magento\AuthorizenetAcceptjs\Setup\Patch\Data\CopyCurrentConfig; +use Magento\Config\Model\ResourceModel\Config as ResourceConfig; +use Magento\Framework\App\Config; +use Magento\Framework\Encryption\Encryptor; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Setup\Module\DataSetup; +use Magento\Setup\Model\ModuleContext; +use Magento\Store\Model\StoreManagerInterface; +use Magento\Store\Model\Website; +use PHPUnit\Framework\TestCase; + +class CopyCurrentConfigTest extends TestCase +{ + /** + * @var \Magento\Framework\App\Config + */ + private $scopeConfig; + + /** + * @var \Magento\Config\Model\ResourceModel\Config + */ + private $resourceConfig; + + /** + * @var \Magento\Framework\Encryption\Encryptor + */ + private $encryptor; + + /** + * @var \Magento\Setup\Module\DataSetup + */ + private $setup; + + /** + * @var \Magento\Setup\Model\ModuleContext + */ + private $context; + + /** + * @var \Magento\Store\Model\StoreManager + */ + private $storeManager; + + /** + * @var \Magento\Store\Model\Website + */ + private $website; + + protected function setUp(): void + { + $this->scopeConfig = $this->createMock(Config::class); + $this->resourceConfig = $this->createMock(ResourceConfig::class); + $this->encryptor = $this->createMock(Encryptor::class); + $this->setup = $this->createMock(DataSetup::class); + + $this->setup->expects($this->once()) + ->method('startSetup') + ->willReturn(null); + + $this->setup->expects($this->once()) + ->method('endSetup') + ->willReturn(null); + + $this->context = $this->createMock(ModuleContext::class); + $this->storeManager = $this->createMock(StoreManagerInterface::class); + $this->website = $this->createMock(Website::class); + } + + public function testMigrateData(): void + { + $this->scopeConfig->expects($this->exactly(26)) + ->method('getValue') + ->willReturn('TestValue'); + + $this->resourceConfig->expects($this->exactly(26)) + ->method('saveConfig') + ->willReturn(null); + + $this->encryptor->expects($this->exactly(6)) + ->method('encrypt') + ->willReturn('TestValue'); + + $this->website->expects($this->once()) + ->method('getId') + ->willReturn(1); + + $this->storeManager->expects($this->once()) + ->method('getWebsites') + ->willReturn([$this->website]); + + $objectManager = new ObjectManager($this); + + $installer = $objectManager->getObject( + CopyCurrentConfig::class, + [ + 'moduleDataSetup' => $this->setup, + 'scopeConfig' => $this->scopeConfig, + 'resourceConfig' => $this->resourceConfig, + 'encryptor' => $this->encryptor, + 'storeManager' => $this->storeManager + ] + ); + + $installer->apply($this->context); + } + + public function testMigrateDataNullFields(): void + { + $this->scopeConfig->expects($this->exactly(13)) + ->method('getValue') + ->will($this->onConsecutiveCalls(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)); + + $this->resourceConfig->expects($this->exactly(10)) + ->method('saveConfig') + ->willReturn(null); + + $this->encryptor->expects($this->never()) + ->method('encrypt'); + + $this->storeManager->expects($this->once()) + ->method('getWebsites') + ->willReturn([]); + + $objectManager = new ObjectManager($this); + + $installer = $objectManager->getObject( + CopyCurrentConfig::class, + [ + 'moduleDataSetup' => $this->setup, + 'scopeConfig' => $this->scopeConfig, + 'resourceConfig' => $this->resourceConfig, + 'encryptor' => $this->encryptor, + 'storeManager' => $this->storeManager + ] + ); + + $installer->apply($this->context); + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/composer.json b/app/code/Magento/AuthorizenetAcceptjs/composer.json new file mode 100644 index 00000000000..be2cd6d4e70 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/composer.json @@ -0,0 +1,31 @@ +{ + "name": "magento/module-authorizenet-acceptjs", + "description": "N/A", + "config": { + "sort-packages": true + }, + "require": { + "php": "~7.1.3||~7.2.0", + "magento/framework": "*", + "magento/module-payment": "*", + "magento/module-sales": "*", + "magento/module-config": "*", + "magento/module-backend": "*", + "magento/module-checkout": "*", + "magento/module-store": "*", + "magento/module-quote": "*" + }, + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "Magento\\AuthorizenetAcceptjs\\": "" + } + } +} diff --git a/app/code/Magento/AuthorizenetAcceptjs/etc/adminhtml/di.xml b/app/code/Magento/AuthorizenetAcceptjs/etc/adminhtml/di.xml new file mode 100644 index 00000000000..320f8f79ee2 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/etc/adminhtml/di.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:ObjectManager/etc/config.xsd"> + <type name="Magento\AuthorizenetAcceptjs\Block\Payment"> + <arguments> + <argument name="config" xsi:type="object">Magento\AuthorizenetAcceptjs\Model\Ui\ConfigProvider</argument> + </arguments> + </type> +</config> diff --git a/app/code/Magento/AuthorizenetAcceptjs/etc/adminhtml/system.xml b/app/code/Magento/AuthorizenetAcceptjs/etc/adminhtml/system.xml new file mode 100644 index 00000000000..279a904d916 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/etc/adminhtml/system.xml @@ -0,0 +1,118 @@ +<?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="payment"> + <group id="authorizenet_acceptjs" translate="label" type="text" sortOrder="34" showInDefault="1" showInWebsite="1" showInStore="1"> + <label>Authorize.Net</label> + <field id="active" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <label>Enabled</label> + <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> + <requires> + <group id="authorizenet_acceptjs_required"/> + </requires> + </field> + <group id="required" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="5"> + <label>Basic Authorize.Net Settings</label> + <attribute type="expanded">1</attribute> + <frontend_model>Magento\Config\Block\System\Config\Form\Fieldset</frontend_model> + <field id="title" translate="label" type="text" sortOrder="10" showInDefault="10" showInWebsite="1" showInStore="1" canRestore="1"> + <label>Title</label> + <config_path>payment/authorizenet_acceptjs/title</config_path> + </field> + <field id="environment" translate="label" type="select" sortOrder="15" showInDefault="1" showInWebsite="1" showInStore="0"> + <label>Environment</label> + <source_model>Magento\AuthorizenetAcceptjs\Model\Adminhtml\Source\Environment</source_model> + <config_path>payment/authorizenet_acceptjs/environment</config_path> + </field> + <field id="payment_action" translate="label" type="select" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <label>Payment Action</label> + <source_model>Magento\AuthorizenetAcceptjs\Model\Adminhtml\Source\PaymentAction</source_model> + <config_path>payment/authorizenet_acceptjs/payment_action</config_path> + </field> + <field id="login" translate="label" type="obscure" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="0"> + <label>API Login ID</label> + <backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model> + <config_path>payment/authorizenet_acceptjs/login</config_path> + </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> + </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> + </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> + </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> + </field> + </group> + <group id="advanced" translate="label" showInDefault="1" showInWebsite="1" showInStore="0" sortOrder="20"> + <label>Advanced Authorize.Net Settings</label> + <attribute type="expanded">0</attribute> + <field id="currency" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <label>Accepted Currency</label> + <source_model>Magento\Config\Model\Config\Source\Locale\Currency</source_model> + <config_path>payment/authorizenet_acceptjs/currency</config_path> + </field> + <field id="debug" translate="label" type="select" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <label>Debug</label> + <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> + <config_path>payment/authorizenet_acceptjs/debug</config_path> + </field> + <field id="email_customer" translate="label" type="select" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <label>Email Customer</label> + <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> + <config_path>payment/authorizenet_acceptjs/email_customer</config_path> + </field> + <field id="cvv_enabled" translate="label" type="select" sortOrder="40" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <label>Enable Credit Card Verification Field</label> + <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> + <config_path>payment/authorizenet_acceptjs/cvv_enabled</config_path> + </field> + <field id="cctypes" translate="label" type="multiselect" sortOrder="50" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <label>Credit Card Types</label> + <source_model>Magento\AuthorizenetAcceptjs\Model\Adminhtml\Source\Cctype</source_model> + <config_path>payment/authorizenet_acceptjs/cctypes</config_path> + </field> + <field id="allowspecific" translate="label" type="allowspecific" sortOrder="60" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <label>Payment from Applicable Countries</label> + <source_model>Magento\Payment\Model\Config\Source\Allspecificcountries</source_model> + <config_path>payment/authorizenet_acceptjs/allowspecific</config_path> + </field> + <field id="specificcountry" translate="label" type="multiselect" sortOrder="70" showInDefault="1" showInWebsite="1" showInStore="0"> + <label>Payment from Specific Countries</label> + <source_model>Magento\Directory\Model\Config\Source\Country</source_model> + <config_path>payment/authorizenet_acceptjs/specificcountry</config_path> + </field> + <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> + </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> + </field> + <field id="sort_order" translate="label" type="text" sortOrder="100" showInDefault="1" showInWebsite="1" showInStore="0"> + <label>Sort Order</label> + <frontend_class>validate-number</frontend_class> + <config_path>payment/authorizenet_acceptjs/sort_order</config_path> + </field> + </group> + </group> + </section> + </system> +</config> diff --git a/app/code/Magento/AuthorizenetAcceptjs/etc/authorizenet_acceptjs_error_mapping.xml b/app/code/Magento/AuthorizenetAcceptjs/etc/authorizenet_acceptjs_error_mapping.xml new file mode 100644 index 00000000000..507a9b14f91 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/etc/authorizenet_acceptjs_error_mapping.xml @@ -0,0 +1,15 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<mapping xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Payment:etc/error_mapping.xsd"> + <message_list> + <message code="E00003" translate="true">Invalid request to gateway.</message> + <message code="E00007" translate="true">Invalid gateway credentials.</message> + <message code="E00027" translate="true">Transaction has been declined. Please try again later.</message> + <message code="ETHV" translate="true">The authenticity of the gateway response could not be verified.</message> + </message_list> +</mapping> diff --git a/app/code/Magento/AuthorizenetAcceptjs/etc/config.xml b/app/code/Magento/AuthorizenetAcceptjs/etc/config.xml new file mode 100644 index 00000000000..24291187c05 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/etc/config.xml @@ -0,0 +1,49 @@ +<?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> + <payment> + <authorizenet_acceptjs> + <active>0</active> + <cctypes>AE,VI,MC,DI,JCB,DN</cctypes> + <debug>0</debug> + <can_use_checkout>1</can_use_checkout> + <can_use_internal>1</can_use_internal> + <can_capture_partial>0</can_capture_partial> + <can_authorize>1</can_authorize> + <can_refund>1</can_refund> + <can_capture>1</can_capture> + <can_void>1</can_void> + <can_accept_payment>1</can_accept_payment> + <can_deny_payment>1</can_deny_payment> + <can_cancel>1</can_cancel> + <can_review_payment>1</can_review_payment> + <can_edit>1</can_edit> + <can_fetch_transaction_info>1</can_fetch_transaction_info> + <can_fetch_transaction_information>1</can_fetch_transaction_information> + <model>AuthorizenetAcceptjsFacade</model> + <email_customer>0</email_customer> + <login backend_model="Magento\Config\Model\Config\Backend\Encrypted" /> + <order_status>processing</order_status> + <payment_action>authorize</payment_action> + <title>Credit Card (Authorize.Net) + 1 + + + + + 0 + USD + production + authCode,avsResultCode,cvvResultCode,cavvResultCode + accountType,ccLast4,authCode,avsResultCode,cvvResultCode,cavvResultCode + transactionStatus,responseCode,responseReasonCode,authCode,AVSResponse,cardCodeResponse,CAVVResponse + + + + diff --git a/app/code/Magento/AuthorizenetAcceptjs/etc/di.xml b/app/code/Magento/AuthorizenetAcceptjs/etc/di.xml new file mode 100644 index 00000000000..cf10557d386 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/etc/di.xml @@ -0,0 +1,428 @@ + + + + + + Magento\AuthorizenetAcceptjs\Gateway\Config::METHOD + + + + + authorizenet_acceptjs + Magento\AuthorizenetAcceptjs\Block\Form + AuthorizenetAcceptjsInfoBlock + AuthorizenetAcceptjsValueHandlerPool + AuthorizenetAcceptjsValidatorPool + AuthorizenetAcceptjsCommandPool + + + + + + AuthorizenetAcceptjsAuthorizeCommand + AuthorizenetAcceptjsCaptureCommand + AuthorizenetAcceptjsSaleCommand + AuthorizenetAcceptjsSettleCommand + AuthorizenetAcceptjsVoidCommand + AuthorizenetAcceptjsRefundCommand + AuthorizenetAcceptjsRefundSettledCommand + AuthorizenetAcceptjsCancelCommand + AuthorizenetAcceptjsAcceptPaymentCommand + AuthorizenetAcceptjsAcceptFdsCommand + AuthorizenetAcceptjsCancelCommand + AuthorizenetAcceptjsTransactionDetailsCommand + AuthorizenetAcceptjsFetchTransactionInfoCommand + + + + + + + + + Magento\AuthorizenetAcceptjs\Gateway\Validator\GeneralResponseValidator + + + + + + + true + + + Magento\AuthorizenetAcceptjs\Gateway\Validator\GeneralResponseValidator + Magento\AuthorizenetAcceptjs\Gateway\Validator\TransactionResponseValidator + Magento\AuthorizenetAcceptjs\Gateway\Validator\TransactionHashValidator + + + + + + + Magento\AuthorizenetAcceptjs\Gateway\Validator\GeneralResponseValidator + + + + + + Magento\AuthorizenetAcceptjs\Gateway\Config + + + + + + AuthorizenetAcceptjsCountryValidator + + + + + + + + + AuthorizenetAcceptjsCommandPool + + + + + AuthorizenetAcceptjsTransactionDetailsRequest + AuthorizenetAcceptjsDefaultTransferFactory + Magento\AuthorizenetAcceptjs\Gateway\Http\Client + AuthorizenetAcceptjsTransactionDetailsValidator + + + + + AuthorizenetAcceptjsAuthorizeRequest + AuthorizenetAcceptjsDefaultTransferFactory + Magento\AuthorizenetAcceptjs\Gateway\Http\Client + AuthorizenetAcceptjsAuthorizationHandler + AuthorizenetAcceptjsTransactionValidator + AuthorizenetAcceptjsVirtualErrorMessageMapper + + + + + AuthorizenetAcceptjsAcceptsFdsRequest + AuthorizenetAcceptjsDefaultTransferFactory + Magento\AuthorizenetAcceptjs\Gateway\Http\Client + AuthorizenetAcceptjsAcceptsFdsRequestValidator + + + + + AuthorizenetAcceptjsCommandPool + + + + + AuthorizenetAcceptjsSaleRequest + AuthorizenetAcceptjsSaleHandler + + + + + AuthorizenetAcceptjsCommandPool + + + + + AuthorizenetAcceptjsRefundRequest + AuthorizenetAcceptjsRefundSettledHandler + + + + + AuthorizenetAcceptjsCommandPool + + + + + AuthorizenetAcceptjsCaptureRequest + AuthorizenetAcceptjsCaptureTransactionHandler + + + + + AuthorizenetAcceptjsVoidRequest + AuthorizenetAcceptjsDefaultTransferFactory + Magento\AuthorizenetAcceptjs\Gateway\Http\Client + AuthorizenetAcceptjsVoidHandler + AuthorizenetAcceptjsTransactionValidator + AuthorizenetAcceptjsVirtualErrorMessageMapper + + + + + AuthorizenetAcceptjsCancelHandler + + + + + + + + + Magento\AuthorizenetAcceptjs\Gateway\Response\PaymentReviewStatusHandler + + + + + + + Magento\AuthorizenetAcceptjs\Gateway\Response\TransactionIdHandler + Magento\AuthorizenetAcceptjs\Gateway\Response\PaymentResponseHandler + Magento\AuthorizenetAcceptjs\Gateway\Response\TransactionDetailsResponseHandler + + + + + + + Magento\AuthorizenetAcceptjs\Gateway\Response\CloseParentTransactionHandler + + + + + + + Magento\AuthorizenetAcceptjs\Gateway\Response\CloseParentTransactionHandler + + + + + + + Magento\AuthorizenetAcceptjs\Gateway\Response\TransactionIdHandler + Magento\AuthorizenetAcceptjs\Gateway\Response\CloseParentTransactionHandler + Magento\AuthorizenetAcceptjs\Gateway\Response\CloseTransactionHandler + + + + + + + Magento\AuthorizenetAcceptjs\Gateway\Response\VoidResponseHandler + + + + + + + Magento\AuthorizenetAcceptjs\Gateway\Response\CloseTransactionHandler + Magento\AuthorizenetAcceptjs\Gateway\Response\CloseParentTransactionHandler + + + + + + + + + + + AuthorizenetAcceptjsTransactionDetailsRequestTypeBuilder + Magento\AuthorizenetAcceptjs\Gateway\Request\StoreConfigBuilder + Magento\AuthorizenetAcceptjs\Gateway\Request\AuthenticationDataBuilder + Magento\AuthorizenetAcceptjs\Gateway\Request\TransactionDetailsDataBuilder + + + + + + + AuthorizenetAcceptjsAcceptsFdsRequestTypeBuilder + Magento\AuthorizenetAcceptjs\Gateway\Request\StoreConfigBuilder + Magento\AuthorizenetAcceptjs\Gateway\Request\AuthenticationDataBuilder + Magento\AuthorizenetAcceptjs\Gateway\Request\AcceptFdsDataBuilder + + + + + + + AuthorizenetAcceptjsTransactionRequestTypeBuilder + Magento\AuthorizenetAcceptjs\Gateway\Request\StoreConfigBuilder + Magento\AuthorizenetAcceptjs\Gateway\Request\AuthenticationDataBuilder + Magento\AuthorizenetAcceptjs\Gateway\Request\AuthorizeDataBuilder + Magento\AuthorizenetAcceptjs\Gateway\Request\AmountDataBuilder + Magento\AuthorizenetAcceptjs\Gateway\Request\PaymentDataBuilder + Magento\AuthorizenetAcceptjs\Gateway\Request\ShippingDataBuilder + Magento\AuthorizenetAcceptjs\Gateway\Request\SolutionDataBuilder + Magento\AuthorizenetAcceptjs\Gateway\Request\OrderDataBuilder + Magento\AuthorizenetAcceptjs\Gateway\Request\PoDataBuilder + Magento\AuthorizenetAcceptjs\Gateway\Request\CustomerDataBuilder + Magento\AuthorizenetAcceptjs\Gateway\Request\AddressDataBuilder + Magento\AuthorizenetAcceptjs\Gateway\Request\CustomSettingsBuilder + Magento\AuthorizenetAcceptjs\Gateway\Request\PassthroughDataBuilder + + + + + + + Magento\AuthorizenetAcceptjs\Gateway\Request\SaleDataBuilder + + + + + + + AuthorizenetAcceptjsTransactionRequestTypeBuilder + Magento\AuthorizenetAcceptjs\Gateway\Request\StoreConfigBuilder + Magento\AuthorizenetAcceptjs\Gateway\Request\AuthenticationDataBuilder + Magento\AuthorizenetAcceptjs\Gateway\Request\RefundTransactionTypeDataBuilder + Magento\AuthorizenetAcceptjs\Gateway\Request\AmountDataBuilder + Magento\AuthorizenetAcceptjs\Gateway\Request\RefundPaymentDataBuilder + Magento\AuthorizenetAcceptjs\Gateway\Request\ShippingDataBuilder + Magento\AuthorizenetAcceptjs\Gateway\Request\RefundReferenceTransactionDataBuilder + Magento\AuthorizenetAcceptjs\Gateway\Request\OrderDataBuilder + Magento\AuthorizenetAcceptjs\Gateway\Request\PoDataBuilder + Magento\AuthorizenetAcceptjs\Gateway\Request\CustomerDataBuilder + Magento\AuthorizenetAcceptjs\Gateway\Request\AddressDataBuilder + Magento\AuthorizenetAcceptjs\Gateway\Request\CustomSettingsBuilder + Magento\AuthorizenetAcceptjs\Gateway\Request\PassthroughDataBuilder + + + + + + + AuthorizenetAcceptjsTransactionRequestTypeBuilder + Magento\AuthorizenetAcceptjs\Gateway\Request\StoreConfigBuilder + Magento\AuthorizenetAcceptjs\Gateway\Request\AuthenticationDataBuilder + Magento\AuthorizenetAcceptjs\Gateway\Request\CaptureDataBuilder + Magento\AuthorizenetAcceptjs\Gateway\Request\PassthroughDataBuilder + + + + + + + AuthorizenetAcceptjsTransactionRequestTypeBuilder + Magento\AuthorizenetAcceptjs\Gateway\Request\StoreConfigBuilder + Magento\AuthorizenetAcceptjs\Gateway\Request\AuthenticationDataBuilder + Magento\AuthorizenetAcceptjs\Gateway\Request\VoidDataBuilder + Magento\AuthorizenetAcceptjs\Gateway\Request\PassthroughDataBuilder + + + + + + + + + createTransactionRequest + + + + + getTransactionDetailsRequest + + + + + updateHeldTransactionRequest + + + + + + + + authorizenet_acceptjs_error_mapping.xml + + + + + AuthorizenetAcceptjsErrorMappingConfigReader + authorizenet_acceptjs_error_mapper + + + + + AuthorizenetAcceptjsErrorMappingData + + + + + + + AuthorizenetAcceptjsPaymentReviewStatusHandler + + + + + + AuthorizenetAcceptjsCommandManager + + + + + + + 1 + 1 + 1 + 1 + 1 + 1 + + + 1 + 1 + + + + + + Magento\AuthorizenetAcceptjs\Gateway\Config + + + + + + AuthorizenetAcceptjsDefaultValueHandler + + + + + + AuthorizenetAcceptjsCommandPool + + + + + Magento\AuthorizenetAcceptjs\Gateway\Config + + + + + AuthorizenetAcceptjsLogger + + + + + + store_id + + + + + + + AuthorizenetAcceptjsRemoveStoreConfigFilter + + + + + + Magento\AuthorizenetAcceptjs\Gateway\Config + + + diff --git a/app/code/Magento/AuthorizenetAcceptjs/etc/events.xml b/app/code/Magento/AuthorizenetAcceptjs/etc/events.xml new file mode 100644 index 00000000000..93dc448d1d8 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/etc/events.xml @@ -0,0 +1,13 @@ + + + + + + + + diff --git a/app/code/Magento/AuthorizenetAcceptjs/etc/frontend/di.xml b/app/code/Magento/AuthorizenetAcceptjs/etc/frontend/di.xml new file mode 100644 index 00000000000..8b0e570abbd --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/etc/frontend/di.xml @@ -0,0 +1,30 @@ + + + + + + + 1 + + + + + + + Magento\AuthorizenetAcceptjs\Model\Ui\ConfigProvider + + + + + + + Magento\AuthorizenetAcceptjs\Gateway\Config::METHOD + + + + diff --git a/app/code/Magento/AuthorizenetAcceptjs/etc/module.xml b/app/code/Magento/AuthorizenetAcceptjs/etc/module.xml new file mode 100644 index 00000000000..6bc8fe3c4da --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/etc/module.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + diff --git a/app/code/Magento/AuthorizenetAcceptjs/etc/payment.xml b/app/code/Magento/AuthorizenetAcceptjs/etc/payment.xml new file mode 100644 index 00000000000..b8292839c3b --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/etc/payment.xml @@ -0,0 +1,15 @@ + + + + + + 1 + + + diff --git a/app/code/Magento/AuthorizenetAcceptjs/i18n/en_US.csv b/app/code/Magento/AuthorizenetAcceptjs/i18n/en_US.csv new file mode 100644 index 00000000000..da518301652 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/i18n/en_US.csv @@ -0,0 +1,21 @@ +Authorize.net,Authorize.net +"Gateway URL","Gateway URL" +"Invalid payload type.","Invalid payload type." +"Something went wrong in the payment gateway.","Something went wrong in the payment gateway." +"Merchant MD5 (deprecated","Merchant MD5 (deprecated" +"Signature Key","Signature Key" +"Basic Authorize.Net Settings","Basic Authorize.Net Settings" +"Advanced Authorie.Net Settings","Advanced Authorie.Net Settings" +"Public Client Key","Public Client Key" +"Environment","Environment" +"Production","Production" +"Sandbox","Sandbox" +"accountType","Account Type" +"authCode", "Processor Response Text" +"avsResultCode", "AVS Response Code" +"cvvResultCode","CVV Response Code" +"cavvResultCode","CAVV Response Code" +"Enable Credit Card Verification Field","Enable Credit Card Verification Field" +"ccLast4","Last 4 Digits of Card" +"There was an error while trying to process the refund.","There was an error while trying to process the refund." +"This transaction cannot be refunded with its current status.","This transaction cannot be refunded with its current status." diff --git a/app/code/Magento/AuthorizenetAcceptjs/registration.php b/app/code/Magento/AuthorizenetAcceptjs/registration.php new file mode 100644 index 00000000000..5338c9a4ddc --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/registration.php @@ -0,0 +1,11 @@ + + + + + + + Magento\AuthorizenetAcceptjs\Gateway\Config::METHOD + Magento_AuthorizenetAcceptjs::form/cc.phtml + + + + + + + diff --git a/app/code/Magento/AuthorizenetAcceptjs/view/adminhtml/layout/sales_order_create_load_block_billing_method.xml b/app/code/Magento/AuthorizenetAcceptjs/view/adminhtml/layout/sales_order_create_load_block_billing_method.xml new file mode 100644 index 00000000000..13f6d38e2b8 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/view/adminhtml/layout/sales_order_create_load_block_billing_method.xml @@ -0,0 +1,17 @@ + + + + + + + Magento\AuthorizenetAcceptjs\Gateway\Config::METHOD + Magento_AuthorizenetAcceptjs::form/cc.phtml + + + + diff --git a/app/code/Magento/AuthorizenetAcceptjs/view/adminhtml/templates/form/cc.phtml b/app/code/Magento/AuthorizenetAcceptjs/view/adminhtml/templates/form/cc.phtml new file mode 100644 index 00000000000..045bd5cfd81 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/view/adminhtml/templates/form/cc.phtml @@ -0,0 +1,93 @@ +escapeHtml($block->getMethodCode()); +$ccType = $block->getInfoData('cc_type'); +$ccExpMonth = $block->getInfoData('cc_exp_month'); +$ccExpYear = $block->getInfoData('cc_exp_year'); +?> + diff --git a/app/code/Magento/AuthorizenetAcceptjs/view/adminhtml/templates/payment/script.phtml b/app/code/Magento/AuthorizenetAcceptjs/view/adminhtml/templates/payment/script.phtml new file mode 100644 index 00000000000..6960bddf696 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/view/adminhtml/templates/payment/script.phtml @@ -0,0 +1,17 @@ + + \ No newline at end of file diff --git a/app/code/Magento/AuthorizenetAcceptjs/view/adminhtml/web/js/authorizenet.js b/app/code/Magento/AuthorizenetAcceptjs/view/adminhtml/web/js/authorizenet.js new file mode 100644 index 00000000000..0eb865d7666 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/view/adminhtml/web/js/authorizenet.js @@ -0,0 +1,196 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +define([ + 'jquery', + 'uiComponent', + 'Magento_Ui/js/modal/alert', + 'Magento_AuthorizenetAcceptjs/js/view/payment/acceptjs-client' +], function ($, Class, alert, AcceptjsClient) { + 'use strict'; + + return Class.extend({ + defaults: { + acceptjsClient: null, + $selector: null, + selector: 'edit_form', + container: 'payment_form_authorizenet_acceptjs', + active: false, + imports: { + onActiveChange: 'active' + } + }, + + /** + * @{inheritdoc} + */ + initConfig: function (config) { + this._super(); + + this.acceptjsClient = AcceptjsClient({ + environment: config.environment + }); + + return this; + }, + + /** + * @{inheritdoc} + */ + initObservable: function () { + this.$selector = $('#' + this.selector); + this._super() + .observe('active'); + + // re-init payment method events + this.$selector.off('changePaymentMethod.' + this.code) + .on('changePaymentMethod.' + this.code, this.changePaymentMethod.bind(this)); + + return this; + }, + + /** + * Enable/disable current payment method + * + * @param {Object} event + * @param {String} method + * @returns {Object} + */ + changePaymentMethod: function (event, method) { + this.active(method === this.code); + + return this; + }, + + /** + * Triggered when payment changed + * + * @param {Boolean} isActive + */ + onActiveChange: function (isActive) { + if (!isActive) { + + return; + } + + this.disableEventListeners(); + + window.order.addExcludedPaymentMethod(this.code); + + this.enableEventListeners(); + }, + + /** + * Sets the payment details on the form + * + * @param {Object} tokens + */ + setPaymentDetails: function (tokens) { + var $ccNumber = $(this.getSelector('cc_number')), + ccLast4 = $ccNumber.val().replace(/[^\d]/g, '').substr(-4); + + $(this.getSelector('opaque_data_descriptor')).val(tokens.opaqueDataDescriptor); + $(this.getSelector('opaque_data_value')).val(tokens.opaqueDataValue); + $(this.getSelector('cc_last_4')).val(ccLast4); + $ccNumber.val(''); + $(this.getSelector('cc_exp_month')).val(''); + $(this.getSelector('cc_exp_year')).val(''); + + if (this.useCvv) { + $(this.getSelector('cc_cid')).val(''); + } + }, + + /** + * Trigger order submit + */ + submitOrder: function () { + var authData = {}, + cardData = {}, + secureData = {}; + + this.$selector.validate().form(); + this.$selector.trigger('afterValidate.beforeSubmit'); + + authData.clientKey = this.clientKey; + authData.apiLoginID = this.apiLoginID; + + cardData.cardNumber = $(this.getSelector('cc_number')).val(); + cardData.month = $(this.getSelector('cc_exp_month')).val(); + cardData.year = $(this.getSelector('cc_exp_year')).val(); + + if (this.useCvv) { + cardData.cardCode = $(this.getSelector('cc_cid')).val(); + } + + secureData.authData = authData; + secureData.cardData = cardData; + + this.disableEventListeners(); + + this.acceptjsClient.createTokens(secureData) + .always(function () { + $('body').trigger('processStop'); + this.enableEventListeners(); + }.bind(this)) + .done(function (tokens) { + this.setPaymentDetails(tokens); + this.placeOrder(); + }.bind(this)) + .fail(function (messages) { + this.tokens = null; + + if (messages.length > 0) { + this._showError(messages[0]); + } + }.bind(this)); + + return false; + }, + + /** + * Place order + */ + placeOrder: function () { + this.$selector.trigger('realOrder'); + }, + + /** + * Get jQuery selector + * + * @param {String} field + * @returns {String} + */ + getSelector: function (field) { + return '#' + this.code + '_' + field; + }, + + /** + * Show alert message + * + * @param {String} message + */ + _showError: function (message) { + alert({ + content: message + }); + }, + + /** + * Enable form event listeners + */ + enableEventListeners: function () { + this.$selector.on('submitOrder.authorizenetacceptjs', this.submitOrder.bind(this)); + }, + + /** + * Disable form event listeners + */ + disableEventListeners: function () { + this.$selector.off('submitOrder'); + this.$selector.off('submit'); + } + + }); +}); diff --git a/app/code/Magento/AuthorizenetAcceptjs/view/adminhtml/web/js/payment-form.js b/app/code/Magento/AuthorizenetAcceptjs/view/adminhtml/web/js/payment-form.js new file mode 100644 index 00000000000..68c2f22f6ed --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/view/adminhtml/web/js/payment-form.js @@ -0,0 +1,18 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +define([ + 'Magento_AuthorizenetAcceptjs/js/authorizenet', + 'jquery' +], function (AuthorizenetAcceptjs, $) { + 'use strict'; + + return function (data, element) { + var $form = $(element), + config = data.config; + + config.active = $form.length > 0 && !$form.is(':hidden'); + new AuthorizenetAcceptjs(config); + }; +}); diff --git a/app/code/Magento/AuthorizenetAcceptjs/view/base/requirejs-config.js b/app/code/Magento/AuthorizenetAcceptjs/view/base/requirejs-config.js new file mode 100644 index 00000000000..83ddd1094ea --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/view/base/requirejs-config.js @@ -0,0 +1,19 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +var config = { + shim: { + acceptjs: { + exports: 'Accept' + }, + acceptjssandbox: { + exports: 'Accept' + } + }, + paths: { + acceptjssandbox: 'https://jstest.authorize.net/v1/Accept', + acceptjs: 'https://js.authorize.net/v1/Accept' + } +}; diff --git a/app/code/Magento/AuthorizenetAcceptjs/view/base/web/js/view/payment/acceptjs-client.js b/app/code/Magento/AuthorizenetAcceptjs/view/base/web/js/view/payment/acceptjs-client.js new file mode 100644 index 00000000000..935465f5298 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/view/base/web/js/view/payment/acceptjs-client.js @@ -0,0 +1,73 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'jquery', + 'uiClass', + 'Magento_AuthorizenetAcceptjs/js/view/payment/acceptjs-factory', + 'Magento_AuthorizenetAcceptjs/js/view/payment/validator-handler' +], function ($, Class, acceptjsFactory, validatorHandler) { + 'use strict'; + + return Class.extend({ + defaults: { + environment: 'production' + }, + + /** + * @{inheritdoc} + */ + initialize: function () { + validatorHandler.initialize(); + + this._super(); + + return this; + }, + + /** + * Creates the token pair with the provided data + * + * @param {Object} data + * @return {jQuery.Deferred} + */ + createTokens: function (data) { + var deferred = $.Deferred(); + + if (this.acceptjsClient) { + this._createTokens(deferred, data); + } else { + acceptjsFactory(this.environment) + .done(function (client) { + this.acceptjsClient = client; + this._createTokens(deferred, data); + }.bind(this)); + } + + return deferred.promise(); + }, + + /** + * Creates a token from the payment information in the form + * + * @param {jQuery.Deferred} deferred + * @param {Object} data + */ + _createTokens: function (deferred, data) { + this.acceptjsClient.dispatchData(data, function (response) { + validatorHandler.validate(response, function (valid, messages) { + if (valid) { + deferred.resolve({ + opaqueDataDescriptor: response.opaqueData.dataDescriptor, + opaqueDataValue: response.opaqueData.dataValue + }); + } else { + deferred.reject(messages); + } + }); + }); + } + }); +}); diff --git a/app/code/Magento/AuthorizenetAcceptjs/view/base/web/js/view/payment/acceptjs-factory.js b/app/code/Magento/AuthorizenetAcceptjs/view/base/web/js/view/payment/acceptjs-factory.js new file mode 100644 index 00000000000..e98a204e36c --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/view/base/web/js/view/payment/acceptjs-factory.js @@ -0,0 +1,38 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'jquery' +], function ($) { + 'use strict'; + + return function (environment) { + var deferred = $.Deferred(), + dependency = 'acceptjs'; + + if (environment === 'sandbox') { + dependency = 'acceptjssandbox'; + } + + require([dependency], function (accept) { + var $body = $('body'); + + /* + * Acceptjs doesn't safely load dependent files which leads to a race condition when trying to use + * the sdk right away. + * @see https://community.developer.authorize.net/t5/Integration-and-Testing/ + * Dynamically-loading-Accept-js-E-WC-03-Accept-js-is-not-loaded/td-p/63283 + */ + $body.on('handshake.acceptjs', function () { + deferred.resolve(accept); + $body.off('handshake.acceptjs'); + }); + }, + deferred.reject + ); + + return deferred.promise(); + }; +}); diff --git a/app/code/Magento/AuthorizenetAcceptjs/view/base/web/js/view/payment/response-validator.js b/app/code/Magento/AuthorizenetAcceptjs/view/base/web/js/view/payment/response-validator.js new file mode 100644 index 00000000000..3c44ca2f9e4 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/view/base/web/js/view/payment/response-validator.js @@ -0,0 +1,38 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'jquery', + 'mage/translate' +], function ($, $t) { + 'use strict'; + + return { + /** + * Validate Authorizenet-Acceptjs response + * + * @param {Object} context + * @returns {jQuery.Deferred} + */ + validate: function (context) { + var state = $.Deferred(), + messages = []; + + if (context.messages.resultCode === 'Ok') { + state.resolve(); + } else { + if (context.messages.message.length > 0) { + $.each(context.messages.message, function (index, element) { + messages.push($t(element.text)); + }); + } + state.reject(messages); + } + + return state.promise(); + } + }; +}); + diff --git a/app/code/Magento/AuthorizenetAcceptjs/view/base/web/js/view/payment/validator-handler.js b/app/code/Magento/AuthorizenetAcceptjs/view/base/web/js/view/payment/validator-handler.js new file mode 100644 index 00000000000..109f159c9a7 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/view/base/web/js/view/payment/validator-handler.js @@ -0,0 +1,59 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'jquery', + 'Magento_AuthorizenetAcceptjs/js/view/payment/response-validator' +], function ($, responseValidator) { + 'use strict'; + + return { + validators: [], + + /** + * Init list of validators + */ + initialize: function () { + this.add(responseValidator); + }, + + /** + * Add new validator + * @param {Object} validator + */ + add: function (validator) { + this.validators.push(validator); + }, + + /** + * Run pull of validators + * @param {Object} context + * @param {Function} callback + */ + validate: function (context, callback) { + var self = this, + deferred; + + // no available validators + if (!self.validators.length) { + callback(true); + + return; + } + + // get list of deferred validators + deferred = $.map(self.validators, function (current) { + return current.validate(context); + }); + + $.when.apply($, deferred) + .done(function () { + callback(true); + }).fail(function (error) { + callback(false, error); + }); + } + }; +}); diff --git a/app/code/Magento/AuthorizenetAcceptjs/view/frontend/layout/checkout_index_index.xml b/app/code/Magento/AuthorizenetAcceptjs/view/frontend/layout/checkout_index_index.xml new file mode 100644 index 00000000000..f31b06c9be9 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/view/frontend/layout/checkout_index_index.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + uiComponent + + + + + + + + Magento_AuthorizenetAcceptjs/js/view/payment/authorizenet + + + true + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/code/Magento/AuthorizenetAcceptjs/view/frontend/web/js/view/payment/authorizenet.js b/app/code/Magento/AuthorizenetAcceptjs/view/frontend/web/js/view/payment/authorizenet.js new file mode 100644 index 00000000000..a05fe739a44 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/view/frontend/web/js/view/payment/authorizenet.js @@ -0,0 +1,20 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'uiComponent', + 'Magento_Checkout/js/model/payment/renderer-list' +], +function (Component, rendererList) { + 'use strict'; + + rendererList.push({ + type: 'authorizenet_acceptjs', + component: 'Magento_AuthorizenetAcceptjs/js/view/payment/method-renderer/authorizenet-accept' + }); + + /** Add view logic here if needed */ + return Component.extend({}); +}); 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 new file mode 100644 index 00000000000..983318c4cda --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/view/frontend/web/js/view/payment/method-renderer/authorizenet-accept.js @@ -0,0 +1,146 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'jquery', + 'Magento_Payment/js/view/payment/cc-form', + 'Magento_AuthorizenetAcceptjs/js/view/payment/acceptjs-client', + 'Magento_Checkout/js/model/full-screen-loader', + 'Magento_Ui/js/model/messageList', + 'Magento_Payment/js/model/credit-card-validation/validator' +], function ($, Component, AcceptjsClient, fullScreenLoader, globalMessageList) { + 'use strict'; + + return Component.extend({ + defaults: { + active: false, + template: 'Magento_AuthorizenetAcceptjs/payment/authorizenet-acceptjs', + tokens: null, + ccForm: 'Magento_Payment/payment/cc-form', + acceptjsClient: null + }, + + /** + * Set list of observable attributes + * + * @returns {exports.initObservable} + */ + initObservable: function () { + this._super() + .observe(['active']); + + return this; + }, + + /** + * @returns {String} + */ + getCode: function () { + return 'authorizenet_acceptjs'; + }, + + /** + * Initialize form elements for validation + */ + initFormElement: function (element) { + this.formElement = element; + this.acceptjsClient = AcceptjsClient({ + environment: window.checkoutConfig.payment[this.getCode()].environment + }); + $(this.formElement).validation(); + }, + + /** + * @returns {Object} + */ + getData: function () { + return { + method: this.getCode(), + 'additional_data': { + opaqueDataDescriptor: this.tokens ? this.tokens.opaqueDataDescriptor : null, + opaqueDataValue: this.tokens ? this.tokens.opaqueDataValue : null, + ccLast4: this.creditCardNumber().substr(-4) + } + }; + }, + + /** + * Check if payment is active + * + * @returns {Boolean} + */ + isActive: function () { + var active = this.getCode() === this.isChecked(); + + this.active(active); + + return active; + }, + + /** + * Prepare data to place order + */ + beforePlaceOrder: function () { + var authData = {}, + cardData = {}, + secureData = {}; + + if (!$(this.formElement).valid()) { + return; + } + + authData.clientKey = window.checkoutConfig.payment[this.getCode()].clientKey; + authData.apiLoginID = window.checkoutConfig.payment[this.getCode()].apiLoginID; + + cardData.cardNumber = this.creditCardNumber(); + cardData.month = this.creditCardExpMonth(); + cardData.year = this.creditCardExpYear(); + + if (this.hasVerification()) { + cardData.cardCode = this.creditCardVerificationNumber(); + } + + secureData.authData = authData; + secureData.cardData = cardData; + + fullScreenLoader.startLoader(); + + this.acceptjsClient.createTokens(secureData) + .always(function () { + fullScreenLoader.stopLoader(); + }) + .done(function (tokens) { + this.tokens = tokens; + this.placeOrder(); + }.bind(this)) + .fail(function (messages) { + this.tokens = null; + this._showErrors(messages); + }.bind(this)); + }, + + /** + * Should the cvv field be used + * + * @return {Boolean} + */ + hasVerification: function () { + return window.checkoutConfig.payment[this.getCode()].useCvv; + }, + + /** + * Show error messages + * + * @param {String[]} errorMessages + */ + _showErrors: function (errorMessages) { + $.each(errorMessages, function (index, message) { + globalMessageList.addErrorMessage({ + message: message + }); + }); + } + }); +}); 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 new file mode 100644 index 00000000000..6db52a2b102 --- /dev/null +++ b/app/code/Magento/AuthorizenetAcceptjs/view/frontend/web/template/payment/authorizenet-acceptjs.html @@ -0,0 +1,45 @@ + +
+
+ + +
+
+ +
+ +
+
+ + +
+ +
+
+
+ +
+
+
+
diff --git a/app/code/Magento/Backend/Controller/Adminhtml/Dashboard/RefreshStatistics.php b/app/code/Magento/Backend/Controller/Adminhtml/Dashboard/RefreshStatistics.php index c10d1a77997..c709859adb1 100644 --- a/app/code/Magento/Backend/Controller/Adminhtml/Dashboard/RefreshStatistics.php +++ b/app/code/Magento/Backend/Controller/Adminhtml/Dashboard/RefreshStatistics.php @@ -6,7 +6,13 @@ namespace Magento\Backend\Controller\Adminhtml\Dashboard; -class RefreshStatistics extends \Magento\Reports\Controller\Adminhtml\Report\Statistics +use Magento\Framework\App\Action\HttpPostActionInterface; +use Magento\Reports\Controller\Adminhtml\Report\Statistics; + +/** + * Refresh Dashboard statistics action. + */ +class RefreshStatistics extends Statistics implements HttpPostActionInterface { /** * @param \Magento\Backend\App\Action\Context $context @@ -25,6 +31,8 @@ public function __construct( } /** + * Refresh statistics. + * * @return \Magento\Backend\Model\View\Result\Redirect */ public function execute() diff --git a/app/code/Magento/Backend/Model/Search/Customer.php b/app/code/Magento/Backend/Model/Search/Customer.php index 35a7359ce99..e76a1b77ab2 100644 --- a/app/code/Magento/Backend/Model/Search/Customer.php +++ b/app/code/Magento/Backend/Model/Search/Customer.php @@ -89,7 +89,7 @@ public function load() $this->searchCriteriaBuilder->setCurrentPage($this->getStart()); $this->searchCriteriaBuilder->setPageSize($this->getLimit()); - $searchFields = ['firstname', 'lastname', 'company']; + $searchFields = ['firstname', 'lastname', 'billing_company']; $filters = []; foreach ($searchFields as $field) { $filters[] = $this->filterBuilder diff --git a/app/code/Magento/Backend/Test/Mftf/ActionGroup/SetAdminAccountActionGroup.xml b/app/code/Magento/Backend/Test/Mftf/ActionGroup/SetAdminAccountActionGroup.xml new file mode 100644 index 00000000000..9e5c0bb3f39 --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/ActionGroup/SetAdminAccountActionGroup.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/code/Magento/Backend/Test/Mftf/Page/AdminDashboardPage.xml b/app/code/Magento/Backend/Test/Mftf/Page/AdminDashboardPage.xml index 8c258accdf0..ed30395406f 100644 --- a/app/code/Magento/Backend/Test/Mftf/Page/AdminDashboardPage.xml +++ b/app/code/Magento/Backend/Test/Mftf/Page/AdminDashboardPage.xml @@ -7,7 +7,7 @@ --> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd">
diff --git a/app/code/Magento/Sales/Test/Mftf/Page/StorefrontCreateNewReturnPage.xml b/app/code/Magento/Backend/Test/Mftf/Page/AdminSystemAccountPage.xml similarity index 63% rename from app/code/Magento/Sales/Test/Mftf/Page/StorefrontCreateNewReturnPage.xml rename to app/code/Magento/Backend/Test/Mftf/Page/AdminSystemAccountPage.xml index 2a14f814eac..2f04c2c11d2 100644 --- a/app/code/Magento/Sales/Test/Mftf/Page/StorefrontCreateNewReturnPage.xml +++ b/app/code/Magento/Backend/Test/Mftf/Page/AdminSystemAccountPage.xml @@ -8,7 +8,7 @@ - -
+ +
diff --git a/app/code/Magento/Backend/Test/Mftf/Section/AdminMainActionsSection.xml b/app/code/Magento/Backend/Test/Mftf/Section/AdminMainActionsSection.xml index bc576559e7a..4867b5ba5ae 100644 --- a/app/code/Magento/Backend/Test/Mftf/Section/AdminMainActionsSection.xml +++ b/app/code/Magento/Backend/Test/Mftf/Section/AdminMainActionsSection.xml @@ -10,6 +10,8 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
+ +
diff --git a/app/code/Magento/Backend/Test/Mftf/Section/AdminMenuSection.xml b/app/code/Magento/Backend/Test/Mftf/Section/AdminMenuSection.xml index 9e4a6d92195..278a738b60f 100644 --- a/app/code/Magento/Backend/Test/Mftf/Section/AdminMenuSection.xml +++ b/app/code/Magento/Backend/Test/Mftf/Section/AdminMenuSection.xml @@ -16,5 +16,10 @@ + + + + +
diff --git a/app/code/Magento/Backend/Test/Mftf/Section/AdminMessagesSection.xml b/app/code/Magento/Backend/Test/Mftf/Section/AdminMessagesSection.xml index b1350d5dcc1..88e740d689c 100644 --- a/app/code/Magento/Backend/Test/Mftf/Section/AdminMessagesSection.xml +++ b/app/code/Magento/Backend/Test/Mftf/Section/AdminMessagesSection.xml @@ -12,5 +12,6 @@ +
diff --git a/app/code/Magento/Backend/Test/Mftf/Section/AdminSlideOutDialogSection.xml b/app/code/Magento/Backend/Test/Mftf/Section/AdminSlideOutDialogSection.xml index a2645c9cbf9..a01e025ba3d 100644 --- a/app/code/Magento/Backend/Test/Mftf/Section/AdminSlideOutDialogSection.xml +++ b/app/code/Magento/Backend/Test/Mftf/Section/AdminSlideOutDialogSection.xml @@ -1,11 +1,10 @@ - + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + -->
diff --git a/app/code/Magento/Backend/Test/Mftf/Section/AdminSystemAccountSection.xml b/app/code/Magento/Backend/Test/Mftf/Section/AdminSystemAccountSection.xml new file mode 100644 index 00000000000..b9570ce9459 --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/Section/AdminSystemAccountSection.xml @@ -0,0 +1,15 @@ + + + + +
+ + +
+
diff --git a/app/code/Magento/Backend/Test/Mftf/Section/LocaleOptionsSection.xml b/app/code/Magento/Backend/Test/Mftf/Section/LocaleOptionsSection.xml index c50bf0664f9..a460aaebf10 100644 --- a/app/code/Magento/Backend/Test/Mftf/Section/LocaleOptionsSection.xml +++ b/app/code/Magento/Backend/Test/Mftf/Section/LocaleOptionsSection.xml @@ -11,5 +11,6 @@
+
diff --git a/app/code/Magento/Backend/Test/Mftf/Test/AdminAttributeTextSwatchesCanBeFiledTest.xml b/app/code/Magento/Backend/Test/Mftf/Test/AdminAttributeTextSwatchesCanBeFiledTest.xml new file mode 100644 index 00000000000..2c061e54f55 --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/Test/AdminAttributeTextSwatchesCanBeFiledTest.xml @@ -0,0 +1,116 @@ + + + + + + + + + + <description value="Check that attribute text swatches can be filed"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-96710"/> + <useCaseId value="MAGETWO-96409"/> + <group value="backend"/> + <group value="ui"/> + </annotations> + <before> + + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + + </before> + <after> + <!-- Delete all 10 store views --> + <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteStoreView1"> + <argument name="customStore" value="customStore"/> + </actionGroup> + <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteStoreView2"> + <argument name="customStore" value="NewStoreViewData"/> + </actionGroup> + <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteStoreView3"> + <argument name="customStore" value="storeViewData"/> + </actionGroup> + <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteStoreView4"> + <argument name="customStore" value="storeViewData1"/> + </actionGroup> + <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteStoreView5"> + <argument name="customStore" value="storeViewData2"/> + </actionGroup> + <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteStoreView6"> + <argument name="customStore" value="storeViewData3"/> + </actionGroup> + <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteStoreView7"> + <argument name="customStore" value="storeViewData4"/> + </actionGroup> + <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteStoreView8"> + <argument name="customStore" value="storeViewData5"/> + </actionGroup> + <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteStoreView9"> + <argument name="customStore" value="storeViewData6"/> + </actionGroup> + <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteStoreView10"> + <argument name="customStore" value="storeViewData7"/> + </actionGroup> + + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Create 10 store views --> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createStoreView1"> + <argument name="customStore" value="customStore"/> + </actionGroup> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createStoreView2"> + <argument name="customStore" value="NewStoreViewData"/> + </actionGroup> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createStoreView3"> + <argument name="customStore" value="storeViewData"/> + </actionGroup> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createStoreView4"> + <argument name="customStore" value="storeViewData1"/> + </actionGroup> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createStoreView5"> + <argument name="customStore" value="storeViewData2"/> + </actionGroup> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createStoreView6"> + <argument name="customStore" value="storeViewData3"/> + </actionGroup> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createStoreView7"> + <argument name="customStore" value="storeViewData4"/> + </actionGroup> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createStoreView8"> + <argument name="customStore" value="storeViewData5"/> + </actionGroup> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createStoreView9"> + <argument name="customStore" value="storeViewData6"/> + </actionGroup> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createStoreView10"> + <argument name="customStore" value="storeViewData7"/> + </actionGroup> + + <!--Navigate to Product attribute page--> + <amOnPage url="{{ProductAttributePage.url}}" stepKey="navigateToNewProductAttributePage"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <fillField userInput="test_label" selector="{{AttributePropertiesSection.DefaultLabel}}" stepKey="fillDefaultLabel"/> + <selectOption selector="{{AttributePropertiesSection.InputType}}" userInput="Text Swatch" stepKey="selectInputType"/> + <click selector="{{AttributePropertiesSection.addSwatch}}" stepKey="clickAddSwatch"/> + <waitForAjaxLoad stepKey="waitForAjaxLoad"/> + + <!-- Fill Swatch and Description fields for Admin --> + <fillField selector="{{AttributeManageSwatchSection.swatchField('Admin')}}" userInput="test" stepKey="fillSwatchForAdmin"/> + <fillField selector="{{AttributeManageSwatchSection.descriptionField('Admin')}}" userInput="test" stepKey="fillDescriptionForAdmin"/> + + <!-- Grab value Swatch and Description fields for Admin --> + <grabValueFrom selector="{{AttributeManageSwatchSection.swatchField('Admin')}}" stepKey="grabSwatchForAdmin"/> + <grabValueFrom selector="{{AttributeManageSwatchSection.descriptionField('Admin')}}" stepKey="grabDescriptionForAdmin"/> + + <!-- Check that Swatch and Description fields for Admin are not empty--> + <assertNotEmpty actual="$grabSwatchForAdmin" stepKey="checkSwatchFieldForAdmin"/> + <assertNotEmpty actual="$grabDescriptionForAdmin" stepKey="checkDescriptionFieldForAdmin"/> + </test> +</tests> diff --git a/app/code/Magento/Backend/etc/module.xml b/app/code/Magento/Backend/etc/module.xml index 3a5cd822675..03976396f6f 100644 --- a/app/code/Magento/Backend/etc/module.xml +++ b/app/code/Magento/Backend/etc/module.xml @@ -9,6 +9,7 @@ <module name="Magento_Backend"> <sequence> <module name="Magento_Directory"/> + <module name="Magento_Theme"/> </sequence> </module> </config> 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 062528e7422..c76f10da0f9 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/widget/tabshoriz.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/widget/tabshoriz.phtml @@ -18,7 +18,7 @@ <?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="<?= /* @escapeNotVerified */ $_tabHref ?>" id="<?= /* @escapeNotVerified */ $block->getTabId($_tab) ?>" title="<?= /* @escapeNotVerified */ $block->getTabTitle($_tab) ?>" class="<?php $_tabClass ?>" data-tab-type="<?php $_tabType ?>"> + <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> diff --git a/app/code/Magento/Backend/view/adminhtml/ui_component/design_config_listing.xml b/app/code/Magento/Backend/view/adminhtml/ui_component/design_config_listing.xml index 93309c9a22e..b0abec3aa9b 100644 --- a/app/code/Magento/Backend/view/adminhtml/ui_component/design_config_listing.xml +++ b/app/code/Magento/Backend/view/adminhtml/ui_component/design_config_listing.xml @@ -6,6 +6,7 @@ */ --> <listing xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd"> + <listingToolbar name="listing_top" /> <columns name="design_config_columns"> <column name="theme_theme_id" component="Magento_Ui/js/grid/columns/select" sortOrder="40"> <settings> diff --git a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/AdminOrderBraintreeFillActionGroup.xml b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/AdminOrderBraintreeFillActionGroup.xml index 412513c59c6..ce1d0a9aecc 100644 --- a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/AdminOrderBraintreeFillActionGroup.xml +++ b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/AdminOrderBraintreeFillActionGroup.xml @@ -5,9 +5,9 @@ * See COPYING.txt for license details. */ --> -<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="AdminOrderBraintreeFillActionGroup"> <!--Select Braintree Payment method on Admin Order Create Page--> <click stepKey="chooseBraintree" selector="{{NewOrderSection.creditCardBraintree}}"/> @@ -19,24 +19,24 @@ <!--Choose Master Card from drop-down list--> <switchToIFrame stepKey="switchToCardNumber" selector="{{NewOrderSection.cardFrame}}"/> + <waitForElementVisible selector="{{NewOrderSection.creditCardNumber}}" stepKey="waitForFillCardNumber"/> <fillField stepKey="fillCardNumber" selector="{{NewOrderSection.creditCardNumber}}" userInput="{{PaymentAndShippingInfo.cardNumber}}"/> - <waitForPageLoad stepKey="waitForFillCardNumber"/> <switchToIFrame stepKey="switchBackFromCard"/> <!--Fill expire date--> <switchToIFrame stepKey="switchToExpirationMonth" selector="{{NewOrderSection.monthFrame}}"/> + <waitForElementVisible selector="{{NewOrderSection.expirationMonth}}" stepKey="waitForFillMonth"/> <fillField stepKey="fillMonth" selector="{{NewOrderSection.expirationMonth}}" userInput="{{PaymentAndShippingInfo.month}}"/> - <waitForPageLoad stepKey="waitForFillMonth"/> <switchToIFrame stepKey="switchBackFromMonth"/> <switchToIFrame stepKey="switchToExpirationYear" selector="{{NewOrderSection.yearFrame}}"/> + <waitForElementVisible selector="{{NewOrderSection.expirationYear}}" stepKey="waitForFillYear"/> <fillField stepKey="fillYear" selector="{{NewOrderSection.expirationYear}}" userInput="{{PaymentAndShippingInfo.year}}"/> - <waitForPageLoad stepKey="waitForFillYear"/> <switchToIFrame stepKey="switchBackFromYear"/> <!--Fill CVW code--> <switchToIFrame stepKey="switchToCVV" selector="{{NewOrderSection.cvvFrame}}"/> + <waitForElementVisible selector="{{NewOrderSection.cvv}}" stepKey="waitForFillCVV"/> <fillField stepKey="fillCVV" selector="{{NewOrderSection.cvv}}" userInput="{{PaymentAndShippingInfo.cvv}}"/> - <wait stepKey="waitForFillCVV" time="1"/> <switchToIFrame stepKey="switchBackFromCVV"/> </actionGroup> -</actionGroups> \ No newline at end of file +</actionGroups> diff --git a/app/code/Magento/User/Test/Mftf/ActionGroup/AdminRoleActionGroup.xml b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/AdminRoleActionGroup.xml similarity index 83% rename from app/code/Magento/User/Test/Mftf/ActionGroup/AdminRoleActionGroup.xml rename to app/code/Magento/Braintree/Test/Mftf/ActionGroup/AdminRoleActionGroup.xml index d8a6a60299f..09ac0b77f86 100644 --- a/app/code/Magento/User/Test/Mftf/ActionGroup/AdminRoleActionGroup.xml +++ b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/AdminRoleActionGroup.xml @@ -5,8 +5,9 @@ * See COPYING.txt for license details. */ --> + <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="GoToUserRoles"> <click selector="#menu-magento-backend-system" stepKey="clickOnSystemIcon"/> <waitForPageLoad stepKey="waitForSystemsPageToOpen"/> @@ -15,24 +16,24 @@ </actionGroup> <!--Create new role--> - <actionGroup name="AdminCreateRole"> + <actionGroup name="AdminCreateNewRole"> <arguments> <argument name="role" type="string" defaultValue=""/> <argument name="resource" type="string" defaultValue="All"/> + <argument name="scope" type="string" defaultValue="Custom"/> + <argument name="websites" type="string" defaultValue="Main Website"/> </arguments> <click selector="{{AdminCreateRoleSection.create}}" stepKey="clickToAddNewRole"/> <fillField selector="{{AdminCreateRoleSection.name}}" userInput="{{role.name}}" stepKey="setRoleName"/> <fillField stepKey="setPassword" selector="{{AdminCreateRoleSection.password}}" userInput="{{_ENV.MAGENTO_ADMIN_PASSWORD}}"/> <click selector="{{AdminCreateRoleSection.roleResources}}" stepKey="clickToOpenRoleResources"/> <waitForPageLoad stepKey="waitForRoleResourcePage" time="5"/> - <click selector="{{AdminCreateRoleSection.roleScope}}" stepKey="clickToExpandScopeAccess"/> - <click selector="{{AdminCreateRoleSection.scopeValue(resource)}}" stepKey="clickToSelectScopeAccess"/> + <click stepKey="checkSales" selector="//a[text()='Sales']"/> <click selector="{{AdminCreateRoleSection.save}}" stepKey="clickToSaveRole"/> <waitForPageLoad stepKey="waitForPageLoad" time="10"/> <see userInput="You saved the role." stepKey="seeSuccessMessage" /> </actionGroup> - <!--Delete role--> <actionGroup name="AdminDeleteRoleActionGroup"> <arguments> @@ -46,4 +47,4 @@ <waitForPageLoad stepKey="waitForPageLoad" time="10"/> <see stepKey="seeSuccessMessage" userInput="You deleted the role."/> </actionGroup> -</actionGroups> \ No newline at end of file +</actionGroups> diff --git a/app/code/Magento/User/Test/Mftf/ActionGroup/AdminUserActionGroup.xml b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/AdminUserActionGroup.xml similarity index 91% rename from app/code/Magento/User/Test/Mftf/ActionGroup/AdminUserActionGroup.xml rename to app/code/Magento/Braintree/Test/Mftf/ActionGroup/AdminUserActionGroup.xml index 3e776df9fb9..3f8bdaa4cd6 100644 --- a/app/code/Magento/User/Test/Mftf/ActionGroup/AdminUserActionGroup.xml +++ b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/AdminUserActionGroup.xml @@ -5,9 +5,9 @@ * See COPYING.txt for license details. */ --> -<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <!--Go to all users--> <actionGroup name="GoToAllUsers"> <click selector="{{AdminCreateUserSection.system}}" stepKey="clickOnSystemIcon"/> @@ -39,11 +39,10 @@ <see userInput="You saved the user." stepKey="seeSuccessMessage" /> </actionGroup> - <!--Delete User--> - <actionGroup name="AdminDeleteUserActionGroup"> + <actionGroup name="AdminDeleteNewUserActionGroup"> + <click stepKey="clickOnUser" selector="{{AdminDeleteUserSection.theUser}}"/> - <waitForPageLoad stepKey="waitForUserPageToLoad"/> <fillField stepKey="TypeCurrentPassword" selector="{{AdminDeleteUserSection.password}}" userInput="{{_ENV.MAGENTO_ADMIN_PASSWORD}}"/> <scrollToTopOfPage stepKey="scrollToTop"/> <click stepKey="clickToDeleteUser" selector="{{AdminDeleteUserSection.delete}}"/> @@ -52,4 +51,5 @@ <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 9eaae8b33e7..cbb065704fb 100644 --- a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/ConfigureBraintreeActionGroup.xml +++ b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/ConfigureBraintreeActionGroup.xml @@ -12,7 +12,7 @@ <!-- GoTo ConfigureBraintree fields --> <click stepKey="clickOnSTORES" selector="{{AdminMenuSection.stores}}"/> <waitForPageLoad stepKey="waitForConfiguration" time="2"/> - <click stepKey="clickOnConfigurations" selector="{{StoresSubmenuSection.configuration}}" /> + <click stepKey="clickOnConfigurations" selector="{{AdminMenuSection.configuration}}" /> <waitForPageLoad stepKey="waitForSales" time="2"/> <click stepKey="clickOnSales" selector="{{ConfigurationListSection.sales}}" /> <waitForPageLoad stepKey="waitForPaymentMethods" time="2"/> diff --git a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/StorefrontFillCartDataActionGroup.xml b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/StorefrontFillCartDataActionGroup.xml index bc6d6c2b46d..bf06bc7df52 100644 --- a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/StorefrontFillCartDataActionGroup.xml +++ b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/StorefrontFillCartDataActionGroup.xml @@ -6,24 +6,27 @@ */ --> <actionGroups 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/actionGroupSchema.xsd"> <actionGroup name="StorefrontFillCartDataActionGroup"> <arguments> <argument name="cartData" defaultValue="PaymentAndShippingInfo"/> </arguments> <switchToIFrame selector="{{BraintreeConfigurationPaymentSection.cartFrame}}" stepKey="switchToIframe"/> + <waitForElementVisible selector="{{BraintreeConfigurationPaymentSection.cartCode}}" stepKey="waitCartCodeElement"/> <fillField selector="{{BraintreeConfigurationPaymentSection.cartCode}}" userInput="{{cartData.cardNumber}}" stepKey="setCartCode"/> <switchToIFrame stepKey="switchBack"/> <switchToIFrame selector="{{BraintreeConfigurationPaymentSection.monthFrame}}" stepKey="switchToIframe1"/> + <waitForElementVisible selector="{{BraintreeConfigurationPaymentSection.month}}" stepKey="waitMonthElement"/> <fillField selector="{{BraintreeConfigurationPaymentSection.month}}" userInput="{{cartData.month}}" stepKey="setMonth"/> <switchToIFrame stepKey="switchBack1"/> <switchToIFrame selector="{{BraintreeConfigurationPaymentSection.yearFrame}}" stepKey="switchToIframe2"/> + <waitForElementVisible selector="{{BraintreeConfigurationPaymentSection.year}}" stepKey="waitYearElement"/> <fillField selector="{{BraintreeConfigurationPaymentSection.year}}" userInput="{{cartData.year}}" stepKey="setYear"/> <switchToIFrame stepKey="switchBack2"/> <switchToIFrame selector="{{BraintreeConfigurationPaymentSection.codeFrame}}" stepKey="switchToIframe3"/> + <waitForElementVisible selector="{{BraintreeConfigurationPaymentSection.verificationNumber}}" stepKey="waitVerificationNumber"/> <fillField selector="{{BraintreeConfigurationPaymentSection.verificationNumber}}" userInput="{{cartData.cvv}}" stepKey="setVerificationNumber"/> <switchToIFrame stepKey="SwitchBackToWindow"/> - </actionGroup> </actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/AdminEditRoleInfoSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/AdminEditRoleInfoSection.xml index e37ce8f4738..a34cdf15e7a 100644 --- a/app/code/Magento/Braintree/Test/Mftf/Section/AdminEditRoleInfoSection.xml +++ b/app/code/Magento/Braintree/Test/Mftf/Section/AdminEditRoleInfoSection.xml @@ -5,7 +5,9 @@ * See COPYING.txt for license details. */ --> -<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminEditRoleInfoSection"> <element name="roleName" type="input" selector="#role_name"/> <element name="password" type="input" selector="#current_password"/> @@ -18,4 +20,4 @@ <element name="cancel" type="button" selector=".modal-popup.confirm button.action-dismiss"/> <element name="ok" type="button" selector=".modal-popup.confirm button.action-accept" timeout="60"/> </section> -</sections> \ No newline at end of file +</sections> diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/AdminEditUserRoleSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/AdminEditUserRoleSection.xml index e999413c96d..216292b8116 100644 --- a/app/code/Magento/Braintree/Test/Mftf/Section/AdminEditUserRoleSection.xml +++ b/app/code/Magento/Braintree/Test/Mftf/Section/AdminEditUserRoleSection.xml @@ -5,7 +5,9 @@ * See COPYING.txt for license details. */ --> -<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminEditUserRoleSection"> <element name="usernameTextField" type="input" selector="#user_username"/> <element name="roleNameFilterTextField" type="input" selector="#permissionsUserRolesGrid_filter_role_name"/> @@ -14,4 +16,4 @@ <element name="roleNameInFirstRow" type="text" selector=".col-role_name"/> <element name="searchResultFirstRow" type="text" selector=".data-grid>tbody>tr"/> </section> -</sections> \ No newline at end of file +</sections> diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/AdminEditUserSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/AdminEditUserSection.xml index 2e5fcfb7b5c..cee262864d8 100644 --- a/app/code/Magento/Braintree/Test/Mftf/Section/AdminEditUserSection.xml +++ b/app/code/Magento/Braintree/Test/Mftf/Section/AdminEditUserSection.xml @@ -5,7 +5,9 @@ * See COPYING.txt for license details. */ --> -<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminEditUserSection"> <element name="system" type="input" selector="#menu-magento-backend-system"/> <element name="allUsers" type="input" selector="//span[contains(text(), 'All Users')]"/> @@ -25,4 +27,4 @@ <element name="searchResultFirstRow" type="text" selector=".data-grid>tbody>tr"/> <element name="saveButton" type="button" selector="#save"/> </section> -</sections> \ No newline at end of file +</sections> diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/AdminMenuSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/AdminMenuSection.xml index eb7a9ce2c37..24e5efdc610 100644 --- a/app/code/Magento/Braintree/Test/Mftf/Section/AdminMenuSection.xml +++ b/app/code/Magento/Braintree/Test/Mftf/Section/AdminMenuSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminMenuSection"> <element name="dashboard" type="button" selector="//li[@id='menu-magento-backend-dashboard']"/> <element name="sales" type="button" selector="//li[@id='menu-magento-sales-sales']"/> diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/AdminRoleGridSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/AdminRoleGridSection.xml index 63cbadc71d3..1cf54bf94e7 100644 --- a/app/code/Magento/Braintree/Test/Mftf/Section/AdminRoleGridSection.xml +++ b/app/code/Magento/Braintree/Test/Mftf/Section/AdminRoleGridSection.xml @@ -5,7 +5,9 @@ * See COPYING.txt for license details. */ --> -<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminRoleGridSection"> <element name="idFilterTextField" type="input" selector="#roleGrid_filter_role_id"/> <element name="roleNameFilterTextField" type="input" selector="#roleGrid_filter_role_name"/> @@ -14,4 +16,4 @@ <element name="roleNameInFirstRow" type="text" selector=".col-role_name"/> <element name="searchResultFirstRow" type="text" selector=".data-grid>tbody>tr"/> </section> -</sections> \ No newline at end of file +</sections> diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/BraintreeConfiguraionSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/BraintreeConfiguraionSection.xml index 016af2e1027..f8802e9a34a 100644 --- a/app/code/Magento/Braintree/Test/Mftf/Section/BraintreeConfiguraionSection.xml +++ b/app/code/Magento/Braintree/Test/Mftf/Section/BraintreeConfiguraionSection.xml @@ -5,7 +5,9 @@ * See COPYING.txt for license details. */ --> -<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="BraintreeConfiguraionSection"> <element name="titleForBraintreeSettings" type="input" selector="//input[@id='payment_us_braintree_section_braintree_braintree_required_title']"/> <element name="environment" type="select" selector="//select[@id='payment_us_braintree_section_braintree_braintree_required_environment']"/> @@ -29,6 +31,5 @@ <element name="actionAuthorize" type="text" selector="//select[@id='payment_us_braintree_section_braintree_braintree_paypal_payment_action']/option[text()='Authorize']"/> <element name="save" type="button" selector="//span[text()='Save Config']"/> <element name="successfulMessage" type="text" selector="//*[@data-ui-id='messages-message-success']"/> - </section> -</sections> \ No newline at end of file +</sections> diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/ConfigurationPaymentSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/ConfigurationPaymentSection.xml index 885a45be721..2192dd935c3 100644 --- a/app/code/Magento/Braintree/Test/Mftf/Section/ConfigurationPaymentSection.xml +++ b/app/code/Magento/Braintree/Test/Mftf/Section/ConfigurationPaymentSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="ConfigurationPaymentSection"> <element name="configureButton" type="button" selector="//button[@id='payment_us_braintree_section_braintree-head']"/> </section> diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/StoresSubmenuSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/StoresSubmenuSection.xml index f094baa9f34..806762f8264 100644 --- a/app/code/Magento/Braintree/Test/Mftf/Section/StoresSubmenuSection.xml +++ b/app/code/Magento/Braintree/Test/Mftf/Section/StoresSubmenuSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StoresSubmenuSection"> <element name="configuration" type="button" selector="//li[@id='menu-magento-backend-stores']//li[@data-ui-id='menu-magento-config-system-config']"/> </section> diff --git a/app/code/Magento/Braintree/Test/Mftf/Test/CreateAnAdminOrderUsingBraintreePaymentTest1.xml b/app/code/Magento/Braintree/Test/Mftf/Test/CreateAnAdminOrderUsingBraintreePaymentTest1.xml index 2ddefa40b53..244052371e7 100644 --- a/app/code/Magento/Braintree/Test/Mftf/Test/CreateAnAdminOrderUsingBraintreePaymentTest1.xml +++ b/app/code/Magento/Braintree/Test/Mftf/Test/CreateAnAdminOrderUsingBraintreePaymentTest1.xml @@ -40,16 +40,18 @@ <!--Create New Role--> <actionGroup ref="GoToUserRoles" stepKey="GoToUserRoles"/> - <actionGroup ref="AdminCreateRole" stepKey="AdminCreateNewRole"/> + <waitForPageLoad stepKey="waitForAllRoles" time="15"/> + <actionGroup ref="AdminCreateNewRole" stepKey="AdminCreateNewRole"/> - <!--Create New User With Specific Role--> + <!--Create new admin user--> <actionGroup ref="GoToAllUsers" stepKey="GoToAllUsers"/> + <waitForPageLoad stepKey="waitForUsers" time="15"/> <actionGroup ref="AdminCreateUserAction" stepKey="AdminCreateNewUser"/> <!--SignOut--> <actionGroup ref="logout" stepKey="signOutFromAdmin"/> - <!--SignIn New User--> + <!--Log in as new user--> <actionGroup ref="LoginNewUser" stepKey="signInNewUser"/> <waitForPageLoad stepKey="waitForLogin" time="3"/> @@ -58,24 +60,28 @@ <argument name="customer" value="Simple_US_Customer"/> </actionGroup> + <!--Add Product to Order--> <actionGroup ref="addSimpleProductToOrder" stepKey="addProduct"> <argument name="product" value="_defaultProduct"/> </actionGroup> + <!--Fill Order Customer Information--> <actionGroup ref="fillOrderCustomerInformation" stepKey="fillCustomerAddress"> <argument name="customer" value="Simple_US_Customer"/> <argument name="address" value="US_Address_TX"/> </actionGroup> + <!--Select Shipping--> <actionGroup ref="orderSelectFlatRateShipping" stepKey="selectFlatRateShipping"/> - <waitForPageLoad stepKey="waitForShippingToFinish"/> + <!--Pay with Braintree --> <actionGroup ref="useBraintreeForMasterCard" stepKey="selectCardWithBraintree"/> + <!--Submit Order--> <click stepKey="submitOrder" selector="{{NewOrderSection.submitOrder}}"/> - <waitForPageLoad stepKey="waitForSaveConfig" time="5"/> - <waitForElementVisible selector="{{NewOrderSection.successMessage}}" stepKey="waitForSuccessMessage" time="1"/> + <waitForPageLoad stepKey="waitForSaveConfig"/> + <waitForElementVisible selector="{{NewOrderSection.successMessage}}" stepKey="waitForSuccessMessage"/> <after> <!-- Disable BrainTree --> @@ -93,7 +99,7 @@ <!--Delete User --> <actionGroup ref="GoToAllUsers" stepKey="GoBackToAllUsers"/> - <actionGroup ref="AdminDeleteUserActionGroup" stepKey="AdminDeleteUserActionGroup"/> + <actionGroup ref="AdminDeleteNewUserActionGroup" stepKey="AdminDeleteUserActionGroup"/> <!--Delete Role--> <actionGroup ref="GoToUserRoles" stepKey="GoBackToUserRoles"/> diff --git a/app/code/Magento/Braintree/etc/config.xml b/app/code/Magento/Braintree/etc/config.xml index a830c293687..9de4773af02 100644 --- a/app/code/Magento/Braintree/etc/config.xml +++ b/app/code/Magento/Braintree/etc/config.xml @@ -42,6 +42,7 @@ <paymentInfoKeys>cc_type,cc_number,avsPostalCodeResponseCode,avsStreetAddressResponseCode,cvvResponseCode,processorAuthorizationCode,processorResponseCode,processorResponseText,liabilityShifted,liabilityShiftPossible,riskDataId,riskDataDecision</paymentInfoKeys> <avs_ems_adapter>Magento\Braintree\Model\AvsEmsCodeMapper</avs_ems_adapter> <cvv_ems_adapter>Magento\Braintree\Model\CvvEmsCodeMapper</cvv_ems_adapter> + <group>braintree</group> </braintree> <braintree_paypal> <model>BraintreePayPalFacade</model> @@ -67,6 +68,7 @@ <privateInfoKeys>processorResponseCode,processorResponseText,paymentId</privateInfoKeys> <paymentInfoKeys>processorResponseCode,processorResponseText,paymentId,payerEmail</paymentInfoKeys> <supported_locales>en_US,en_GB,en_AU,da_DK,fr_FR,fr_CA,de_DE,zh_HK,it_IT,nl_NL,no_NO,pl_PL,es_ES,sv_SE,tr_TR,pt_BR,ja_JP,id_ID,ko_KR,pt_PT,ru_RU,th_TH,zh_CN,zh_TW</supported_locales> + <group>braintree</group> </braintree_paypal> <braintree_cc_vault> <model>BraintreeCreditCardVaultFacade</model> @@ -76,6 +78,7 @@ <tokenFormat>Magento\Braintree\Model\InstantPurchase\CreditCard\TokenFormatter</tokenFormat> <additionalInformation>Magento\Braintree\Model\InstantPurchase\PaymentAdditionalInformationProvider</additionalInformation> </instant_purchase> + <group>braintree</group> </braintree_cc_vault> <braintree_paypal_vault> <model>BraintreePayPalVaultFacade</model> @@ -85,6 +88,7 @@ <tokenFormat>Magento\Braintree\Model\InstantPurchase\PayPal\TokenFormatter</tokenFormat> <additionalInformation>Magento\Braintree\Model\InstantPurchase\PaymentAdditionalInformationProvider</additionalInformation> </instant_purchase> + <group>braintree</group> </braintree_paypal_vault> </payment> </default> diff --git a/app/code/Magento/Braintree/etc/payment.xml b/app/code/Magento/Braintree/etc/payment.xml new file mode 100644 index 00000000000..dbabd911510 --- /dev/null +++ b/app/code/Magento/Braintree/etc/payment.xml @@ -0,0 +1,15 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<payment xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Payment:etc/payment.xsd"> + <groups> + <group id="braintree"> + <label>Braintree</label> + </group> + </groups> +</payment> diff --git a/app/code/Magento/Braintree/view/adminhtml/templates/form/cc.phtml b/app/code/Magento/Braintree/view/adminhtml/templates/form/cc.phtml index 535a5a852fe..4c15fffa818 100644 --- a/app/code/Magento/Braintree/view/adminhtml/templates/form/cc.phtml +++ b/app/code/Magento/Braintree/view/adminhtml/templates/form/cc.phtml @@ -83,7 +83,7 @@ $ccType = $block->getInfoData('cc_type'); id="<?= /* @noEscape */ $code ?>_vault" name="payment[is_active_payment_token_enabler]" class="admin__control-checkbox"/> - <label class="label" for="<?= /* @noEscape */ $code ?>_vault"> + <label class="label admin__field-label" for="<?= /* @noEscape */ $code ?>_vault"> <span><?= $block->escapeHtml(__('Save for later use.')) ?></span> </label> </div> diff --git a/app/code/Magento/Bundle/Block/Adminhtml/Sales/Order/Items/Renderer.php b/app/code/Magento/Bundle/Block/Adminhtml/Sales/Order/Items/Renderer.php index 23fc2026ab1..82a0086ad67 100644 --- a/app/code/Magento/Bundle/Block/Adminhtml/Sales/Order/Items/Renderer.php +++ b/app/code/Magento/Bundle/Block/Adminhtml/Sales/Order/Items/Renderer.php @@ -100,6 +100,8 @@ public function getChildren($item) } /** + * Check if item can be shipped separately + * * @param mixed $item * @return bool * @SuppressWarnings(PHPMD.CyclomaticComplexity) @@ -136,6 +138,8 @@ public function isShipmentSeparately($item = null) } /** + * Check if child items calculated + * * @param mixed $item * @return bool * @SuppressWarnings(PHPMD.CyclomaticComplexity) @@ -174,6 +178,8 @@ public function isChildCalculated($item = null) } /** + * Retrieve selection attributes values + * * @param mixed $item * @return mixed|null */ @@ -191,6 +197,8 @@ public function getSelectionAttributes($item) } /** + * Retrieve order item options array + * * @return array */ public function getOrderOptions() @@ -212,6 +220,8 @@ public function getOrderOptions() } /** + * Retrieve order item + * * @return mixed */ public function getOrderItem() @@ -223,6 +233,8 @@ public function getOrderItem() } /** + * Get html info for item + * * @param mixed $item * @return string */ @@ -245,6 +257,8 @@ public function getValueHtml($item) } /** + * Check if we can show price info for this item + * * @param object $item * @return bool */ diff --git a/app/code/Magento/Bundle/Model/Plugin/Frontend/Product.php b/app/code/Magento/Bundle/Model/Plugin/Frontend/Product.php new file mode 100644 index 00000000000..499f0cd2ca9 --- /dev/null +++ b/app/code/Magento/Bundle/Model/Plugin/Frontend/Product.php @@ -0,0 +1,48 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Bundle\Model\Plugin\Frontend; + +use Magento\Bundle\Model\Product\Type; +use Magento\Catalog\Model\Product as CatalogProduct; + +/** + * Add child identities to product identities on storefront. + */ +class Product +{ + /** + * @var Type + */ + private $type; + + /** + * @param Type $type + */ + public function __construct(Type $type) + { + $this->type = $type; + } + + /** + * Add child identities to product identities + * + * @param CatalogProduct $product + * @param array $identities + * @return array + */ + public function afterGetIdentities(CatalogProduct $product, array $identities): array + { + foreach ($this->type->getChildrenIds($product->getEntityId()) as $childIds) { + foreach ($childIds as $childId) { + $identities[] = CatalogProduct::CACHE_TAG . '_' . $childId; + } + } + + return array_unique($identities); + } +} diff --git a/app/code/Magento/Bundle/Model/Product/Type.php b/app/code/Magento/Bundle/Model/Product/Type.php index 641f4490874..2dc519dbf15 100644 --- a/app/code/Magento/Bundle/Model/Product/Type.php +++ b/app/code/Magento/Bundle/Model/Product/Type.php @@ -13,6 +13,7 @@ use Magento\Framework\EntityManager\MetadataPool; use Magento\Framework\Pricing\PriceCurrencyInterface; use Magento\Framework\Serialize\Serializer\Json; +use Magento\Framework\Stdlib\ArrayUtils; /** * Bundle Type Model @@ -160,6 +161,11 @@ class Type extends \Magento\Catalog\Model\Product\Type\AbstractType */ private $selectionCollectionFilterApplier; + /** + * @var ArrayUtils + */ + private $arrayUtility; + /** * @param \Magento\Catalog\Model\Product\Option $catalogProductOption * @param \Magento\Eav\Model\Config $eavConfig @@ -185,6 +191,7 @@ class Type extends \Magento\Catalog\Model\Product\Type\AbstractType * @param \Magento\Framework\Serialize\Serializer\Json $serializer * @param MetadataPool|null $metadataPool * @param SelectionCollectionFilterApplier|null $selectionCollectionFilterApplier + * @param ArrayUtils|null $arrayUtility * * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ @@ -212,7 +219,8 @@ public function __construct( \Magento\CatalogInventory\Api\StockStateInterface $stockState, Json $serializer = null, MetadataPool $metadataPool = null, - SelectionCollectionFilterApplier $selectionCollectionFilterApplier = null + SelectionCollectionFilterApplier $selectionCollectionFilterApplier = null, + ArrayUtils $arrayUtility = null ) { $this->_catalogProduct = $catalogProduct; $this->_catalogData = $catalogData; @@ -232,6 +240,7 @@ public function __construct( $this->selectionCollectionFilterApplier = $selectionCollectionFilterApplier ?: ObjectManager::getInstance()->get(SelectionCollectionFilterApplier::class); + $this->arrayUtility= $arrayUtility ?: ObjectManager::getInstance()->get(ArrayUtils::class); parent::__construct( $catalogProductOption, @@ -673,7 +682,7 @@ protected function _prepareProduct(\Magento\Framework\DataObject $buyRequest, $p $options ); - $selectionIds = $this->multiToFlatArray($options); + $selectionIds = array_values($this->arrayUtility->flatten($options)); // If product has not been configured yet then $selections array should be empty if (!empty($selectionIds)) { $selections = $this->getSelectionsByIds($selectionIds, $product); @@ -814,26 +823,6 @@ private function recursiveIntval(array $array) return $array; } - /** - * Convert multi dimensional array to flat - * - * @param array $array - * @return int[] - */ - private function multiToFlatArray(array $array) - { - $flatArray = []; - foreach ($array as $value) { - if (is_array($value)) { - $flatArray = array_merge($flatArray, $this->multiToFlatArray($value)); - } else { - $flatArray[] = $value; - } - } - - return $flatArray; - } - /** * Retrieve message for specify option(s) * diff --git a/app/code/Magento/Bundle/Setup/Patch/Data/UpdateBundleRelatedEntityTytpes.php b/app/code/Magento/Bundle/Setup/Patch/Data/UpdateBundleRelatedEntityTypes.php similarity index 94% rename from app/code/Magento/Bundle/Setup/Patch/Data/UpdateBundleRelatedEntityTytpes.php rename to app/code/Magento/Bundle/Setup/Patch/Data/UpdateBundleRelatedEntityTypes.php index 44647ea76a1..701def7fc13 100644 --- a/app/code/Magento/Bundle/Setup/Patch/Data/UpdateBundleRelatedEntityTytpes.php +++ b/app/code/Magento/Bundle/Setup/Patch/Data/UpdateBundleRelatedEntityTypes.php @@ -6,19 +6,19 @@ namespace Magento\Bundle\Setup\Patch\Data; +use Magento\Catalog\Api\Data\ProductAttributeInterface; +use Magento\Eav\Setup\EavSetup; use Magento\Eav\Setup\EavSetupFactory; -use Magento\Framework\App\ResourceConnection; use Magento\Framework\Setup\ModuleDataSetupInterface; use Magento\Framework\Setup\Patch\DataPatchInterface; use Magento\Framework\Setup\Patch\PatchVersionInterface; -use Magento\Catalog\Api\Data\ProductAttributeInterface; -use Magento\Eav\Setup\EavSetup; /** - * Class UpdateBundleRelatedEntityTytpes + * Class UpdateBundleRelatedEntityTypes + * * @package Magento\Bundle\Setup\Patch */ -class UpdateBundleRelatedEntityTytpes implements DataPatchInterface, PatchVersionInterface +class UpdateBundleRelatedEntityTypes implements DataPatchInterface, PatchVersionInterface { /** * @var ModuleDataSetupInterface @@ -31,7 +31,7 @@ class UpdateBundleRelatedEntityTytpes implements DataPatchInterface, PatchVersio private $eavSetupFactory; /** - * UpdateBundleRelatedEntityTytpes constructor. + * UpdateBundleRelatedEntityTypes constructor. * @param ModuleDataSetupInterface $moduleDataSetup * @param EavSetupFactory $eavSetupFactory */ @@ -44,7 +44,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function apply() { @@ -177,7 +177,7 @@ private function upgradeShipmentType(EavSetup $eavSetup) } /** - * {@inheritdoc} + * @inheritdoc */ public static function getDependencies() { @@ -187,7 +187,7 @@ public static function getDependencies() } /** - * {@inheritdoc} + * @inheritdoc */ public static function getVersion() { @@ -195,7 +195,7 @@ public static function getVersion() } /** - * {@inheritdoc} + * @inheritdoc */ public function getAliases() { diff --git a/app/code/Magento/Bundle/Test/Mftf/ActionGroup/CreateBundleProductActionGroup.xml b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/CreateBundleProductActionGroup.xml index 72e72911194..d86d720ed7f 100644 --- a/app/code/Magento/Bundle/Test/Mftf/ActionGroup/CreateBundleProductActionGroup.xml +++ b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/CreateBundleProductActionGroup.xml @@ -56,4 +56,74 @@ <fillField selector="{{AdminProductFormBundleSection.bundleOptionXProductYQuantity(x, '0')}}" userInput="50" stepKey="fillQuantity1"/> <fillField selector="{{AdminProductFormBundleSection.bundleOptionXProductYQuantity(x, '1')}}" userInput="50" stepKey="fillQuantity2"/> </actionGroup> + + <actionGroup name="addBundleOptionWithOneProduct" extends="addBundleOptionWithTwoProducts"> + <remove keyForRemoval="openProductFilters2"/> + <remove keyForRemoval="fillProductSkuFilter2"/> + <remove keyForRemoval="clickApplyFilters2"/> + <remove keyForRemoval="waitForFilteredGridLoad2"/> + <remove keyForRemoval="selectProduct2"/> + <remove keyForRemoval="selectProduct2"/> + <remove keyForRemoval="fillQuantity1"/> + <remove keyForRemoval="fillQuantity2"/> + <fillField selector="{{AdminProductFormBundleSection.bundleOptionXProductYQuantity(x, '0')}}" userInput="1" stepKey="fillQuantity" after="clickAddButton1"/> + </actionGroup> + + <actionGroup name="addBundleOptionWithTreeProducts" extends="addBundleOptionWithTwoProducts"> + <arguments> + <argument name="prodTreeSku" type="string"/> + </arguments> + <remove keyForRemoval="fillQuantity1"/> + <remove keyForRemoval="fillQuantity2"/> + <conditionalClick selector="{{AdminProductGridFilterSection.clearFilters}}" dependentSelector="{{AdminProductGridFilterSection.clearFilters}}" visible="true" stepKey="clickClearFilters3" after="selectProduct2"/> + <click selector="{{AdminProductGridFilterSection.filters}}" stepKey="openProductFilters3" after="clickClearFilters3"/> + <fillField selector="{{AdminProductGridFilterSection.skuFilter}}" userInput="{{prodTreeSku}}" stepKey="fillProductSkuFilter3" after="openProductFilters3"/> + <click selector="{{AdminProductGridFilterSection.applyFilters}}" stepKey="clickApplyFilters3" after="fillProductSkuFilter3"/> + <waitForElementNotVisible selector="{{AdminProductGridSection.loadingMask}}" stepKey="waitForFilteredGridLoad3" time="30" after="clickApplyFilters3"/> + <checkOption selector="{{AdminAddProductsToOptionPanel.firstCheckbox}}" stepKey="selectProduct3" after="waitForFilteredGridLoad3"/> + <fillField selector="{{AdminProductFormBundleSection.bundleOptionXProductYQuantity(x, '0')}}" userInput="1" stepKey="fillQuantity1" after="clickAddButton1"/> + <fillField selector="{{AdminProductFormBundleSection.bundleOptionXProductYQuantity(x, '1')}}" userInput="1" stepKey="fillQuantity2" after="fillQuantity1"/> + <fillField selector="{{AdminProductFormBundleSection.bundleOptionXProductYQuantity(x, '2')}}" userInput="1" stepKey="fillQuantity3" after="fillQuantity2"/> + </actionGroup> + + <actionGroup name="addBundleOptionWithSixProducts" extends="addBundleOptionWithTwoProducts"> + <arguments> + <argument name="prodTreeSku" type="string"/> + <argument name="prodFourSku" type="string"/> + <argument name="prodFiveSku" type="string"/> + <argument name="prodSixSku" type="string"/> + </arguments> + <remove keyForRemoval="fillQuantity1"/> + <remove keyForRemoval="fillQuantity2"/> + <conditionalClick selector="{{AdminProductGridFilterSection.clearFilters}}" dependentSelector="{{AdminProductGridFilterSection.clearFilters}}" visible="true" stepKey="clickClearFilters3" after="selectProduct2"/> + <click selector="{{AdminProductGridFilterSection.filters}}" stepKey="openProductFilters3" after="clickClearFilters3"/> + <fillField selector="{{AdminProductGridFilterSection.skuFilter}}" userInput="{{prodTreeSku}}" stepKey="fillProductSkuFilter3" after="openProductFilters3"/> + <click selector="{{AdminProductGridFilterSection.applyFilters}}" stepKey="clickApplyFilters3" after="fillProductSkuFilter3"/> + <waitForElementNotVisible selector="{{AdminProductGridSection.loadingMask}}" stepKey="waitForFilteredGridLoad3" time="30" after="clickApplyFilters3"/> + <checkOption selector="{{AdminAddProductsToOptionPanel.firstCheckbox}}" stepKey="selectProduct3" after="waitForFilteredGridLoad3"/> + <conditionalClick selector="{{AdminProductGridFilterSection.clearFilters}}" dependentSelector="{{AdminProductGridFilterSection.clearFilters}}" visible="true" stepKey="clickClearFilters4" after="selectProduct3"/> + <click selector="{{AdminProductGridFilterSection.filters}}" stepKey="openProductFilters4" after="clickClearFilters4"/> + <fillField selector="{{AdminProductGridFilterSection.skuFilter}}" userInput="{{prodFourSku}}" stepKey="fillProductSkuFilter4" after="openProductFilters4"/> + <click selector="{{AdminProductGridFilterSection.applyFilters}}" stepKey="clickApplyFilters4" after="fillProductSkuFilter4"/> + <waitForElementNotVisible selector="{{AdminProductGridSection.loadingMask}}" stepKey="waitForFilteredGridLoad4" time="30" after="clickApplyFilters4"/> + <checkOption selector="{{AdminAddProductsToOptionPanel.firstCheckbox}}" stepKey="selectProduct4" after="clickApplyFilters4"/> + <conditionalClick selector="{{AdminProductGridFilterSection.clearFilters}}" dependentSelector="{{AdminProductGridFilterSection.clearFilters}}" visible="true" stepKey="clickClearFilters5" after="selectProduct4"/> + <click selector="{{AdminProductGridFilterSection.filters}}" stepKey="openProductFilters5" after="clickClearFilters5"/> + <fillField selector="{{AdminProductGridFilterSection.skuFilter}}" userInput="{{prodFiveSku}}" stepKey="fillProductSkuFilter5" after="openProductFilters5"/> + <click selector="{{AdminProductGridFilterSection.applyFilters}}" stepKey="clickApplyFilters5" after="fillProductSkuFilter5"/> + <waitForElementNotVisible selector="{{AdminProductGridSection.loadingMask}}" stepKey="waitForFilteredGridLoad5" time="30" after="clickApplyFilters5"/> + <checkOption selector="{{AdminAddProductsToOptionPanel.firstCheckbox}}" stepKey="selectProduct5" after="waitForFilteredGridLoad5"/> + <conditionalClick selector="{{AdminProductGridFilterSection.clearFilters}}" dependentSelector="{{AdminProductGridFilterSection.clearFilters}}" visible="true" stepKey="clickClearFilters6" after="selectProduct5"/> + <click selector="{{AdminProductGridFilterSection.filters}}" stepKey="openProductFilters6" after="clickClearFilters6"/> + <fillField selector="{{AdminProductGridFilterSection.skuFilter}}" userInput="{{prodSixSku}}" stepKey="fillProductSkuFilter6" after="openProductFilters6"/> + <click selector="{{AdminProductGridFilterSection.applyFilters}}" stepKey="clickApplyFilters6" after="fillProductSkuFilter6"/> + <waitForElementNotVisible selector="{{AdminProductGridSection.loadingMask}}" stepKey="waitForFilteredGridLoad6" time="30" after="clickApplyFilters6"/> + <checkOption selector="{{AdminAddProductsToOptionPanel.firstCheckbox}}" stepKey="selectProduct6" after="waitForFilteredGridLoad6"/> + <fillField selector="{{AdminProductFormBundleSection.bundleOptionXProductYQuantity(x, '0')}}" userInput="2" stepKey="fillQuantity1" after="clickAddButton1"/> + <fillField selector="{{AdminProductFormBundleSection.bundleOptionXProductYQuantity(x, '1')}}" userInput="2" stepKey="fillQuantity2" after="fillQuantity1"/> + <fillField selector="{{AdminProductFormBundleSection.bundleOptionXProductYQuantity(x, '2')}}" userInput="2" stepKey="fillQuantity3" after="fillQuantity2"/> + <fillField selector="{{AdminProductFormBundleSection.bundleOptionXProductYQuantity(x, '3')}}" userInput="2" stepKey="fillQuantity4" after="fillQuantity3"/> + <fillField selector="{{AdminProductFormBundleSection.bundleOptionXProductYQuantity(x, '4')}}" userInput="2" stepKey="fillQuantity5" after="fillQuantity4"/> + <fillField selector="{{AdminProductFormBundleSection.bundleOptionXProductYQuantity(x, '5')}}" userInput="2" stepKey="fillQuantity6" after="fillQuantity5"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/Bundle/Test/Mftf/ActionGroup/EnableDisableProductActionGroup.xml b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/EnableDisableProductActionGroup.xml index e3ac6483bc7..20bde5f87bd 100644 --- a/app/code/Magento/Bundle/Test/Mftf/ActionGroup/EnableDisableProductActionGroup.xml +++ b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/EnableDisableProductActionGroup.xml @@ -14,7 +14,8 @@ <fillField selector="{{AdminProductFormBundleSection.productSku}}" userInput="{{BundleProduct.sku}}" stepKey="fillProductSku"/> <!--Trigger SEO drop down--> - <conditionalClick selector="{{AdminProductFormBundleSection.seoDropdown}}" dependentSelector="{{AdminProductFormBundleSection.seoDependent}}" visible="false" stepKey="OpenDropDownIfClosed"/> + <scrollTo selector="{{AdminProductFormBundleSection.seoDropdown}}" stepKey="moveToSEOSection"/> + <conditionalClick selector="{{AdminProductFormBundleSection.seoDropdown}}" dependentSelector="{{AdminProductFormBundleSection.urlKey}}" visible="false" stepKey="openDropDownIfClosed"/> <waitForPageLoad stepKey="WaitForDropDownSEO"/> <!--Fill URL input--> diff --git a/app/code/Magento/Bundle/Test/Mftf/Data/BundleProductsSummaryData.xml b/app/code/Magento/Bundle/Test/Mftf/Data/BundleProductsSummaryData.xml new file mode 100644 index 00000000000..5cd286c0c6a --- /dev/null +++ b/app/code/Magento/Bundle/Test/Mftf/Data/BundleProductsSummaryData.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="BundleProductsSummary" type="Quote"> + <data key="subtotal">1,968.00</data> + <data key="shipping">5.00</data> + <data key="total">1,973.00</data> + <data key="shippingMethod">Flat Rate - Fixed</data> + </entity> +</entities> diff --git a/app/code/Magento/Bundle/Test/Mftf/Data/CustomAttributeData.xml b/app/code/Magento/Bundle/Test/Mftf/Data/CustomAttributeData.xml index e6866ae74a7..256bfd77469 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Data/CustomAttributeData.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Data/CustomAttributeData.xml @@ -23,4 +23,12 @@ <data key="attribute_code">price_view</data> <data key="value">0</data> </entity> + <entity name="CustomAttributeFixWeight" type="custom_attribute"> + <data key="attribute_code">weight_type</data> + <data key="value">1</data> + </entity> + <entity name="CustomAttributeFixSku" type="custom_attribute"> + <data key="attribute_code">sku_type</data> + <data key="value">1</data> + </entity> </entities> diff --git a/app/code/Magento/Bundle/Test/Mftf/Data/ProductData.xml b/app/code/Magento/Bundle/Test/Mftf/Data/ProductData.xml index 9dc30e73228..0a0c77755fc 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Data/ProductData.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Data/ProductData.xml @@ -31,6 +31,22 @@ <data key="fixedPriceFormatted">$10.00</data> <data key="defaultAttribute">Default</data> </entity> + <entity name="FixedBundleProduct" type="product2"> + <data key="name" unique="suffix">FixedBundleProduct</data> + <data key="sku" unique="suffix">fixed-bundle-product</data> + <data key="type_id">bundle</data> + <data key="attribute_set_id">4</data> + <data key="price">1.23</data> + <data key="visibility">4</data> + <data key="status">1</data> + <data key="urlKey" unique="suffix">fixed-bundle-product</data> + <requiredEntity type="custom_attribute">CustomAttributeCategoryIds</requiredEntity> + <requiredEntity type="product_extension_attribute">EavStockItem</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> <entity name="ApiBundleProduct" type="product2"> <data key="name" unique="suffix">Api Bundle Product</data> <data key="sku" unique="suffix">api-bundle-product</data> diff --git a/app/code/Magento/Bundle/Test/Mftf/Section/AdminProductFormBundleSection.xml b/app/code/Magento/Bundle/Test/Mftf/Section/AdminProductFormBundleSection.xml index 814d03c52f4..516f40ac2e7 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Section/AdminProductFormBundleSection.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Section/AdminProductFormBundleSection.xml @@ -22,6 +22,8 @@ <element name="bundleOptionXProductYQuantity" type="input" selector="[name='bundle_options[bundle_options][{{x}}][bundle_selections][{{y}}][selection_qty]']" parameterized="true"/> <element name="addProductsToOption" type="button" selector="[data-index='modal_set']" timeout="30"/> <element name="nthAddProductsToOption" type="button" selector="//tr[{{var}}]//button[@data-index='modal_set']" timeout="30" parameterized="true"/> + <element name="bundlePriceType" type="select" selector="bundle_options[bundle_options][0][bundle_selections][0][selection_price_type]"/> + <element name="bundlePriceValue" type="input" selector="bundle_options[bundle_options][0][bundle_selections][0][selection_price_value]"/> <!--Select"url Key"InputForm--> <element name="urlKey" type="input" selector="//input[@name='product[url_key]']" timeout="30"/> <!--AddSelectedProducts--> diff --git a/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontBundledSection.xml b/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontBundledSection.xml index 946992f1efe..dbe48c46c82 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontBundledSection.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontBundledSection.xml @@ -9,6 +9,8 @@ <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontBundledSection"> + <element name="productCheckbox" type="select" selector="//*[@id='customizeTitle']/following-sibling::div[{{arg1}}]//div[{{arg2}}][@class='field choice']/input" parameterized="true"/> + <element name="bundleProductsPrice" type="text" selector="//*[@class='bundle-info']//*[contains(@id,'product-price')]/span"/> <element name="nthBundledOption" type="input" selector=".option:nth-of-type({{numOption}}) .choice:nth-of-type({{numOptionSelect}}) input" parameterized="true"/> <element name="addToCart" type="button" selector="#bundle-slide" timeout="30"/> <element name="addToCartConfigured" type="button" selector="#product-addtocart-button" timeout="30"/> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminAddDefaultVideoBundleProductTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminAddDefaultVideoBundleProductTest.xml index 3c003446976..c49202f31ae 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/AdminAddDefaultVideoBundleProductTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminAddDefaultVideoBundleProductTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminAddDefaultVideoBundleProductTest" extends="AdminAddDefaultVideoSimpleProductTest"> <annotations> <features value="Bundle"/> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminDeleteBundleDynamicProductTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminDeleteBundleDynamicProductTest.xml new file mode 100644 index 00000000000..bc9a3dba9a5 --- /dev/null +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminDeleteBundleDynamicProductTest.xml @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminDeleteBundleDynamicProductTest"> + <annotations> + <features value="Bundle"/> + <stories value="Delete products"/> + <title value="Delete Bundle Dynamic Product"/> + <description value="Admin should be able to delete a bundle dynamic product"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-11016"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="ApiBundleProductPriceViewRange" stepKey="createDynamicBundleProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + </before> + <after> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="deleteProductUsingProductGrid" stepKey="deleteBundleProductFilteredBySkuAndName"> + <argument name="product" value="$$createDynamicBundleProduct$$"/> + </actionGroup> + <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="A total of 1 record(s) have been deleted." stepKey="deleteMessage"/> + <!-- Verify product on Product Page --> + <amOnPage url="{{StorefrontProductPage.url($$createDynamicBundleProduct.name$$)}}" stepKey="amOnBundleProductPage"/> + <see selector="{{StorefrontProductInfoMainSection.productName}}" userInput="Whoops, our bad..." stepKey="seeWhoops"/> + <!-- Search for the product by sku --> + <fillField selector="{{StorefrontQuickSearchSection.searchPhrase}}" userInput="$$createDynamicBundleProduct.sku$$" stepKey="fillSearchBarByProductSku"/> + <waitForPageLoad stepKey="waitForSearchButton"/> + <click selector="{{StorefrontQuickSearchSection.searchButton}}" stepKey="clickSearchButton"/> + <waitForPageLoad stepKey="waitForSearchResults"/> + <!-- Should not see any search results --> + <dontSee userInput="$$createDynamicBundleProduct.sku$$" selector="{{StorefrontCatalogSearchMainSection.searchResults}}" stepKey="dontSeeProduct"/> + <see selector="{{StorefrontCatalogSearchMainSection.message}}" userInput="Your search returned no results." stepKey="seeCantFindProductOneMessage"/> + <!-- Go to the category page that we created in the before block --> + <amOnPage url="{{StorefrontCategoryPage.url($$createCategory.name$$)}}" stepKey="onCategoryPage"/> + <!-- Should not see the product --> + <dontSee userInput="$$createDynamicBundleProduct.name$$" selector="{{StorefrontCategoryMainSection.productsList}}" stepKey="dontSeeProductInCategory"/> + <see selector="{{StorefrontCategoryMainSection.emptyProductMessage}}" userInput="We can't find products matching the selection." stepKey="seeEmptyProductMessage"/> + </test> +</tests> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminDeleteBundleFixedProductTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminDeleteBundleFixedProductTest.xml new file mode 100644 index 00000000000..2527dae7ead --- /dev/null +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminDeleteBundleFixedProductTest.xml @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminDeleteBundleFixedProductTest"> + <annotations> + <features value="Bundle"/> + <stories value="Delete products"/> + <title value="Delete Bundle Fixed Product"/> + <description value="Admin should be able to delete a bundle fixed product"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-11017"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="FixedBundleProduct" stepKey="createFixedBundleProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + </before> + <after> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="deleteProductUsingProductGrid" stepKey="deleteBundleProductFilteredBySkuAndName"> + <argument name="product" value="$$createFixedBundleProduct$$"/> + </actionGroup> + <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="A total of 1 record(s) have been deleted." stepKey="deleteMessage"/> + <!-- Verify product on Product Page --> + <amOnPage url="{{StorefrontProductPage.url($$createFixedBundleProduct.name$$)}}" stepKey="amOnBundleProductPage"/> + <see selector="{{StorefrontProductInfoMainSection.productName}}" userInput="Whoops, our bad..." stepKey="seeWhoops"/> + <!-- Search for the product by sku --> + <fillField selector="{{StorefrontQuickSearchSection.searchPhrase}}" userInput="$$createFixedBundleProduct.sku$$" stepKey="fillSearchBarByProductSku"/> + <waitForPageLoad stepKey="waitForSearchButton"/> + <click selector="{{StorefrontQuickSearchSection.searchButton}}" stepKey="clickSearchButton"/> + <waitForPageLoad stepKey="waitForSearchResults"/> + <!-- Should not see any search results --> + <dontSee userInput="$$createFixedBundleProduct.sku$$" selector="{{StorefrontCatalogSearchMainSection.searchResults}}" stepKey="dontSeeProduct"/> + <see selector="{{StorefrontCatalogSearchMainSection.message}}" userInput="Your search returned no results." stepKey="seeCantFindProductOneMessage"/> + <!-- Go to the category page that we created in the before block --> + <amOnPage url="{{StorefrontCategoryPage.url($$createCategory.name$$)}}" stepKey="onCategoryPage"/> + <!-- Should not see the product --> + <dontSee userInput="$$createFixedBundleProduct.name$$" selector="{{StorefrontCategoryMainSection.productsList}}" stepKey="dontSeeProductInCategory"/> + <see selector="{{StorefrontCategoryMainSection.emptyProductMessage}}" userInput="We can't find products matching the selection." stepKey="seeEmptyProductMessage"/> + </test> +</tests> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminMassDeleteBundleProducts.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminMassDeleteBundleProducts.xml index c0edbf14e89..2f891fcc8f1 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/AdminMassDeleteBundleProducts.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminMassDeleteBundleProducts.xml @@ -99,8 +99,8 @@ <fillField selector="{{AdminProductFormBundleSection.productSku}}" userInput="{{BundleProduct.sku2}}" stepKey="fillProductSku2"/> <!--Trigger SEO drop down--> - <conditionalClick selector="{{AdminProductFormBundleSection.seoDropdown}}" dependentSelector="{{AdminProductFormBundleSection.seoDependent}}" visible="false" stepKey="OpenDropDownIfClosed2"/> - <waitForPageLoad stepKey="WaitForDropDownSEO"/> + <scrollTo selector="{{AdminProductFormBundleSection.seoDropdown}}" stepKey="moveToSEOSection"/> + <conditionalClick selector="{{AdminProductFormBundleSection.seoDropdown}}" dependentSelector="{{AdminProductFormBundleSection.urlKey}}" visible="false" stepKey="openDropDownIfClosed"/> <!--Fill URL input--> <fillField userInput="{{BundleProduct.urlKey2}}" selector="{{AdminProductFormBundleSection.urlKey}}" stepKey="FillsinSEOlinkExtension2"/> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminRemoveDefaultVideoBundleProductTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminRemoveDefaultVideoBundleProductTest.xml index e3cb68b6664..d050c5443d1 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/AdminRemoveDefaultVideoBundleProductTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminRemoveDefaultVideoBundleProductTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminRemoveDefaultVideoBundleProductTest" extends="AdminRemoveDefaultVideoSimpleProductTest"> <annotations> <features value="Bundle"/> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleProductTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleProductTest.xml index 0b220efaad4..52bce676008 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleProductTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleProductTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdvanceCatalogSearchBundleByNameTest" extends="AdvanceCatalogSearchSimpleProductByNameTest"> <annotations> <features value="Bundle"/> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/MassEnableDisableBundleProductsTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/MassEnableDisableBundleProductsTest.xml index 5b2b771434b..ff192538637 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/MassEnableDisableBundleProductsTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/MassEnableDisableBundleProductsTest.xml @@ -60,15 +60,7 @@ <fillField selector="{{AdminProductFormBundleSection.bundleOptionXProductYQuantity('0', '0')}}" userInput="{{BundleProduct.defaultQuantity}}" stepKey="fillProductDefaultQty1"/> <fillField selector="{{AdminProductFormBundleSection.bundleOptionXProductYQuantity('0', '1')}}" userInput="{{BundleProduct.defaultQuantity}}" stepKey="fillProductDefaultQty2"/> - <fillField selector="{{AdminProductFormBundleSection.productName}}" userInput="{{BundleProduct.name}}" stepKey="fillProductName"/> - <fillField selector="{{AdminProductFormBundleSection.productSku}}" userInput="{{BundleProduct.sku}}" stepKey="fillProductSku"/> - - <!--Trigger SEO drop down--> - <conditionalClick selector="{{AdminProductFormBundleSection.seoDropdown}}" dependentSelector="{{AdminProductFormBundleSection.seoDependent}}" visible="false" stepKey="OpenDropDownIfClosed"/> - <waitForPageLoad stepKey="WaitForDropDownSEO"/> - - <!--Fill URL input--> - <fillField userInput="{{BundleProduct.urlKey}}" selector="{{AdminProductFormBundleSection.urlKey}}" stepKey="FillsinSEOlinkExtension"/> + <actionGroup ref="AncillaryPrepBundleProduct" stepKey="createBundledProductForTwoSimpleProducts"/> <!--Save the product--> <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="clickSaveButton"/> @@ -104,7 +96,8 @@ <fillField selector="{{AdminProductFormBundleSection.productSku}}" userInput="{{BundleProduct.sku2}}" stepKey="fillProductSku2"/> <!--Trigger SEO drop down--> - <conditionalClick selector="{{AdminProductFormBundleSection.seoDropdown}}" dependentSelector="{{AdminProductFormBundleSection.seoDependent}}" visible="false" stepKey="OpenDropDownIfClosed2"/> + <scrollTo selector="{{AdminProductFormBundleSection.seoDropdown}}" stepKey="moveToSEOSection"/> + <conditionalClick selector="{{AdminProductFormBundleSection.seoDropdown}}" dependentSelector="{{AdminProductFormBundleSection.urlKey}}" visible="false" stepKey="openDropDownIfClosed"/> <waitForPageLoad stepKey="WaitForDropDownSEO2"/> <!--Fill URL input--> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontAddBundleOptionsToCartTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontAddBundleOptionsToCartTest.xml new file mode 100644 index 00000000000..a1630128638 --- /dev/null +++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontAddBundleOptionsToCartTest.xml @@ -0,0 +1,140 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontAddBundleOptionsToCartTest"> + <annotations> + <features value="Bundle"/> + <stories value="MAGETWO-95813: Only two bundle options are added to the cart"/> + <title value="Checking adding of bundle options to the cart"/> + <description value="Verifying adding of bundle options to the cart"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-95933"/> + <group value="Bundle"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="login"/> + <createData entity="SimpleProduct2" stepKey="simpleProduct1"/> + <createData entity="SimpleProduct2" stepKey="simpleProduct2"/> + <createData entity="SimpleProduct2" stepKey="simpleProduct3"/> + <createData entity="SimpleProduct2" stepKey="simpleProduct4"/> + <createData entity="SimpleProduct2" stepKey="simpleProduct5"/> + <createData entity="SimpleProduct2" stepKey="simpleProduct6"/> + <createData entity="SimpleProduct2" stepKey="simpleProduct7"/> + <createData entity="SimpleProduct2" stepKey="simpleProduct8"/> + <createData entity="SimpleProduct2" stepKey="simpleProduct9"/> + <createData entity="SimpleProduct2" stepKey="simpleProduct10"/> + </before> + <after> + <deleteData createDataKey="simpleProduct1" stepKey="deleteSimpleProduct1"/> + <deleteData createDataKey="simpleProduct2" stepKey="deleteSimpleProduct2"/> + <deleteData createDataKey="simpleProduct3" stepKey="deleteSimpleProduct3"/> + <deleteData createDataKey="simpleProduct4" stepKey="deleteSimpleProduct4"/> + <deleteData createDataKey="simpleProduct5" stepKey="deleteSimpleProduct5"/> + <deleteData createDataKey="simpleProduct6" stepKey="deleteSimpleProduct6"/> + <deleteData createDataKey="simpleProduct7" stepKey="deleteSimpleProduct7"/> + <deleteData createDataKey="simpleProduct8" stepKey="deleteSimpleProduct8"/> + <deleteData createDataKey="simpleProduct9" stepKey="deleteSimpleProduct9"/> + <deleteData createDataKey="simpleProduct10" stepKey="deleteSimpleProduct10"/> + <!--delete created bundle product--> + <actionGroup stepKey="deleteProduct1" ref="deleteProductBySku"> + <argument name="sku" value="{{BundleProduct.sku}}"/> + </actionGroup> + <conditionalClick selector="{{AdminProductGridFilterSection.clearFilters}}" + dependentSelector="{{AdminProductGridFilterSection.clearFilters}}" visible="true" stepKey="clickClearFilters"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Start creating a bundle product --> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="goToProductList"/> + <waitForPageLoad stepKey="waitForProductList"/> + <actionGroup ref="goToCreateProductPage" stepKey="goToCreateProduct"> + <argument name="product" value="BundleProduct"/> + </actionGroup> + <actionGroup ref="fillProductNameAndSkuInProductForm" stepKey="fillNameAndSku"> + <argument name="product" value="BundleProduct"/> + </actionGroup> + + <!-- Add Option One, a "Checkbox" type option, with tree products --> + <actionGroup ref="addBundleOptionWithTreeProducts" stepKey="addBundleOptionWithTreeProducts"> + <argument name="x" value="0"/> + <argument name="n" value="1"/> + <argument name="prodOneSku" value="$$simpleProduct1.sku$$"/> + <argument name="prodTwoSku" value="$$simpleProduct2.sku$$"/> + <argument name="prodTreeSku" value="$$simpleProduct3.sku$$"/> + <argument name="optionTitle" value="Option One"/> + <argument name="inputType" value="checkbox"/> + </actionGroup> + + <!-- Add Option Two, a "Radio Buttons" type option, with one product --> + <actionGroup ref="addBundleOptionWithOneProduct" stepKey="addBundleOptionWithOneProduct"> + <argument name="x" value="1"/> + <argument name="n" value="2"/> + <argument name="prodOneSku" value="$$simpleProduct4.sku$$"/> + <argument name="prodTwoSku" value=""/> + <argument name="optionTitle" value="Option Two"/> + <argument name="inputType" value="radio"/> + </actionGroup> + + <!-- Add Option Tree, a "Checkbox" type option, with six products --> + <actionGroup ref="addBundleOptionWithSixProducts" stepKey="addBundleOptionWithSixProducts"> + <argument name="x" value="2"/> + <argument name="n" value="3"/> + <argument name="prodOneSku" value="$$simpleProduct5.sku$$"/> + <argument name="prodTwoSku" value="$$simpleProduct6.sku$$"/> + <argument name="prodTreeSku" value="$$simpleProduct7.sku$$"/> + <argument name="prodFourSku" value="$$simpleProduct8.sku$$"/> + <argument name="prodFiveSku" value="$$simpleProduct9.sku$$"/> + <argument name="prodSixSku" value="$$simpleProduct10.sku$$"/> + <argument name="optionTitle" value="Option Tree"/> + <argument name="inputType" value="checkbox"/> + </actionGroup> + + <!-- Save product--> + <actionGroup ref="saveProductForm" stepKey="saveProduct"/> + + <!--Go to Storefront and open Bundle Product page--> + <amOnPage url="{{BundleProduct.sku}}.html" stepKey="goToStorefront"/> + <waitForPageLoad stepKey="waitForStorefront"/> + + <!--Click "Customize and Add to Cart" button--> + <click selector="{{StorefrontBundledSection.addToCart}}" stepKey="clickCustomize"/> + + <!--Assert Bundle Product Price--> + <grabTextFrom selector="{{StorefrontBundledSection.bundleProductsPrice}}" stepKey="grabProductsPrice"/> + <assertEquals expected='$123.00' expectedType="string" actual="$grabProductsPrice" message="ExpectedPrice" stepKey="assertBundleProductPrice"/> + + <!--Chose all products from 1st & 3rd options --> + <click stepKey="selectProduct1" selector="{{StorefrontBundledSection.productCheckbox('1','1')}}"/> + <click stepKey="selectProduct2" selector="{{StorefrontBundledSection.productCheckbox('1','2')}}"/> + <click stepKey="selectProduct3" selector="{{StorefrontBundledSection.productCheckbox('1','3')}}"/> + <click stepKey="selectProduct5" selector="{{StorefrontBundledSection.productCheckbox('3','1')}}"/> + <click stepKey="selectProduct6" selector="{{StorefrontBundledSection.productCheckbox('3','2')}}"/> + <click stepKey="selectProduct7" selector="{{StorefrontBundledSection.productCheckbox('3','3')}}"/> + <click stepKey="selectProduct8" selector="{{StorefrontBundledSection.productCheckbox('3','4')}}"/> + <click stepKey="selectProduct9" selector="{{StorefrontBundledSection.productCheckbox('3','5')}}"/> + <click stepKey="selectProduct10" selector="{{StorefrontBundledSection.productCheckbox('3','6')}}"/> + + <!--Click "Add to Cart" button--> + <click selector="{{StorefrontBundleProductActionSection.addToCartButton}}" stepKey="clickAddBundleProductToCart"/> + <waitForPageLoad time="30" stepKey="waitForAddBundleProductPageLoad"/> + + <!--Click "mini cart" icon--> + <actionGroup ref="StorefrontOpenCartFromMinicartActionGroup" stepKey="openCart"/> + <waitForPageLoad stepKey="waitForDetailsOpen"/> + + <!--Check all products and Cart Subtotal --> + <actionGroup ref="StorefrontCheckCartActionGroup" stepKey="cartAssert" after="waitForDetailsOpen"> + <argument name="subtotal" value="1,968.00"/> + <argument name="shipping" value="5.00"/> + <argument name="shippingMethod" value="Flat Rate - Fixed"/> + <argument name="total" value="1,973.00"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Bundle/Test/Unit/Model/Plugin/Frontend/ProductTest.php b/app/code/Magento/Bundle/Test/Unit/Model/Plugin/Frontend/ProductTest.php new file mode 100644 index 00000000000..ee08618eab5 --- /dev/null +++ b/app/code/Magento/Bundle/Test/Unit/Model/Plugin/Frontend/ProductTest.php @@ -0,0 +1,73 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Bundle\Test\Unit\Model\Plugin\Frontend; + +use Magento\Bundle\Model\Plugin\Frontend\Product as ProductPlugin; +use Magento\Bundle\Model\Product\Type; +use Magento\Catalog\Model\Product; +use PHPUnit_Framework_MockObject_MockObject as MockObject; + +class ProductTest extends \PHPUnit\Framework\TestCase +{ + /** @var \Magento\Bundle\Model\Plugin\Product */ + private $plugin; + + /** @var MockObject|Type */ + private $type; + + /** @var MockObject|\Magento\Catalog\Model\Product */ + private $product; + + protected function setUp() + { + $this->product = $this->getMockBuilder(Product::class) + ->disableOriginalConstructor() + ->setMethods(['getEntityId']) + ->getMock(); + + $this->type = $this->getMockBuilder(Type::class) + ->disableOriginalConstructor() + ->setMethods(['getChildrenIds']) + ->getMock(); + + $this->plugin = new ProductPlugin($this->type); + } + + public function testAfterGetIdentities() + { + $baseIdentities = [ + 'SomeCacheId', + 'AnotherCacheId', + ]; + $id = 12345; + $childIds = [ + 1 => [1, 2, 5, 100500], + 12 => [7, 22, 45, 24612] + ]; + $expectedIdentities = [ + 'SomeCacheId', + 'AnotherCacheId', + Product::CACHE_TAG . '_' . 1, + Product::CACHE_TAG . '_' . 2, + Product::CACHE_TAG . '_' . 5, + Product::CACHE_TAG . '_' . 100500, + Product::CACHE_TAG . '_' . 7, + Product::CACHE_TAG . '_' . 22, + Product::CACHE_TAG . '_' . 45, + Product::CACHE_TAG . '_' . 24612, + ]; + $this->product->expects($this->once()) + ->method('getEntityId') + ->will($this->returnValue($id)); + $this->type->expects($this->once()) + ->method('getChildrenIds') + ->with($id) + ->will($this->returnValue($childIds)); + $identities = $this->plugin->afterGetIdentities($this->product, $baseIdentities); + $this->assertEquals($expectedIdentities, $identities); + } +} diff --git a/app/code/Magento/Bundle/Test/Unit/Model/Product/TypeTest.php b/app/code/Magento/Bundle/Test/Unit/Model/Product/TypeTest.php index 59f7f008ed3..9d7629c6f0a 100644 --- a/app/code/Magento/Bundle/Test/Unit/Model/Product/TypeTest.php +++ b/app/code/Magento/Bundle/Test/Unit/Model/Product/TypeTest.php @@ -15,6 +15,7 @@ use Magento\Framework\EntityManager\MetadataPool; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Serialize\Serializer\Json; +use Magento\Framework\Stdlib\ArrayUtils; /** * Class TypeTest @@ -87,6 +88,11 @@ class TypeTest extends \PHPUnit\Framework\TestCase */ private $serializer; + /** + * @var ArrayUtils|\PHPUnit_Framework_MockObject_MockObject + */ + private $arrayUtility; + /** * @return void */ @@ -159,6 +165,11 @@ protected function setUp() ->disableOriginalConstructor() ->getMock(); + $this->arrayUtility = $this->getMockBuilder(ArrayUtils::class) + ->setMethods(['flatten']) + ->disableOriginalConstructor() + ->getMock(); + $objectHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); $this->model = $objectHelper->getObject( \Magento\Bundle\Model\Product\Type::class, @@ -175,6 +186,7 @@ protected function setUp() 'priceCurrency' => $this->priceCurrency, 'serializer' => $this->serializer, 'metadataPool' => $this->metadataPool, + 'arrayUtility' => $this->arrayUtility ] ); } @@ -421,6 +433,8 @@ function ($key) use ($optionCollection, $selectionCollection) { return $resultValue; } ); + $bundleOptions = [3 => 5]; + $product->expects($this->any()) ->method('getId') ->willReturn(333); @@ -438,9 +452,7 @@ function ($key) use ($optionCollection, $selectionCollection) { ->with($selectionCollection, true, true); $productType->expects($this->once()) ->method('setStoreFilter'); - $buyRequest->expects($this->once()) - ->method('getBundleOption') - ->willReturn([3 => 5]); + $buyRequest->expects($this->once())->method('getBundleOption')->willReturn($bundleOptions); $selectionCollection->expects($this->any()) ->method('getItems') ->willReturn([$selection]); @@ -491,6 +503,9 @@ function ($key) use ($optionCollection, $selectionCollection) { $option->expects($this->once()) ->method('getTitle') ->willReturn('Title for option'); + + $this->arrayUtility->expects($this->once())->method('flatten')->willReturn($bundleOptions); + $buyRequest->expects($this->once()) ->method('getBundleOptionQty') ->willReturn([3 => 5]); @@ -653,6 +668,8 @@ function ($key) use ($optionCollection, $selectionCollection) { return $resultValue; } ); + $bundleOptions = [3 => 5]; + $product->expects($this->any()) ->method('getId') ->willReturn(333); @@ -672,7 +689,10 @@ function ($key) use ($optionCollection, $selectionCollection) { ->method('setStoreFilter'); $buyRequest->expects($this->once()) ->method('getBundleOption') - ->willReturn([3 => 5]); + ->willReturn($bundleOptions); + + $this->arrayUtility->expects($this->once())->method('flatten')->willReturn($bundleOptions); + $selectionCollection->expects($this->any()) ->method('getItems') ->willReturn([$selection]); @@ -890,9 +910,10 @@ function ($key) use ($optionCollection, $selectionCollection) { ->with($selectionCollection, true, true); $productType->expects($this->once()) ->method('setStoreFilter'); - $buyRequest->expects($this->once()) - ->method('getBundleOption') - ->willReturn([3 => 5]); + + $bundleOptions = [3 => 5]; + $buyRequest->expects($this->once())->method('getBundleOption')->willReturn($bundleOptions); + $selectionCollection->expects($this->any()) ->method('getItems') ->willReturn([$selection]); @@ -943,6 +964,9 @@ function ($key) use ($optionCollection, $selectionCollection) { $option->expects($this->once()) ->method('getTitle') ->willReturn('Title for option'); + + $this->arrayUtility->expects($this->once())->method('flatten')->willReturn($bundleOptions); + $buyRequest->expects($this->once()) ->method('getBundleOptionQty') ->willReturn([3 => 5]); @@ -1053,13 +1077,15 @@ function ($key) use ($optionCollection) { ->willReturn(333); $productType->expects($this->once()) ->method('setStoreFilter'); - $buyRequest->expects($this->once()) - ->method('getBundleOption') - ->willReturn([]); + + $bundleOptions = []; + $buyRequest->expects($this->once())->method('getBundleOption')->willReturn($bundleOptions); $buyRequest->expects($this->once()) ->method('getBundleOptionQty') ->willReturn([3 => 5]); + $this->arrayUtility->expects($this->once())->method('flatten')->willReturn($bundleOptions); + $result = $this->model->prepareForCartAdvanced($buyRequest, $product, 'single'); $this->assertEquals([$product], $result); } @@ -1165,9 +1191,12 @@ function ($key) use ($optionCollection, $selectionCollection) { ->with($selectionCollection, true, true); $productType->expects($this->once()) ->method('setStoreFilter'); - $buyRequest->expects($this->once()) - ->method('getBundleOption') - ->willReturn([3 => 5]); + + $bundleOptions = [3 => 5]; + $buyRequest->expects($this->once())->method('getBundleOption')->willReturn($bundleOptions); + + $this->arrayUtility->expects($this->once())->method('flatten')->willReturn($bundleOptions); + $selectionCollection->expects($this->at(0)) ->method('getItems') ->willReturn([$selection]); @@ -1289,9 +1318,12 @@ function ($key) use ($optionCollection, $selectionCollection) { ->willReturn($option); $productType->expects($this->once()) ->method('setStoreFilter'); - $buyRequest->expects($this->once()) - ->method('getBundleOption') - ->willReturn([3 => 5]); + + $bundleOptions = [3 => 5]; + $buyRequest->expects($this->once())->method('getBundleOption')->willReturn($bundleOptions); + + $this->arrayUtility->expects($this->once())->method('flatten')->willReturn($bundleOptions); + $selectionCollection->expects($this->any()) ->method('getItems') ->willReturn([$selection]); diff --git a/app/code/Magento/Bundle/Test/Unit/Pricing/Price/SpecialPriceTest.php b/app/code/Magento/Bundle/Test/Unit/Pricing/Price/SpecialPriceTest.php index f38dfc5538c..3e60e057fe6 100644 --- a/app/code/Magento/Bundle/Test/Unit/Pricing/Price/SpecialPriceTest.php +++ b/app/code/Magento/Bundle/Test/Unit/Pricing/Price/SpecialPriceTest.php @@ -6,6 +6,7 @@ namespace Magento\Bundle\Test\Unit\Pricing\Price; use \Magento\Bundle\Pricing\Price\SpecialPrice; +use Magento\Store\Api\Data\WebsiteInterface; class SpecialPriceTest extends \PHPUnit\Framework\TestCase { @@ -77,12 +78,6 @@ public function testGetValue($regularPrice, $specialPrice, $isScopeDateInInterva ->method('getSpecialPrice') ->will($this->returnValue($specialPrice)); - $store = $this->getMockBuilder(\Magento\Store\Model\Store::class) - ->disableOriginalConstructor() - ->getMock(); - $this->saleable->expects($this->once()) - ->method('getStore') - ->will($this->returnValue($store)); $this->saleable->expects($this->once()) ->method('getSpecialFromDate') ->will($this->returnValue($specialFromDate)); @@ -92,7 +87,7 @@ public function testGetValue($regularPrice, $specialPrice, $isScopeDateInInterva $this->localeDate->expects($this->once()) ->method('isScopeDateInInterval') - ->with($store, $specialFromDate, $specialToDate) + ->with(WebsiteInterface::ADMIN_CODE, $specialFromDate, $specialToDate) ->will($this->returnValue($isScopeDateInInterval)); $this->priceCurrencyMock->expects($this->never()) diff --git a/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePanel.php b/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePanel.php index 98fd96c52cc..ad6fc12712c 100644 --- a/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePanel.php +++ b/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePanel.php @@ -14,6 +14,7 @@ use Magento\Framework\UrlInterface; use Magento\Ui\Component\Container; use Magento\Ui\Component\Form; +use Magento\Ui\Component\Form\Fieldset; use Magento\Ui\Component\Modal; /** @@ -69,13 +70,26 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc + * * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ public function modifyMeta(array $meta) { $meta = $this->removeFixedTierPrice($meta); - $path = $this->arrayManager->findPath(static::CODE_BUNDLE_DATA, $meta, null, 'children'); + + $groupCode = static::CODE_BUNDLE_DATA; + $path = $this->arrayManager->findPath($groupCode, $meta, null, 'children'); + if (empty($path)) { + $meta[$groupCode]['children'] = []; + $meta[$groupCode]['arguments']['data']['config'] = [ + 'componentType' => Fieldset::NAME, + 'label' => __('Bundle Items'), + 'collapsible' => true + ]; + + $path = $this->arrayManager->findPath($groupCode, $meta, null, 'children'); + } $meta = $this->arrayManager->merge( $path, @@ -220,7 +234,7 @@ private function removeFixedTierPrice(array $meta) } /** - * {@inheritdoc} + * @inheritdoc */ public function modifyData(array $data) { diff --git a/app/code/Magento/Bundle/etc/frontend/di.xml b/app/code/Magento/Bundle/etc/frontend/di.xml index 54f6d3b4b0f..fc820ff87a1 100644 --- a/app/code/Magento/Bundle/etc/frontend/di.xml +++ b/app/code/Magento/Bundle/etc/frontend/di.xml @@ -13,4 +13,7 @@ </argument> </arguments> </type> + <type name="Magento\Catalog\Model\Product"> + <plugin name="bundle" type="Magento\Bundle\Model\Plugin\Frontend\Product" sortOrder="100" /> + </type> </config> diff --git a/app/code/Magento/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 ff26d67bd83..12da960a9c6 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 @@ -28,8 +28,17 @@ <?php endif; ?> <?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 ($shipTogether) { + continue; + } + ?> <?php $attributes = $block->getSelectionAttributes($_item) ?> <?php if ($_prevOptionId != $attributes['option_id']): ?> <tr> @@ -60,14 +69,14 @@ </td> <?php endif; ?> <td class="col-price"> - <?php if ($block->canShowPriceInfo($_item)): ?> + <?php if ($block->canShowPriceInfo($_item) || $shipTogether): ?> <?= $block->getColumnHtml($_item, 'price') ?> <?php else: ?>   <?php endif; ?> </td> <td class="col-qty"> - <?php if ($block->canShowPriceInfo($_item)): ?> + <?php if ($block->canShowPriceInfo($_item) || $shipTogether): ?> <table class="qty-table"> <tr> <th><?= /* @escapeNotVerified */ __('Ordered') ?></th> @@ -116,7 +125,7 @@ <?php endif; ?> </td> <td class="col-qty-invoice"> - <?php if ($block->canShowPriceInfo($_item)): ?> + <?php if ($block->canShowPriceInfo($_item) || $shipTogether): ?> <?php if ($block->canEditQty()) : ?> <input type="text" class="input-text admin__control-text qty-input" 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 063d66edb9e..74e1c5f8749 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 @@ -7,95 +7,111 @@ // @codingStandardsIgnoreFile /** @var $block \Magento\Bundle\Block\Sales\Order\Items\Renderer */ +$parentItem = $block->getItem(); +$items = array_merge([$parentItem], $parentItem->getChildrenItems()); +$index = 0; +$prevOptionId = ''; ?> -<?php $parentItem = $block->getItem() ?> -<?php $items = array_merge([$parentItem], $parentItem->getChildrenItems()); ?> -<?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 $_showlastRow = true ?> + <?php if ($block->getItemOptions() + || $parentItem->getDescription() + || $this->helper('Magento\GiftMessage\Helper\Message')->isMessagesAllowed('order_item', $parentItem) + && $parentItem->getGiftMessageId()): ?> + <?php $showLastRow = true; ?> <?php else: ?> - <?php $_showlastRow = false ?> + <?php $showLastRow = false; ?> <?php endif; ?> - <?php if ($_item->getParentItem()): ?> - <?php $attributes = $block->getSelectionAttributes($_item) ?> - <?php if ($_prevOptionId != $attributes['option_id']): ?> + <?php if ($item->getParentItem()): ?> + <?php $attributes = $block->getSelectionAttributes($item) ?> + <?php if ($prevOptionId != $attributes['option_id']): ?> <tr class="options-label"> - <td class="col label" colspan="5"><?= /* @escapeNotVerified */ $attributes['option_label'] ?></td> + <td class="col label" colspan="5"><?= $block->escapeHtml($attributes['option_label']); ?></td> </tr> - <?php $_prevOptionId = $attributes['option_id'] ?> + <?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')) ?>"> - <strong class="product name product-item-name"><?= $block->escapeHtml($_item->getName()) ?></strong> +<tr id="order-item-row-<?= /* @noEscape */ $item->getId() ?>" + class="<?php if ($item->getParentItem()): ?> + item-options-container + <?php else: ?> + item-parent + <?php endif; ?>" + <?php if ($item->getParentItem()): ?> + data-th="<?= $block->escapeHtml($attributes['option_label']); ?>" + <?php endif; ?>> + <?php if (!$item->getParentItem()): ?> + <td class="col name" data-th="<?= $block->escapeHtml(__('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> + <td class="col value" data-th="<?= $block->escapeHtml(__('Product Name')); ?>"> + <?= $block->getValueHtml($item); ?> + </td> <?php endif; ?> - <td class="col sku" data-th="<?= $block->escapeHtml(__('SKU')) ?>"><?= /* @escapeNotVerified */ $block->prepareSku($_item->getSku()) ?></td> - <td class="col price" data-th="<?= $block->escapeHtml(__('Price')) ?>"> - <?php if (!$_item->getParentItem()): ?> - <?= $block->getItemPriceHtml() ?> + <td class="col sku" data-th="<?= $block->escapeHtml(__('SKU')); ?>"> + <?= /* @noEscape */ $block->prepareSku($item->getSku()); ?> + </td> + <td class="col price" data-th="<?= $block->escapeHtml(__('Price')); ?>"> + <?php if (!$item->getParentItem()): ?> + <?= /* @noEscape */ $block->getItemPriceHtml(); ?> <?php else: ?>   <?php endif; ?> </td> - <td class="col qty" data-th="<?= $block->escapeHtml(__('Quantity')) ?>"> + <td class="col qty" data-th="<?= $block->escapeHtml(__('Quantity')); ?>"> <?php if ( - ($_item->getParentItem() && $block->isChildCalculated()) || - (!$_item->getParentItem() && !$block->isChildCalculated()) || ($_item->getQtyShipped() > 0 && $_item->getParentItem() && $block->isShipmentSeparately())):?> + ($item->getParentItem() && $block->isChildCalculated()) || + (!$item->getParentItem() && !$block->isChildCalculated()) || + ($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): ?> + <?php if (($item->getParentItem() && $block->isChildCalculated()) || + (!$item->getParentItem() && !$block->isChildCalculated())): ?> + <?php if ($item->getQtyOrdered() > 0): ?> <li class="item"> - <span class="title"><?= /* @escapeNotVerified */ __('Ordered') ?></span> - <span class="content"><?= /* @escapeNotVerified */ $_item->getQtyOrdered()*1 ?></span> + <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"><?= /* @escapeNotVerified */ __('Shipped') ?></span> - <span class="content"><?= /* @escapeNotVerified */ $_item->getQtyShipped()*1 ?></span> + <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"><?= /* @escapeNotVerified */ __('Canceled') ?></span> - <span class="content"><?= /* @escapeNotVerified */ $_item->getQtyCanceled()*1 ?></span> + <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"><?= /* @escapeNotVerified */ __('Refunded') ?></span> - <span class="content"><?= /* @escapeNotVerified */ $_item->getQtyRefunded()*1 ?></span> + <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"><?= /* @escapeNotVerified */ __('Shipped') ?></span> - <span class="content"><?= /* @escapeNotVerified */ $_item->getQtyShipped()*1 ?></span> + <span class="title"><?= $block->escapeHtml(__('Shipped')); ?></span> + <span class="content"><?= /* @noEscape */ $item->getQtyShipped() * 1; ?></span> </li> <?php else: ?> -   + <span class="content"><?= /* @noEscape */ $parentItem->getQtyOrdered() * 1; ?></span> <?php endif; ?> <?php if ( - ($_item->getParentItem() && $block->isChildCalculated()) || - (!$_item->getParentItem() && !$block->isChildCalculated()) || ($_item->getQtyShipped() > 0 && $_item->getParentItem() && $block->isShipmentSeparately())):?> + ($item->getParentItem() && $block->isChildCalculated()) || + (!$item->getParentItem() && !$block->isChildCalculated()) || + ($item->getQtyShipped() > 0 && $item->getParentItem() && $block->isShipmentSeparately())):?> </ul> <?php endif; ?> </td> <td class="col subtotal" data-th="<?= $block->escapeHtml(__('Subtotal')) ?>"> - <?php if (!$_item->getParentItem()): ?> - <?= $block->getItemRowTotalHtml() ?> + <?php if (!$item->getParentItem()): ?> + <?= /* @noEscape */ $block->getItemRowTotalHtml(); ?> <?php else: ?>   <?php endif; ?> @@ -103,33 +119,38 @@ </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 foreach ($options as $option) : ?> + <dt><?= $block->escapeHtml($option['label']) ?></dt> <?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'])): ?> + <?php $formattedOptionValue = $block->getFormatedOptionValue($option) ?> + <dd<?php if (isset($formattedOptionValue['full_view'])): ?> + class="tooltip wrapper" + <?php endif; ?>> + <?= /* @noEscape */ $formattedOptionValue['value'] ?> + <?php if (isset($formattedOptionValue['full_view'])): ?> <div class="tooltip content"> <dl class="item options"> - <dt><?= $block->escapeHtml($_option['label']) ?></dt> - <dd><?= /* @escapeNotVerified */ $_formatedOptionValue['full_view'] ?></dd> + <dt><?= $block->escapeHtml($option['label']); ?></dt> + <dd><?= /* @noEscape */ $formattedOptionValue['full_view']; ?></dd> </dl> </div> <?php endif; ?> </dd> <?php else: ?> - <dd><?= $block->escapeHtml((isset($_option['print_value']) ? $_option['print_value'] : $_option['value'])) ?></dd> + <dd><?= $block->escapeHtml((isset($option['print_value']) ? + $option['print_value'] : + $option['value'])); ?> + </dd> <?php endif; ?> <?php endforeach; ?> </dl> <?php endif; ?> - <?= $block->escapeHtml($_item->getDescription()) ?> + <?= $block->escapeHtml($item->getDescription()); ?> </td> </tr> <?php endif; ?> diff --git a/app/code/Magento/BundleGraphQl/Model/BundleProductTypeResolver.php b/app/code/Magento/BundleGraphQl/Model/BundleProductTypeResolver.php index b904d3f62a7..211d625fbc7 100644 --- a/app/code/Magento/BundleGraphQl/Model/BundleProductTypeResolver.php +++ b/app/code/Magento/BundleGraphQl/Model/BundleProductTypeResolver.php @@ -8,19 +8,22 @@ namespace Magento\BundleGraphQl\Model; use Magento\Framework\GraphQl\Query\Resolver\TypeResolverInterface; +use Magento\Bundle\Model\Product\Type as Type; /** - * {@inheritdoc} + * @inheritdoc */ class BundleProductTypeResolver implements TypeResolverInterface { + const BUNDLE_PRODUCT = 'BundleProduct'; + /** - * {@inheritdoc} + * @inheritdoc */ public function resolveType(array $data) : string { - if (isset($data['type_id']) && $data['type_id'] == 'bundle') { - return 'BundleProduct'; + if (isset($data['type_id']) && $data['type_id'] == Type::TYPE_CODE) { + return self::BUNDLE_PRODUCT; } return ''; } diff --git a/app/code/Magento/BundleGraphQl/Model/Resolver/Options/Collection.php b/app/code/Magento/BundleGraphQl/Model/Resolver/Options/Collection.php index 149155c8627..7608d6e9e4d 100644 --- a/app/code/Magento/BundleGraphQl/Model/Resolver/Options/Collection.php +++ b/app/code/Magento/BundleGraphQl/Model/Resolver/Options/Collection.php @@ -61,6 +61,7 @@ public function __construct( * Add parent id/sku pair to use for option filter at fetch time. * * @param int $parentId + * @param int $parentEntityId * @param string $sku */ public function addParentFilterData(int $parentId, int $parentEntityId, string $sku) : void diff --git a/app/code/Magento/CacheInvalidate/Model/PurgeCache.php b/app/code/Magento/CacheInvalidate/Model/PurgeCache.php index 727e18280d7..2152f842d4a 100644 --- a/app/code/Magento/CacheInvalidate/Model/PurgeCache.php +++ b/app/code/Magento/CacheInvalidate/Model/PurgeCache.php @@ -94,7 +94,7 @@ private function splitTags($tagsPattern) $formattedTags = explode('|', $tagsPattern); foreach ($formattedTags as $formattedTag) { if ($tagsBatchSize + strlen($formattedTag) > $this->requestSize - count($formattedTagsChunk) - 1) { - yield implode('|', array_unique($formattedTagsChunk)); + yield implode('|', $formattedTagsChunk); $formattedTagsChunk = []; $tagsBatchSize = 0; } @@ -103,7 +103,7 @@ private function splitTags($tagsPattern) $formattedTagsChunk[] = $formattedTag; } if (!empty($formattedTagsChunk)) { - yield implode('|', array_unique($formattedTagsChunk)); + yield implode('|', $formattedTagsChunk); } } diff --git a/app/code/Magento/Captcha/Test/Mftf/Test/CaptchaFormsDisplayingTest.xml b/app/code/Magento/Captcha/Test/Mftf/Test/CaptchaFormsDisplayingTest.xml index a088266f760..035e58de06c 100644 --- a/app/code/Magento/Captcha/Test/Mftf/Test/CaptchaFormsDisplayingTest.xml +++ b/app/code/Magento/Captcha/Test/Mftf/Test/CaptchaFormsDisplayingTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="CaptchaFormsDisplayingTest"> <annotations> <features value="Captcha"/> diff --git a/app/code/Magento/Catalog/Api/CategoryLinkRepositoryInterface.php b/app/code/Magento/Catalog/Api/CategoryLinkRepositoryInterface.php index 30ac06107ba..a65355c6909 100644 --- a/app/code/Magento/Catalog/Api/CategoryLinkRepositoryInterface.php +++ b/app/code/Magento/Catalog/Api/CategoryLinkRepositoryInterface.php @@ -32,18 +32,20 @@ public function save(\Magento\Catalog\Api\Data\CategoryProductLinkInterface $pro * * @throws \Magento\Framework\Exception\CouldNotSaveException * @throws \Magento\Framework\Exception\StateException + * @throws \Magento\Framework\Exception\InputException */ public function delete(\Magento\Catalog\Api\Data\CategoryProductLinkInterface $productLink); /** * Remove the product assignment from the category by category id and sku * - * @param string $sku + * @param int $categoryId * @param string $sku * @return bool will returned True if products successfully deleted * * @throws \Magento\Framework\Exception\CouldNotSaveException * @throws \Magento\Framework\Exception\StateException + * @throws \Magento\Framework\Exception\InputException */ public function deleteByIds($categoryId, $sku); } diff --git a/app/code/Magento/Catalog/Api/Data/ProductRender/ImageInterface.php b/app/code/Magento/Catalog/Api/Data/ProductRender/ImageInterface.php index 4cdb2631ede..45b070d2706 100644 --- a/app/code/Magento/Catalog/Api/Data/ProductRender/ImageInterface.php +++ b/app/code/Magento/Catalog/Api/Data/ProductRender/ImageInterface.php @@ -92,6 +92,7 @@ public function setWidth($width); /** * Retrieve image label + * * Image label is short description of this image * * @return string @@ -111,7 +112,7 @@ public function setLabel($label); /** * Retrieve resize width * - * This width is image dimension, which represents the width, that can be used for perfomance improvements + * This width is image dimension, which represents the width, that can be used for performance improvements * * @return float * @since 101.1.0 @@ -128,6 +129,8 @@ public function getResizedWidth(); public function setResizedWidth($width); /** + * Set resized height + * * @param string $height * @return void * @since 101.1.0 diff --git a/app/code/Magento/Catalog/Api/Data/ProductRender/PriceInfoInterface.php b/app/code/Magento/Catalog/Api/Data/ProductRender/PriceInfoInterface.php index 2ef4d068317..9768b3c08c8 100644 --- a/app/code/Magento/Catalog/Api/Data/ProductRender/PriceInfoInterface.php +++ b/app/code/Magento/Catalog/Api/Data/ProductRender/PriceInfoInterface.php @@ -8,6 +8,7 @@ /** * Price interface. + * * @api * @since 101.1.0 */ @@ -23,6 +24,7 @@ public function getFinalPrice(); /** * Set the final price: usually it calculated as minimal price of the product + * * Can be different depends on type of product * * @param float $finalPrice @@ -33,6 +35,7 @@ public function setFinalPrice($finalPrice); /** * Retrieve max price of a product + * * E.g. for product with custom options is price with the most expensive custom option * * @return float @@ -51,6 +54,7 @@ public function setMaxPrice($maxPrice); /** * Set max regular price + * * Max regular price is the same, as maximum price, except of excluding calculating special price and catalog rules * in it * @@ -105,6 +109,8 @@ public function setSpecialPrice($specialPrice); public function getSpecialPrice(); /** + * Retrieve minimal price + * * @return float * @since 101.1.0 */ @@ -129,6 +135,7 @@ public function getRegularPrice(); /** * Regular price - is price of product without discounts and special price with taxes and fixed product tax + * * Usually this price is corresponding to price in admin panel of product * * @param float $regularPrice @@ -148,7 +155,7 @@ public function getFormattedPrices(); /** * Set dto with formatted prices * - * @param string[] $formattedPriceInfo + * @param FormattedPriceInfoInterface $formattedPriceInfo * @return void * @since 101.1.0 */ diff --git a/app/code/Magento/Catalog/Api/Data/ProductRenderInterface.php b/app/code/Magento/Catalog/Api/Data/ProductRenderInterface.php index 910168d8854..166a1aba76b 100644 --- a/app/code/Magento/Catalog/Api/Data/ProductRenderInterface.php +++ b/app/code/Magento/Catalog/Api/Data/ProductRenderInterface.php @@ -30,7 +30,7 @@ public function getAddToCartButton(); /** * Set information needed for render "Add To Cart" button on front * - * @param \Magento\Catalog\Api\Data\ProductRender\ButtonInterface $addToCartData + * @param ButtonInterface $cartAddToCartButton * @return void * @since 101.1.0 */ @@ -47,7 +47,7 @@ public function getAddToCompareButton(); /** * Set information needed for render "Add To Compare" button on front * - * @param ButtonInterface $compareUrlData + * @param ButtonInterface $compareButton * @return string * @since 101.1.0 */ @@ -55,6 +55,7 @@ public function setAddToCompareButton(ButtonInterface $compareButton); /** * Provide information needed for render prices and adjustments for different product types on front + * * Prices are represented in raw format and in current currency * * @return \Magento\Catalog\Api\Data\ProductRender\PriceInfoInterface @@ -73,6 +74,7 @@ public function setPriceInfo(PriceInfoInterface $priceInfo); /** * Provide enough information, that needed to render image on front + * * Images can be separated by image codes * * @return \Magento\Catalog\Api\Data\ProductRender\ImageInterface[] @@ -167,6 +169,7 @@ public function getIsSalable(); /** * Set information about product saleability (Stock, other conditions) + * * Is used to provide information to frontend JS renders * You can add plugin, in order to hide product on product page or product list on front * @@ -178,6 +181,7 @@ public function setIsSalable($isSalable); /** * Provide information about current store id or requested store id + * * Product should be assigned to provided store id * This setting affect store scope attributes * @@ -197,6 +201,7 @@ public function setStoreId($storeId); /** * Provide current or desired currency code to product + * * This setting affect formatted prices* * * @return string diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Category/AbstractCategory.php b/app/code/Magento/Catalog/Block/Adminhtml/Category/AbstractCategory.php index 33167987462..ffb648cdf43 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Category/AbstractCategory.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Category/AbstractCategory.php @@ -67,6 +67,8 @@ public function getCategory() } /** + * Get category id + * * @return int|string|null */ public function getCategoryId() @@ -78,6 +80,8 @@ public function getCategoryId() } /** + * Get category name + * * @return string */ public function getCategoryName() @@ -86,6 +90,8 @@ public function getCategoryName() } /** + * Get category path + * * @return mixed */ public function getCategoryPath() @@ -97,6 +103,8 @@ public function getCategoryPath() } /** + * Check store root category + * * @return bool */ public function hasStoreRootCategory() @@ -109,6 +117,8 @@ public function hasStoreRootCategory() } /** + * Get store from request + * * @return Store */ public function getStore() @@ -118,6 +128,8 @@ public function getStore() } /** + * Get root category for tree + * * @param mixed|null $parentNodeCategory * @param int $recursionLevel * @return Node|array|null @@ -149,10 +161,11 @@ public function getRoot($parentNodeCategory = null, $recursionLevel = 3) $root = $tree->getNodeById($rootId); - if ($root && $rootId != \Magento\Catalog\Model\Category::TREE_ROOT_ID) { + if ($root) { $root->setIsVisible(true); - } elseif ($root && $root->getId() == \Magento\Catalog\Model\Category::TREE_ROOT_ID) { - $root->setName(__('Root')); + if ($root->getId() == \Magento\Catalog\Model\Category::TREE_ROOT_ID) { + $root->setName(__('Root')); + } } $this->_coreRegistry->register('root', $root); @@ -162,6 +175,8 @@ public function getRoot($parentNodeCategory = null, $recursionLevel = 3) } /** + * Get Default Store Id + * * @return int */ protected function _getDefaultStoreId() @@ -170,6 +185,8 @@ protected function _getDefaultStoreId() } /** + * Get category collection + * * @return \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection */ public function getCategoryCollection() @@ -227,6 +244,8 @@ public function getRootByIds($ids) } /** + * Get category node for tree + * * @param mixed $parentNodeCategory * @param int $recursionLevel * @return Node @@ -249,6 +268,8 @@ public function getNode($parentNodeCategory, $recursionLevel = 2) } /** + * Get category save url + * * @param array $args * @return string */ @@ -260,6 +281,8 @@ public function getSaveUrl(array $args = []) } /** + * Get category edit url + * * @return string */ public function getEditUrl() diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Category/Helper/Pricestep.php b/app/code/Magento/Catalog/Block/Adminhtml/Category/Helper/Pricestep.php index b77a5e2e952..3266922d116 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Category/Helper/Pricestep.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Category/Helper/Pricestep.php @@ -11,6 +11,9 @@ */ namespace Magento\Catalog\Block\Adminhtml\Category\Helper; +/** + * Pricestep Helper + */ class Pricestep extends \Magento\Framework\Data\Form\Element\Text { /** @@ -40,7 +43,7 @@ public function getElementHtml() $disabled = true; } - parent::addClass('validate-number validate-number-range number-range-0.01-1000000000'); + parent::addClass('validate-number validate-number-range number-range-0.01-9999999999999999'); $html = parent::getElementHtml(); $htmlId = 'use_config_' . $this->getHtmlId(); $html .= '<br/><input id="' . $htmlId . '" name="use_config[]" value="' . $this->getId() . '"'; diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Form/Renderer/Fieldset/Element.php b/app/code/Magento/Catalog/Block/Adminhtml/Form/Renderer/Fieldset/Element.php index ad6df27b893..8f1d1dcf7ee 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Form/Renderer/Fieldset/Element.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Form/Renderer/Fieldset/Element.php @@ -4,13 +4,13 @@ * See COPYING.txt for license details. */ +namespace Magento\Catalog\Block\Adminhtml\Form\Renderer\Fieldset; + /** * Catalog fieldset element renderer * * @author Magento Core Team <core@magentocommerce.com> */ -namespace Magento\Catalog\Block\Adminhtml\Form\Renderer\Fieldset; - class Element extends \Magento\Backend\Block\Widget\Form\Renderer\Fieldset\Element { /** @@ -29,7 +29,7 @@ public function getDataObject() } /** - * Retireve associated with element attribute object + * Retrieve associated with element attribute object * * @return \Magento\Catalog\Model\ResourceModel\Eav\Attribute */ diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Action/Attribute/Tab/Inventory.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Action/Attribute/Tab/Inventory.php index 4aa01b467d4..964872b6e51 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Action/Attribute/Tab/Inventory.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Action/Attribute/Tab/Inventory.php @@ -70,11 +70,11 @@ public function getFieldSuffix() * Retrieve current store id * * @return int + * @SuppressWarnings(PHPMD.RequestAwareBlockMethod) */ public function getStoreId() { - $storeId = $this->getRequest()->getParam('store'); - return (int) $storeId; + return (int)$this->getRequest()->getParam('store'); } /** @@ -99,6 +99,8 @@ public function getTabLabel() } /** + * Return Tab title. + * * @return \Magento\Framework\Phrase */ public function getTabTitle() @@ -107,7 +109,7 @@ public function getTabTitle() } /** - * @return bool + * @inheritdoc */ public function canShowTab() { @@ -115,7 +117,7 @@ public function canShowTab() } /** - * @return bool + * @inheritdoc */ public function isHidden() { @@ -123,6 +125,8 @@ public function isHidden() } /** + * Get availability status. + * * @param string $fieldName * @return bool * @SuppressWarnings(PHPMD.UnusedFormalParameter) diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Related.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Related.php index c8153df4143..23b927598e8 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Related.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Related.php @@ -322,7 +322,7 @@ protected function _prepareColumns() } /** - * Rerieve grid URL + * Retrieve grid URL * * @return string */ diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tabs.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tabs.php index 04c3a208b97..37ad3f4bea2 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tabs.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tabs.php @@ -8,7 +8,7 @@ use Magento\Backend\Block\Template\Context; use Magento\Backend\Block\Widget\Accordion; -use Magento\Backend\Block\Widget\Tabs as WigetTabs; +use Magento\Backend\Block\Widget\Tabs as WidgetTabs; use Magento\Backend\Model\Auth\Session; use Magento\Catalog\Helper\Catalog; use Magento\Catalog\Helper\Data; @@ -22,7 +22,7 @@ * Admin product edit tabs * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class Tabs extends WigetTabs +class Tabs extends WidgetTabs { const BASIC_TAB_GROUP_CODE = 'basic'; @@ -109,7 +109,7 @@ public function __construct( } /** - * @return void + * @inheritdoc */ protected function _construct() { @@ -119,6 +119,8 @@ protected function _construct() } /** + * Get group collection. + * * @param int $attributeSetId * @return \Magento\Eav\Model\ResourceModel\Entity\Attribute\Group\Collection */ @@ -131,10 +133,11 @@ public function getGroupCollection($attributeSetId) } /** - * @return $this + * @inheritdoc * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.NPathComplexity) * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @SuppressWarnings(PHPMD.NPathComplexity) + * @SuppressWarnings(PHPMD.RequestAwareBlockMethod) */ protected function _prepareLayout() { @@ -315,6 +318,8 @@ public function getAttributeTabBlock() } /** + * Set attribute tab block. + * * @param string $attributeTabBlock * @return $this */ @@ -337,6 +342,8 @@ protected function _translateHtml($html) } /** + * Get accordion. + * * @param string $parentTab * @return string */ diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Frontend/Product/Watermark.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Frontend/Product/Watermark.php index 1f74969c3d1..7f80aece60e 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Frontend/Product/Watermark.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Frontend/Product/Watermark.php @@ -4,15 +4,15 @@ * See COPYING.txt for license details. */ +namespace Magento\Catalog\Block\Adminhtml\Product\Frontend\Product; + +use Magento\Framework\Data\Form\Element\AbstractElement; + /** * Fieldset config form element renderer * * @author Magento Core Team <core@magentocommerce.com> */ -namespace Magento\Catalog\Block\Adminhtml\Product\Frontend\Product; - -use Magento\Framework\Data\Form\Element\AbstractElement; - class Watermark extends \Magento\Backend\Block\AbstractBlock implements \Magento\Framework\Data\Form\Element\Renderer\RendererInterface { @@ -60,6 +60,8 @@ public function __construct( } /** + * Render form element as HTML + * * @param AbstractElement $element * @return string */ @@ -124,13 +126,14 @@ public function render(AbstractElement $element) } /** + * Get header html for render + * * @param AbstractElement $element * @return string * @SuppressWarnings(PHPMD.UnusedLocalVariable) */ protected function _getHeaderHtml($element) { - $id = $element->getHtmlId(); $default = !$this->getRequest()->getParam('website') && !$this->getRequest()->getParam('store'); $html = '<h4 class="icon-head head-edit-form">' . $element->getLegend() . '</h4>'; @@ -148,6 +151,8 @@ protected function _getHeaderHtml($element) } /** + * Get footer html for render + * * @param AbstractElement $element * @return string * @SuppressWarnings(PHPMD.UnusedFormalParameter) diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Rss/NotifyStock.php b/app/code/Magento/Catalog/Block/Adminhtml/Rss/NotifyStock.php index ac1fd8c692e..c296a5aa0db 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Rss/NotifyStock.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Rss/NotifyStock.php @@ -9,6 +9,7 @@ /** * Class NotifyStock + * * @package Magento\Catalog\Block\Adminhtml\Rss */ class NotifyStock extends \Magento\Backend\Block\AbstractBlock implements DataProviderInterface @@ -41,7 +42,7 @@ public function __construct( } /** - * @return void + * @inheritdoc */ protected function _construct() { @@ -50,12 +51,12 @@ protected function _construct() } /** - * {@inheritdoc} + * @inheritdoc */ public function getRssData() { - $newUrl = $this->rssUrlBuilder->getUrl(['_secure' => true, '_nosecret' => true, 'type' => 'notifystock']); - $title = __('Low Stock Products'); + $newUrl = $this->rssUrlBuilder->getUrl(['_secure' => true, '_nosecret' => true, 'type' => 'notifystock']); + $title = __('Low Stock Products')->render(); $data = ['title' => $title, 'description' => $title, 'link' => $newUrl, 'charset' => 'UTF-8']; foreach ($this->rssModel->getProductsCollection() as $item) { @@ -65,7 +66,7 @@ public function getRssData() ['id' => $item->getId(), '_secure' => true, '_nosecret' => true] ); $qty = 1 * $item->getQty(); - $description = __('%1 has reached a quantity of %2.', $item->getName(), $qty); + $description = __('%1 has reached a quantity of %2.', $item->getName(), $qty)->render(); $data['entries'][] = ['title' => $item->getName(), 'link' => $url, 'description' => $description]; } @@ -73,7 +74,7 @@ public function getRssData() } /** - * {@inheritdoc} + * @inheritdoc */ public function getCacheLifetime() { @@ -81,7 +82,7 @@ public function getCacheLifetime() } /** - * {@inheritdoc} + * @inheritdoc */ public function isAllowed() { @@ -89,7 +90,7 @@ public function isAllowed() } /** - * {@inheritdoc} + * @inheritdoc */ public function getFeeds() { @@ -97,7 +98,7 @@ public function getFeeds() } /** - * {@inheritdoc} + * @inheritdoc */ public function isAuthRequired() { diff --git a/app/code/Magento/Catalog/Block/Product/View/Details.php b/app/code/Magento/Catalog/Block/Product/View/Details.php new file mode 100644 index 00000000000..e76c5bf2013 --- /dev/null +++ b/app/code/Magento/Catalog/Block/Product/View/Details.php @@ -0,0 +1,47 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Catalog\Block\Product\View; + +/** + * Product details block. + * + * Holds a group of blocks to show as tabs. + * + * @api + */ +class Details extends \Magento\Framework\View\Element\Template +{ + /** + * Get sorted child block names. + * + * @param string $groupName + * @param string $callback + * @throws \Magento\Framework\Exception\LocalizedException + * + * @return array + */ + public function getGroupSortedChildNames(string $groupName, string $callback): array + { + $groupChildNames = $this->getGroupChildNames($groupName, $callback); + $layout = $this->getLayout(); + + $childNamesSortOrder = []; + + foreach ($groupChildNames as $childName) { + $alias = $layout->getElementAlias($childName); + $sortOrder = (int)$this->getChildData($alias, 'sort_order') ?? 0; + + $childNamesSortOrder[$sortOrder] = $childName; + } + + ksort($childNamesSortOrder, SORT_NUMERIC); + + return $childNamesSortOrder; + } +} diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Move.php b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Move.php index ba6bfddca9c..082101ff078 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Move.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Move.php @@ -8,6 +8,9 @@ use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; +/** + * Move category admin controller + */ class Move extends \Magento\Catalog\Controller\Adminhtml\Category implements HttpPostActionInterface { /** @@ -46,7 +49,7 @@ public function __construct( /** * Move category action * - * @return \Magento\Framework\Controller\Result\Raw + * @return \Magento\Framework\Controller\Result\Json */ public function execute() { diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Category/RefreshPath.php b/app/code/Magento/Catalog/Controller/Adminhtml/Category/RefreshPath.php index 046ebbb119e..e3d40bee214 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Category/RefreshPath.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Category/RefreshPath.php @@ -1,6 +1,5 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ @@ -8,6 +7,9 @@ use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; +/** + * Class RefreshPath + */ class RefreshPath extends \Magento\Catalog\Controller\Adminhtml\Category implements HttpGetActionInterface { /** @@ -44,6 +46,7 @@ public function execute() 'id' => $categoryId, 'path' => $category->getPath(), 'parentId' => $category->getParentId(), + 'level' => $category->getLevel() ]); } } diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Save.php b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Save.php index 11c0a73a737..77518fd9bf5 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Save.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Save.php @@ -147,6 +147,7 @@ public function execute() $parentCategory = $this->getParentCategory($parentId, $storeId); $category->setPath($parentCategory->getPath()); $category->setParentId($parentCategory->getId()); + $category->setLevel(null); } /** diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Validate.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Validate.php index 381ca5d08d8..50f58efae71 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Validate.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Validate.php @@ -163,7 +163,7 @@ private function isUniqueAdminValues(array $optionsValues, array $deletedOptions { $adminValues = []; foreach ($optionsValues as $optionKey => $values) { - if (!(isset($deletedOptions[$optionKey]) and $deletedOptions[$optionKey] === '1')) { + if (!(isset($deletedOptions[$optionKey]) && $deletedOptions[$optionKey] === '1')) { $adminValues[] = reset($values); } } diff --git a/app/code/Magento/Catalog/Controller/Index/Index.php b/app/code/Magento/Catalog/Controller/Index/Index.php index eae3325df9f..bd00c972049 100644 --- a/app/code/Magento/Catalog/Controller/Index/Index.php +++ b/app/code/Magento/Catalog/Controller/Index/Index.php @@ -5,12 +5,17 @@ */ namespace Magento\Catalog\Controller\Index; -class Index extends \Magento\Framework\App\Action\Action +use Magento\Framework\App\Action\HttpGetActionInterface; + +/** + * Catalog index page controller. + */ +class Index extends \Magento\Framework\App\Action\Action implements HttpGetActionInterface { /** * Index action * - * @return $this + * @return \Magento\Framework\Controller\Result\Redirect */ public function execute() { diff --git a/app/code/Magento/Catalog/Helper/Product/Configuration.php b/app/code/Magento/Catalog/Helper/Product/Configuration.php index 9b47e299009..5b8f6fad6e1 100644 --- a/app/code/Magento/Catalog/Helper/Product/Configuration.php +++ b/app/code/Magento/Catalog/Helper/Product/Configuration.php @@ -55,6 +55,7 @@ class Configuration extends AbstractHelper implements ConfigurationInterface * @param \Magento\Framework\Filter\FilterManager $filter * @param \Magento\Framework\Stdlib\StringUtils $string * @param Json $serializer + * @param Escaper $escaper */ public function __construct( \Magento\Framework\App\Helper\Context $context, diff --git a/app/code/Magento/Catalog/Helper/Product/ProductList.php b/app/code/Magento/Catalog/Helper/Product/ProductList.php index fbea73a6324..3aa6aeed377 100644 --- a/app/code/Magento/Catalog/Helper/Product/ProductList.php +++ b/app/code/Magento/Catalog/Helper/Product/ProductList.php @@ -42,6 +42,7 @@ class ProductList /** * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig + * @param \Magento\Framework\Registry $coreRegistry */ public function __construct( \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig, diff --git a/app/code/Magento/Catalog/Helper/Product/View.php b/app/code/Magento/Catalog/Helper/Product/View.php index 1509e489aee..74f40a18971 100644 --- a/app/code/Magento/Catalog/Helper/Product/View.php +++ b/app/code/Magento/Catalog/Helper/Product/View.php @@ -10,7 +10,9 @@ /** * Catalog category helper + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) */ class View extends \Magento\Framework\App\Helper\AbstractHelper { @@ -105,7 +107,7 @@ public function __construct( * * @param \Magento\Framework\View\Result\Page $resultPage * @param \Magento\Catalog\Model\Product $product - * @return \Magento\Framework\View\Result\Page + * @return $this */ private function preparePageMetadata(ResultPage $resultPage, $product) { diff --git a/app/code/Magento/Catalog/Model/Category.php b/app/code/Magento/Catalog/Model/Category.php index 29fb0d282a8..d911bec0aaa 100644 --- a/app/code/Magento/Catalog/Model/Category.php +++ b/app/code/Magento/Catalog/Model/Category.php @@ -72,11 +72,6 @@ class Category extends \Magento\Catalog\Model\AbstractModel implements const CACHE_TAG = 'cat_c'; - /** - * Category Store Id - */ - const STORE_ID = 'store_id'; - /**#@-*/ protected $_eventPrefix = 'catalog_category'; @@ -573,8 +568,8 @@ public function getStoreIds() */ public function getStoreId() { - if ($this->hasData(self::STORE_ID)) { - return (int)$this->_getData(self::STORE_ID); + if ($this->hasData('store_id')) { + return (int)$this->_getData('store_id'); } return (int)$this->_storeManager->getStore()->getId(); } @@ -590,7 +585,7 @@ public function setStoreId($storeId) if (!is_numeric($storeId)) { $storeId = $this->_storeManager->getStore($storeId)->getId(); } - $this->setData(self::STORE_ID, $storeId); + $this->setData('store_id', $storeId); $this->getResource()->setStoreId($storeId); return $this; } @@ -1124,10 +1119,15 @@ public function reindex() } } $productIndexer = $this->indexerRegistry->get(Indexer\Category\Product::INDEXER_ID); - if (!$productIndexer->isScheduled() - && (!empty($this->getAffectedProductIds()) || $this->dataHasChangedFor('is_anchor')) - ) { - $productIndexer->reindexList($this->getPathIds()); + + if (!empty($this->getAffectedProductIds()) + || $this->dataHasChangedFor('is_anchor') + || $this->dataHasChangedFor('is_active')) { + if (!$productIndexer->isScheduled()) { + $productIndexer->reindexList($this->getPathIds()); + } else { + $productIndexer->invalidate(); + } } } @@ -1152,13 +1152,22 @@ public function getIdentities() $identities = [ self::CACHE_TAG . '_' . $this->getId(), ]; - if (!$this->getId() || $this->hasDataChanges() - || $this->isDeleted() || $this->dataHasChangedFor(self::KEY_INCLUDE_IN_MENU) - ) { + + if ($this->hasDataChanges()) { + $identities[] = Product::CACHE_PRODUCT_CATEGORY_TAG . '_' . $this->getId(); + } + + if ($this->dataHasChangedFor('is_anchor') || $this->dataHasChangedFor('is_active')) { + foreach ($this->getPathIds() as $id) { + $identities[] = Product::CACHE_PRODUCT_CATEGORY_TAG . '_' . $id; + } + } + + if (!$this->getId() || $this->isDeleted() || $this->dataHasChangedFor(self::KEY_INCLUDE_IN_MENU)) { $identities[] = self::CACHE_TAG; $identities[] = Product::CACHE_PRODUCT_CATEGORY_TAG . '_' . $this->getId(); } - return $identities; + return array_unique($identities); } /** diff --git a/app/code/Magento/Catalog/Model/Category/Attribute/Source/Layout.php b/app/code/Magento/Catalog/Model/Category/Attribute/Source/Layout.php index 1890ea0f7d9..20ea899a3d0 100644 --- a/app/code/Magento/Catalog/Model/Category/Attribute/Source/Layout.php +++ b/app/code/Magento/Catalog/Model/Category/Attribute/Source/Layout.php @@ -17,6 +17,12 @@ class Layout extends \Magento\Eav\Model\Entity\Attribute\Source\AbstractSource */ protected $pageLayoutBuilder; + /** + * @inheritdoc + * @deprecated since the cache is now handled by \Magento\Theme\Model\PageLayout\Config\Builder::$configFiles + */ + protected $_options = null; + /** * @param \Magento\Framework\View\Model\PageLayout\Config\BuilderInterface $pageLayoutBuilder */ @@ -26,14 +32,14 @@ public function __construct(\Magento\Framework\View\Model\PageLayout\Config\Buil } /** - * {@inheritdoc} + * @inheritdoc */ public function getAllOptions() { - if (!$this->_options) { - $this->_options = $this->pageLayoutBuilder->getPageLayoutsConfig()->toOptionArray(); - array_unshift($this->_options, ['value' => '', 'label' => __('No layout updates')]); - } - return $this->_options; + $options = $this->pageLayoutBuilder->getPageLayoutsConfig()->toOptionArray(); + array_unshift($options, ['value' => '', 'label' => __('No layout updates')]); + $this->_options = $options; + + return $options; } } diff --git a/app/code/Magento/Catalog/Model/CategoryList.php b/app/code/Magento/Catalog/Model/CategoryList.php index 790ea6b921f..e3318db5054 100644 --- a/app/code/Magento/Catalog/Model/CategoryList.php +++ b/app/code/Magento/Catalog/Model/CategoryList.php @@ -15,6 +15,9 @@ use Magento\Framework\Api\SearchCriteriaInterface; use Magento\Framework\Api\SearchCriteria\CollectionProcessorInterface; +/** + * Class for getting category list. + */ class CategoryList implements CategoryListInterface { /** @@ -64,7 +67,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function getList(SearchCriteriaInterface $searchCriteria) { @@ -73,10 +76,11 @@ public function getList(SearchCriteriaInterface $searchCriteria) $this->extensionAttributesJoinProcessor->process($collection); $this->collectionProcessor->process($searchCriteria, $collection); + $collection->load(); $items = []; - foreach ($collection->getAllIds() as $id) { - $items[] = $this->categoryRepository->get($id); + foreach ($collection->getItems() as $category) { + $items[] = $this->categoryRepository->get($category->getId()); } /** @var CategorySearchResultsInterface $searchResult */ diff --git a/app/code/Magento/Catalog/Model/ImageExtractor.php b/app/code/Magento/Catalog/Model/ImageExtractor.php index dcc70cbcd2a..1cb1f305a22 100644 --- a/app/code/Magento/Catalog/Model/ImageExtractor.php +++ b/app/code/Magento/Catalog/Model/ImageExtractor.php @@ -20,6 +20,7 @@ class ImageExtractor implements TypeDataExtractorInterface * @param \DOMElement $mediaNode * @param string $mediaParentTag * @return array + * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ public function process(\DOMElement $mediaNode, $mediaParentTag) { @@ -40,6 +41,12 @@ public function process(\DOMElement $mediaNode, $mediaParentTag) $nodeValue = $this->processImageBackground($attribute->nodeValue); } elseif ($attributeTagName === 'width' || $attributeTagName === 'height') { $nodeValue = (int) $attribute->nodeValue; + } elseif ($attributeTagName === 'constrain' + || $attributeTagName === 'aspect_ratio' + || $attributeTagName === 'frame' + || $attributeTagName === 'transparency' + ) { + $nodeValue = in_array($attribute->nodeValue, [true, 1, 'true', '1'], true) ?? false; } else { $nodeValue = $attribute->nodeValue; } diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Category/Action/Rows.php b/app/code/Magento/Catalog/Model/Indexer/Product/Category/Action/Rows.php index a218266c250..cb708695255 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Category/Action/Rows.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Category/Action/Rows.php @@ -17,6 +17,8 @@ use Magento\Store\Model\StoreManagerInterface; /** + * Category rows indexer. + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Rows extends \Magento\Catalog\Model\Indexer\Category\Product\AbstractAction @@ -213,6 +215,7 @@ protected function isRangingNeeded() /** * Returns a list of category ids which are assigned to product ids in the index * + * @param array $productIds * @return \Magento\Framework\Indexer\CacheContext */ private function getCategoryIdsFromIndex(array $productIds) diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Indexer.php b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Indexer.php index a669fb73f64..c14bc0dd7e5 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Indexer.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Indexer.php @@ -54,7 +54,7 @@ public function __construct( * @param int $storeId * @param int $productId * @param string $valueFieldSuffix - * @return \Magento\Catalog\Model\Indexer\Product\Flat + * @return $this * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.ExcessiveMethodLength) * @SuppressWarnings(PHPMD.NPathComplexity) diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/FlatTableBuilder.php b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/FlatTableBuilder.php index fbe0d4b550f..2252b3e3d55 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/FlatTableBuilder.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/FlatTableBuilder.php @@ -10,7 +10,8 @@ use Magento\Framework\EntityManager\MetadataPool; /** - * Class FlatTableBuilder + * Class for building flat index + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class FlatTableBuilder @@ -346,12 +347,21 @@ protected function _updateTemporaryTableByStoreValues( } //Update not simple attributes (eg. dropdown) - if (isset($flatColumns[$attributeCode . $valueFieldSuffix])) { - $select = $this->_connection->select()->joinInner( - ['t' => $this->_productIndexerHelper->getTable('eav_attribute_option_value')], - 't.option_id = et.' . $attributeCode . ' AND t.store_id=' . $storeId, - [$attributeCode . $valueFieldSuffix => 't.value'] - ); + $columnName = $attributeCode . $valueFieldSuffix; + if (isset($flatColumns[$columnName])) { + $columnValue = $this->_connection->getIfNullSql('ts.value', 't0.value'); + $select = $this->_connection->select(); + $select->joinLeft( + ['t0' => $this->_productIndexerHelper->getTable('eav_attribute_option_value')], + 't0.option_id = et.' . $attributeCode . ' AND t0.store_id = 0', + [] + )->joinLeft( + ['ts' => $this->_productIndexerHelper->getTable('eav_attribute_option_value')], + 'ts.option_id = et.' . $attributeCode . ' AND ts.store_id = ' . $storeId, + [] + )->columns( + [$columnName => $columnValue] + )->where($columnValue . ' IS NOT NULL'); if (!empty($changedIds)) { $select->where($this->_connection->quoteInto('et.entity_id IN (?)', $changedIds)); } @@ -374,6 +384,8 @@ protected function _getTemporaryTableName($tableName) } /** + * Get metadata pool + * * @return \Magento\Framework\EntityManager\MetadataPool */ private function getMetadataPool() diff --git a/app/code/Magento/Catalog/Model/Layer/Filter/AbstractFilter.php b/app/code/Magento/Catalog/Model/Layer/Filter/AbstractFilter.php index d21a8666ec0..f2e2e67f944 100644 --- a/app/code/Magento/Catalog/Model/Layer/Filter/AbstractFilter.php +++ b/app/code/Magento/Catalog/Model/Layer/Filter/AbstractFilter.php @@ -139,7 +139,7 @@ public function apply(\Magento\Framework\App\RequestInterface $request) } /** - * Get fiter items count + * Get filter items count * * @return int */ diff --git a/app/code/Magento/Catalog/Model/Layer/Filter/Decimal.php b/app/code/Magento/Catalog/Model/Layer/Filter/Decimal.php index dac2632ff6d..d76711cb21d 100644 --- a/app/code/Magento/Catalog/Model/Layer/Filter/Decimal.php +++ b/app/code/Magento/Catalog/Model/Layer/Filter/Decimal.php @@ -32,8 +32,8 @@ class Decimal extends \Magento\Catalog\Model\Layer\Filter\AbstractFilter * @param \Magento\Store\Model\StoreManagerInterface $storeManager * @param \Magento\Catalog\Model\Layer $layer * @param \Magento\Catalog\Model\Layer\Filter\Item\DataBuilder $itemDataBuilder - * @param \Magento\Catalog\Model\ResourceModel\Layer\Filter\DecimalFactory $filterDecimalFactory * @param \Magento\Framework\Pricing\PriceCurrencyInterface $priceCurrency + * @param \Magento\Catalog\Model\Layer\Filter\DataProvider\DecimalFactory $dataProviderFactory * @param array $data */ public function __construct( diff --git a/app/code/Magento/Catalog/Model/Layer/Filter/Item/DataBuilder.php b/app/code/Magento/Catalog/Model/Layer/Filter/Item/DataBuilder.php index 4d2878b0b1e..07c9c2eaa24 100644 --- a/app/code/Magento/Catalog/Model/Layer/Filter/Item/DataBuilder.php +++ b/app/code/Magento/Catalog/Model/Layer/Filter/Item/DataBuilder.php @@ -4,11 +4,11 @@ * See COPYING.txt for license details. */ +namespace Magento\Catalog\Model\Layer\Filter\Item; + /** * Item Data Builder */ -namespace Magento\Catalog\Model\Layer\Filter\Item; - class DataBuilder { /** @@ -29,7 +29,7 @@ class DataBuilder * Add Item Data * * @param string $label - * @param string $label + * @param string $value * @param int $count * @return void */ diff --git a/app/code/Magento/Catalog/Model/Plugin/ProductRepository/TransactionWrapper.php b/app/code/Magento/Catalog/Model/Plugin/ProductRepository/TransactionWrapper.php index c88215d9235..f51b2e4f90a 100644 --- a/app/code/Magento/Catalog/Model/Plugin/ProductRepository/TransactionWrapper.php +++ b/app/code/Magento/Catalog/Model/Plugin/ProductRepository/TransactionWrapper.php @@ -7,6 +7,9 @@ */ namespace Magento\Catalog\Model\Plugin\ProductRepository; +/** + * Transaction wrapper for product repository CRUD. + */ class TransactionWrapper { /** @@ -24,8 +27,10 @@ public function __construct( } /** + * Transaction wrapper for save action. + * * @param \Magento\Catalog\Api\ProductRepositoryInterface $subject - * @param callable $proceed + * @param \Closure $proceed * @param \Magento\Catalog\Api\Data\ProductInterface $product * @param bool $saveOptions * @return \Magento\Catalog\Api\Data\ProductInterface @@ -51,8 +56,10 @@ public function aroundSave( } /** + * Transaction wrapper for delete action. + * * @param \Magento\Catalog\Api\ProductRepositoryInterface $subject - * @param callable $proceed + * @param \Closure $proceed * @param \Magento\Catalog\Api\Data\ProductInterface $product * @return bool * @throws \Exception @@ -76,8 +83,10 @@ public function aroundDelete( } /** + * Transaction wrapper for delete by id action. + * * @param \Magento\Catalog\Api\ProductRepositoryInterface $subject - * @param callable $proceed + * @param \Closure $proceed * @param string $productSku * @return bool * @throws \Exception diff --git a/app/code/Magento/Catalog/Model/Product.php b/app/code/Magento/Catalog/Model/Product.php index 5d47e85768f..1e774e45df4 100644 --- a/app/code/Magento/Catalog/Model/Product.php +++ b/app/code/Magento/Catalog/Model/Product.php @@ -71,11 +71,6 @@ class Product extends \Magento\Catalog\Model\AbstractModel implements */ const STORE_ID = 'store_id'; - /** - * Product Url path. - */ - const URL_PATH = 'url_path'; - /** * @var string */ diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/Source/Layout.php b/app/code/Magento/Catalog/Model/Product/Attribute/Source/Layout.php index 63b1444d1db..dbc7535dccf 100644 --- a/app/code/Magento/Catalog/Model/Product/Attribute/Source/Layout.php +++ b/app/code/Magento/Catalog/Model/Product/Attribute/Source/Layout.php @@ -17,6 +17,12 @@ class Layout extends \Magento\Eav\Model\Entity\Attribute\Source\AbstractSource */ protected $pageLayoutBuilder; + /** + * @inheritdoc + * @deprecated since the cache is now handled by \Magento\Theme\Model\PageLayout\Config\Builder::$configFiles + */ + protected $_options = null; + /** * @param \Magento\Framework\View\Model\PageLayout\Config\BuilderInterface $pageLayoutBuilder */ @@ -26,14 +32,14 @@ public function __construct(\Magento\Framework\View\Model\PageLayout\Config\Buil } /** - * @return array + * @inheritdoc */ public function getAllOptions() { - if (!$this->_options) { - $this->_options = $this->pageLayoutBuilder->getPageLayoutsConfig()->toOptionArray(); - array_unshift($this->_options, ['value' => '', 'label' => __('No layout updates')]); - } - return $this->_options; + $options = $this->pageLayoutBuilder->getPageLayoutsConfig()->toOptionArray(); + array_unshift($options, ['value' => '', 'label' => __('No layout updates')]); + $this->_options = $options; + + return $options; } } diff --git a/app/code/Magento/Catalog/Model/Product/Copier.php b/app/code/Magento/Catalog/Model/Product/Copier.php index ce6b4d98bbc..53fa11df04b 100644 --- a/app/code/Magento/Catalog/Model/Product/Copier.php +++ b/app/code/Magento/Catalog/Model/Product/Copier.php @@ -83,7 +83,7 @@ public function copy(Product $product) ? $matches[1] . '-' . ($matches[2] + 1) : $urlKey . '-1'; $duplicate->setUrlKey($urlKey); - $duplicate->setData(Product::URL_PATH, null); + $duplicate->setData('url_path', null); try { $duplicate->save(); $isDuplicateSaved = true; diff --git a/app/code/Magento/Catalog/Model/Product/Gallery/CreateHandler.php b/app/code/Magento/Catalog/Model/Product/Gallery/CreateHandler.php index 65111979c5d..42b9639d271 100644 --- a/app/code/Magento/Catalog/Model/Product/Gallery/CreateHandler.php +++ b/app/code/Magento/Catalog/Model/Product/Gallery/CreateHandler.php @@ -318,7 +318,7 @@ protected function duplicate($product) $this->resourceModel->duplicate( $this->getAttribute()->getAttributeId(), - isset($mediaGalleryData['duplicate']) ? $mediaGalleryData['duplicate'] : [], + $mediaGalleryData['duplicate'] ?? [], $product->getOriginalLinkId(), $product->getData($this->metadata->getLinkField()) ); diff --git a/app/code/Magento/Catalog/Model/Product/Image/ParamsBuilder.php b/app/code/Magento/Catalog/Model/Product/Image/ParamsBuilder.php index f6be7f7392b..4a55714a27e 100644 --- a/app/code/Magento/Catalog/Model/Product/Image/ParamsBuilder.php +++ b/app/code/Magento/Catalog/Model/Product/Image/ParamsBuilder.php @@ -161,9 +161,7 @@ private function getWatermark(string $type): array */ private function hasDefaultFrame(): bool { - return (bool) $this->viewConfig->getViewConfig()->getVarValue( - 'Magento_Catalog', - 'product_image_white_borders' - ); + return (bool) $this->viewConfig->getViewConfig(['area' => \Magento\Framework\App\Area::AREA_FRONTEND]) + ->getVarValue('Magento_Catalog', 'product_image_white_borders'); } } diff --git a/app/code/Magento/Catalog/Model/Product/Option/Repository.php b/app/code/Magento/Catalog/Model/Product/Option/Repository.php index 9dc9695daff..bb4e247de32 100644 --- a/app/code/Magento/Catalog/Model/Product/Option/Repository.php +++ b/app/code/Magento/Catalog/Model/Product/Option/Repository.php @@ -14,6 +14,8 @@ use Magento\Framework\App\ObjectManager; /** + * Product custom options repository + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Repository implements \Magento\Catalog\Api\ProductCustomOptionRepositoryInterface @@ -83,7 +85,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function getList($sku) { @@ -92,7 +94,7 @@ public function getList($sku) } /** - * {@inheritdoc} + * @inheritdoc */ public function getProductOptions(ProductInterface $product, $requiredOnly = false) { @@ -104,7 +106,7 @@ public function getProductOptions(ProductInterface $product, $requiredOnly = fal } /** - * {@inheritdoc} + * @inheritdoc */ public function get($sku, $optionId) { @@ -117,7 +119,7 @@ public function get($sku, $optionId) } /** - * {@inheritdoc} + * @inheritdoc */ public function delete(\Magento\Catalog\Api\Data\ProductCustomOptionInterface $entity) { @@ -126,7 +128,7 @@ public function delete(\Magento\Catalog\Api\Data\ProductCustomOptionInterface $e } /** - * {@inheritdoc} + * @inheritdoc */ public function duplicate( \Magento\Catalog\Api\Data\ProductInterface $product, @@ -142,7 +144,7 @@ public function duplicate( } /** - * {@inheritdoc} + * @inheritdoc */ public function save(\Magento\Catalog\Api\Data\ProductCustomOptionInterface $option) { @@ -184,7 +186,7 @@ public function save(\Magento\Catalog\Api\Data\ProductCustomOptionInterface $opt } /** - * {@inheritdoc} + * @inheritdoc */ public function deleteByIdentifier($sku, $optionId) { @@ -209,8 +211,8 @@ public function deleteByIdentifier($sku, $optionId) /** * Mark original values for removal if they are absent among new values * - * @param $newValues array - * @param $originalValues \Magento\Catalog\Model\Product\Option\Value[] + * @param array $newValues + * @param \Magento\Catalog\Model\Product\Option\Value[] $originalValues * @return array */ protected function markRemovedValues($newValues, $originalValues) @@ -234,6 +236,8 @@ protected function markRemovedValues($newValues, $originalValues) } /** + * Get hydrator pool + * * @return \Magento\Framework\EntityManager\HydratorPool * @deprecated 101.0.0 */ diff --git a/app/code/Magento/Catalog/Model/Product/Option/SaveHandler.php b/app/code/Magento/Catalog/Model/Product/Option/SaveHandler.php index c4a2d60414a..0941aa24789 100644 --- a/app/code/Magento/Catalog/Model/Product/Option/SaveHandler.php +++ b/app/code/Magento/Catalog/Model/Product/Option/SaveHandler.php @@ -28,6 +28,8 @@ public function __construct( } /** + * Perform action on relation/extension attribute + * * @param object $entity * @param array $arguments * @return \Magento\Catalog\Api\Data\ProductInterface|object @@ -35,6 +37,10 @@ public function __construct( */ public function execute($entity, $arguments = []) { + if ($entity->getOptionsSaved()) { + return $entity; + } + $options = $entity->getOptions(); $optionIds = []; diff --git a/app/code/Magento/Catalog/Model/Product/Option/Type/Date.php b/app/code/Magento/Catalog/Model/Product/Option/Type/Date.php index b19906ecd6c..2b4739ebeb7 100644 --- a/app/code/Magento/Catalog/Model/Product/Option/Type/Date.php +++ b/app/code/Magento/Catalog/Model/Product/Option/Type/Date.php @@ -12,6 +12,7 @@ * Catalog product option date type * * @author Magento Core Team <core@magentocommerce.com> + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) */ class Date extends \Magento\Catalog\Model\Product\Option\Type\DefaultType { @@ -147,7 +148,6 @@ public function validateUserValue($values) public function prepareForCart() { if ($this->getIsValid() && $this->getUserValue() !== null) { - $option = $this->getOption(); $value = $this->getUserValue(); if (isset($value['date_internal']) && $value['date_internal'] != '') { 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 4a257a47810..d88dd583628 100644 --- a/app/code/Magento/Catalog/Model/Product/Option/Type/Select.php +++ b/app/code/Magento/Catalog/Model/Product/Option/Type/Select.php @@ -71,7 +71,7 @@ public function validateUserValue($values) } if (!$this->_isSingleSelection()) { $valuesCollection = $option->getOptionValuesByOptionId($value, $this->getProduct()->getStoreId())->load(); - $valueCount = is_array($value) ? count($value) : 1; + $valueCount = is_array($value) ? count($value) : 0; if ($valuesCollection->count() != $valueCount) { $this->setIsValid(false); throw new LocalizedException( diff --git a/app/code/Magento/Catalog/Model/Product/Option/Validator/DefaultValidator.php b/app/code/Magento/Catalog/Model/Product/Option/Validator/DefaultValidator.php index 99d5016f5cd..08455430cca 100644 --- a/app/code/Magento/Catalog/Model/Product/Option/Validator/DefaultValidator.php +++ b/app/code/Magento/Catalog/Model/Product/Option/Validator/DefaultValidator.php @@ -28,13 +28,20 @@ class DefaultValidator extends \Magento\Framework\Validator\AbstractValidator */ protected $priceTypes; + /** + * @var \Magento\Framework\Locale\FormatInterface + */ + private $localeFormat; + /** * @param \Magento\Catalog\Model\ProductOptions\ConfigInterface $productOptionConfig * @param \Magento\Catalog\Model\Config\Source\Product\Options\Price $priceConfig + * @param \Magento\Framework\Locale\FormatInterface|null $localeFormat */ public function __construct( \Magento\Catalog\Model\ProductOptions\ConfigInterface $productOptionConfig, - \Magento\Catalog\Model\Config\Source\Product\Options\Price $priceConfig + \Magento\Catalog\Model\Config\Source\Product\Options\Price $priceConfig, + \Magento\Framework\Locale\FormatInterface $localeFormat = null ) { foreach ($productOptionConfig->getAll() as $option) { foreach ($option['types'] as $type) { @@ -45,6 +52,9 @@ public function __construct( foreach ($priceConfig->toOptionArray() as $item) { $this->priceTypes[] = $item['value']; } + + $this->localeFormat = $localeFormat ?: \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Magento\Framework\Locale\FormatInterface::class); } /** @@ -137,11 +147,11 @@ protected function validateOptionType(Option $option) */ protected function validateOptionValue(Option $option) { - return $this->isInRange($option->getPriceType(), $this->priceTypes); + return $this->isInRange($option->getPriceType(), $this->priceTypes) && $this->isNumber($option->getPrice()); } /** - * Check whether value is empty + * Check whether the value is empty * * @param mixed $value * @return bool @@ -152,7 +162,7 @@ protected function isEmpty($value) } /** - * Check whether value is in range + * Check whether the value is in range * * @param string $value * @param array $range @@ -164,13 +174,24 @@ protected function isInRange($value, array $range) } /** - * Check whether value is not negative + * Check whether the value is negative * * @param string $value * @return bool */ protected function isNegative($value) { - return (int) $value < 0; + return $this->localeFormat->getNumber($value) < 0; + } + + /** + * Check whether the value is a number + * + * @param string $value + * @return bool + */ + public function isNumber($value) + { + return is_numeric($this->localeFormat->getNumber($value)); } } diff --git a/app/code/Magento/Catalog/Model/Product/Option/Validator/Select.php b/app/code/Magento/Catalog/Model/Product/Option/Validator/Select.php index 44756890b6e..209531f5998 100644 --- a/app/code/Magento/Catalog/Model/Product/Option/Validator/Select.php +++ b/app/code/Magento/Catalog/Model/Product/Option/Validator/Select.php @@ -8,6 +8,9 @@ use Magento\Catalog\Model\Product\Option; +/** + * Select validator class + */ class Select extends DefaultValidator { /** @@ -83,7 +86,7 @@ protected function isValidOptionPrice($priceType, $price, $storeId) if (!$priceType && !$price) { return true; } - if (!$this->isInRange($priceType, $this->priceTypes)) { + if (!$this->isInRange($priceType, $this->priceTypes) || !$this->isNumber($price)) { return false; } diff --git a/app/code/Magento/Catalog/Model/Product/Type/FrontSpecialPrice.php b/app/code/Magento/Catalog/Model/Product/Type/FrontSpecialPrice.php new file mode 100644 index 00000000000..f6893a41113 --- /dev/null +++ b/app/code/Magento/Catalog/Model/Product/Type/FrontSpecialPrice.php @@ -0,0 +1,128 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Model\Product\Type; + +use Magento\Store\Model\Store; +use Magento\Catalog\Model\ResourceModel\Product\Price\SpecialPrice; +use Magento\Catalog\Api\Data\SpecialPriceInterface; +use Magento\Store\Api\Data\WebsiteInterface; + +/** + * Product special price model. + * + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class FrontSpecialPrice extends Price +{ + /** + * @var SpecialPrice + */ + private $specialPrice; + + /** + * @param \Magento\CatalogRule\Model\ResourceModel\RuleFactory $ruleFactory + * @param \Magento\Store\Model\StoreManagerInterface $storeManager + * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate + * @param \Magento\Customer\Model\Session $customerSession + * @param \Magento\Framework\Event\ManagerInterface $eventManager + * @param \Magento\Framework\Pricing\PriceCurrencyInterface $priceCurrency + * @param \Magento\Customer\Api\GroupManagementInterface $groupManagement + * @param \Magento\Catalog\Api\Data\ProductTierPriceInterfaceFactory $tierPriceFactory + * @param \Magento\Framework\App\Config\ScopeConfigInterface $config + * @param SpecialPrice $specialPrice + * @SuppressWarnings(PHPMD.ExcessiveParameterList) + */ + public function __construct( + \Magento\CatalogRule\Model\ResourceModel\RuleFactory $ruleFactory, + \Magento\Store\Model\StoreManagerInterface $storeManager, + \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate, + \Magento\Customer\Model\Session $customerSession, + \Magento\Framework\Event\ManagerInterface $eventManager, + \Magento\Framework\Pricing\PriceCurrencyInterface $priceCurrency, + \Magento\Customer\Api\GroupManagementInterface $groupManagement, + \Magento\Catalog\Api\Data\ProductTierPriceInterfaceFactory $tierPriceFactory, + \Magento\Framework\App\Config\ScopeConfigInterface $config, + SpecialPrice $specialPrice + ) { + $this->specialPrice = $specialPrice; + parent::__construct( + $ruleFactory, + $storeManager, + $localeDate, + $customerSession, + $eventManager, + $priceCurrency, + $groupManagement, + $tierPriceFactory, + $config + ); + } + + /** + * @inheritdoc + */ + protected function _applySpecialPrice($product, $finalPrice) + { + if (!$product->getSpecialPrice()) { + return $finalPrice; + } + + $specialPrices = $this->getSpecialPrices($product); + $specialPrice = !(empty($specialPrices)) ? min($specialPrices) : $product->getSpecialPrice(); + + $specialPrice = $this->calculateSpecialPrice( + $finalPrice, + $specialPrice, + $product->getSpecialFromDate(), + $product->getSpecialToDate(), + WebsiteInterface::ADMIN_CODE + ); + $product->setData('special_price', $specialPrice); + + return $specialPrice; + } + + /** + * Get special prices. + * + * @param mixed $product + * @return array + */ + private function getSpecialPrices($product): array + { + $allSpecialPrices = $this->specialPrice->get([$product->getSku()]); + $specialPrices = []; + foreach ($allSpecialPrices as $price) { + if ($this->isSuitableSpecialPrice($product, $price)) { + $specialPrices[] = $price['value']; + } + } + + return $specialPrices; + } + + /** + * Price is suitable from default and current store + start and end date are equal. + * + * @param mixed $product + * @param array $price + * @return bool + */ + private function isSuitableSpecialPrice($product, array $price): bool + { + $priceStoreId = $price[Store::STORE_ID]; + if (($priceStoreId == Store::DEFAULT_STORE_ID || $product->getStoreId() == $priceStoreId) + && $price[SpecialPriceInterface::PRICE_FROM] == $product->getSpecialFromDate() + && $price[SpecialPriceInterface::PRICE_TO] == $product->getSpecialToDate()) { + return true; + } + + return false; + } +} diff --git a/app/code/Magento/Catalog/Model/Product/Type/Price.php b/app/code/Magento/Catalog/Model/Product/Type/Price.php index f6caa299d66..b30624b79dd 100644 --- a/app/code/Magento/Catalog/Model/Product/Type/Price.php +++ b/app/code/Magento/Catalog/Model/Product/Type/Price.php @@ -11,12 +11,14 @@ use Magento\Store\Model\Store; use Magento\Catalog\Api\Data\ProductTierPriceExtensionFactory; use Magento\Framework\App\ObjectManager; +use Magento\Store\Api\Data\WebsiteInterface; /** * Product type price model * * @api * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) * @since 100.0.2 */ class Price @@ -184,6 +186,8 @@ public function getFinalPrice($qty, $product) } /** + * Retrieve final price for child product + * * @param Product $product * @param float $productQty * @param Product $childProduct @@ -428,6 +432,8 @@ public function setTierPrices($product, array $tierPrices = null) } /** + * Retrieve customer group id from product + * * @param Product $product * @return int */ @@ -453,7 +459,7 @@ protected function _applySpecialPrice($product, $finalPrice) $product->getSpecialPrice(), $product->getSpecialFromDate(), $product->getSpecialToDate(), - $product->getStore() + WebsiteInterface::ADMIN_CODE ); } @@ -601,7 +607,7 @@ public function calculatePrice( $specialPrice, $specialPriceFrom, $specialPriceTo, - $sId + WebsiteInterface::ADMIN_CODE ); if ($rulePrice === false) { diff --git a/app/code/Magento/Catalog/Model/Product/Website/ReadHandler.php b/app/code/Magento/Catalog/Model/Product/Website/ReadHandler.php index e81cdedd6d3..8acb4a6593a 100644 --- a/app/code/Magento/Catalog/Model/Product/Website/ReadHandler.php +++ b/app/code/Magento/Catalog/Model/Product/Website/ReadHandler.php @@ -9,6 +9,9 @@ use Magento\Catalog\Model\ResourceModel\Product\Website\Link as ProductWebsiteLink; use Magento\Framework\EntityManager\Operation\ExtensionInterface; +/** + * Add websites ids to product extension attributes. + */ class ReadHandler implements ExtensionInterface { /** @@ -18,7 +21,7 @@ class ReadHandler implements ExtensionInterface /** * ReadHandler constructor. - * @param ProductWebsiteLink $resourceModel + * @param ProductWebsiteLink $productWebsiteLink */ public function __construct( ProductWebsiteLink $productWebsiteLink @@ -27,6 +30,8 @@ public function __construct( } /** + * Add website ids to product extension attributes, if no set. + * * @param ProductInterface $product * @param array $arguments * @SuppressWarnings(PHPMD.UnusedFormalParameter) diff --git a/app/code/Magento/Catalog/Model/ProductRender.php b/app/code/Magento/Catalog/Model/ProductRender.php index 702c04b910d..5efb0343cd9 100644 --- a/app/code/Magento/Catalog/Model/ProductRender.php +++ b/app/code/Magento/Catalog/Model/ProductRender.php @@ -206,7 +206,7 @@ public function getExtensionAttributes() * Set an extension attributes object. * * @param \Magento\Catalog\Api\Data\ProductRenderExtensionInterface $extensionAttributes - * @return $this + * @return void */ public function setExtensionAttributes( \Magento\Catalog\Api\Data\ProductRenderExtensionInterface $extensionAttributes diff --git a/app/code/Magento/Catalog/Model/ProductRender/Image.php b/app/code/Magento/Catalog/Model/ProductRender/Image.php index 774199a0dbf..5e024938d37 100644 --- a/app/code/Magento/Catalog/Model/ProductRender/Image.php +++ b/app/code/Magento/Catalog/Model/ProductRender/Image.php @@ -9,14 +9,16 @@ use Magento\Catalog\Api\Data\ProductRender\ImageInterface; /** - * @inheritdoc + * Product image renderer model. */ class Image extends \Magento\Framework\Model\AbstractExtensibleModel implements ImageInterface { /** + * Set url to image. + * * @param string $url - * @return @return void + * @return void */ public function setUrl($url) { @@ -34,6 +36,8 @@ public function getUrl() } /** + * Retrieve image code. + * * @return string */ public function getCode() @@ -42,6 +46,8 @@ public function getCode() } /** + * Set image code. + * * @param string $code * @return void */ @@ -51,6 +57,8 @@ public function setCode($code) } /** + * Set image height. + * * @param string $height * @return void */ @@ -60,6 +68,8 @@ public function setHeight($height) } /** + * Retrieve image height. + * * @return float */ public function getHeight() @@ -68,6 +78,8 @@ public function getHeight() } /** + * Retrieve image width. + * * @return float */ public function getWidth() @@ -76,6 +88,8 @@ public function getWidth() } /** + * Set image width. + * * @param string $width * @return void */ @@ -85,6 +99,8 @@ public function setWidth($width) } /** + * Retrieve image label. + * * @return string */ public function getLabel() @@ -93,6 +109,8 @@ public function getLabel() } /** + * Set image label. + * * @param string $label * @return void */ @@ -102,6 +120,8 @@ public function setLabel($label) } /** + * Retrieve image width after image resize. + * * @return float */ public function getResizedWidth() @@ -110,6 +130,8 @@ public function getResizedWidth() } /** + * Set image width after image resize. + * * @param string $width * @return void */ @@ -119,6 +141,8 @@ public function setResizedWidth($width) } /** + * Set image height after image resize. + * * @param string $height * @return void */ @@ -128,6 +152,8 @@ public function setResizedHeight($height) } /** + * Retrieve image height after image resize. + * * @return float */ public function getResizedHeight() @@ -149,7 +175,7 @@ public function getExtensionAttributes() * Set an extension attributes object. * * @param \Magento\Catalog\Api\Data\ProductRender\ImageExtensionInterface $extensionAttributes - * @return $this + * @return void */ public function setExtensionAttributes( \Magento\Catalog\Api\Data\ProductRender\ImageExtensionInterface $extensionAttributes diff --git a/app/code/Magento/Catalog/Model/ProductRenderList.php b/app/code/Magento/Catalog/Model/ProductRenderList.php index a3d906cf10c..d1f60c09863 100644 --- a/app/code/Magento/Catalog/Model/ProductRenderList.php +++ b/app/code/Magento/Catalog/Model/ProductRenderList.php @@ -17,8 +17,8 @@ /** * Provide product render information (this information should be enough for rendering product on front) - * for one or few products * + * Render information provided for one or few products */ class ProductRenderList implements ProductRenderListInterface { @@ -64,7 +64,6 @@ class ProductRenderList implements ProductRenderListInterface * @param ProductRenderSearchResultsFactory $searchResultFactory * @param ProductRenderFactory $productRenderDtoFactory * @param Config $config - * @param Product\Visibility $productVisibility * @param CollectionModifier $collectionModifier * @param array $productAttributes */ diff --git a/app/code/Magento/Catalog/Model/ResourceModel/AbstractCollection.php b/app/code/Magento/Catalog/Model/ResourceModel/AbstractCollection.php index d4f5fdd5137..2896849b76c 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/AbstractCollection.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/AbstractCollection.php @@ -7,6 +7,7 @@ /** * Flat abstract collection + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ abstract class AbstractCollection extends \Magento\Framework\Model\ResourceModel\Db\VersionControl\Collection @@ -34,7 +35,7 @@ public function setSelectCountSql(\Magento\Framework\DB\Select $countSelect) } /** - * get select count sql + * Get select count sql * * @return \Magento\Framework\DB\Select */ @@ -69,6 +70,7 @@ protected function _attributeToField($attribute) /** * Add attribute to select result set. + * * Backward compatibility with EAV collection * * @param string $attribute @@ -82,6 +84,7 @@ public function addAttributeToSelect($attribute) /** * Specify collection select filter by attribute value + * * Backward compatibility with EAV collection * * @param string|\Magento\Eav\Model\Entity\Attribute $attribute @@ -96,6 +99,7 @@ public function addAttributeToFilter($attribute, $condition = null) /** * Specify collection select order by attribute value + * * Backward compatibility with EAV collection * * @param string $attribute @@ -110,6 +114,7 @@ public function addAttributeToSort($attribute, $dir = 'asc') /** * Set collection page start and records to show + * * Backward compatibility with EAV collection * * @param int $pageNum @@ -124,11 +129,12 @@ public function setPage($pageNum, $pageSize) /** * Create all ids retrieving select with limitation + * * Backward compatibility with EAV collection * * @param int $limit * @param int $offset - * @return \Magento\Eav\Model\Entity\Collection\AbstractCollection + * @return \Magento\Framework\DB\Select */ protected function _getAllIdsSelect($limit = null, $offset = null) { @@ -144,6 +150,7 @@ protected function _getAllIdsSelect($limit = null, $offset = null) /** * Retrieve all ids for collection + * * Backward compatibility with EAV collection * * @param int $limit diff --git a/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php b/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php index b9e629912a5..9cf14313179 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php @@ -7,6 +7,10 @@ namespace Magento\Catalog\Model\ResourceModel; use Magento\Eav\Model\Entity\Attribute\AbstractAttribute; +use Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend; +use Magento\Eav\Model\Entity\Attribute\Frontend\AbstractFrontend; +use Magento\Eav\Model\Entity\Attribute\Source\AbstractSource; +use Magento\Eav\Model\Entity\Attribute\UniqueValidationInterface; /** * Catalog entity abstract model @@ -37,16 +41,18 @@ abstract class AbstractResource extends \Magento\Eav\Model\Entity\AbstractEntity * @param \Magento\Store\Model\StoreManagerInterface $storeManager * @param \Magento\Catalog\Model\Factory $modelFactory * @param array $data + * @param UniqueValidationInterface|null $uniqueValidator */ public function __construct( \Magento\Eav\Model\Entity\Context $context, \Magento\Store\Model\StoreManagerInterface $storeManager, \Magento\Catalog\Model\Factory $modelFactory, - $data = [] + $data = [], + UniqueValidationInterface $uniqueValidator = null ) { $this->_storeManager = $storeManager; $this->_modelFactory = $modelFactory; - parent::__construct($context, $data); + parent::__construct($context, $data, $uniqueValidator); } /** @@ -86,9 +92,7 @@ protected function _isApplicableAttribute($object, $attribute) /** * Check whether attribute instance (attribute, backend, frontend or source) has method and applicable * - * @param AbstractAttribute|\Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend - * |\Magento\Eav\Model\Entity\Attribute\Frontend\AbstractFrontend - * |\Magento\Eav\Model\Entity\Attribute\Source\AbstractSource $instance + * @param AbstractAttribute|AbstractBackend|AbstractFrontend|AbstractSource $instance * @param string $method * @param array $args array of arguments * @return boolean @@ -112,6 +116,7 @@ protected function _isCallableAttributeInstance($instance, $method, $args) /** * Retrieve select object for loading entity attributes values + * * Join attribute store value * * @param \Magento\Framework\DataObject $object @@ -244,6 +249,7 @@ protected function _saveAttributeValue($object, $attribute, $value) /** * Check if attribute present for non default Store View. + * * Prevent "delete" query locking in a case when nothing to delete * * @param AbstractAttribute $attribute diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Category.php b/app/code/Magento/Catalog/Model/ResourceModel/Category.php index 90f55cd44bd..536fda7e093 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Category.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Category.php @@ -200,7 +200,7 @@ protected function _getTree() * delete child categories * * @param \Magento\Framework\DataObject $object - * @return $this + * @return void */ protected function _beforeDelete(\Magento\Framework\DataObject $object) { diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product.php b/app/code/Magento/Catalog/Model/ResourceModel/Product.php index d71ec238819..24174391be8 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product.php @@ -8,6 +8,7 @@ use Magento\Catalog\Model\ResourceModel\Product\Website\Link as ProductWebsiteLink; use Magento\Framework\App\ObjectManager; use Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer; +use Magento\Eav\Model\Entity\Attribute\UniqueValidationInterface; /** * Product entity resource model @@ -101,6 +102,7 @@ class Product extends AbstractResource * @param \Magento\Catalog\Model\Product\Attribute\DefaultAttributes $defaultAttributes * @param array $data * @param TableMaintainer|null $tableMaintainer + * @param UniqueValidationInterface|null $uniqueValidator * * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ @@ -115,7 +117,8 @@ public function __construct( \Magento\Eav\Model\Entity\TypeFactory $typeFactory, \Magento\Catalog\Model\Product\Attribute\DefaultAttributes $defaultAttributes, $data = [], - TableMaintainer $tableMaintainer = null + TableMaintainer $tableMaintainer = null, + UniqueValidationInterface $uniqueValidator = null ) { $this->_categoryCollectionFactory = $categoryCollectionFactory; $this->_catalogCategory = $catalogCategory; @@ -127,7 +130,8 @@ public function __construct( $context, $storeManager, $modelFactory, - $data + $data, + $uniqueValidator ); $this->connectionName = 'catalog'; $this->tableMaintainer = $tableMaintainer ?: ObjectManager::getInstance()->get(TableMaintainer::class); @@ -289,7 +293,7 @@ protected function _afterSave(\Magento\Framework\DataObject $product) } /** - * {@inheritdoc} + * @inheritdoc */ public function delete($object) { @@ -593,7 +597,7 @@ public function countAll() } /** - * {@inheritdoc} + * @inheritdoc */ public function validate($object) { @@ -633,7 +637,7 @@ public function load($object, $entityId, $attributes = []) } /** - * {@inheritdoc} + * @inheritdoc * @SuppressWarnings(PHPMD.UnusedLocalVariable) * @since 101.0.0 */ @@ -675,6 +679,8 @@ public function save(\Magento\Framework\Model\AbstractModel $object) } /** + * Retrieve entity manager object + * * @return \Magento\Framework\EntityManager\EntityManager */ private function getEntityManager() @@ -687,6 +693,8 @@ private function getEntityManager() } /** + * Retrieve ProductWebsiteLink object + * * @deprecated 101.1.0 * @return ProductWebsiteLink */ @@ -696,6 +704,8 @@ private function getProductWebsiteLink() } /** + * Retrieve CategoryLink object + * * @deprecated 101.1.0 * @return \Magento\Catalog\Model\ResourceModel\Product\CategoryLink */ @@ -710,9 +720,10 @@ private function getProductCategoryLink() /** * Extends parent method to be appropriate for product. + * * Store id is required to correctly identify attribute value we are working with. * - * {@inheritdoc} + * @inheritdoc * @since 101.1.0 */ protected function getAttributeRow($entity, $object, $attribute) diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php index bd314c0192d..8aeb52e75c7 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php @@ -2218,7 +2218,7 @@ private function getTierPriceSelect(array $productIds) $this->getLinkField() . ' IN(?)', $productIds )->order( - $this->getLinkField() + 'qty' ); return $select; } diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Gallery.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Gallery.php index 635715a6074..a9741cd8e1e 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Gallery.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Gallery.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Catalog\Model\ResourceModel\Product; use Magento\Store\Model\Store; @@ -149,7 +150,7 @@ public function loadProductGalleryByAttributeId($product, $attributeId) */ protected function createBaseLoadSelect($entityId, $storeId, $attributeId) { - $select = $this->createBatchBaseSelect($storeId, $attributeId); + $select = $this->createBatchBaseSelect($storeId, $attributeId); $select = $select->where( 'entity.' . $this->metadata->getLinkField() . ' = ?', @@ -378,9 +379,9 @@ public function deleteGalleryValueInStore($valueId, $entityId, $storeId) $conditions = implode( ' AND ', [ - $this->getConnection()->quoteInto('value_id = ?', (int) $valueId), - $this->getConnection()->quoteInto($this->metadata->getLinkField() . ' = ?', (int) $entityId), - $this->getConnection()->quoteInto('store_id = ?', (int) $storeId) + $this->getConnection()->quoteInto('value_id = ?', (int)$valueId), + $this->getConnection()->quoteInto($this->metadata->getLinkField() . ' = ?', (int)$entityId), + $this->getConnection()->quoteInto('store_id = ?', (int)$storeId) ] ); @@ -408,7 +409,7 @@ public function duplicate($attributeId, $newFiles, $originalProductId, $newProdu $select = $this->getConnection()->select()->from( [$this->getMainTableAlias() => $this->getMainTable()], - ['value_id', 'value'] + ['value_id', 'value', 'media_type', 'disabled'] )->joinInner( ['entity' => $this->getTable(self::GALLERY_VALUE_TO_ENTITY_TABLE)], $this->getMainTableAlias() . '.value_id = entity.value_id', @@ -425,16 +426,16 @@ public function duplicate($attributeId, $newFiles, $originalProductId, $newProdu // Duplicate main entries of gallery foreach ($this->getConnection()->fetchAll($select) as $row) { - $data = [ - 'attribute_id' => $attributeId, - 'value' => isset($newFiles[$row['value_id']]) ? $newFiles[$row['value_id']] : $row['value'], - ]; + $data = $row; + $data['attribute_id'] = $attributeId; + $data['value'] = $newFiles[$row['value_id']] ?? $row['value']; + unset($data['value_id']); $valueIdMap[$row['value_id']] = $this->insertGallery($data); $this->bindValueToEntity($valueIdMap[$row['value_id']], $newProductId); } - if (count($valueIdMap) == 0) { + if (count($valueIdMap) === 0) { return []; } diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/AbstractEav.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/AbstractEav.php index c33ea7c781a..e024f0d30f1 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/AbstractEav.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/AbstractEav.php @@ -24,13 +24,11 @@ abstract class AbstractEav extends \Magento\Catalog\Model\ResourceModel\Product\ protected $_eventManager = null; /** - * AbstractEav constructor. * @param \Magento\Framework\Model\ResourceModel\Db\Context $context * @param \Magento\Framework\Indexer\Table\StrategyInterface $tableStrategy * @param \Magento\Eav\Model\Config $eavConfig * @param \Magento\Framework\Event\ManagerInterface $eventManager - * @param null $connectionName - * @param \Magento\Indexer\Model\Indexer\StateFactory|null $stateFactory + * @param string $connectionName */ public function __construct( \Magento\Framework\Model\ResourceModel\Db\Context $context, @@ -70,7 +68,6 @@ public function reindexAll() /** * Rebuild index data by entities * - * * @param int|array $processIds * @return $this * @throws \Exception @@ -88,8 +85,8 @@ public function reindexEntities($processIds) /** * Rebuild index data by attribute id - * If attribute is not indexable remove data by attribute * + * If attribute is not indexable remove data by attribute * * @param int $attributeId * @param bool $isIndexable @@ -245,7 +242,8 @@ protected function _prepareRelationIndex($parentIds = null) /** * Retrieve condition for retrieve indexable attribute select - * the catalog/eav_attribute table must have alias is ca + * + * The catalog/eav_attribute table must have alias is ca * * @return string */ 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 0005ac8dea5..95fecc832fa 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 @@ -16,6 +16,7 @@ /** * Prepare base select for Product Price index limited by specified dimensions: website and customer group + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class BaseFinalPrice @@ -66,10 +67,11 @@ class BaseFinalPrice private $metadataPool; /** - * BaseFinalPrice constructor. * @param \Magento\Framework\App\ResourceConnection $resource * @param JoinAttributeProcessor $joinAttributeProcessor * @param \Magento\Framework\Module\Manager $moduleManager + * @param \Magento\Framework\Event\ManagerInterface $eventManager + * @param \Magento\Framework\EntityManager\MetadataPool $metadataPool * @param string $connectionName */ public function __construct( @@ -89,6 +91,8 @@ public function __construct( } /** + * Build query for base final price. + * * @param Dimension[] $dimensions * @param string $productType * @param array $entityIds @@ -285,7 +289,7 @@ private function getTotalTierPriceExpression(\Zend_Db_Expr $priceExpression) /** * Get tier price expression for table * - * @param $tableAlias + * @param string $tableAlias * @param \Zend_Db_Expr $priceExpression * @return \Zend_Db_Expr */ @@ -305,7 +309,7 @@ private function getTierPriceExpressionForTable($tableAlias, \Zend_Db_Expr $pric /** * 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/Catalog/Model/ResourceModel/Product/Indexer/TemporaryTableStrategy.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/TemporaryTableStrategy.php index 54673cb01bb..89daab28859 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/TemporaryTableStrategy.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/TemporaryTableStrategy.php @@ -30,7 +30,7 @@ class TemporaryTableStrategy implements \Magento\Framework\Indexer\Table\Strateg /** * TemporaryTableStrategy constructor. - * @param \Magento\Framework\Indexer\Table\Strategy $strategy + * @param \Magento\Framework\Indexer\Table\StrategyInterface $strategy * @param \Magento\Framework\App\ResourceConnection $resource */ public function __construct( @@ -66,9 +66,10 @@ public function getTableName($tablePrefix) } /** - * Create temporary index table based on memory table + * Create temporary index table based on memory table{@inheritdoc} * - * {@inheritdoc} + * @param string $tablePrefix + * @return string */ public function prepareTableName($tablePrefix) { diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Link/DeleteHandler.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Link/DeleteHandler.php index 024c87c9fc8..a554ff2641d 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Link/DeleteHandler.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Link/DeleteHandler.php @@ -60,9 +60,11 @@ public function __construct( } /** + * Delete linked product. + * * @param string $entityType * @param object $entity - * @return object + * @return void * @throws CouldNotDeleteException * @throws NoSuchEntityException * @SuppressWarnings(PHPMD.UnusedFormalParameter) diff --git a/app/code/Magento/Catalog/Model/Rss/Category.php b/app/code/Magento/Catalog/Model/Rss/Category.php index a58569d1b59..653d86b177a 100644 --- a/app/code/Magento/Catalog/Model/Rss/Category.php +++ b/app/code/Magento/Catalog/Model/Rss/Category.php @@ -6,8 +6,7 @@ namespace Magento\Catalog\Model\Rss; /** - * Class Category - * @package Magento\Catalog\Model\Rss + * Rss Category model. */ class Category { @@ -42,9 +41,11 @@ public function __construct( } /** + * Get products for given category. + * * @param \Magento\Catalog\Model\Category $category * @param int $storeId - * @return $this + * @return \Magento\Catalog\Model\ResourceModel\Product\Collection */ public function getProductCollection(\Magento\Catalog\Model\Category $category, $storeId) { diff --git a/app/code/Magento/Catalog/Model/Template/Filter.php b/app/code/Magento/Catalog/Model/Template/Filter.php index 1eb30ff95a4..8cd61415b95 100644 --- a/app/code/Magento/Catalog/Model/Template/Filter.php +++ b/app/code/Magento/Catalog/Model/Template/Filter.php @@ -66,7 +66,7 @@ public function __construct( * Set use absolute links flag * * @param bool $flag - * @return \Magento\Email\Model\Template\Filter + * @return $this */ public function setUseAbsoluteLinks($flag) { @@ -76,10 +76,11 @@ public function setUseAbsoluteLinks($flag) /** * Setter whether SID is allowed in store directive + * * Doesn't set anything intentionally, since SID is not allowed in any kind of emails * * @param bool $flag - * @return \Magento\Email\Model\Template\Filter + * @return $this */ public function setUseSessionInUrl($flag) { @@ -132,6 +133,7 @@ public function mediaDirective($construction) /** * Retrieve store URL directive + * * Support url and direct_url properties * * @param array $construction diff --git a/app/code/Magento/Catalog/Plugin/Model/Product/Option/UpdateProductCustomOptionsAttributes.php b/app/code/Magento/Catalog/Plugin/Model/Product/Option/UpdateProductCustomOptionsAttributes.php new file mode 100644 index 00000000000..dd750cfbc69 --- /dev/null +++ b/app/code/Magento/Catalog/Plugin/Model/Product/Option/UpdateProductCustomOptionsAttributes.php @@ -0,0 +1,56 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Plugin\Model\Product\Option; + +/** + * Plugin for updating product 'has_options' and 'required_options' attributes + */ +class UpdateProductCustomOptionsAttributes +{ + /** + * @var \Magento\Catalog\Api\ProductRepositoryInterface + */ + private $productRepository; + + /** + * @param \Magento\Catalog\Api\ProductRepositoryInterface $productRepository + */ + public function __construct(\Magento\Catalog\Api\ProductRepositoryInterface $productRepository) + { + $this->productRepository = $productRepository; + } + + /** + * Update product 'has_options' and 'required_options' attributes after option save + * + * @param \Magento\Catalog\Api\ProductCustomOptionRepositoryInterface $subject + * @param \Magento\Catalog\Api\Data\ProductCustomOptionInterface $option + * + * @return \Magento\Catalog\Api\Data\ProductCustomOptionInterface + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterSave( + \Magento\Catalog\Api\ProductCustomOptionRepositoryInterface $subject, + \Magento\Catalog\Api\Data\ProductCustomOptionInterface $option + ) { + $product = $this->productRepository->get($option->getProductSku()); + if (!$product->getHasOptions() || + ($option->getIsRequire() && !$product->getRequiredOptions())) { + $product->setCanSaveCustomOptions(true); + $product->setOptionsSaved(true); + $currentOptions = array_filter($product->getOptions(), function ($iOption) use ($option) { + return $option->getOptionId() != $iOption->getOptionId(); + }); + $currentOptions[] = $option; + $product->setOptions($currentOptions); + $product->save(); + } + + return $option; + } +} diff --git a/app/code/Magento/Catalog/Plugin/Model/ResourceModel/Config.php b/app/code/Magento/Catalog/Plugin/Model/ResourceModel/Config.php index dfa06b6ebe6..b942f5570f5 100644 --- a/app/code/Magento/Catalog/Plugin/Model/ResourceModel/Config.php +++ b/app/code/Magento/Catalog/Plugin/Model/ResourceModel/Config.php @@ -8,6 +8,9 @@ use Magento\Framework\App\ObjectManager; use Magento\Framework\Serialize\SerializerInterface; +/** + * Config cache plugin. + */ class Config { /**#@+ @@ -46,8 +49,10 @@ public function __construct( } /** + * Cache attribute used in listing. + * * @param \Magento\Catalog\Model\ResourceModel\Config $config - * @param callable $proceed + * @param \Closure $proceed * @return array */ public function aroundGetAttributesUsedInListing( @@ -73,8 +78,10 @@ public function aroundGetAttributesUsedInListing( } /** + * Cache attributes used for sorting. + * * @param \Magento\Catalog\Model\ResourceModel\Config $config - * @param callable $proceed + * @param \Closure $proceed * @return array */ public function aroundGetAttributesUsedForSortBy( diff --git a/app/code/Magento/Catalog/Pricing/Price/SpecialPrice.php b/app/code/Magento/Catalog/Pricing/Price/SpecialPrice.php index b1bfc6ff4ad..77c48fdb166 100644 --- a/app/code/Magento/Catalog/Pricing/Price/SpecialPrice.php +++ b/app/code/Magento/Catalog/Pricing/Price/SpecialPrice.php @@ -11,6 +11,7 @@ use Magento\Framework\Pricing\Price\AbstractPrice; use Magento\Framework\Pricing\Price\BasePriceProviderInterface; use Magento\Framework\Stdlib\DateTime\TimezoneInterface; +use Magento\Store\Api\Data\WebsiteInterface; /** * Special price model @@ -46,6 +47,8 @@ public function __construct( } /** + * Retrieve special price. + * * @return bool|float */ public function getValue() @@ -96,19 +99,19 @@ public function getSpecialToDate() } /** - * @return bool + * @inheritdoc */ public function isScopeDateInInterval() { return $this->localeDate->isScopeDateInInterval( - $this->product->getStore(), + WebsiteInterface::ADMIN_CODE, $this->getSpecialFromDate(), $this->getSpecialToDate() ); } /** - * @return bool + * @inheritdoc */ public function isPercentageDiscount() { diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssignImageRolesActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssignImageRolesActionGroup.xml new file mode 100644 index 00000000000..90ceb1e4a1f --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssignImageRolesActionGroup.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminAssignImageRolesActionGroup"> + <arguments> + <argument name="image"/> + </arguments> + <conditionalClick selector="{{AdminProductImagesSection.productImagesToggleState('closed')}}" dependentSelector="{{AdminProductImagesSection.productImagesToggleState('open')}}" visible="false" stepKey="clickSectionImage"/> + <click selector="{{AdminProductImagesSection.imageFile(image.fileName)}}" stepKey="clickProductImage"/> + <waitForElementVisible selector="{{AdminProductImagesSection.altText}}" stepKey="seeAltTextSection"/> + <checkOption selector="{{AdminProductImagesSection.roleBase}}" stepKey="checkRoleBase"/> + <checkOption selector="{{AdminProductImagesSection.roleSmall}}" stepKey="checkRoleSmall"/> + <checkOption selector="{{AdminProductImagesSection.roleThumbnail}}" stepKey="checkRoleThumbnail"/> + <checkOption selector="{{AdminProductImagesSection.roleSwatch}}" stepKey="checkRoleSwatch"/> + <click selector="{{AdminSlideOutDialogSection.closeButton}}" stepKey="clickCloseButton"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml index 2b5fbfbe6a7..1eb76e66677 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml @@ -70,7 +70,10 @@ <!--Save product and see success message--> <actionGroup name="saveProductForm"> + <scrollToTopOfPage stepKey="scrollTopPageProduct"/> + <waitForElementVisible selector="{{AdminProductFormActionSection.saveButton}}" stepKey="waitForSaveProductButton" /> <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="clickSaveProduct"/> + <waitForPageLoad stepKey="waitForProductToSave"/> <see selector="{{AdminProductMessagesSection.successMessage}}" userInput="You saved the product." stepKey="seeSaveConfirmation"/> </actionGroup> @@ -159,7 +162,7 @@ <click selector="{{AdminProductCustomizableOptionsSection.customizableOptions}}" stepKey="openCustomOptionsSection"/> <click selector="{{AdminProductCustomizableOptionsSection.addOptionBtn}}" stepKey="clickAddOption"/> - <fillField userInput="option1" selector="{{AdminProductCustomizableOptionsSection.optionTitleInput}}" stepKey="fillOptionTitle"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.optionTitleInput('0')}}" userInput="option1" stepKey="fillOptionTitle"/> <click selector="{{AdminProductCustomizableOptionsSection.optionTypeOpenDropDown}}" stepKey="openTypeDropDown"/> <click selector="{{AdminProductCustomizableOptionsSection.optionTypeTextField}}" stepKey="selectTypeTextField"/> <fillField userInput="20" selector="{{AdminProductCustomizableOptionsSection.maxCharactersInput}}" stepKey="fillMaxChars"/> @@ -234,7 +237,7 @@ <fillField selector="{{AdminProductGridFilterSection.skuFilter}}" userInput="{{sku}}" stepKey="fillProductSkuFilter"/> <click selector="{{AdminProductGridFilterSection.applyFilters}}" stepKey="clickApplyFilters"/> <waitForPageLoad stepKey="waitForPageToLoad"/> - <click selector="{{AdminProductModalSlideGridSection.productGridXRowYColumnButton('1', '1')}}" stepKey="selectProduct"/> + <checkOption selector="{{AdminProductModalSlideGridSection.productRowCheckboxBySku(sku)}}" stepKey="selectProduct"/> <click selector="{{AdminAddRelatedProductsModalSection.AddSelectedProductsButton}}" stepKey="addRelatedProductSelected"/> </actionGroup> @@ -257,11 +260,25 @@ <argument name="website" type="string"/> </arguments> <scrollTo selector="{{ProductInWebsitesSection.sectionHeader}}" stepKey="scrollToWebsites"/> - <click selector="{{ProductInWebsitesSection.sectionHeader}}" stepKey="clickToOpenProductInWebsite"/> + <conditionalClick selector="{{ProductInWebsitesSection.sectionHeader}}" dependentSelector="{{AdminProductContentSection.sectionHeaderShow}}" visible="false" stepKey="expandSection"/> <waitForPageLoad stepKey="waitForPageOpened"/> <checkOption selector="{{ProductInWebsitesSection.website(website)}}" stepKey="selectWebsite"/> </actionGroup> + <actionGroup name="AdminProductAddSpecialPrice"> + <arguments> + <argument name="specialPrice" type="string"/> + </arguments> + <click selector="{{AdminProductFormSection.advancedPricingLink}}" stepKey="clickAdvancedPricingLink1"/> + <waitForElementVisible selector="{{AdminProductFormAdvancedPricingSection.specialPrice}}" stepKey="waitSpecialPrice1"/> + <click selector="{{AdminProductFormAdvancedPricingSection.useDefaultPrice}}" stepKey="checkUseDefault"/> + <fillField userInput="{{specialPrice}}" selector="{{AdminProductFormAdvancedPricingSection.specialPrice}}" stepKey="fillSpecialPrice"/> + <click selector="{{AdminProductFormAdvancedPricingSection.doneButton}}" stepKey="clickDone"/> + <waitForElementNotVisible selector="{{AdminProductFormAdvancedPricingSection.specialPrice}}" stepKey="waitForCloseModalWindow"/> + <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="clickOnSaveButton"/> + <seeElement selector="{{AdminProductMessagesSection.successMessage}}" stepKey="seeSaveProductMessage"/> + </actionGroup> + <!--Switch to New Store view--> <actionGroup name="SwitchToTheNewStoreView"> <arguments> @@ -301,9 +318,7 @@ <argument name="website"/> <argument name="product"/> </arguments> - <amOnPage url="{{AdminCatalogProductPage.url}}" stepKey="navigateToProductPage"/> - <waitForPageLoad stepKey="waitForProductsList"/> - <click stepKey="openProduct" selector="{{AdminProductGridActionSection.productName(product.name)}}"/> + <click stepKey="openProduct" selector="{{AdminProductGridActionSection.productName(product.sku)}}"/> <waitForPageLoad stepKey="waitForProductPage"/> <scrollTo selector="{{ProductInWebsitesSection.sectionHeader}}" stepKey="ScrollToWebsites"/> <click selector="{{ProductInWebsitesSection.sectionHeader}}" stepKey="openWebsitesList"/> @@ -327,4 +342,47 @@ </assertEquals> </actionGroup> + <actionGroup name="expandAdminProductSection"> + <arguments> + <argument name="sectionSelector" defaultValue="{{AdminProductContentSection.sectionHeader}}" type="string"/> + <argument name="sectionDependentSelector" defaultValue="{{AdminProductContentSection.sectionHeaderShow}}" type="string"/> + </arguments> + <scrollToTopOfPage stepKey="scrollToTopOfPage"/> + <waitForElementVisible time="30" selector="{{sectionSelector}}" stepKey="waitForSection"/> + <conditionalClick selector="{{sectionSelector}}" dependentSelector="{{sectionDependentSelector}}" visible="false" stepKey="expandSection"/> + <waitForPageLoad time="30" stepKey="waitForSectionToExpand"/> + </actionGroup> + <actionGroup name="navigateToCreatedProductEditPage"> + <arguments> + <argument name="product" defaultValue="_defaultProduct"/> + </arguments> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="goToAdminProductIndexPage"/> + <waitForPageLoad stepKey="waitForProductIndexPageToLoad"/> + <conditionalClick selector="{{AdminProductGridFilterSection.clearFilters}}" dependentSelector="{{AdminProductGridFilterSection.clearFilters}}" visible="true" stepKey="clickClearFilters"/> + <waitForPageLoad stepKey="waitForClearFilters"/> + <dontSeeElement selector="{{AdminProductGridFilterSection.clearFilters}}" stepKey="dontSeeClearFilters"/> + <click selector="{{AdminProductGridFilterSection.viewDropdown}}" stepKey="openViewBookmarksTab"/> + <click selector="{{AdminProductGridFilterSection.viewBookmark('Default View')}}" stepKey="resetToDefaultGridView"/> + <waitForPageLoad stepKey="waitForResetToDefaultView"/> + <see selector="{{AdminProductGridFilterSection.viewDropdown}}" userInput="Default View" stepKey="seeDefaultViewSelected"/> + <click selector="{{AdminProductGridFilterSection.filters}}" stepKey="openProductFilters"/> + <fillField selector="{{AdminProductGridFilterSection.skuFilter}}" userInput="{{product.sku}}" stepKey="fillProductSkuFilter"/> + <click selector="{{AdminProductGridFilterSection.applyFilters}}" stepKey="clickApplyFilters"/> + <waitForPageLoad stepKey="waitForFilterOnGrid"/> + <click selector="{{AdminProductGridSection.selectRowBasedOnName(product.name)}}" stepKey="clickProduct"/> + <waitForPageLoad stepKey="waitForProductEditPageLoad"/> + <waitForElementVisible selector="{{AdminProductFormBundleSection.productSku}}" stepKey="waitForProductSKUField"/> + <seeInField selector="{{AdminProductFormBundleSection.productSku}}" userInput="{{product.sku}}" stepKey="seeProductSKU"/> + </actionGroup> + <actionGroup name="addUpSellProductBySku" extends="addRelatedProductBySku"> + <click selector="{{AdminProductFormRelatedUpSellCrossSellSection.AddUpSellProductsButton}}" stepKey="clickAddRelatedProductButton"/> + <conditionalClick selector="{{AdminAddUpSellProductsModalSection.Modal}} {{AdminProductGridFilterSection.clearFilters}}" dependentSelector="{{AdminAddUpSellProductsModalSection.Modal}} {{AdminProductGridFilterSection.clearFilters}}" visible="true" stepKey="clickClearFilters"/> + <click selector="{{AdminAddUpSellProductsModalSection.Modal}} {{AdminProductGridFilterSection.filters}}" stepKey="openProductFilters"/> + <fillField selector="{{AdminAddUpSellProductsModalSection.Modal}} {{AdminProductGridFilterSection.skuFilter}}" userInput="{{sku}}" stepKey="fillProductSkuFilter"/> + <click selector="{{AdminAddUpSellProductsModalSection.Modal}} {{AdminProductGridFilterSection.applyFilters}}" stepKey="clickApplyFilters"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + <click selector="{{AdminAddUpSellProductsModalSection.Modal}}{{AdminProductModalSlideGridSection.productGridXRowYColumnButton('1', '1')}}" stepKey="selectProduct"/> + <click selector="{{AdminAddUpSellProductsModalSection.AddSelectedProductsButton}}" stepKey="addRelatedProductSelected"/> + <waitForPageLoad stepKey="waitForPageToLoad1"/> + </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 80cadbb6571..0d82ba3817d 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeActionGroup.xml @@ -47,4 +47,70 @@ <seeElement selector="{{AdminProductMessagesSection.successMessage}}" stepKey="waitForSuccessMessage"/> </actionGroup> + <actionGroup name="deleteProductAttributeByLabel"> + <arguments> + <argument name="ProductAttribute"/> + </arguments> + <amOnPage url="{{AdminProductAttributeGridPage.url}}" stepKey="navigateToProductAttributeGrid"/> + <fillField selector="{{AdminProductAttributeGridSection.FilterByAttributeCode}}" userInput="{{ProductAttribute.default_label}}" stepKey="setAttributeCode"/> + <click selector="{{AdminProductAttributeGridSection.Search}}" stepKey="searchForAttributeFromTheGrid"/> + <click selector="{{AdminProductAttributeGridSection.FirstRow}}" stepKey="clickOnAttributeRow"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <click selector="{{AttributePropertiesSection.DeleteAttribute}}" stepKey="deleteAttribute"/> + <click selector="{{ModalConfirmationSection.OkButton}}" stepKey="ClickOnDeleteButton"/> + <waitForPageLoad stepKey="waitForPageLoad1"/> + <seeElement selector="{{AdminProductMessagesSection.successMessage}}" stepKey="waitForSuccessMessage"/> + </actionGroup> + <!-- Delete product attribute by Attribute Code --> + <actionGroup name="deleteProductAttributeByAttributeCode"> + <arguments> + <argument name="ProductAttributeCode" type="string"/> + </arguments> + <amOnPage url="{{AdminProductAttributeGridPage.url}}" stepKey="navigateToProductAttributeGrid"/> + <fillField selector="{{AdminProductAttributeGridSection.FilterByAttributeCode}}" userInput="{{ProductAttributeCode}}" stepKey="setAttributeCode"/> + <click selector="{{AdminProductAttributeGridSection.Search}}" stepKey="searchForAttributeFromTheGrid"/> + <click selector="{{AdminProductAttributeGridSection.FirstRow}}" stepKey="clickOnAttributeRow"/> + <waitForPageLoad stepKey="waitForPageLoad2" /> + <click selector="{{AttributePropertiesSection.DeleteAttribute}}" stepKey="deleteAttribute"/> + <click selector="{{ModalConfirmationSection.OkButton}}" stepKey="ClickOnDeleteButton"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <seeElement selector="{{AdminProductMessagesSection.successMessage}}" stepKey="waitForSuccessMessage"/> + </actionGroup> + <!--Filter product attribute by Attribute Code --> + <actionGroup name="filterProductAttributeByAttributeCode"> + <arguments> + <argument name="ProductAttributeCode" type="string"/> + </arguments> + <click selector="{{AdminProductAttributeGridSection.ResetFilter}}" stepKey="resetFiltersOnGrid"/> + <fillField selector="{{AdminProductAttributeGridSection.FilterByAttributeCode}}" userInput="{{ProductAttributeCode}}" stepKey="setAttributeCode"/> + <waitForPageLoad stepKey="waitForUserInput"/> + <click selector="{{AdminProductAttributeGridSection.Search}}" stepKey="searchForAttributeFromTheGrid"/> + </actionGroup> + <!--Filter product attribute by Default Label --> + <actionGroup name="filterProductAttributeByDefaultLabel"> + <arguments> + <argument name="productAttributeLabel" type="string"/> + </arguments> + <click selector="{{AdminProductAttributeGridSection.ResetFilter}}" stepKey="resetFiltersOnGrid"/> + <fillField selector="{{AdminProductAttributeGridSection.GridFilterFrontEndLabel}}" userInput="{{productAttributeLabel}}" stepKey="setDefaultLabel"/> + <waitForPageLoad stepKey="waitForUserInput"/> + <click selector="{{AdminProductAttributeGridSection.Search}}" stepKey="searchForAttributeFromTheGrid"/> + </actionGroup> + <actionGroup name="saveProductAttribute"> + <waitForElementVisible selector="{{AttributePropertiesSection.Save}}" stepKey="waitForSaveButton"/> + <click selector="{{AttributePropertiesSection.Save}}" stepKey="clickSaveButton"/> + <waitForPageLoad stepKey="waitForAttributeToSave"/> + <seeElement selector="{{AdminProductMessagesSection.successMessage}}" stepKey="seeSuccessMessage"/> + </actionGroup> + <actionGroup name="confirmChangeInputTypeModal"> + <waitForElementVisible selector="{{AdminEditProductAttributesSection.ProductDataMayBeLostConfirmButton}}" stepKey="waitForChangeInputTypeButton"/> + <click selector="{{AdminEditProductAttributesSection.ProductDataMayBeLostConfirmButton}}" stepKey="clickChangeInputTypeButton"/> + <waitForElementNotVisible selector="{{AdminEditProductAttributesSection.ProductDataMayBeLostModal}}" stepKey="waitForChangeInputTypeModalGone"/> + </actionGroup> + <actionGroup name="saveProductAttributeInUse"> + <waitForElementVisible selector="{{AttributePropertiesSection.Save}}" stepKey="waitForSaveButton"/> + <click selector="{{AttributePropertiesSection.Save}}" stepKey="clickSaveButton"/> + <waitForPageLoad stepKey="waitForAttributeToSave"/> + <seeElement selector="{{AdminProductMessagesSection.successMessage}}" stepKey="seeSuccessMessage"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeSetActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeSetActionGroup.xml index 5948ca12dcf..a3b4203b7a6 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeSetActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeSetActionGroup.xml @@ -45,4 +45,13 @@ <fillField selector="{{AdminProductAttributeSetSection.name}}" userInput="{{label}}" stepKey="fillName"/> <click selector="{{AdminProductAttributeSetSection.saveBtn}}" stepKey="clickSave1"/> </actionGroup> + <!-- Filter By Attribute Label --> + <actionGroup name="filterProductAttributeByAttributeLabel"> + <arguments> + <argument name="productAttributeLabel" type="string"/> + </arguments> + <fillField selector="{{AdminProductAttributeGridSection.attributeLabelFilter}}" userInput="{{productAttributeLabel}}" stepKey="setAttributeLabel"/> + <waitForPageLoad stepKey="waitForUserInput"/> + <click selector="{{AdminProductAttributeGridSection.Search}}" stepKey="searchForAttributeFromTheGrid"/> + </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 8d16d35ab0d..f0367fb72c6 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml @@ -144,6 +144,18 @@ <click selector="{{AdminProductGridFilterSection.clearFilters}}" stepKey="clickClearFiltersAfter"/> </actionGroup> + <!-- Filter product grid by sku, name --> + <actionGroup name="filterProductGridBySkuAndName"> + <arguments> + <argument name="product" defaultValue="_defaultProduct"/> + </arguments> + <conditionalClick selector="{{AdminProductGridFilterSection.clearFilters}}" dependentSelector="{{AdminProductGridFilterSection.clearFilters}}" visible="true" stepKey="clickClearFiltersInitial"/> + <click selector="{{AdminProductGridFilterSection.filters}}" stepKey="openProductFilters"/> + <fillField selector="{{AdminProductGridFilterSection.skuFilter}}" userInput="{{product.sku}}" stepKey="fillProductSkuFilter"/> + <fillField selector="{{AdminProductGridFilterSection.nameFilter}}" userInput="{{product.name}}" stepKey="fillProductNameFilter"/> + <click selector="{{AdminProductGridFilterSection.applyFilters}}" stepKey="clickApplyFilters"/> + </actionGroup> + <!--Delete a product by filtering grid and using delete action--> <actionGroup name="deleteProductUsingProductGrid"> <arguments> @@ -155,6 +167,7 @@ <conditionalClick selector="{{AdminProductGridFilterSection.clearFilters}}" dependentSelector="{{AdminProductGridFilterSection.clearFilters}}" visible="true" stepKey="clickClearFiltersInitial"/> <click selector="{{AdminProductGridFilterSection.filters}}" stepKey="openProductFilters"/> <fillField selector="{{AdminProductGridFilterSection.skuFilter}}" userInput="{{product.sku}}" stepKey="fillProductSkuFilter"/> + <fillField selector="{{AdminProductGridFilterSection.nameFilter}}" userInput="{{product.name}}" stepKey="fillProductNameFilter"/> <click selector="{{AdminProductGridFilterSection.applyFilters}}" stepKey="clickApplyFilters"/> <see selector="{{AdminProductGridSection.productGridCell('1', 'SKU')}}" userInput="{{product.sku}}" stepKey="seeProductSkuInGrid"/> <click selector="{{AdminProductGridSection.multicheckDropdown}}" stepKey="openMulticheckDropdown"/> @@ -243,8 +256,20 @@ <click selector="{{AdminProductGridSection.bulkActionDropdown}}" stepKey="clickActionDropdown"/> <click selector="{{AdminProductGridSection.bulkActionOption('Change status')}}" stepKey="clickChangeStatusAction"/> <click selector="{{AdminProductGridSection.changeStatus('status')}}" stepKey="clickChangeStatusDisabled" parameterized="true"/> + <waitForPageLoad stepKey="waitForStatusToBeChanged"/> <see selector="{{AdminMessagesSection.success}}" userInput="A total of 1 record(s) have been updated." stepKey="seeSuccessMessage"/> <waitForLoadingMaskToDisappear stepKey="waitForMaskToDisappear"/> <conditionalClick selector="{{AdminProductGridFilterSection.clearFilters}}" dependentSelector="{{AdminProductGridFilterSection.clearFilters}}" visible="true" stepKey="clickClearFiltersInitial2"/> </actionGroup> + + <actionGroup name="NavigateToAndResetProductGridToDefaultView" extends="resetProductGridToDefaultView"> + <amOnPage url="{{AdminProductIndexPage.url}}" before="clickClearFilters" stepKey="goToAdminProductIndexPage"/> + <waitForPageLoad after="goToAdminProductIndexPage" stepKey="waitForProductIndexPageToLoad"/> + </actionGroup> + <actionGroup name="NavigateToAndResetProductAttributeGridToDefaultView"> + <amOnPage url="{{AdminProductAttributeGridPage.url}}" stepKey="navigateToProductAttributeGrid"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <conditionalClick selector="{{AdminProductGridFilterSection.clearFilters}}" dependentSelector="{{AdminProductGridFilterSection.clearFilters}}" visible="true" stepKey="clickClearFilters"/> + <waitForPageLoad stepKey="waitForGridLoad"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/CreateNewProductActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CreateNewProductActionGroup.xml similarity index 86% rename from app/code/Magento/Braintree/Test/Mftf/ActionGroup/CreateNewProductActionGroup.xml rename to app/code/Magento/Catalog/Test/Mftf/ActionGroup/CreateNewProductActionGroup.xml index 19de3e859ae..53de47f8106 100644 --- a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/CreateNewProductActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CreateNewProductActionGroup.xml @@ -5,10 +5,10 @@ * See COPYING.txt for license details. */ --> + <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="CreateNewProductActionGroup"> - <click stepKey="openCatalog" selector="{{AdminMenuSection.catalog}}"/> <waitForPageLoad stepKey="waitForCatalogSubmenu" time="5"/> <click stepKey="clickOnProducts" selector="{{CatalogSubmenuSection.products}}"/> @@ -22,4 +22,4 @@ <waitForElementVisible stepKey="waitForSuccessfullyCreatedMessage" selector="{{NewProductPageSection.createdSuccessMessage}}" time="10"/> <waitForPageLoad stepKey="waitForPageLoad" time="10"/> </actionGroup> -</actionGroups> \ No newline at end of file +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CustomOptionsActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CustomOptionsActionGroup.xml index 7373d5baea0..2d966dde64c 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CustomOptionsActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CustomOptionsActionGroup.xml @@ -15,7 +15,6 @@ <argument name="productOption"/> <argument name="productOption2"/> </arguments> - <click stepKey="clickAddOptions" selector="{{AdminProductCustomizableOptionsSection.addOptionBtn}}"/> <waitForPageLoad stepKey="waitForAddProductPageLoad"/> @@ -48,10 +47,9 @@ <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}}" stepKey="waitForElements"/> - <fillField selector="{{AdminProductCustomizableOptionsSection.optionPrice}}" userInput="{{option.price}}" stepKey="fillPrice"/> - <selectOption selector="{{AdminProductCustomizableOptionsSection.optionPriceType}}" userInput="{{option.price_type}}" stepKey="selectPriceType"/> - <fillField selector="{{AdminProductCustomizableOptionsSection.optionFileExtensions}}" userInput="{{option.file_extension}}" stepKey="fillCompatibleExtensions"/> + <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"/> </actionGroup> - </actionGroups> diff --git a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/DeleteProductActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/DeleteProductActionGroup.xml similarity index 85% rename from app/code/Magento/Braintree/Test/Mftf/ActionGroup/DeleteProductActionGroup.xml rename to app/code/Magento/Catalog/Test/Mftf/ActionGroup/DeleteProductActionGroup.xml index 724c6d92846..7491b39aa8f 100644 --- a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/DeleteProductActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/DeleteProductActionGroup.xml @@ -5,8 +5,9 @@ * See COPYING.txt for license details. */ --> + <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="DeleteProductActionGroup"> <arguments> <argument name="productName" defaultValue=""/> @@ -23,4 +24,4 @@ <click stepKey="clickOnOk" selector="{{ProductsPageSection.ok}}"/> <waitForElementVisible stepKey="waitForSuccessfullyDeletedMessage" selector="{{ProductsPageSection.deletedSuccessMessage}}" time="10"/> </actionGroup> -</actionGroups> \ No newline at end of file +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/SearchForProductOnBackendActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/SearchForProductOnBackendActionGroup.xml index a303511ffe5..aca9ba24c11 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/SearchForProductOnBackendActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/SearchForProductOnBackendActionGroup.xml @@ -19,6 +19,14 @@ <click selector="{{AdminProductFiltersSection.apply}}" stepKey="clickApplyFiltersButton"/> </actionGroup> + <actionGroup name="SearchForProductOnBackendByNameActionGroup" extends="SearchForProductOnBackendActionGroup"> + <arguments> + <argument name="productName" type="string"/> + </arguments> + <remove keyForRemoval="fillSkuFieldOnFiltersSection"/> + <fillField userInput="{{productName}}" selector="{{AdminProductFiltersSection.nameInput}}" after="cleanFiltersIfTheySet" stepKey="fillNameFieldOnFiltersSection"/> + </actionGroup> + <actionGroup name="ClearProductsFilterActionGroup"> <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="navigateToProductIndex"/> <waitForPageLoad time="30" stepKey="waitForProductsPageToLoad"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/CatalogPriceData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/CatalogPriceData.xml index cad8a8cd03e..0f7f4da1b68 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/CatalogPriceData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/CatalogPriceData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="CatalogPriceScopeWebsite" type="catalog_price_config_state"> <requiredEntity type="scope">scopeWebsite</requiredEntity> <requiredEntity type="default_product_price">defaultProductPrice</requiredEntity> @@ -29,5 +29,4 @@ <entity name="defaultProductPrice" type="default_product_price"> <data key="value"/> </entity> - </entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/CatalogStorefrontConfigData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/CatalogStorefrontConfigData.xml index d8ec84013d9..abf01f00dbb 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/CatalogStorefrontConfigData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/CatalogStorefrontConfigData.xml @@ -38,4 +38,26 @@ <entity name="DefaultFlatCatalogProduct" type="flat_catalog_product"> <data key="value">0</data> </entity> + + <entity name="UseFlatCatalogCategoryAndProduct" type="catalog_storefront_config"> + <requiredEntity type="flat_catalog_product">UseFlatCatalogProduct</requiredEntity> + <requiredEntity type="flat_catalog_category">UseFlatCatalogCategory</requiredEntity> + </entity> + + <entity name="UseFlatCatalogProduct" type="flat_catalog_product"> + <data key="value">1</data> + </entity> + + <entity name="UseFlatCatalogCategory" type="flat_catalog_category"> + <data key="value">1</data> + </entity> + + <entity name="DefaultFlatCatalogCategoryAndProduct" type="catalog_storefront_config"> + <requiredEntity type="flat_catalog_product">DefaultFlatCatalogProduct</requiredEntity> + <requiredEntity type="flat_catalog_category">DefaultFlatCatalogCategory</requiredEntity> + </entity> + + <entity name="DefaultFlatCatalogCategory" type="flat_catalog_category"> + <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 ff3c5cb4403..a11c3fd0d7a 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml @@ -44,11 +44,11 @@ </entity> <entity name="FirstLevelSubCat" type="category"> <data key="name" unique="suffix">FirstLevelSubCategory</data> - <data key="name_lwr" unique="suffix">subcategory</data> + <data key="name_lwr" unique="suffix">firstlevelsubcategory</data> </entity> <entity name="SecondLevelSubCat" type="category"> <data key="name" unique="suffix">SecondLevelSubCategory</data> - <data key="name_lwr" unique="suffix">subcategory</data> + <data key="name_lwr" unique="suffix">secondlevelsubcategory</data> </entity> <entity name="ThirdLevelSubCat" type="category"> <data key="name" unique="suffix">ThirdLevelSubCategory</data> @@ -62,4 +62,46 @@ <data key="name" unique="suffix">FifthLevelCategory</data> <data key="name_lwr" unique="suffix">category</data> </entity> + <entity name="SimpleRootSubCategory" type="category"> + <data key="name" unique="suffix">SimpleRootSubCategory</data> + <data key="name_lwr" unique="suffix">simplerootsubcategory</data> + <data key="is_active">true</data> + <data key="include_in_menu">true</data> + <data key="url_key" unique="suffix">simplerootsubcategory</data> + <var key="parent_id" entityType="category" entityKey="id" /> + </entity> + <entity name="SubCategory" type="category"> + <data key="name" unique="suffix">SubCategory</data> + <data key="name_lwr" unique="suffix">subcategory</data> + <data key="is_active">true</data> + <data key="include_in_menu">true</data> + </entity> + <entity name="Two_nested_categories" type="category"> + <data key="name" unique="suffix">SecondLevel</data> + <data key="url_key" unique="suffix">secondlevel</data> + <data key="name_lwr" unique="suffix">secondlevel</data> + <data key="is_active">true</data> + <data key="include_in_menu">true</data> + <var key="parent_id" entityType="category" entityKey="id" /> + </entity> + <entity name="Three_nested_categories" type="category"> + <data key="name" unique="suffix">ThirdLevel</data> + <data key="url_key" unique="suffix">thirdlevel</data> + <data key="name_lwr" unique="suffix">thirdlevel</data> + <data key="is_active">true</data> + <data key="include_in_menu">true</data> + <var key="parent_id" entityType="category" entityKey="id" /> + </entity> + <entity name="CatNotIncludeInMenu" type="category"> + <data key="name" unique="suffix">NotInclMenu</data> + <data key="name_lwr" unique="suffix">notinclemenu</data> + <data key="is_active">true</data> + <data key="include_in_menu">false</data> + </entity> + <entity name="CatNotActive" type="category"> + <data key="name" unique="suffix">NotActive</data> + <data key="name_lwr" unique="suffix">notactive</data> + <data key="is_active">false</data> + <data key="include_in_menu">true</data> + </entity> </entities> diff --git a/app/code/Magento/Braintree/Test/Mftf/Data/NewProductData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/NewProductData.xml similarity index 66% rename from app/code/Magento/Braintree/Test/Mftf/Data/NewProductData.xml rename to app/code/Magento/Catalog/Test/Mftf/Data/NewProductData.xml index 72661ae9407..4479805cb12 100644 --- a/app/code/Magento/Braintree/Test/Mftf/Data/NewProductData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/NewProductData.xml @@ -7,11 +7,10 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="NewProductData" type="braintree_config_state"> <data key="ProductName">ProductTest</data> <data key="Price">100</data> <data key="Quantity">100</data> </entity> - </entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml index b367cdcab9d..03a004e500a 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml @@ -115,4 +115,27 @@ <data key="used_for_sort_by">true</data> <requiredEntity type="FrontendLabel">ProductAttributeFrontendLabel</requiredEntity> </entity> + <entity name="productAttributeText" type="ProductAttribute"> + <data key="attribute_code" unique="suffix">attribute</data> + <data key="frontend_input">text</data> + <data key="scope">global</data> + <data key="is_required">false</data> + <data key="is_unique">false</data> + <data key="is_searchable">false</data> + <data key="is_visible">true</data> + <data key="backend_type">text</data> + <data key="is_wysiwyg_enabled">false</data> + <data key="is_visible_in_advanced_search">false</data> + <data key="is_visible_on_front">true</data> + <data key="is_filterable">false</data> + <data key="is_filterable_in_search">false</data> + <data key="used_in_product_listing">false</data> + <data key="is_used_for_promo_rules">false</data> + <data key="is_comparable">true</data> + <data key="is_used_in_grid">false</data> + <data key="is_visible_in_grid">false</data> + <data key="is_filterable_in_grid">false</data> + <data key="used_for_sort_by">false</data> + <requiredEntity type="FrontendLabel">ProductAttributeFrontendLabel</requiredEntity> + </entity> </entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeSetData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeSetData.xml index 68f51559a9f..713c453bb7a 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeSetData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeSetData.xml @@ -14,4 +14,10 @@ <data key="attributeGroupId">7</data> <data key="sortOrder">0</data> </entity> + <entity name="AddToDefaultSetSortOrder1" type="ProductAttributeSet"> + <var key="attributeCode" entityKey="attribute_code" entityType="ProductAttribute"/> + <data key="attributeSetId">4</data> + <data key="attributeGroupId">7</data> + <data key="sortOrder">1</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 23253ad6ad8..6d0d953e446 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml @@ -35,6 +35,9 @@ <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> <requiredEntity type="custom_attribute_array">CustomAttributeCategoryIds</requiredEntity> </entity> + <entity name="ApiSimpleProductWithCustomPrice" type="product" extends="ApiSimpleProduct"> + <data key="price">100</data> + </entity> <entity name="ApiSimpleProductUpdateDescription" type="product2"> <requiredEntity type="custom_attribute">ApiProductDescription</requiredEntity> <requiredEntity type="custom_attribute">ApiProductShortDescription</requiredEntity> @@ -260,7 +263,7 @@ <data key="status">1</data> <data key="quantity">100</data> <data key="weight">0</data> - <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> + <requiredEntity type="product_extension_attribute">EavStock100</requiredEntity> <requiredEntity type="custom_attribute_array">CustomAttributeCategoryIds</requiredEntity> </entity> <entity name="productWithDescription" type="product"> @@ -480,8 +483,113 @@ <requiredEntity type="product_extension_attribute">EavStock1</requiredEntity> <requiredEntity type="custom_attribute">CustomAttributeProductAttribute</requiredEntity> </entity> + <entity name="virtualProductWithRequiredFields" type="product"> + <data key="name" unique="suffix">virtualProduct</data> + <data key="sku" unique="suffix">virtualsku</data> + <data key="price">10</data> + <data key="visibility">Catalog, Search</data> + <data key="urlKey" unique="suffix">virtualproduct</data> + <data key="type_id">virtual</data> + </entity> + <entity name="virtualProductBigQty" type="product"> + <data key="name" unique="suffix">VirtualProduct</data> + <data key="sku" unique="suffix">virtual_sku</data> + <data key="price">100.00</data> + <data key="productTaxClass">None</data> + <data key="quantity">999</data> + <data key="status">In Stock</data> + <data key="visibility">Catalog, Search</data> + <data key="urlKey" unique="suffix">virtual-product</data> + <data key="type_id">virtual</data> + </entity> + <entity name="virtualProductGeneralGroup" type="product"> + <data key="name" unique="suffix">VirtualProduct</data> + <data key="sku" unique="suffix">virtual_sku</data> + <data key="price">100.00</data> + <data key="productTaxClass">None</data> + <data key="quantity">999</data> + <data key="status">In Stock</data> + <data key="visibility">Catalog, Search</data> + <data key="urlKey" unique="suffix">virtual-product</data> + <data key="type_id">virtual</data> + </entity> + <entity name="virtualProductCustomImportOptions" type="product"> + <data key="name" unique="suffix">VirtualProduct</data> + <data key="sku" unique="suffix">virtual_sku</data> + <data key="price">9,000.00</data> + <data key="quantity">999</data> + <data key="status">In Stock</data> + <data key="visibility">Catalog, Search</data> + <data key="urlKey" unique="suffix">virtual-product</data> + <data key="storefrontStatus">IN STOCK</data> + <data key="type_id">virtual</data> + </entity> + <entity name="virtualProductWithoutManageStock" type="product"> + <data key="name" unique="suffix">VirtualProduct</data> + <data key="sku" unique="suffix">virtual_sku</data> + <data key="price">100.00</data> + <data key="quantity">999</data> + <data key="urlKey" unique="suffix">virtual-product</data> + <data key="special_price">90.00</data> + <data key="storefrontStatus">IN STOCK</data> + <data key="type_id">virtual</data> + </entity> + <entity name="virtualProductOutOfStock" type="product"> + <data key="name" unique="suffix">VirtualProduct</data> + <data key="sku" unique="suffix">virtual_sku</data> + <data key="price">9,000.00</data> + <data key="quantity">999</data> + <data key="status">Out of Stock</data> + <data key="visibility">Catalog, Search</data> + <data key="urlKey" unique="suffix">virtual-product</data> + <data key="storefrontStatus">OUT OF STOCK</data> + <data key="type_id">virtual</data> + </entity> + <entity name="virtualProductAssignToCategory" type="product"> + <data key="name" unique="suffix">VirtualProduct</data> + <data key="sku" unique="suffix">virtual_sku</data> + <data key="price">10.00</data> + <data key="quantity">999</data> + <data key="urlKey" unique="suffix">virtual-product</data> + <data key="type_id">virtual</data> + </entity> + <entity name="updateVirtualProductRegularPriceInStock" type="product"> + <data key="name" unique="suffix">VirtualProduct</data> + <data key="sku" unique="suffix">virtual_sku</data> + <data key="price">120.00</data> + <data key="productTaxClass">None</data> + <data key="quantity">999</data> + <data key="status">In Stock</data> + <data key="storefrontStatus">IN STOCK</data> + <data key="visibility">Search</data> + <data key="urlKey" unique="suffix">virtual-product</data> + <data key="type_id">virtual</data> + </entity> + <entity name="updateVirtualProductWithTierPriceInStock" type="product"> + <data key="name" unique="suffix">VirtualProduct</data> + <data key="sku" unique="suffix">virtual_sku</data> + <data key="price">99.99</data> + <data key="productTaxClass">None</data> + <data key="quantity">999</data> + <data key="status">In Stock</data> + <data key="storefrontStatus">IN STOCK</data> + <data key="visibility">Catalog</data> + <data key="urlKey" unique="suffix">virtual-product</data> + <data key="type_id">virtual</data> + </entity> + <entity name="updateVirtualProductRegularPrice99OutOfStock" type="product"> + <data key="name" unique="suffix">VirtualProduct</data> + <data key="sku" unique="suffix">virtual_sku</data> + <data key="price">99.99</data> + <data key="productTaxClass">Taxable Goods</data> + <data key="status">Out of Stock</data> + <data key="storefrontStatus">OUT OF STOCK</data> + <data key="visibility">Search</data> + <data key="urlKey" unique="suffix">virtual-product</data> + <data key="type_id">virtual</data> + </entity> <entity name="defaultSimpleProduct" type="product"> - <data key="name" unique="suffix">Test </data> + <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> @@ -497,4 +605,119 @@ <data key="name" unique="suffix">Product With Long Name And Sku - But not too long</data> <data key="sku" unique="suffix">Product With Long Name And Sku - But not too long</data> </entity> + <entity name="PaginationProduct" type="product"> + <data key="name" unique="suffix">pagi</data> + <data key="sku" unique="suffix">pagisku</data> + <data key="type_id">simple</data> + <data key="attribute_set_id">4</data> + <data key="visibility">4</data> + <data key="price">780.00</data> + <data key="urlKey" unique="suffix">pagiurl-</data> + <data key="status">1</data> + <data key="quantity">50</data> + <data key="weight">5</data> + <requiredEntity type="product_extension_attribute">EavStock100</requiredEntity> + </entity> + <entity name="Magento3" type="image"> + <data key="title" unique="suffix">Magento3</data> + <data key="price">1.00</data> + <data key="file_type">Upload File</data> + <data key="shareable">Yes</data> + <data key="file">magento3.jpg</data> + <data key="filename">magento3</data> + <data key="file_extension">jpg</data> + </entity> + <entity name="updateVirtualProductRegularPrice" type="product"> + <data key="name" unique="suffix">VirtualProduct</data> + <data key="sku" unique="suffix">virtual_sku</data> + <data key="price">99.99</data> + <data key="productTaxClass">None</data> + <data key="quantity">999</data> + <data key="status">In Stock</data> + <data key="storefrontStatus">IN STOCK</data> + <data key="visibility">Catalog</data> + <data key="urlKey" unique="suffix">virtual-product</data> + <data key="type_id">virtual</data> + </entity> + <entity name="updateVirtualProductRegularPrice5OutOfStock" type="product"> + <data key="name" unique="suffix">VirtualProduct</data> + <data key="sku" unique="suffix">virtual_sku</data> + <data key="price">5.00</data> + <data key="productTaxClass">None</data> + <data key="status">Out of Stock</data> + <data key="storefrontStatus">OUT OF STOCK</data> + <data key="visibility">Catalog</data> + <data key="urlKey" unique="suffix">virtual-product</data> + <data key="type_id">virtual</data> + </entity> + <entity name="updateVirtualProductSpecialPrice" type="product"> + <data key="name" unique="suffix">VirtualProduct</data> + <data key="sku" unique="suffix">virtual_sku</data> + <data key="price">120.00</data> + <data key="productTaxClass">Taxable Goods</data> + <data key="quantity">999</data> + <data key="status">In Stock</data> + <data key="storefrontStatus">IN STOCK</data> + <data key="visibility">Catalog, Search</data> + <data key="urlKey" unique="suffix">virtual-product</data> + <data key="special_price">45.00</data> + <data key="type_id">virtual</data> + </entity> + <entity name="updateVirtualProductSpecialPriceOutOfStock" type="product"> + <data key="name" unique="suffix">VirtualProduct</data> + <data key="sku" unique="suffix">virtual_sku</data> + <data key="price">99.99</data> + <data key="productTaxClass">None</data> + <data key="status">Out of Stock</data> + <data key="storefrontStatus">OUT OF STOCK</data> + <data key="visibility">Catalog, Search</data> + <data key="urlKey" unique="suffix">virtual-product</data> + <data key="special_price">45.00</data> + <data key="type_id">virtual</data> + </entity> + <entity name="updateVirtualProductTierPriceInStock" type="product"> + <data key="name" unique="suffix">VirtualProduct</data> + <data key="sku" unique="suffix">virtual_sku</data> + <data key="price">145.00</data> + <data key="productTaxClass">Taxable Goods</data> + <data key="quantity">999</data> + <data key="status">In Stock</data> + <data key="storefrontStatus">IN STOCK</data> + <data key="visibility">Catalog, Search</data> + <data key="urlKey" unique="suffix">virtual-product</data> + <data key="type_id">virtual</data> + </entity> + <entity name="updateVirtualTierPriceOutOfStock" type="product"> + <data key="name" unique="suffix">VirtualProduct</data> + <data key="sku" unique="suffix">virtual_sku</data> + <data key="price">185.00</data> + <data key="productTaxClass">None</data> + <data key="quantity">999</data> + <data key="status">Out of Stock</data> + <data key="storefrontStatus">OUT OF STOCK</data> + <data key="visibility">Catalog, Search</data> + <data key="urlKey" unique="suffix">virtual-product</data> + <data key="type_id">virtual</data> + </entity> + <entity name="nameAndAttributeSkuMaskSimpleProduct" type="product"> + <data key="urlKey" unique="suffix">simple-product</data> + <data key="name" unique="suffix">SimpleProduct</data> + <data key="price">10000.00</data> + <data key="quantity">657</data> + <data key="weight">50</data> + <data key="country_of_manufacture">UA</data> + <data key="country_of_manufacture_label">Ukraine</data> + <data key="type_id">simple</data> + <data key="status">1</data> + <requiredEntity type="product_extension_attribute">EavStock100</requiredEntity> + </entity> + <entity name="ProductShortDescription" type="ProductAttribute"> + <data key="attribute_code">short_description</data> + </entity> + <entity name="AddToDefaultSetTopOfContentSection" type="ProductAttributeSet"> + <var key="attributeCode" entityKey="attribute_code" entityType="ProductAttribute"/> + <data key="attributeSetId">4</data> + <data key="attributeGroupId">13</data> + <data key="sortOrder">0</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 0aec1244d26..cb8bb47f3cc 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/TierPriceData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/TierPriceData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="testDataTierPrice" type="data"> <data key="goldenPrice1">$676.50</data> <data key="goldenPrice2">$615.00</data> @@ -20,4 +20,26 @@ <data key="name">secondStoreView</data> <data key="code">second_store_view</data> </entity> + <entity name="tierPriceOnVirtualProduct" type="data"> + <data key="website">All Websites [USD]</data> + <data key="customer_group">ALL GROUPS</data> + <data key="price">90.00</data> + <data key="qty">2</data> + </entity> + <entity name="tierPriceOnGeneralGroup" type="data"> + <data key="website">All Websites [USD]</data> + <data key="customer_group">General</data> + <data key="price">80.00</data> + <data key="qty">2</data> + </entity> + <entity name="tierPriceOnDefault" type="data"> + <data key="website_0">All Websites [USD]</data> + <data key="customer_group_0">ALL GROUPS</data> + <data key="price_0">15.00</data> + <data key="qty_0">3</data> + <data key="website_1">All Websites [USD]</data> + <data key="customer_group_1">ALL GROUPS</data> + <data key="price_1">24.00</data> + <data key="qty_1">15</data> + </entity> </entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/VirtualProductOptionData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/VirtualProductOptionData.xml new file mode 100644 index 00000000000..fe1d49e4daa --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Data/VirtualProductOptionData.xml @@ -0,0 +1,60 @@ +<?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="virtualProductCustomizableOption1"> + <data key="title" unique="suffix">Test1 option </data> + <data key="is_required">1</data> + <data key="type">Field</data> + <data key="option_0_price">120.03</data> + <data key="option_0_price_type">Fixed</data> + <data key="option_0_sku" unique="suffix">sku1_</data> + <data key="option_0_max_characters">45</data> + </entity> + <entity name="virtualProductCustomizableOption2"> + <data key="title" unique="suffix">Test2 option </data> + <data key="is_required">1</data> + <data key="type">Field</data> + <data key="option_0_price">120.03</data> + <data key="option_0_price_type">Fixed</data> + <data key="option_0_sku" unique="suffix">sku2_</data> + <data key="option_0_max_characters">45</data> + </entity> + <entity name="virtualProductCustomizableOption3"> + <data key="title" unique="suffix">Test3 option </data> + <data key="is_required">1</data> + <data key="type">Drop-down</data> + <data key="option_0_title" unique="suffix">Test3-1 </data> + <data key="option_0_price">110.01</data> + <data key="option_0_expected_price">9,900.90</data> + <data key="option_0_price_type">Percent</data> + <data key="option_0_sku" unique="suffix">sku3-1_</data> + <data key="option_0_sort_order">0</data> + <data key="option_1_title" unique="suffix">Test3-2 </data> + <data key="option_1_price">210.02</data> + <data key="option_1_price_type">Fixed</data> + <data key="option_1_sku" unique="suffix">sku3-2_</data> + <data key="option_1_sort_order">1</data> + </entity> + <entity name="virtualProductCustomizableOption4"> + <data key="title" unique="suffix">Test4 option </data> + <data key="is_required">1</data> + <data key="type">Drop-down</data> + <data key="option_0_title" unique="suffix">Test4-1 </data> + <data key="option_0_price">10.01</data> + <data key="option_0_price_type">Percent</data> + <data key="option_0_sku" unique="suffix">sku4-1_</data> + <data key="option_0_sort_order">0</data> + <data key="option_1_title" unique="suffix">Test4-2 </data> + <data key="option_1_price">20.02</data> + <data key="option_1_price_type">Fixed</data> + <data key="option_1_sku" unique="suffix">sku4-2_</data> + <data key="option_1_sort_order">1</data> + </entity> +</entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Metadata/catalog_price-meta.xml b/app/code/Magento/Catalog/Test/Mftf/Metadata/catalog_price-meta.xml index e16688ba0d3..1ee57c89b2b 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Metadata/catalog_price-meta.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Metadata/catalog_price-meta.xml @@ -5,8 +5,9 @@ * See COPYING.txt for license details. */ --> + <operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> <operation name="CatalogPriceConfigState" dataType="catalog_price_config_state" type="create" auth="adminFormKey" url="/admin/system_config/save/section/catalog/" method="POST"> <object key="groups" dataType="catalog_price_config_state"> <object key="price" dataType="catalog_price_config_state"> @@ -21,4 +22,4 @@ </object> </object> </operation> -</operations> \ No newline at end of file +</operations> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryBasicFieldSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryBasicFieldSection.xml index 651ee54c083..977e63b9ec9 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryBasicFieldSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryBasicFieldSection.xml @@ -22,7 +22,7 @@ <element name="ContentTab" type="input" selector="input[name='name']"/> <element name="FieldError" type="text" selector=".admin__field-error[data-bind='attr: {for: {{field}}}, text: error']" parameterized="true"/> <element name="panelFieldControl" type="input" selector='//aside//div[@data-index="{{arg1}}"]/descendant::*[@name="{{arg2}}"]' parameterized="true"/> - <element name="productsInCategory" type="input" selector="div[data-index='assign_products']"/> + <element name="productsInCategory" type="input" selector="div[data-index='assign_products']" timeout="30"/> </section> <section name="CategoryContentSection"> <element name="SelectFromGalleryBtn" type="button" selector="//label[text()='Select from Gallery']"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryProductsGridSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryProductsGridSection.xml index a449836fa08..df79ec61ef7 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryProductsGridSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryProductsGridSection.xml @@ -16,5 +16,6 @@ <element name="rowPosition" type="input" selector="#catalog_category_products_table tbody tr:nth-of-type({{row}}) .col-position .position input" timeout="30" parameterized="true"/> <element name="productGridNameProduct" type="text" selector="//table[@id='catalog_category_products_table']//td[contains(., '{{productName}}')]" parameterized="true"/> <element name="productVisibility" type="select" selector="//*[@name='product[visibility]']"/> + <element name="productSelectAll" type="checkbox" selector="input.admin__control-checkbox"/> </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarTreeSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarTreeSection.xml index ef6fb99e88e..14e714cb2b6 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarTreeSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarTreeSection.xml @@ -13,7 +13,7 @@ <element name="expandAll" type="button" selector=".tree-actions a:last-child"/> <element name="categoryTreeRoot" type="text" selector="div.x-tree-root-node>li.x-tree-node:first-of-type>div.x-tree-node-el:first-of-type" timeout="30"/> <element name="categoryInTree" type="text" selector="//a/span[contains(text(), '{{name}}')]" parameterized="true" timeout="30"/> - <element name="categoryInTreeUnderRoot" type="text" selector="//div[@class='x-tree-root-node']/li/ul/li[@class='x-tree-node']/div/a/span[contains(text(), '{{name}}')]" parameterized="true"/> + <element name="categoryInTreeUnderRoot" type="text" selector="//li/ul/li[@class='x-tree-node']/div/a/span[contains(text(), '{{name}}')]" parameterized="true"/> <element name="lastCreatedCategory" type="block" selector=".x-tree-root-ct li li:last-child" /> <element name="treeContainer" type="block" selector=".tree-holder" /> </section> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateProductAttributeSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateProductAttributeSection.xml index 324f261f3a5..ee6af87b8e2 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateProductAttributeSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateProductAttributeSection.xml @@ -22,6 +22,11 @@ <element name="TinyMCE4" type="button" selector="//span[text()='Default Value']/parent::label/following-sibling::div//div[@class='mce-branding-powered-by']"/> <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"/> + </section> + <section name="AttributeManageSwatchSection"> + <element name="swatchField" type="input" selector="//th[contains(@class, 'col-swatch')]/span[contains(text(), '{{arg}}')]/ancestor::thead/following-sibling::tbody//input[@placeholder='Swatch']" parameterized="true"/> + <element name="descriptionField" type="input" selector="//th[contains(@class, 'col-swatch')]/span[contains(text(), '{{arg}}')]/ancestor::thead/following-sibling::tbody//input[@placeholder='Description']" parameterized="true"/> </section> <section name="AttributeOptionsSection"> <element name="AddOption" type="button" selector="#add_new_option_button"/> @@ -73,6 +78,7 @@ type="button" selector="#advanced_fieldset-wrapper"/> <element name="AttributeCode" type="text" selector="#attribute_code"/> <element name="Scope" type="select" selector="#is_global"/> + <element name="UniqueValue" type="select" selector="#is_unique"/> <element name="AddToColumnOptions" type="select" selector="#is_used_in_grid"/> <element name="UseInFilterOptions" type="select" selector="#is_filterable_in_grid"/> <element name="UseInProductListing" type="select" selector="#used_in_product_listing"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminEditProductAttributesSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminEditProductAttributesSection.xml index 5df17cb4f5a..63bdcd52cdd 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminEditProductAttributesSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminEditProductAttributesSection.xml @@ -18,6 +18,8 @@ <element name="AttributeDescription" type="text" selector="#description"/> <element name="ChangeAttributeDescriptionToggle" type="checkbox" selector="#toggle_description"/> <element name="Save" type="button" selector="button[title=Save]" timeout="30"/> + <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"/> </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeGridSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeGridSection.xml index 160948f8f1f..d3aaeefdc6b 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeGridSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeGridSection.xml @@ -16,5 +16,6 @@ <element name="ResetFilter" type="button" selector="button[data-action='grid-filter-reset']" timeout="30"/> <element name="FirstRow" type="button" selector="//*[@id='attributeGrid_table']/tbody/tr[1]" timeout="30"/> <element name="FilterByAttributeCode" type="input" selector="#attributeGrid_filter_attribute_code"/> + <element name="attributeLabelFilter" type="input" selector="//input[@name='frontend_label']"/> </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeOptionsSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeOptionsSection.xml index 0f438540603..5f1112eef36 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeOptionsSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeOptionsSection.xml @@ -10,5 +10,6 @@ <section name="DropdownAttributeOptionsSection"> <element name="nthOptionAdminLabel" type="input" selector="(//*[@id='manage-options-panel']//tr[{{var}}]//input[contains(@name, 'option[value]')])[1]" parameterized="true"/> + <element name="deleteButton" type="button" selector="(//td[@class='col-delete'])[1]" timeout="30"/> </section> </sections> \ No newline at end of file diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCategoryCreationSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCategoryCreationSection.xml index 337cf0527dd..755add18ec1 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCategoryCreationSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCategoryCreationSection.xml @@ -11,12 +11,10 @@ <section name="AdminProductCategoryCreationSection"> <element name="firstExampleProduct" type="button" selector=".data-row:nth-of-type(1)"/> <element name="newCategory" type="button" selector="//button/span[text()='New Category']"/> - <element name="nameInput" type="input" selector="input[name='name']"/> - <element name="parentCategory" type="block" selector=".product_form_product_form_create_category_modal div[data-role='selected-option']"/> <element name="parentSearch" type="input" selector="aside input[data-role='advanced-select-text']"/> <element name="parentSearchResult" type="block" selector="aside .admin__action-multiselect-menu-inner"/> - <element name="createCategory" type="button" selector="#save"/> + <element name="createCategory" type="button" selector="#save" timeout="30"/> </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductContentSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductContentSection.xml index 784eff12a6c..fafae5d5355 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductContentSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductContentSection.xml @@ -10,6 +10,7 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminProductContentSection"> <element name="sectionHeader" type="button" selector="div[data-index='content']" timeout="30"/> + <element name="sectionHeaderShow" type="button" selector="div[data-index='content']._show" timeout="30"/> <element name="descriptionTextArea" type="textarea" selector="#product_form_description"/> <element name="shortDescriptionTextArea" type="textarea" selector="#product_form_short_description"/> <element name="sectionHeaderIfNotShowing" type="button" selector="//div[@data-index='content']//div[contains(@class, '_hide')]"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCustomizableOptionsSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCustomizableOptionsSection.xml index 052195ec1aa..fc78c25ec49 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCustomizableOptionsSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCustomizableOptionsSection.xml @@ -13,13 +13,15 @@ <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"/> - <element name="addOptionBtn" type="button" selector="button[data-index='button_add']"/> + <element name="addOptionBtn" type="button" selector="button[data-index='button_add']" timeout="30"/> <element name="fillOptionTitle" type="input" selector="//span[text()='{{var1}}']/parent::div/parent::div/parent::div//span[text()='Option Title']/parent::label/parent::div/parent::div//input[@class='admin__control-text']" parameterized="true"/> - <element name="optionTitleInput" type="input" selector="input[name='product[options][0][title]']"/> - <element name="optionTypeOpenDropDown" type="button" selector=".admin__dynamic-rows[data-index='options'] .action-select"/> - <element name="optionTypeTextField" type="button" selector=".admin__dynamic-rows[data-index='options'] .action-menu._active li li"/> + <element name="optionTitleInput" type="input" selector="input[name='product[options][{{index}}][title]']" parameterized="true"/> + <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="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="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="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"/> @@ -28,19 +30,20 @@ <element name="clickSelectPriceType" type="select" selector="//span[text()='{{var1}}']/parent::div/parent::div/parent::div//tbody//tr[@data-repeat-index='{{var2}}']//span[text()='Price Type']/parent::label/parent::div/parent::div//select" parameterized="true"/> <element name="checkboxUseDefaultTitle" type="checkbox" selector="//span[text()='Option Title']/parent::label/parent::div/parent::div/div//input[@type='checkbox']"/> <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"/> <!-- Elements that make it easier to select the most recently added element --> <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')]" /> <!-- 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']" /> + <element name="addValue" type="button" selector="//*[@data-index='custom_options']//*[@data-index='options']/tbody/tr[last()]//*[@data-action='add_new_row']" timeout="30"/> <element name="valueTitle" type="input" selector="//*[@data-index='custom_options']//*[@data-index='options']/tbody/tr[last()]//*[contains(@class, 'admin__control-table')]//tbody/tr[last()]//*[@data-index='title']//input" /> <element name="valuePrice" type="input" selector="//*[@data-index='custom_options']//*[@data-index='options']/tbody/tr[last()]//*[contains(@class, 'admin__control-table')]//tbody/tr[last()]//*[@data-index='price']//input" /> - - <element name="optionPrice" type="input" selector="//*[@data-index='custom_options']//*[@data-index='options']/tbody/tr[last()]//*[@name='product[options][0][price]']"/> - <element name="optionPriceType" type="select" selector="//*[@data-index='custom_options']//*[@data-index='options']/tbody/tr[last()]//*[@name='product[options][0][price_type]']"/> - <element name="optionSku" type="input" selector="//*[@data-index='custom_options']//*[@data-index='options']/tbody/tr[last()]//*[@name='product[options][0][sku]']"/> - <element name="optionFileExtensions" type="input" selector="//*[@data-index='custom_options']//*[@data-index='options']/tbody/tr[last()]//*[@name='product[options][0][file_extension]']"/> + <element name="optionPrice" type="input" selector="//*[@data-index='custom_options']//*[@data-index='options']/tbody/tr//*[@name='product[options][{{index}}][price]']" parameterized="true"/> + <element name="optionPriceType" type="select" selector="//*[@data-index='custom_options']//*[@data-index='options']/tbody/tr//*[@name='product[options][{{var}}][price_type]']" parameterized="true"/> + <element name="optionSku" type="input" selector="//*[@data-index='custom_options']//*[@data-index='options']/tbody/tr//*[@name='product[options][{{index}}][sku]']" parameterized="true"/> + <element name="optionFileExtensions" type="input" selector="//*[@data-index='custom_options']//*[@data-index='options']/tbody/tr//*[@name='product[options][{{index}}][file_extension]']" parameterized="true"/> </section> </sections> \ No newline at end of file diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedInventorySection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedInventorySection.xml index 0314534dcdd..a1bb27bb45a 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedInventorySection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedInventorySection.xml @@ -17,6 +17,8 @@ <element name="qtyIncrements" type="input" selector="//input[@name='product[stock_data][qty_increments]']"/> <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]']"/> </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml index 5791d0bfeda..337a3dd53f5 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml @@ -25,11 +25,12 @@ <element name="productPrice" type="input" selector=".admin__field[data-index=price] input"/> <element name="productTaxClass" type="select" selector="//*[@name='product[tax_class_id]']"/> <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']"/> + <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="productQuantity" type="input" selector=".admin__field[data-index=qty] input"/> - <element name="advancedInventoryLink" type="button" selector="//button[contains(@data-index, 'advanced_inventory_button')]"/> + <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="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"/> <element name="productWeightSelect" type="select" selector="select[name='product[product_has_weight]']"/> <element name="contentTab" type="button" selector="//strong[contains(@class, 'admin__collapsible-title')]/span[text()='Content']"/> @@ -37,7 +38,9 @@ <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"/> + <element name="save" type="button" selector="#save-button"/> + <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']"/> <element name="attributeLabel" type="input" selector="//input[@name='frontend_label[0]']"/> <element name="frontendInput" type="select" selector="select[name = 'frontend_input']"/> @@ -50,6 +53,11 @@ <element name="setProductAsNewTo" type="input" selector="input[name='product[news_to_date]']"/> <element name="attributeLabelByText" type="text" selector="//*[@class='admin__field']//span[text()='{{attributeLabel}}']" parameterized="true"/> <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="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]']"/> </section> <section name="ProductInWebsitesSection"> <element name="sectionHeader" type="button" selector="div[data-index='websites']" timeout="30"/> @@ -60,10 +68,13 @@ <element name="LayoutDropdown" type="select" selector="select[name='product[page_layout]']"/> </section> <section name="AdminProductFormRelatedUpSellCrossSellSection"> + <element name="relatedProductsHeader" type="button" selector=".admin__collapsible-block-wrapper[data-index='related']" timeout="30"/> <element name="AddRelatedProductsButton" type="button" selector="button[data-index='button_related']" timeout="30"/> + <element name="addUpSellProduct" type="button" selector="button[data-index='button_upsell']" timeout="30"/> </section> <section name="AdminAddRelatedProductsModalSection"> - <element name="AddSelectedProductsButton" type="button" selector="//aside[contains(@class, 'product_form_product_form_related_related_modal')]//button/span[contains(text(), 'Add Selected Products')]" timeout="30"/> + <element name="AddSelectedProductsButton" type="button" selector="//aside[contains(@class, 'related_modal')]//button[contains(@class, 'action-primary')]" timeout="30"/> + <element name="AddUpSellProductsButton" type="button" selector="//aside[contains(@class, 'upsell_modal')]//button[contains(@class, 'action-primary')]" timeout="30"/> </section> <section name="ProductWYSIWYGSection"> <element name="Switcher" type="button" selector="//select[@id='dropdown-switcher']"/> @@ -176,5 +187,6 @@ <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="useDefaultPrice" type="checkbox" selector="//input[@name='product[special_price]']/parent::div/following-sibling::div/input[@name='use_default[special_price]']"/> </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridFilterSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridFilterSection.xml index 611f12a39b5..43345c69e6c 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridFilterSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridFilterSection.xml @@ -31,5 +31,8 @@ <element name="newFromDateFilter" type="input" selector="input.admin__control-text[name='news_from_date[from]']"/> <element name="keywordSearch" type="input" selector="input#fulltext"/> <element name="keywordSearchButton" type="button" selector=".data-grid-search-control-wrap button.action-submit" timeout="30"/> + <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"/> </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml index fe87c81ad6a..02bdbac3130 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml @@ -8,7 +8,8 @@ <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminProductGridSection"> - <element name="productRowBySku" type="block" selector="//div[@id='container']//tr//td[count(../../..//th[./*[.='SKU']]/preceding-sibling::th) + 1][./*[.='{{sku}}']]" parameterized="true" /> + <element name="productRowBySku" type="block" selector="//td[count(../../..//th[./*[.='SKU']]/preceding-sibling::th) + 1][./*[.='{{sku}}']]" parameterized="true" /> + <element name="productRowCheckboxBySku" type="block" selector="//td[count(../../..//th[./*[.='SKU']]/preceding-sibling::th) + 1][./*[.='{{sku}}']]/../td//input[@data-action='select-row']" parameterized="true" /> <element name="loadingMask" type="text" selector=".admin__data-grid-loading-mask[data-component*='product_listing']"/> <element name="columnHeader" type="button" selector="//div[@data-role='grid-wrapper']//table[contains(@class, 'data-grid')]/thead/tr/th[contains(@class, 'data-grid-th')]/span[text() = '{{label}}']" parameterized="true" timeout="30"/> <element name="column" type="text" selector="//tr//td[count(//div[@data-role='grid-wrapper']//tr//th[contains(., '{{column}}')]/preceding-sibling::th) +1 ]" parameterized="true"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductImagesSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductImagesSection.xml index eca0cb6f02e..89eb1ed678c 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductImagesSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductImagesSection.xml @@ -16,6 +16,7 @@ <element name="removeImageButton" type="button" selector=".action-remove"/> <element name="modalOkBtn" type="button" selector="button.action-primary.action-accept"/> <element name="uploadProgressBar" type="text" selector=".uploader .file-row"/> + <element name="productImagesToggleState" type="button" selector="[data-index='gallery'] > [data-state-collapsible='{{status}}']" parameterized="true"/> <element name="nthProductImage" type="button" selector="#media_gallery_content > div:nth-child({{var}}) img.product-image" parameterized="true"/> <element name="nthRemoveImageBtn" type="button" selector="#media_gallery_content > div:nth-child({{var}}) button.action-remove" parameterized="true"/> @@ -32,4 +33,4 @@ <element name="isThumbnailSelected" type="button" selector="//div[contains(@class, 'field-image-role')]//ul/li[contains(@class, 'selected')]/label[normalize-space(.) = 'Thumbnail']"/> <element name="isSwatchSelected" type="button" selector="//div[contains(@class, 'field-image-role')]//ul/li[contains(@class, 'selected')]/label[normalize-space(.) = 'Swatch']"/> </section> -</sections> \ No newline at end of file +</sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductModalSlideGridSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductModalSlideGridSection.xml index adc3a753f06..dbdc8202694 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductModalSlideGridSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductModalSlideGridSection.xml @@ -10,5 +10,6 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminProductModalSlideGridSection"> <element name="productGridXRowYColumnButton" type="input" selector=".modal-slide table.data-grid tr.data-row:nth-child({{row}}) td:nth-child({{column}})" parameterized="true" timeout="30"/> + <element name="productRowCheckboxBySku" type="input" selector="//td[count(../../..//th[./*[.='SKU']]/preceding-sibling::th) + 1][./*[.='{{sku}}']]/../td//input[@data-action='select-row']" 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 e90d806805f..be046955a66 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductRelatedUpSellCrossSellSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductRelatedUpSellCrossSellSection.xml @@ -10,6 +10,7 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminProductFormRelatedUpSellCrossSellSection"> <element name="AddRelatedProductsButton" type="button" selector="button[data-index='button_related']" timeout="30"/> + <element name="AddUpSellProductsButton" type="button" selector="button[data-index='button_upsell']" timeout="30"/> <element name="relatedProductSectionText" type="text" selector=".fieldset-wrapper.admin__fieldset-section[data-index='related']"/> <element name="upSellProductSectionText" type="text" selector=".fieldset-wrapper.admin__fieldset-section[data-index='upsell']"/> <element name="crossSellProductSectionText" type="text" selector=".fieldset-wrapper.admin__fieldset-section[data-index='crosssell']"/> @@ -18,4 +19,8 @@ <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"/> </section> + <section name="AdminAddUpSellProductsModalSection"> + <element name="Modal" type="button" selector=".product_form_product_form_related_upsell_modal"/> + <element name="AddSelectedProductsButton" type="button" selector="//aside[contains(@class, 'product_form_product_form_related_upsell_modal')]//button/span[contains(text(), 'Add Selected Products')]" timeout="30"/> + </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductSEOSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductSEOSection.xml index c545fcd4088..53231a2a686 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductSEOSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductSEOSection.xml @@ -11,5 +11,6 @@ <section name="AdminProductSEOSection"> <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]']"/> </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 bf8812b3ace..53af1d5bd6e 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminUpdateAttributesSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminUpdateAttributesSection.xml @@ -38,4 +38,8 @@ <element name="description" type="input" selector="#description"/> </section> + <section name="AdminUpdateAttributesWebsiteSection"> + <element name="website" type="button" selector="#attributes_update_tabs_websites"/> + <element name="addProductToWebsite" type="checkbox" selector="#add-products-to-website-content .website-checkbox"/> + </section> </sections> diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/CatalogSubmenuSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/CatalogSubmenuSection.xml similarity index 68% rename from app/code/Magento/Braintree/Test/Mftf/Section/CatalogSubmenuSection.xml rename to app/code/Magento/Catalog/Test/Mftf/Section/CatalogSubmenuSection.xml index 32f02a69f81..84a81c5204a 100644 --- a/app/code/Magento/Braintree/Test/Mftf/Section/CatalogSubmenuSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/CatalogSubmenuSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="CatalogSubmenuSection"> <element name="products" type="button" selector="//li[@id='menu-magento-catalog-catalog']//li[@data-ui-id='menu-magento-catalog-catalog-products']"/> </section> diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/NewProductPageSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/NewProductPageSection.xml similarity index 81% rename from app/code/Magento/Braintree/Test/Mftf/Section/NewProductPageSection.xml rename to app/code/Magento/Catalog/Test/Mftf/Section/NewProductPageSection.xml index 42e451940c9..b98bd47b541 100644 --- a/app/code/Magento/Braintree/Test/Mftf/Section/NewProductPageSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/NewProductPageSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="NewProductPageSection"> <element name="productName" type="input" selector="//input[@name='product[name]']"/> <element name="sku" type="input" selector="//input[@name='product[sku]']"/> diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/ProductsPageSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/ProductsPageSection.xml similarity index 84% rename from app/code/Magento/Braintree/Test/Mftf/Section/ProductsPageSection.xml rename to app/code/Magento/Catalog/Test/Mftf/Section/ProductsPageSection.xml index 267efdf3d0e..ea37eb59b67 100644 --- a/app/code/Magento/Braintree/Test/Mftf/Section/ProductsPageSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/ProductsPageSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="ProductsPageSection"> <element name="addProductButton" type="button" selector="//button[@id='add_new_product-button']"/> <element name="checkboxForProduct" type="button" selector="//*[contains(text(),'{{args}}')]/parent::td/preceding-sibling::td/label[@class='data-grid-checkbox-cell-inner']" parameterized="true"/> @@ -15,6 +15,5 @@ <element name="delete" type="button" selector="//*[contains(@class,'admin__data-grid-header-row row row-gutter')]//*[text()='Delete']"/> <element name="ok" type="button" selector="//button[@data-role='action']//span[text()='OK']"/> <element name="deletedSuccessMessage" type="button" selector="//*[@class='message message-success success']"/> - </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryBottomToolbarSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryBottomToolbarSection.xml new file mode 100644 index 00000000000..7ce795c78f2 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryBottomToolbarSection.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="StorefrontCategoryBottomToolbarSection"> + <element name="nextPage" type="button" selector=".//*[@class='toolbar toolbar-products'][2]//a[contains(@class, 'next')]" timeout="30"/> + <element name="previousPage" type="button" selector=".//*[@class='toolbar toolbar-products'][2]//a[contains(@class, 'previous')]" timeout="30"/> + <element name="pageNumber" type="text" selector="//*[@class='toolbar toolbar-products'][2]//a[contains(@class, 'page')]//span[2][contains(text() ,'{{var1}}')]" parameterized="true"/> + <element name="perPage" type="select" selector="//*[@class='toolbar toolbar-products'][2]//select[@id='limiter']"/> + </section> +</sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryMainSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryMainSection.xml index 03566be55ad..de7ee35a40f 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryMainSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryMainSection.xml @@ -17,6 +17,7 @@ <element name="ProductItemInfo" type="button" selector=".product-item-info"/> <element name="specifiedProductItemInfo" type="button" selector="//a[@class='product-item-link'][contains(text(), '{{var1}}')]" parameterized="true"/> <element name="AddToCartBtn" type="button" selector="button.action.tocart.primary"/> + <element name="addToCartProductBySku" type="button" selector="//form[@data-product-sku='{{productSku}}']//button[contains(@class, 'tocart')]" parameterized="true" /> <element name="SuccessMsg" type="button" selector="div.message-success"/> <element name="productCount" type="text" selector="#toolbar-amount"/> <element name="CatalogDescription" type="text" selector="//div[@class='category-description']//p"/> @@ -29,5 +30,7 @@ <element name="categoryImage" type="text" selector=".category-image"/> <element name="emptyProductMessage" type="block" selector=".message.info.empty>div"/> <element name="lineProductName" type="text" selector=".products.list.items.product-items li:nth-of-type({{line}}) .product-item-link" timeout="30" parameterized="true"/> + <element name="asLowAs" type="input" selector="//*[@class='price-box price-final_price']/a/span[@class='price-container price-final_price tax weee']"/> + <element name="productsList" type="block" selector="#maincontent .column.main"/> </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontMiniCartSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontMiniCartSection.xml index ff2e5f2f360..c6ea96715cf 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontMiniCartSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontMiniCartSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontMiniCartSection"> <element name="quantity" type="button" selector="span.counter-number"/> <element name="show" type="button" selector="a.showcart"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontNavigationSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontNavigationSection.xml index e8f35fc6787..c6bad0efb3c 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontNavigationSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontNavigationSection.xml @@ -11,5 +11,6 @@ <element name="topCategory" type="button" selector="//a[contains(@class,'level-top')]/span[contains(text(),'{{var1}}')]" parameterized="true"/> <element name="subCategory" type="button" selector="//ul[contains(@class,'submenu')]//span[contains(text(),'{{var1}}')]" parameterized="true"/> <element name="breadcrumbs" type="textarea" selector=".items"/> + <element name="categoryBreadcrumbs" type="textarea" selector=".breadcrumbs li"/> </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductInfoMainSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductInfoMainSection.xml index b93a70559fc..6a4ac0d7683 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductInfoMainSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductInfoMainSection.xml @@ -16,10 +16,11 @@ <element name="productPrice" type="text" selector="div.price-box.price-final_price"/> <element name="qty" type="input" selector="#qty"/> <element name="specialPrice" type="text" selector=".special-price"/> + <element name="specialPriceAmount" type="text" selector=".special-price span.price"/> <element name="updatedPrice" type="text" selector="div.price-box.price-final_price [data-price-type='finalPrice'] .price"/> <element name="oldPrice" type="text" selector=".old-price"/> <element name="oldPriceTag" type="text" selector=".old-price .price-label"/> - <element name="oldPriceAmount" type="text" selector=".old-price .price"/> + <element name="oldPriceAmount" type="text" selector=".old-price span.price"/> <element name="productStockStatus" type="text" selector=".stock[title=Availability]>span"/> <element name="productImage" type="text" selector="//*[@id='maincontent']//div[@class='gallery-placeholder']//img[@class='fotorama__img']"/> <element name="productImageSrc" type="text" selector="//*[@id='maincontent']//div[@class='gallery-placeholder']//img[contains(@src, '{{src}}')]" parameterized="true"/> @@ -28,14 +29,15 @@ <element name="productOptionAreaInput" type="textarea" selector="//*[@id='product-options-wrapper']//div[@class='fieldset']//label[contains(.,'{{var1}}')]/../div[@class='control']//textarea" parameterized="true"/> <element name="productOptionFile" type="file" selector="//*[@id='product-options-wrapper']//div[@class='fieldset']//label[contains(.,'OptionFile')]/../div[@class='control']//input[@type='file']" parameterized="true"/> <element name="productOptionSelect" type="select" selector="//*[@id='product-options-wrapper']//div[@class='fieldset']//label[contains(.,'{{var1}}')]/../div[@class='control']//select" parameterized="true"/> - + <element name="asLowAs" type="input" selector="span[class='price-wrapper '] "/> + <element name="specialPriceValue" type="text" selector="//span[@class='special-price']//span[@class='price']"/> <!-- The parameter is the nth custom option that you want to get --> <element name="nthCustomOption" type="block" selector="//*[@id='product-options-wrapper']/*[@class='fieldset']/*[contains(@class, 'field')][{{customOptionNum}}]" parameterized="true" /> + <!-- 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="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"/> <element name="productOptionDataYear" type="date" selector="//*[@id='product-options-wrapper']//div[@class='fieldset']//legend[contains(.,'{{var1}}')]/../div[@class='control']//select[@data-calendar-role='year']" parameterized="true"/> @@ -49,7 +51,6 @@ <!-- Only one of Upload/Url Inputs are available for File and Sample depending on the value of the corresponding TypeSelector --> <element name="addLinkFileUploadFile" type="file" selector="//*[@id='product-options-wrapper']//div[@class='fieldset']//label[contains(.,'{{var1}}')]/../div[@class='control']//input[@type='file']" parameterized="true" /> - <element name="productShortDescription" type="text" selector="//div[@class='product attribute overview']//div[@class='value']"/> <element name="productAttributeTitle1" type="text" selector="#product-options-wrapper div[tabindex='0'] label"/> <element name="productAttributeOptions1" type="select" selector="#product-options-wrapper div[tabindex='0'] option"/> @@ -66,8 +67,16 @@ <element name="productOptionDropDownOptionTitle" type="text" selector="//label[contains(.,'{{var1}}')]/../div[@class='control']//select//option[contains(.,'{{var2}}')]" parameterized="true"/> <!-- Tier price selectors --> + <element name="tierPriceText" type="text" selector=".prices-tier li[class='item']" /> <element name="productTierPriceByForTextLabel" type="text" selector="//ul[contains(@class, 'prices-tier')]//li[{{var1}}][contains(text(),'Buy {{var2}} for')]" parameterized="true"/> <element name="productTierPriceAmount" type="text" selector="//ul[contains(@class, 'prices-tier')]//li[{{var1}}]//span[contains(text(), '{{var2}}')]" parameterized="true"/> <element name="productTierPriceSavePercentageAmount" type="text" selector="//ul[contains(@class, 'prices-tier')]//li[{{var1}}]//span[contains(@class, 'percent')][contains(text(), '{{var2}}')]" parameterized="true"/> + + <!-- Customizable Option selectors --> + <element name="allCustomOptionLabels" type="text" selector="#product-options-wrapper label"/> + <element name="customOptionLabel" type="text" selector="//label[contains(., '{{customOptionTitle}}')]" parameterized="true"/> + <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"/> </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 83c3ca53486..45e0b03e8d9 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductMediaSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductMediaSection.xml @@ -10,5 +10,6 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontProductMediaSection"> <element name="imageFile" type="text" selector="//*[@class='product media']//img[contains(@src, '{{filename}}')]" parameterized="true"/> + <element name="productImageActive" type="text" selector=".product.media div[data-active=true] > img[src*='{{filename}}']" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductUpSellProductsSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductUpSellProductsSection.xml new file mode 100644 index 00000000000..f00abbe3c58 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductUpSellProductsSection.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="StorefrontProductUpSellProductsSection"> + <element name="upSellHeading" type="text" selector="#block-upsell-heading"/> + <element name="upSellProducts" type="text" selector="div.upsell .product-item-name"/> + </section> +</sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultVideoSimpleProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultVideoSimpleProductTest.xml index 5456cb02e74..f657fbbdae6 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultVideoSimpleProductTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultVideoSimpleProductTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminAddDefaultVideoSimpleProductTest"> <annotations> <features value="Catalog"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultVideoVirtualProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultVideoVirtualProductTest.xml index f48c352c529..eab36bc90dc 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultVideoVirtualProductTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultVideoVirtualProductTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminAddDefaultVideoVirtualProductTest" extends="AdminAddDefaultVideoSimpleProductTest"> <annotations> <features value="Catalog"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckPaginationInStorefrontTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckPaginationInStorefrontTest.xml new file mode 100644 index 00000000000..f40a62c164e --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckPaginationInStorefrontTest.xml @@ -0,0 +1,176 @@ +<?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="AdminCheckPaginationInStorefrontTest"> + <annotations> + <stories value="Create flat catalog product"/> + <title value="Verify that pagination works when Flat Category is enabled"/> + <description value="Login as admin, create flat catalog product and check pagination"/> + <testCaseId value="MC-6051"/> + <severity value="CRITICAL"/> + <group value="mtf_migrated"/> + <group value="Catalog"/> + </annotations> + <before> + <magentoCLI stepKey="setFlatCatalogCategory" command="config:set catalog/frontend/flat_catalog_category 1 "/> + <magentoCLI stepKey="setFlatCatalogProduct" command="config:set catalog/frontend/flat_catalog_product 1 "/> + <createData entity="_defaultCategory" stepKey="createDefaultCategory"/> + <createData entity="PaginationProduct" stepKey="simpleProduct1"/> + <createData entity="PaginationProduct" stepKey="simpleProduct2"/> + <createData entity="PaginationProduct" stepKey="simpleProduct3"/> + <createData entity="PaginationProduct" stepKey="simpleProduct4"/> + <createData entity="PaginationProduct" stepKey="simpleProduct5"/> + <createData entity="PaginationProduct" stepKey="simpleProduct6"/> + <createData entity="PaginationProduct" stepKey="simpleProduct7"/> + <createData entity="PaginationProduct" stepKey="simpleProduct8"/> + <createData entity="PaginationProduct" stepKey="simpleProduct9"/> + <createData entity="PaginationProduct" stepKey="simpleProduct10"/> + <createData entity="PaginationProduct" stepKey="simpleProduct11"/> + <createData entity="PaginationProduct" stepKey="simpleProduct12"/> + <createData entity="PaginationProduct" stepKey="simpleProduct13"/> + <createData entity="PaginationProduct" stepKey="simpleProduct14"/> + <createData entity="PaginationProduct" stepKey="simpleProduct15"/> + <createData entity="PaginationProduct" stepKey="simpleProduct16"/> + <createData entity="PaginationProduct" stepKey="simpleProduct17"/> + <createData entity="PaginationProduct" stepKey="simpleProduct18"/> + <createData entity="PaginationProduct" stepKey="simpleProduct19"/> + <createData entity="PaginationProduct" stepKey="simpleProduct20"/> + <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminPanel"/> + </before> + <after> + <magentoCLI stepKey="setFlatCatalogCategory" command="config:set catalog/frontend/flat_catalog_category 0" /> + <magentoCLI stepKey="setFlatCatalogProduct" command="config:set catalog/frontend/flat_catalog_product 0" /> + <deleteData createDataKey="createDefaultCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="simpleProduct1" stepKey="deleteSimpleProduct1"/> + <deleteData createDataKey="simpleProduct2" stepKey="deleteSimpleProduct2"/> + <deleteData createDataKey="simpleProduct3" stepKey="deleteSimpleProduct3"/> + <deleteData createDataKey="simpleProduct4" stepKey="deleteSimpleProduct4"/> + <deleteData createDataKey="simpleProduct5" stepKey="deleteSimpleProduct5"/> + <deleteData createDataKey="simpleProduct6" stepKey="deleteSimpleProduct6"/> + <deleteData createDataKey="simpleProduct7" stepKey="deleteSimpleProduct7"/> + <deleteData createDataKey="simpleProduct8" stepKey="deleteSimpleProduct8"/> + <deleteData createDataKey="simpleProduct9" stepKey="deleteSimpleProduct9"/> + <deleteData createDataKey="simpleProduct10" stepKey="deleteSimpleProduct10"/> + <deleteData createDataKey="simpleProduct11" stepKey="deleteSimpleProduct11"/> + <deleteData createDataKey="simpleProduct12" stepKey="deleteSimpleProduct12"/> + <deleteData createDataKey="simpleProduct13" stepKey="deleteSimpleProduct13"/> + <deleteData createDataKey="simpleProduct14" stepKey="deleteSimpleProduct14"/> + <deleteData createDataKey="simpleProduct15" stepKey="deleteSimpleProduct15"/> + <deleteData createDataKey="simpleProduct16" stepKey="deleteSimpleProduct16"/> + <deleteData createDataKey="simpleProduct17" stepKey="deleteSimpleProduct17"/> + <deleteData createDataKey="simpleProduct18" stepKey="deleteSimpleProduct18"/> + <deleteData createDataKey="simpleProduct19" stepKey="deleteSimpleProduct19"/> + <deleteData createDataKey="simpleProduct20" stepKey="deleteSimpleProduct20"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Open Category Page and select created category--> + <amOnPage url="{{AdminCategoryPage.url}}" stepKey="openAdminCategoryIndexPage"/> + <waitForPageLoad stepKey="waitForPageToLoad1"/> + <click selector="{{AdminCategorySidebarTreeSection.expandAll}}" stepKey="clickOnExpandTree"/> + <waitForPageLoad stepKey="waitForPageToLoad0"/> + <click selector="{{AdminCategorySidebarTreeSection.categoryInTree(_defaultCategory.name)}}" stepKey="selectCreatedCategory"/> + <waitForPageLoad stepKey="waitForPageToLoaded2"/> + + <!--Select Products--> + <scrollTo selector="{{AdminCategoryBasicFieldSection.productsInCategory}}" x="0" y="-80" stepKey="scrollToProductInCategory"/> + <click selector="{{AdminCategoryBasicFieldSection.productsInCategory}}" stepKey="clickOnProductInCategory"/> + <waitForPageLoad stepKey="waitForProductsToLoad"/> + <scrollTo selector="{{CatalogProductsSection.resetFilter}}" stepKey="scrollToResetFilter"/> + <waitForElementVisible selector="{{CatalogProductsSection.resetFilter}}" time="30" stepKey="waitForResetButtonToVisible"/> + <click selector="{{CatalogProductsSection.resetFilter}}" stepKey="clickOnResetFilter"/> + <waitForPageLoad stepKey="waitForPageToLoad3"/> + <selectOption selector="{{AdminProductGridFilterSection.productPerPage}}" userInput="20" stepKey="selectPagePerView"/> + <fillField selector="{{AdminCategoryContentSection.productTableColumnName}}" userInput="pagi" stepKey="selectProduct1"/> + <click selector="{{AdminCategoryContentSection.productSearch}}" stepKey="clickSearchButton"/> + <waitForPageLoad stepKey="waitFroPageToLoad1"/> + <see selector="{{AdminProductGridFilterSection.productCount}}" userInput="20" stepKey="seeNumberOfProductsFound"/> + <click selector="{{AdminCategoryProductsGridSection.productSelectAll}}" stepKey="selectSelectAll"/> + <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="clickSaveButton"/> + <waitForPageLoad stepKey="waitForCategorySaved"/> + <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the category." stepKey="assertSuccessMessage"/> + <waitForPageLoad stepKey="waitForPageTitleToBeSaved"/> + + <!--Open Category Store Front Page--> + <amOnPage url="{{_defaultCategory.name}}.html" stepKey="goToStorefront"/> + <waitForPageLoad stepKey="waitForCategoryPageToLoad"/> + <seeElement selector="{{StorefrontHeaderSection.NavigationCategoryByName(_defaultCategory.name)}}" stepKey="seeCategoryOnNavigation"/> + <click selector="{{StorefrontHeaderSection.NavigationCategoryByName(_defaultCategory.name)}}" stepKey="selectCategory"/> + <waitForPageLoad stepKey="waitForProductToLoad"/> + + <!--Select 9 items per page and verify number of products displayed in each page --> + <conditionalClick selector="{{StorefrontCategoryTopToolbarSection.gridMode}}" visible="true" dependentSelector="{{StorefrontCategoryTopToolbarSection.gridMode}}" stepKey="seeProductGridIsActive"/> + <scrollTo selector="{{StorefrontCategoryBottomToolbarSection.perPage}}" stepKey="scrollToBottomToolbarSection"/> + <selectOption selector="{{StorefrontCategoryBottomToolbarSection.perPage}}" userInput="9" stepKey="selectPerPageOption"/> + + <!--Verify number of products displayed in First Page --> + <seeNumberOfElements selector="{{StorefrontCategoryMainSection.productLink}}" userInput="9" stepKey="seeNumberOfProductsInFirstPage"/> + + <!--Verify number of products displayed in Second Page --> + <scrollTo selector="{{StorefrontCategoryBottomToolbarSection.nextPage}}" stepKey="scrollToNextButton"/> + <click selector="{{StorefrontCategoryBottomToolbarSection.nextPage}}" stepKey="clickOnNextPage"/> + <waitForPageLoad stepKey="waitForPageToLoad4"/> + <seeNumberOfElements selector="{{StorefrontCategoryMainSection.productLink}}" userInput="9" stepKey="seeNumberOfProductsInSecondPage"/> + + <!--Verify number of products displayed in third Page --> + <scrollTo selector="{{StorefrontCategoryBottomToolbarSection.nextPage}}" stepKey="scrollToNextButton1"/> + <click selector="{{StorefrontCategoryBottomToolbarSection.nextPage}}" stepKey="clickOnNextPage1"/> + <waitForPageLoad stepKey="waitForPageToLoad2"/> + <seeNumberOfElements selector="{{StorefrontCategoryMainSection.productLink}}" userInput="2" stepKey="seeNumberOfProductsInThirdPage"/> + + <!--Change Pages using Previous Page selector and verify number of products displayed in each page--> + <scrollTo selector="{{StorefrontCategoryBottomToolbarSection.previousPage}}" stepKey="scrollToPreviousPage"/> + <click selector="{{StorefrontCategoryBottomToolbarSection.previousPage}}" stepKey="clickOnPreviousPage1"/> + <waitForPageLoad stepKey="waitForPageToLoad5"/> + <seeNumberOfElements selector="{{StorefrontCategoryMainSection.productLink}}" userInput="9" stepKey="seeNumberOfProductsInSecondPage1"/> + <scrollTo selector="{{StorefrontCategoryBottomToolbarSection.previousPage}}" stepKey="scrollToPreviousPage1"/> + <click selector="{{StorefrontCategoryBottomToolbarSection.previousPage}}" stepKey="clickOnPreviousPage2"/> + <waitForPageLoad stepKey="waitForPageToLoad6"/> + <seeNumberOfElements selector="{{StorefrontCategoryMainSection.productLink}}" userInput="9" stepKey="seeNumberOfProductsInFirstPage1"/> + + <!--Select Pages by using page Number and verify number of products displayed--> + <scrollTo selector="{{StorefrontCategoryBottomToolbarSection.nextPage}}" stepKey="scrollToPreviousPage2"/> + <click selector="{{StorefrontCategoryBottomToolbarSection.pageNumber('2')}}" stepKey="clickOnPage2"/> + <waitForPageLoad stepKey="waitForPageToLoad7"/> + <seeNumberOfElements selector="{{StorefrontCategoryMainSection.productLink}}" userInput="9" stepKey="seeNumberOfProductsInSecondPage2"/> + + <!--Select Third Page using page number--> + <scrollTo selector="{{StorefrontCategoryBottomToolbarSection.nextPage}}" stepKey="scrollToPreviousPage3"/> + <click selector="{{StorefrontCategoryBottomToolbarSection.pageNumber('3')}}" stepKey="clickOnThirdPage"/> + <waitForPageLoad stepKey="waitForPageToLoad8"/> + <seeNumberOfElements selector="{{StorefrontCategoryMainSection.productLink}}" userInput="2" stepKey="seeNumberOfProductsInThirdPage2"/> + + <!--Select First Page using page number--> + <scrollTo selector="{{StorefrontCategoryBottomToolbarSection.previousPage}}" stepKey="scrollToPreviousPage4"/> + <click selector="{{StorefrontCategoryBottomToolbarSection.pageNumber('1')}}" stepKey="clickOnFirstPage"/> + <waitForPageLoad stepKey="waitForPageToLoad9"/> + <seeNumberOfElements selector="{{StorefrontCategoryMainSection.productLink}}" userInput="9" stepKey="seeNumberOfProductsFirstPage2"/> + + <!--Select 15 items per page and verify number of products displayed in each page --> + <scrollTo selector="{{StorefrontCategoryBottomToolbarSection.perPage}}" stepKey="scrollToPerPage"/> + <selectOption selector="{{StorefrontCategoryBottomToolbarSection.perPage}}" userInput="15" stepKey="selectPerPageOption1"/> + <waitForPageLoad stepKey="waitForPageToLoad10"/> + <seeNumberOfElements selector="{{StorefrontCategoryMainSection.productLink}}" userInput="15" stepKey="seeNumberOfProductsInFirstPage3"/> + <scrollTo selector="{{StorefrontCategoryBottomToolbarSection.nextPage}}" stepKey="scrollToNextButton2"/> + <click selector="{{StorefrontCategoryBottomToolbarSection.nextPage}}" stepKey="clickOnNextPage2"/> + <waitForPageLoad stepKey="waitForPageToLoad11"/> + <seeNumberOfElements selector="{{StorefrontCategoryMainSection.productLink}}" userInput="5" stepKey="seeNumberOfProductsInSecondPage3"/> + + <!--Select First Page using page number--> + <scrollTo selector="{{StorefrontCategoryBottomToolbarSection.pageNumber('1')}}" stepKey="scrollToPreviousPage5"/> + <click selector="{{StorefrontCategoryBottomToolbarSection.pageNumber('1')}}" stepKey="clickOnFirstPage2"/> + <waitForPageLoad stepKey="waitForPageToLoad13"/> + + <!--Select 30 items per page and verify number of products displayed in each page --> + <scrollTo selector="{{StorefrontCategoryBottomToolbarSection.perPage}}" stepKey="scrollToPerPage4"/> + <selectOption selector="{{StorefrontCategoryBottomToolbarSection.perPage}}" userInput="30" stepKey="selectPerPageOption2"/> + <waitForPageLoad stepKey="waitForPageToLoad12"/> + <seeNumberOfElements selector="{{StorefrontCategoryMainSection.productLink}}" userInput="20" stepKey="seeNumberOfProductsInFirstPage4"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveFlatCategoryAndUpdateAsInactiveTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveFlatCategoryAndUpdateAsInactiveTest.xml new file mode 100644 index 00000000000..21b3dba7140 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveFlatCategoryAndUpdateAsInactiveTest.xml @@ -0,0 +1,90 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminCreateInactiveFlatCategoryAndUpdateAsInactiveTest"> + <annotations> + <stories value="Create category"/> + <title value="Flat Catalog - Update Inactive Category as Inactive, Should Not be Visible on Storefront"/> + <description value="Login as admin and create inactive flat category and update category as inactive and verify category is not visible in store front"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-11009"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <!--Login as admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminPanel"/> + <!--Create category--> + <createData entity="CatNotActive" stepKey="createCategory"/> + <!-- Create First StoreView --> + <actionGroup ref="CreateStoreView" stepKey="createCustomStoreViewEn"> + <argument name="storeView" value="customStoreEN"/> + </actionGroup> + <!-- Create Second StoreView --> + <actionGroup ref="CreateStoreView" stepKey="createCustomStoreViewFr"> + <argument name="storeView" value="customStoreFR"/> + </actionGroup> + <!--Run full reindex and clear caches --> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + <!--Enable Flat Catalog Category --> + <magentoCLI stepKey="setFlatCatalogCategory" command="config:set catalog/frontend/flat_catalog_category 1"/> + <!--Open Index Management Page and Select Index mode "Update by Schedule" --> + <magentoCLI stepKey="setIndexerMode" command="indexer:set-mode" arguments="schedule" /> + <!-- Run cron twice --> + <magentoCLI command="cron:run" stepKey="runCron1"/> + <magentoCLI command="cron:run" stepKey="runCron2"/> + </before> + <after> + <magentoCLI stepKey="setFlatCatalogCategory" command="config:set catalog/frontend/flat_catalog_category 0 "/> + <magentoCLI stepKey="setIndexerMode" command="indexer:set-mode" arguments="realtime" /> + <magentoCLI stepKey="indexerReindex" command="indexer:reindex" /> + <deleteData stepKey="deleteCategory" createDataKey="createCategory"/> + <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteStoreViewEn"> + <argument name="customStore" value="customStoreEN"/> + </actionGroup> + <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteStoreViewFr"> + <argument name="customStore" value="customStoreFR"/> + </actionGroup> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!-- Select created category and make category inactive--> + <amOnPage url="{{AdminCategoryPage.url}}" stepKey="openAdminCategoryIndexPage"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + <click selector="{{AdminCategorySidebarTreeSection.expandAll}}" stepKey="clickOnExpandTree"/> + <click selector="{{AdminCategorySidebarTreeSection.categoryInTree(CatNotActive.name)}}" stepKey="selectCreatedCategory"/> + <waitForPageLoad stepKey="waitForCategoryPageToLoad"/> + <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="saveSubCategory"/> + <waitForPageLoad stepKey="waitForSecondCategoryToSave"/> + <seeElement selector="{{AdminCategoryMessagesSection.SuccessMessage}}" stepKey="seeSuccessMessage"/> + <see selector="{{AdminCategoryContentSection.categoryPageTitle}}" userInput="{{CatNotActive.name}}" stepKey="seeUpdatedCategoryTitle"/> + <dontSeeCheckboxIsChecked selector="{{AdminCategoryBasicFieldSection.enableCategoryLabel}}" stepKey="verifyInactiveCategory"/> + <!--Run full reindex and clear caches --> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + <!--Open Index Management Page --> + <amOnPage url="{{AdminIndexManagementPage.url}}" stepKey="openIndexManagementPage"/> + <waitForPageLoad stepKey="waitForIndexPageToBeLoaded"/> + <see stepKey="seeIndexStatus" selector="{{AdminIndexManagementSection.indexerStatus('Category Flat Data')}}" userInput="Ready"/> + <!--Verify Category In Store Front--> + <amOnPage url="/$$createCategory.name$$.html" stepKey="openCategoryPage1"/> + <waitForPageLoad stepKey="waitForCategoryStoreFrontPageToLoad"/> + <seeElement selector="{{StorefrontBundledSection.pageNotFound}}" stepKey="seeWhoopsOurBadMessage"/> + <!--Verify category is not visible in First Store View --> + <click stepKey="selectStoreSwitcher" selector="{{StorefrontHeaderSection.storeViewSwitcher}}"/> + <click stepKey="selectForstStoreView" selector="{{StorefrontHeaderSection.storeViewList(customStoreEN.name)}}"/> + <waitForPageLoad stepKey="waitForFirstStoreView"/> + <dontSeeElement selector="{{StorefrontHeaderSection.NavigationCategoryByName($$createCategory.name$$)}}" stepKey="dontSeeCategoryOnNavigation"/> + <!--Verify category is not visible in Second Store View --> + <click stepKey="selectStoreSwitcher1" selector="{{StorefrontHeaderSection.storeViewSwitcher}}"/> + <click stepKey="selectSecondStoreView" selector="{{StorefrontHeaderSection.storeViewList(customStoreFR.name)}}"/> + <waitForPageLoad stepKey="waitForSecondStoreView"/> + <dontSeeElement selector="{{StorefrontHeaderSection.NavigationCategoryByName($$createCategory.name$$)}}" stepKey="dontSeeCategoryOnNavigation1"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveFlatCategoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveFlatCategoryTest.xml new file mode 100644 index 00000000000..aa3dba85dfa --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveFlatCategoryTest.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="AdminCreateInactiveFlatCategoryTest"> + <annotations> + <stories value="Create category"/> + <title value="Flat Catalog - Create Category as Inactive, Should Not be Visible on Storefront"/> + <description value="Login as admin and create flat Inactive category and verify category is not visible in store front"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-11007"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <!--Login as admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminPanel"/> + <!--Create category--> + <createData entity="SimpleSubCategory" stepKey="createCategory"/> + <!-- Create First StoreView --> + <actionGroup ref="CreateStoreView" stepKey="createCustomStoreViewEn"> + <argument name="storeView" value="customStoreEN"/> + </actionGroup> + <!-- Create Second StoreView --> + <actionGroup ref="CreateStoreView" stepKey="createCustomStoreViewFr"> + <argument name="storeView" value="customStoreFR"/> + </actionGroup> + <!--Run full reindex and clear caches --> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + <!--Enable Flat Catalog Category --> + <magentoCLI stepKey="setFlatCatalogCategory" command="config:set catalog/frontend/flat_catalog_category 1"/> + <!--Open Index Management Page and Select Index mode "Update by Schedule" --> + <magentoCLI stepKey="setIndexerMode" command="indexer:set-mode" arguments="schedule" /> + <!-- Run cron twice --> + <magentoCLI command="cron:run" stepKey="runCron1"/> + <magentoCLI command="cron:run" stepKey="runCron2"/> + </before> + <after> + <magentoCLI stepKey="setFlatCatalogCategory" command="config:set catalog/frontend/flat_catalog_category 0 "/> + <magentoCLI stepKey="setIndexerMode" command="indexer:set-mode" arguments="realtime" /> + <magentoCLI stepKey="indexerReindex" command="indexer:reindex" /> + <deleteData stepKey="deleteCategory" createDataKey="createCategory"/> + <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteStoreViewEn"> + <argument name="customStore" value="customStoreEN"/> + </actionGroup> + <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteStoreViewFr"> + <argument name="customStore" value="customStoreFR"/> + </actionGroup> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!-- Select created category and make category inactive--> + <amOnPage url="{{AdminCategoryPage.url}}" stepKey="openAdminCategoryIndexPage"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + <click selector="{{AdminCategorySidebarTreeSection.expandAll}}" stepKey="clickOnExpandTree"/> + <click selector="{{AdminCategorySidebarTreeSection.categoryInTree(SimpleSubCategory.name)}}" stepKey="selectCreatedCategory"/> + <waitForPageLoad stepKey="waitForCategoryPageToLoad"/> + <click selector="{{AdminCategoryBasicFieldSection.enableCategoryLabel}}" stepKey="disableActiveCategory"/> + <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="saveSubCategory"/> + <waitForPageLoad stepKey="waitForSecondCategoryToSave"/> + <seeElement selector="{{AdminCategoryMessagesSection.SuccessMessage}}" stepKey="seeSuccessMessage"/> + <see selector="{{AdminCategoryContentSection.categoryPageTitle}}" userInput="{{SimpleSubCategory.name}}" stepKey="seeUpdatedCategoryTitle"/> + <dontSeeCheckboxIsChecked selector="{{AdminCategoryBasicFieldSection.enableCategoryLabel}}" stepKey="verifyInactiveIncludeInMenu"/> + <!--Run full reindex and clear caches --> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + <!--Open Index Management Page --> + <amOnPage url="{{AdminIndexManagementPage.url}}" stepKey="openIndexManagementPage"/> + <waitForPageLoad stepKey="waitForIndexPageToBeLoaded"/> + <see stepKey="seeIndexStatus" selector="{{AdminIndexManagementSection.indexerStatus('Category Flat Data')}}" userInput="Ready"/> + <!--Verify Category In Store Front--> + <amOnPage url="/$$createCategory.name$$.html" stepKey="openCategoryPage1"/> + <waitForPageLoad stepKey="waitForCategoryStoreFrontPageToLoad"/> + <!--Verify category is not visible in First Store View --> + <click stepKey="selectStoreSwitcher" selector="{{StorefrontHeaderSection.storeViewSwitcher}}"/> + <click stepKey="selectForstStoreView" selector="{{StorefrontHeaderSection.storeViewList(customStoreEN.name)}}"/> + <waitForPageLoad stepKey="waitForFirstStoreView"/> + <dontSeeElement selector="{{StorefrontHeaderSection.NavigationCategoryByName($$createCategory.name$$)}}" stepKey="dontSeeCategoryOnNavigation"/> + <seeElement selector="{{StorefrontBundledSection.pageNotFound}}" stepKey="seeWhoopsOurBadMessage"/> + <!--Verify category is not visible in Second Store View --> + <click stepKey="selectStoreSwitcher1" selector="{{StorefrontHeaderSection.storeViewSwitcher}}"/> + <click stepKey="selectSecondStoreView" selector="{{StorefrontHeaderSection.storeViewList(customStoreFR.name)}}"/> + <waitForPageLoad stepKey="waitForSecondstoreView"/> + <dontSeeElement selector="{{StorefrontHeaderSection.NavigationCategoryByName($$createCategory.name$$)}}" stepKey="dontSeeCategoryOnNavigation1"/> + <seeElement selector="{{StorefrontBundledSection.pageNotFound}}" stepKey="seeWhoopsOurBadMessage1"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveInMenuFlatCategoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveInMenuFlatCategoryTest.xml new file mode 100644 index 00000000000..37417cd7fdb --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveInMenuFlatCategoryTest.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="AdminCreateInactiveInMenuFlatCategoryTest"> + <annotations> + <stories value="Create category"/> + <title value="Flat Catalog - Exclude Category from Navigation Menu"/> + <description value="Login as admin and create inactive Include In Menu flat category and verify category is not displayed in Navigation Menu"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-11008"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <!--Login as admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminPanel"/> + <!--Create category--> + <createData entity="SimpleSubCategory" stepKey="category"/> + <!-- Create First StoreView --> + <actionGroup ref="CreateStoreView" stepKey="createCustomStoreViewEn"> + <argument name="storeView" value="customStoreEN"/> + </actionGroup> + <!-- Create Second StoreView --> + <actionGroup ref="CreateStoreView" stepKey="createCustomStoreViewFr"> + <argument name="storeView" value="customStoreFR"/> + </actionGroup> + <!--Run full reindex and clear caches --> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + <!--Enable Flat Catalog Category --> + <magentoCLI stepKey="setFlatCatalogCategory" command="config:set catalog/frontend/flat_catalog_category 1"/> + <!--Open Index Management Page and Select Index mode "Update by Schedule" --> + <magentoCLI stepKey="setIndexerMode" command="indexer:set-mode" arguments="schedule" /> + <!-- Run cron twice --> + <magentoCLI command="cron:run" stepKey="runCron1"/> + <magentoCLI command="cron:run" stepKey="runCron2"/> + </before> + <after> + <magentoCLI stepKey="setFlatCatalogCategory" command="config:set catalog/frontend/flat_catalog_category 0 "/> + <magentoCLI stepKey="setIndexerMode" command="indexer:set-mode" arguments="realtime" /> + <magentoCLI stepKey="indexerReindex" command="indexer:reindex" /> + <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteStoreViewEn"> + <argument name="customStore" value="customStoreEN"/> + </actionGroup> + <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteStoreViewFr"> + <argument name="customStore" value="customStoreFR"/> + </actionGroup> + <deleteData createDataKey="category" stepKey="deleteCategory"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!-- Select created category and disable Include In Menu option--> + <amOnPage url="{{AdminCategoryPage.url}}" stepKey="openAdminCategoryIndexPage"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + <click selector="{{AdminCategorySidebarTreeSection.expandAll}}" stepKey="clickOnExpandTree"/> + <click selector="{{AdminCategorySidebarTreeSection.categoryInTree(SimpleSubCategory.name)}}" stepKey="selectCreatedCategory"/> + <waitForPageLoad stepKey="waitForCategoryPageToLoad"/> + <click selector="{{AdminCategoryBasicFieldSection.includeInMenuLabel}}" stepKey="disableIcludeInMenuOption"/> + <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="saveSubCategory"/> + <waitForPageLoad stepKey="waitForSecondCategoryToSave"/> + <!--Verify category is saved and Include In Menu Option is disabled in Category Page --> + <seeElement selector="{{AdminCategoryMessagesSection.SuccessMessage}}" stepKey="seeSuccessMessage"/> + <see selector="{{AdminCategoryContentSection.categoryPageTitle}}" userInput="{{SimpleSubCategory.name}}" stepKey="seeUpdatedCategoryTitle"/> + <dontSeeCheckboxIsChecked selector="{{AdminCategoryBasicFieldSection.includeInMenuLabel}}" stepKey="verifyInactiveIncludeInMenu"/> + <!--Run full reindex and clear caches --> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + <!--Open Index Management Page --> + <amOnPage url="{{AdminIndexManagementPage.url}}" stepKey="openIndexManagementPage"/> + <waitForPageLoad stepKey="waitForIndexPageToBeLoaded"/> + <see stepKey="seeIndexStatus" selector="{{AdminIndexManagementSection.indexerStatus('Category Flat Data')}}" userInput="Ready"/> + <!--Verify Category In Store Front--> + <amOnPage url="/$$category.name$$.html" stepKey="openCategoryPage1"/> + <waitForPageLoad stepKey="waitForCategoryStoreFrontPageToLoad"/> + <!--Verify category is not displayed in navigation menu in First Store View --> + <click stepKey="selectStoreSwitcher" selector="{{StorefrontHeaderSection.storeViewSwitcher}}"/> + <click stepKey="selectForstStoreView" selector="{{StorefrontHeaderSection.storeViewList(customStoreEN.name)}}"/> + <waitForPageLoad stepKey="waitForFirstStoreView"/> + <dontSeeElement selector="{{StorefrontHeaderSection.NavigationCategoryByName($$category.name$$)}}" stepKey="seeCategoryOnNavigation"/> + <!--Verify category is not displayed in navigation menu in Second Store View --> + <click stepKey="selectStoreSwitcher1" selector="{{StorefrontHeaderSection.storeViewSwitcher}}"/> + <click stepKey="selectSecondStoreView" selector="{{StorefrontHeaderSection.storeViewList(customStoreFR.name)}}"/> + <waitForPageLoad stepKey="waitForSecondstoreView"/> + <dontSeeElement selector="{{StorefrontHeaderSection.NavigationCategoryByName($$category.name$$)}}" stepKey="seeCategoryOnNavigation1"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductWithCountryOfManufactureAttributeSKUMaskTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductWithCountryOfManufactureAttributeSKUMaskTest.xml new file mode 100644 index 00000000000..3487de65617 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductWithCountryOfManufactureAttributeSKUMaskTest.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="AdminCreateSimpleProductWithCountryOfManufactureAttributeSKUMaskTest"> + <annotations> + <stories value="Create simple product"/> + <title value="Create simple product with (Country of Manufacture) Attribute SKU Mask"/> + <description value="Test log in to Create simple product and Create simple product with (Country of Manufacture) Attribute SKU Mask"/> + <testCaseId value="MC-11024"/> + <severity value="CRITICAL"/> + <group value="catalog"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <magentoCLI stepKey="setCountryOfManufacture" command="config:set catalog/fields_masks/sku" arguments="{{name}}-{{country_of_manufacture}}"/> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <magentoCLI stepKey="setName" command="config:set catalog/fields_masks/sku" arguments="{{name}}"/> + <actionGroup ref="deleteProductBySku" stepKey="deleteCreatedProduct"> + <argument name="sku" value="{{nameAndAttributeSkuMaskSimpleProduct.name}}-{{nameAndAttributeSkuMaskSimpleProduct.country_of_manufacture_label}}" /> + </actionGroup> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <amOnPage url="{{ProductCatalogPage.url}}" stepKey="openProductCatalogPage"/> + <waitForPageLoad stepKey="waitForProductCatalogPage"/> + <click selector="{{AdminProductGridActionSection.addProductToggle}}" stepKey="clickAddProductToggle"/> + <waitForPageLoad stepKey="waitForProductToggleToSelectSimpleProduct"/> + <click selector="{{AdminProductGridActionSection.addSimpleProduct}}" stepKey="clickSimpleProductFromDropDownList"/> + + <!-- Create simple product with country of manufacture attribute --> + <fillField selector="{{AdminProductFormSection.productName}}" userInput="{{nameAndAttributeSkuMaskSimpleProduct.name}}" stepKey="fillSimpleProductName"/> + <selectOption selector="{{AdminProductFormSection.countryOfManufacture}}" userInput="{{nameAndAttributeSkuMaskSimpleProduct.country_of_manufacture_label}}" stepKey="selectCountryOfManufacture"/> + <fillField selector="{{AdminProductFormSection.productPrice}}" userInput="{{nameAndAttributeSkuMaskSimpleProduct.price}}" stepKey="fillSimpleProductPrice"/> + <fillField selector="{{AdminProductFormSection.productWeight}}" userInput="{{nameAndAttributeSkuMaskSimpleProduct.weight}}" stepKey="fillSimpleProductWeight"/> + <fillField selector="{{AdminProductFormSection.productQuantity}}" userInput="{{nameAndAttributeSkuMaskSimpleProduct.quantity}}" stepKey="fillSimpleProductQuantity"/> + <click selector="{{AdminProductFormSection.save}}" stepKey="clickSaveButton"/> + <waitForPageLoad stepKey="waitForSimpleProductToSave"/> + <!-- Verify customer see success message --> + <see selector="{{AdminProductFormSection.successMessage}}" userInput="You saved the product." stepKey="seeAssertSimpleProductSaveSuccessMessage"/> + + <!-- Search created simple product(from above step) in the grid page to verify sku masked as name and country of manufacture --> + <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPageToSearchCreatedSimpleProduct"/> + <waitForPageLoad stepKey="waitForProductCatalogPageToLoad"/> + <conditionalClick selector="{{AdminProductGridFilterSection.clearAll}}" dependentSelector="{{AdminProductGridFilterSection.clearAll}}" visible="true" stepKey="clickClearAll"/> + <click selector="{{AdminProductGridFilterSection.filters}}" stepKey="clickFiltersButton"/> + <fillField selector="{{AdminProductGridFilterSection.skuFilter}}" userInput="{{nameAndAttributeSkuMaskSimpleProduct.name}}-{{nameAndAttributeSkuMaskSimpleProduct.country_of_manufacture_label}}" stepKey="fillSkuFilterFieldWithNameAndCountryOfManufactureInput" /> + <click selector="{{AdminProductGridFilterSection.applyFilters}}" stepKey="clickApplyFiltersButton"/> + <waitForPageLoad stepKey="waitForProductSearchAfterApplyingFilters"/> + <see selector="{{AdminProductGridSection.firstProductRow}}" userInput="{{nameAndAttributeSkuMaskSimpleProduct.name}}-{{nameAndAttributeSkuMaskSimpleProduct.country_of_manufacture_label}}" stepKey="seeSimpleProductSkuMaskedAsNameAndCountryOfManufacture"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductFillingRequiredFieldsOnlyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductFillingRequiredFieldsOnlyTest.xml new file mode 100644 index 00000000000..c3fe666c84f --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductFillingRequiredFieldsOnlyTest.xml @@ -0,0 +1,56 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminCreateVirtualProductFillingRequiredFieldsOnlyTest"> + <annotations> + <stories value="Create virtual product"/> + <title value="Create virtual product filling required fields only"/> + <description value="Test log in to Create virtual product and Create virtual product filling required fields only"/> + <testCaseId value="MC-6031"/> + <severity value="CRITICAL"/> + <group value="catalog"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPage"/> + <waitForPageLoad stepKey="waitForProductCatalogPage"/> + <click selector="{{AdminProductGridActionSection.addProductToggle}}" stepKey="clickAddProductToggle"/> + <waitForPageLoad stepKey="waitForProductToggleToSelectProduct"/> + <click selector="{{AdminProductGridActionSection.addVirtualProduct}}" stepKey="clickVirtualProduct"/> + + <!-- Create virtual product with required fields only --> + <fillField selector="{{AdminProductFormSection.productName}}" userInput="{{virtualProductWithRequiredFields.name}}" stepKey="fillProductName"/> + <fillField selector="{{AdminProductFormSection.productSku}}" userInput="{{virtualProductWithRequiredFields.sku}}" stepKey="fillProductSku"/> + <fillField selector="{{AdminProductFormSection.productPrice}}" userInput="{{virtualProductWithRequiredFields.price}}" stepKey="fillProductPrice"/> + <click selector="{{AdminProductFormSection.save}}" stepKey="clickSaveButton"/> + <waitForPageLoad stepKey="waitForVirtualProductSaved" /> + + <!-- Verify we see success message --> + <see selector="{{AdminProductFormSection.successMessage}}" userInput="You saved the product." stepKey="seeAssertVirtualProductSuccessMessage"/> + + <!-- Verify we see created virtual product(from the above step) on the product grid page --> + <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPage1"/> + <waitForPageLoad stepKey="waitForProductCatalogPage1"/> + <conditionalClick selector="{{AdminProductGridFilterSection.clearAll}}" dependentSelector="{{AdminProductGridFilterSection.clearAll}}" visible="true" stepKey="clickSelector"/> + <click selector="{{AdminProductGridFilterSection.filters}}" stepKey="clickFilter"/> + <fillField selector="{{AdminProductGridFilterSection.nameFilter}}" userInput="{{virtualProductWithRequiredFields.name}}" stepKey="fillProductName1"/> + <fillField selector="{{AdminProductGridFilterSection.skuFilter}}" userInput="{{virtualProductWithRequiredFields.sku}}" stepKey="fillVirtualProductSku"/> + <click selector="{{AdminProductGridFilterSection.applyFilters}}" stepKey="clickSearch2"/> + <waitForPageLoad stepKey="waitForProductSearch"/> + <seeInField selector="{{AdminProductGridFilterSection.nameFilter}}" userInput="{{virtualProductWithRequiredFields.name}}" stepKey="seeVirtualProductName"/> + <seeInField selector="{{AdminProductGridFilterSection.skuFilter}}" userInput="{{virtualProductWithRequiredFields.sku}}" stepKey="seeVirtualProductSku"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductOutOfStockWithTierPriceTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductOutOfStockWithTierPriceTest.xml new file mode 100644 index 00000000000..26ad7a46a73 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductOutOfStockWithTierPriceTest.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="AdminCreateVirtualProductOutOfStockWithTierPriceTest"> + <annotations> + <stories value="Create virtual product"/> + <title value="Create virtual product out of stock with tier price"/> + <description value="Test log in to Create virtual product and Create virtual product out of stock with tier price"/> + <testCaseId value="MC-6036"/> + <severity value="CRITICAL"/> + <group value="catalog"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <createData entity="SimpleSubCategory" stepKey="categoryEntity"/> + </before> + <after> + <deleteData stepKey="deleteSimpleSubCategory" createDataKey="categoryEntity"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPage"/> + <waitForPageLoad stepKey="waitForProductCatalogPage"/> + <click selector="{{AdminProductGridActionSection.addProductToggle}}" stepKey="clickAddProductToggle"/> + <waitForPageLoad stepKey="waitForProductToggleToSelectProduct"/> + <click selector="{{AdminProductGridActionSection.addVirtualProduct}}" stepKey="clickVirtualProduct"/> + + <!-- Create virtual product out of stock with tier price --> + <fillField selector="{{AdminProductFormSection.productName}}" userInput="{{virtualProductOutOfStock.name}}" stepKey="fillProductName"/> + <fillField selector="{{AdminProductFormSection.productSku}}" userInput="{{virtualProductOutOfStock.sku}}" stepKey="fillProductSku"/> + <fillField selector="{{AdminProductFormSection.productPrice}}" userInput="{{virtualProductOutOfStock.price}}" stepKey="fillProductPrice"/> + <click selector="{{AdminProductFormSection.advancedPricingLink}}" stepKey="clickAdvancedPricingLink"/> + <click selector="{{AdminProductFormAdvancedPricingSection.customerGroupPriceAddButton}}" stepKey="clickCustomerGroupPriceAddButton"/> + <selectOption selector="{{AdminProductFormAdvancedPricingSection.productTierPriceWebsiteSelect('0')}}" userInput="{{tierPriceOnDefault.website_0}}" stepKey="selectProductTierPriceWebsite"/> + <selectOption selector="{{AdminProductFormAdvancedPricingSection.productTierPriceCustGroupSelect('0')}}" userInput="{{tierPriceOnDefault.customer_group_0}}" stepKey="selectProductTierPriceCustGroup"/> + <fillField selector="{{AdminProductFormAdvancedPricingSection.productTierPriceQtyInput('0')}}" userInput="{{tierPriceOnDefault.qty_0}}" stepKey="fillProductTierPriceQuantityInput"/> + <fillField selector="{{AdminProductFormAdvancedPricingSection.productTierPriceFixedPriceInput('0')}}" userInput="{{tierPriceOnDefault.price_0}}" stepKey="selectProductTierPriceFixedPrice"/> + <click selector="{{AdminProductFormAdvancedPricingSection.customerGroupPriceAddButton}}" stepKey="clickCustomerGroupPriceAddButtonToAddAnotherRow"/> + <selectOption selector="{{AdminProductFormAdvancedPricingSection.productTierPriceWebsiteSelect('1')}}" userInput="{{tierPriceOnDefault.website_1}}" stepKey="clickProductTierPriceWebsite1"/> + <selectOption selector="{{AdminProductFormAdvancedPricingSection.productTierPriceCustGroupSelect('1')}}" userInput="{{tierPriceOnDefault.customer_group_1}}" stepKey="clickProductTierPriceCustGroup1"/> + <fillField selector="{{AdminProductFormAdvancedPricingSection.productTierPriceQtyInput('1')}}" userInput="{{tierPriceOnDefault.qty_1}}" stepKey="fillProductTierPriceQuantityInput1"/> + <fillField selector="{{AdminProductFormAdvancedPricingSection.productTierPriceFixedPriceInput('1')}}" userInput="{{tierPriceOnDefault.price_1}}" stepKey="selectProductTierPriceFixedPrice1"/> + <click selector="{{AdminProductFormAdvancedPricingSection.doneButton}}" stepKey="clickDoneButton"/> + <fillField selector="{{AdminProductFormSection.productQuantity}}" userInput="{{virtualProductOutOfStock.quantity}}" stepKey="fillVirtualProductQuantity"/> + <selectOption selector="{{AdminProductFormSection.stockStatus}}" userInput="{{virtualProductOutOfStock.status}}" stepKey="selectStockStatusOutOfStock"/> + <click selector="{{AdminProductFormSection.categoriesDropdown}}" stepKey="clickCategoriesDropDown"/> + <fillField selector="{{AdminProductFormSection.searchCategory}}" userInput="$$categoryEntity.name$$" stepKey="fillSearchCategory" /> + <click selector="{{AdminProductFormSection.selectCategory($$categoryEntity.name$$)}}" stepKey="clickOnCategory"/> + <click selector="{{AdminProductFormSection.done}}" stepKey="clickOnDoneAdvancedCategorySelect"/> + <click selector="{{AdminProductSEOSection.sectionHeader}}" stepKey="clickAdminProductSEOSection"/> + <fillField selector="{{AdminProductSEOSection.urlKeyInput}}" userInput="{{virtualProductOutOfStock.urlKey}}" stepKey="fillUrlKey"/> + <scrollToTopOfPage stepKey="scrollToTopOfAdminProductFormSection"/> + <click selector="{{AdminProductFormSection.save}}" stepKey="clickSaveButton"/> + <waitForPageLoad stepKey="waitForVirtualProductSaved"/> + + <!-- Verify we see success message --> + <see selector="{{AdminProductFormSection.successMessage}}" userInput="You saved the product." stepKey="seeAssertVirtualProductSuccessMessage"/> + + <!-- Verify we see created virtual product out of stock with tier price on the storefront page --> + <amOnPage url="{{StorefrontProductPage.url(virtualProductOutOfStock.urlKey)}}" stepKey="goToProductPage"/> + <waitForPageLoad stepKey="waitForStoreFrontProductPageToLoad"/> + <see selector="{{StorefrontProductInfoMainSection.productName}}" userInput="{{virtualProductOutOfStock.name}}" stepKey="seeVirtualProductNameOnStoreFrontPage"/> + <see selector="{{StorefrontProductInfoMainSection.productSku}}" userInput="{{virtualProductOutOfStock.sku}}" stepKey="seeVirtualProductSku"/> + + <!-- Verify customer see product tier price on product page --> + <grabTextFrom selector="{{StorefrontProductInfoMainSection.productTierPriceByForTextLabel('1', tierPriceOnDefault.qty_0)}}" stepKey="firstTierPriceText"/> + <assertEquals stepKey="assertTierPriceTextOnProductPage1"> + <expectedResult type="string">Buy {{tierPriceOnDefault.qty_0}} for ${{tierPriceOnDefault.price_0}} each and save 100%</expectedResult> + <actualResult type="variable">firstTierPriceText</actualResult> + </assertEquals> + <grabTextFrom selector="{{StorefrontProductInfoMainSection.productTierPriceByForTextLabel('2', tierPriceOnDefault.qty_1)}}" stepKey="secondTierPriceText"/> + <assertEquals stepKey="assertTierPriceTextOnProductPage2"> + <expectedResult type="string">Buy {{tierPriceOnDefault.qty_1}} for ${{tierPriceOnDefault.price_1}} each and save 100%</expectedResult> + <actualResult type="variable">secondTierPriceText</actualResult> + </assertEquals> + + <!-- Verify customer see product out of stock status on product page --> + <grabTextFrom selector="{{StorefrontProductInfoMainSection.productStockStatus}}" stepKey="productStockAvailableStatus"/> + <assertEquals stepKey="assertStockAvailableOnProductPage"> + <expectedResult type="string">{{virtualProductOutOfStock.storefrontStatus}}</expectedResult> + <actualResult type="variable">productStockAvailableStatus</actualResult> + </assertEquals> + <grabTextFrom selector="{{StorefrontProductInfoMainSection.productPrice}}" stepKey="productPriceAmount"/> + <assertEquals stepKey="assertOldPriceTextOnProductPage"> + <expectedResult type="string">${{virtualProductOutOfStock.price}}</expectedResult> + <actualResult type="variable">productPriceAmount</actualResult> + </assertEquals> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithCustomOptionsSuiteAndImportOptionsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithCustomOptionsSuiteAndImportOptionsTest.xml new file mode 100644 index 00000000000..70edb0ce3ea --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithCustomOptionsSuiteAndImportOptionsTest.xml @@ -0,0 +1,160 @@ +<?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="AdminCreateVirtualProductWithCustomOptionsSuiteAndImportOptionsTest"> + <annotations> + <stories value="Create virtual product"/> + <title value="Create virtual product with custom options suite and import options"/> + <description value="Test log in to Create virtual product and Create virtual product with custom options suite and import options"/> + <testCaseId value="MC-6034"/> + <severity value="CRITICAL"/> + <group value="catalog"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <createData entity="SimpleSubCategory" stepKey="categoryEntity"/> + </before> + <after> + <deleteData stepKey="deleteSimpleSubCategory" createDataKey="categoryEntity"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPage"/> + <waitForPageLoad stepKey="waitForProductCatalogPage"/> + <click selector="{{AdminProductGridActionSection.addProductToggle}}" stepKey="clickAddProductToggle"/> + <waitForPageLoad stepKey="waitForProductToggleToSelectProduct"/> + <click selector="{{AdminProductGridActionSection.addVirtualProduct}}" stepKey="clickVirtualProduct"/> + + <!-- Create virtual product with custom options suite and import options --> + <fillField selector="{{AdminProductFormSection.productName}}" userInput="{{virtualProductCustomImportOptions.name}}" stepKey="fillProductName"/> + <fillField selector="{{AdminProductFormSection.productSku}}" userInput="{{virtualProductCustomImportOptions.sku}}" stepKey="fillProductSku"/> + <fillField selector="{{AdminProductFormSection.productPrice}}" userInput="{{virtualProductCustomImportOptions.price}}" stepKey="fillProductPrice"/> + <fillField selector="{{AdminProductFormSection.productQuantity}}" userInput="{{virtualProductCustomImportOptions.quantity}}" stepKey="fillProductQuantity"/> + <click selector="{{AdminProductFormSection.productStockStatus}}" stepKey="clickProductStockStatus"/> + <click selector="{{AdminProductFormSection.categoriesDropdown}}" stepKey="clickCategoriesDropDown"/> + <fillField selector="{{AdminProductFormSection.searchCategory}}" userInput="$$categoryEntity.name$$" stepKey="fillSearchCategory" /> + <click selector="{{AdminProductFormSection.selectCategory($$categoryEntity.name$$)}}" stepKey="clickOnCategory"/> + <click selector="{{AdminProductFormSection.done}}" stepKey="clickOnDoneAdvancedCategorySelect"/> + <click selector="{{AdminProductSEOSection.sectionHeader}}" stepKey="clickAdminProductSEOSection"/> + <fillField selector="{{AdminProductSEOSection.urlKeyInput}}" userInput="{{virtualProductCustomImportOptions.urlKey}}" stepKey="fillUrlKey"/> + <click selector="{{AdminProductCustomizableOptionsSection.checkIfCustomizableOptionsTabOpen}}" stepKey="clickAdminProductCustomizableOption"/> + + <!-- Create virtual product with customizable options dataSet1 --> + <click selector="{{AdminProductCustomizableOptionsSection.addOptionBtn}}" stepKey="clickAddOptionButton"/> + <waitForPageLoad stepKey="waitForFirstOption"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.optionTitleInput('0')}}" userInput="{{virtualProductCustomizableOption1.title}}" stepKey="fillOptionTitleForFirstDataSet"/> + <click selector="{{AdminProductCustomizableOptionsSection.optionTypeDropDown('1')}}" stepKey="selectOptionTypeDropDownFirstDataSet"/> + <click selector="{{AdminProductCustomizableOptionsSection.optionTypeItem('1', virtualProductCustomizableOption1.type)}}" stepKey="selectOptionFieldFromDropDownForFirstDataSet"/> + <checkOption selector="{{AdminProductCustomizableOptionsSection.requiredCheckBox('0')}}" stepKey="checkRequiredCheckBoxForFirstDataSet"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.optionPrice('0')}}" userInput="{{virtualProductCustomizableOption1.option_0_price}}" stepKey="fillOptionPriceForFirstDataSet"/> + <selectOption selector="{{AdminProductCustomizableOptionsSection.optionPriceType('0')}}" userInput="{{virtualProductCustomizableOption1.option_0_price_type}}" stepKey="selectOptionPriceTypeForFirstDataSet"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.optionSku('0')}}" userInput="{{virtualProductCustomizableOption1.option_0_sku}}" stepKey="fillOptionSkuForFirstDataSet"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.maxCharactersInput('0')}}" userInput="{{virtualProductCustomizableOption1.option_0_max_characters}}" stepKey="fillOptionMaxCharactersForFirstDataSet"/> + + <!-- Create virtual product with customizable options dataSet2 --> + <click selector="{{AdminProductCustomizableOptionsSection.addOptionBtn}}" stepKey="clickAddOptionButtonForSecondDataSet"/> + <waitForPageLoad stepKey="waitForSecondDataSetToLoad"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.optionTitleInput('1')}}" userInput="{{virtualProductCustomizableOption2.title}}" stepKey="fillOptionTitleForSecondDataSet"/> + <click selector="{{AdminProductCustomizableOptionsSection.optionTypeDropDown('2')}}" stepKey="selectOptionTypeDropDownSecondDataSet"/> + <click selector="{{AdminProductCustomizableOptionsSection.optionTypeItem('2', virtualProductCustomizableOption2.type)}}" stepKey="selectOptionFieldFromDropDownForSecondDataSet"/> + <checkOption selector="{{AdminProductCustomizableOptionsSection.requiredCheckBox('1')}}" stepKey="checkRequiredCheckBoxForSecondDataSet"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.optionPrice('1')}}" userInput="{{virtualProductCustomizableOption2.option_0_price}}" stepKey="fillOptionPriceForSecondDataSet"/> + <selectOption selector="{{AdminProductCustomizableOptionsSection.optionPriceType('1')}}" userInput="{{virtualProductCustomizableOption2.option_0_price_type}}" stepKey="selectOptionPriceTypeForSecondDataSet"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.optionSku('1')}}" userInput="{{virtualProductCustomizableOption2.option_0_sku}}" stepKey="fillOptionSkuForSecondDataSet"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.maxCharactersInput('1')}}" userInput="{{virtualProductCustomizableOption2.option_0_max_characters}}" stepKey="fillOptionMaxCharactersForSecondDataSet"/> + + <!-- Create virtual product with customizable options dataSet3 --> + <click selector="{{AdminProductCustomizableOptionsSection.addOptionBtn}}" stepKey="clickAddOptionButtonForThirdSetOfData"/> + <waitForPageLoad stepKey="waitForThirdSetOfDataToLoad"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.optionTitleInput('2')}}" userInput="{{virtualProductCustomizableOption3.title}}" stepKey="fillOptionTitleForThirdDataSet"/> + <click selector="{{AdminProductCustomizableOptionsSection.optionTypeDropDown('3')}}" stepKey="selectOptionTypeDropDownForThirdDataSet"/> + <click selector="{{AdminProductCustomizableOptionsSection.optionTypeItem('3', virtualProductCustomizableOption3.type)}}" stepKey="selectOptionFieldFromDropDownForThirdDataSet"/> + <checkOption selector="{{AdminProductCustomizableOptionsSection.requiredCheckBox('2')}}" stepKey="checkRequiredCheckBoxForThirdDataSet"/> + <click selector="{{AdminProductCustomizableOptionsSection.addValue}}" stepKey="clickAddOptionButtonForThirdDataSetToAddFirstRow"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.fillOptionValueTitle(virtualProductCustomizableOption3.title,'0')}}" userInput="{{virtualProductCustomizableOption3.option_0_title}}" stepKey="fillOptionTitleForThirdDataSetFirstRow"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.fillOptionValuePrice(virtualProductCustomizableOption3.title,'0')}}" userInput="{{virtualProductCustomizableOption3.option_0_price}}" stepKey="fillOptionPriceForThirdDataSetFirstRow"/> + <selectOption selector="{{AdminProductCustomizableOptionsSection.clickSelectPriceType(virtualProductCustomizableOption3.title,'0')}}" userInput="{{virtualProductCustomizableOption3.option_0_price_type}}" stepKey="selectOptionPriceTypeForThirdDataSetFirstRow"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.fillOptionValueSku(virtualProductCustomizableOption3.title,'0')}}" userInput="{{virtualProductCustomizableOption3.option_0_sku}}" stepKey="fillOptionSkuForThirdDataSetFirstRow"/> + <click selector="{{AdminProductCustomizableOptionsSection.addValue}}" stepKey="clickAddOptionButtonForThirdDataSetToAddSecondRow"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.fillOptionValueTitle(virtualProductCustomizableOption3.title,'1')}}" userInput="{{virtualProductCustomizableOption3.option_1_title}}" stepKey="fillOptionTitleForThirdDataSetSecondRow"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.fillOptionValuePrice(virtualProductCustomizableOption3.title,'1')}}" userInput="{{virtualProductCustomizableOption3.option_1_price}}" stepKey="fillOptionPriceForThirdDataSetSecondRow"/> + <selectOption selector="{{AdminProductCustomizableOptionsSection.clickSelectPriceType(virtualProductCustomizableOption3.title,'1')}}" userInput="{{virtualProductCustomizableOption3.option_1_price_type}}" stepKey="selectOptionPriceTypeForThirdDataSetSecondRow"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.fillOptionValueSku(virtualProductCustomizableOption3.title,'1')}}" userInput="{{virtualProductCustomizableOption3.option_1_sku}}" stepKey="fillOptionSkuForThirdDataSetSecondRow"/> + + <!-- Create virtual product with customizable options dataSet4 --> + <scrollToTopOfPage stepKey="scrollToAddOptionButton"/> + <click selector="{{AdminProductCustomizableOptionsSection.addOptionBtn}}" stepKey="clickAddOptionButtonForFourthDataSet"/> + <waitForPageLoad stepKey="waitForFourthDataSetToLoad"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.optionTitleInput('3')}}" userInput="{{virtualProductCustomizableOption4.title}}" stepKey="fillOptionTitleForFourthDataSet"/> + <click selector="{{AdminProductCustomizableOptionsSection.optionTypeDropDown('4')}}" stepKey="selectOptionTypeDropDownForFourthSetOfData"/> + <click selector="{{AdminProductCustomizableOptionsSection.optionTypeItem('4', virtualProductCustomizableOption4.type)}}" stepKey="selectOptionFieldFromDropDownForFourthDataSet"/> + <checkOption selector="{{AdminProductCustomizableOptionsSection.requiredCheckBox('3')}}" stepKey="checkRequiredCheckBoxForFourthDataSet"/> + <click selector="{{AdminProductCustomizableOptionsSection.addValue}}" stepKey="clickAddOptionButtonForFourthDataSetToAddFirstRow"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.fillOptionValueTitle(virtualProductCustomizableOption4.title,'0')}}" userInput="{{virtualProductCustomizableOption4.option_0_title}}" stepKey="fillOptionTitleForFourthDataSetFirstRow"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.fillOptionValuePrice(virtualProductCustomizableOption4.title,'0')}}" userInput="{{virtualProductCustomizableOption4.option_0_price}}" stepKey="fillOptionPriceForFourthDataSetFirstRow"/> + <selectOption selector="{{AdminProductCustomizableOptionsSection.clickSelectPriceType(virtualProductCustomizableOption4.title,'0')}}" userInput="{{virtualProductCustomizableOption4.option_0_price_type}}" stepKey="selectOptionPriceTypeForFourthDataSetFirstRow"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.fillOptionValueSku(virtualProductCustomizableOption4.title,'0')}}" userInput="{{virtualProductCustomizableOption4.option_0_sku}}" stepKey="fillOptionSkuForFourthDataSetFirstRow"/> + <click selector="{{AdminProductCustomizableOptionsSection.addValue}}" stepKey="clickAddOptionButtonForFourthDataSetToAddSecondRow"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.fillOptionValueTitle(virtualProductCustomizableOption4.title,'1')}}" userInput="{{virtualProductCustomizableOption4.option_1_title}}" stepKey="fillOptionTitleForFourthDataSetSecondRow"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.fillOptionValuePrice(virtualProductCustomizableOption4.title,'1')}}" userInput="{{virtualProductCustomizableOption4.option_1_price}}" stepKey="fillOptionPriceForFourthDataSetSecondRow"/> + <selectOption selector="{{AdminProductCustomizableOptionsSection.clickSelectPriceType(virtualProductCustomizableOption4.title,'1')}}" userInput="{{virtualProductCustomizableOption4.option_1_price_type}}" stepKey="selectOptionPriceTypeForFourthDataSetSecondRow"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.fillOptionValueSku(virtualProductCustomizableOption4.title,'1')}}" userInput="{{virtualProductCustomizableOption4.option_1_sku}}" stepKey="fillOptionSkuForFourthDataSetSecondRow"/> + <scrollToTopOfPage stepKey="scrollToTopOfAdminProductFormSection"/> + <click selector="{{AdminProductFormSection.save}}" stepKey="clickSaveButton"/> + <waitForPageLoad stepKey="waitForVirtualProductSaved"/> + + <!-- Verify we see success message --> + <see selector="{{AdminProductFormSection.successMessage}}" userInput="You saved the product." stepKey="seeAssertVirtualProductSuccessMessage"/> + + <!-- Verify customer see created virtual product with custom options suite and import options(from above step) on storefront page and is searchable by sku --> + <amOnPage url="{{StorefrontProductPage.url(virtualProductCustomImportOptions.urlKey)}}" stepKey="goToProductPage"/> + <waitForPageLoad stepKey="waitForStoreFrontProductPageLoad"/> + <fillField selector="{{StorefrontQuickSearchResultsSection.searchTextBox}}" userInput="{{virtualProductCustomImportOptions.sku}}" stepKey="fillVirtualProductName"/> + <waitForPageLoad stepKey="waitForSearchTextBox"/> + <click selector="{{StorefrontQuickSearchResultsSection.searchTextBoxButton}}" stepKey="clickSearchTextBoxButton"/> + <waitForPageLoad stepKey="waitForSearch"/> + <see selector="{{StorefrontQuickSearchResultsSection.productLink}}" userInput="{{virtualProductCustomImportOptions.name}}" stepKey="seeVirtualProductName"/> + <click selector="{{StorefrontQuickSearchResultsSection.productLink}}" stepKey="openSearchedProduct"/> + + <!-- Verify we see created virtual product with custom options suite and import options on the storefront page --> + <see selector="{{StorefrontProductInfoMainSection.productName}}" userInput="{{virtualProductCustomImportOptions.name}}" stepKey="seeVirtualProductNameOnStoreFrontPage"/> + <see selector="{{StorefrontProductInfoMainSection.productSku}}" userInput="{{virtualProductCustomImportOptions.sku}}" stepKey="seeVirtualProductSku"/> + <grabTextFrom selector="{{StorefrontProductInfoMainSection.productStockStatus}}" stepKey="productStockAvailableStatus"/> + <assertEquals stepKey="assertStockAvailableOnProductPage"> + <expectedResult type="string">{{virtualProductCustomImportOptions.storefrontStatus}}</expectedResult> + <actualResult type="variable">productStockAvailableStatus</actualResult> + </assertEquals> + <grabTextFrom selector="{{StorefrontProductInfoMainSection.productPrice}}" stepKey="productPriceAmount"/> + <assertEquals stepKey="assertOldPriceTextOnProductPage"> + <expectedResult type="string">${{virtualProductCustomImportOptions.price}}</expectedResult> + <actualResult type="variable">productPriceAmount</actualResult> + </assertEquals> + + <!--Verify we see customizable options are Required --> + <seeElement selector="{{StorefrontProductInfoMainSection.requiredCustomInput(virtualProductCustomizableOption1.title)}}" stepKey="verifyFistCustomOptionIsRequired" /> + <seeElement selector="{{StorefrontProductInfoMainSection.requiredCustomInput(virtualProductCustomizableOption2.title)}}" stepKey="verifySecondCustomOptionIsRequired" /> + <seeElement selector="{{StorefrontProductInfoMainSection.requiredCustomSelect(virtualProductCustomizableOption3.title)}}" stepKey="verifyThirdCustomOptionIsRequired" /> + <seeElement selector="{{StorefrontProductInfoMainSection.requiredCustomSelect(virtualProductCustomizableOption4.title)}}" stepKey="verifyFourthCustomOptionIsRequired" /> + + <!--Verify we see customizable option titles and prices --> + <grabMultiple selector="{{StorefrontProductInfoMainSection.allCustomOptionLabels}}" stepKey="allCustomOptionLabels" /> + <assertEquals stepKey="verifyLabels"> + <actualResult type="variable">allCustomOptionLabels</actualResult> + <expectedResult type="array">[{{virtualProductCustomizableOption1.title}} + ${{virtualProductCustomizableOption1.option_0_price}}, {{virtualProductCustomizableOption2.title}} + ${{virtualProductCustomizableOption2.option_0_price}}, {{virtualProductCustomizableOption3.title}}, {{virtualProductCustomizableOption4.title}}]</expectedResult> + </assertEquals> + <grabAttributeFrom userInput="for" selector="{{StorefrontProductInfoMainSection.customOptionLabel(virtualProductCustomizableOption4.title)}}" stepKey="fourthOptionId" /> + <grabMultiple selector="{{StorefrontProductInfoMainSection.customSelectOptions({$fourthOptionId})}}" stepKey="grabFourthOptions" /> + <assertEquals stepKey="assertFourthSelectOptions"> + <actualResult type="variable">grabFourthOptions</actualResult> + <expectedResult type="array">['-- Please Select --', {{virtualProductCustomizableOption4.option_0_title}} +$900.90, {{virtualProductCustomizableOption4.option_1_title}} +$20.02]</expectedResult> + </assertEquals> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithTierPriceForGeneralGroupTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithTierPriceForGeneralGroupTest.xml new file mode 100644 index 00000000000..78247f49435 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithTierPriceForGeneralGroupTest.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="AdminCreateVirtualProductWithTierPriceForGeneralGroupTest"> + <annotations> + <stories value="Create virtual product"/> + <title value="Create virtual product with tier price for General group"/> + <description value="Test log in to Create virtual product and Create virtual product with tier price for General group"/> + <testCaseId value="MC-6033"/> + <severity value="CRITICAL"/> + <group value="catalog"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <createData entity="SimpleSubCategory" stepKey="categoryEntity"/> + <createData entity="Simple_US_CA_Customer" stepKey="customer" /> + </before> + <after> + <deleteData stepKey="deleteSimpleSubCategory" createDataKey="categoryEntity"/> + <deleteData stepKey="deleteCustomer" createDataKey="customer"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPage"/> + <waitForPageLoad stepKey="waitForProductCatalogPage"/> + <click selector="{{AdminProductGridActionSection.addProductToggle}}" stepKey="clickAddProductToggle"/> + <waitForPageLoad stepKey="waitForProductToggleToSelectProduct"/> + <click selector="{{AdminProductGridActionSection.addVirtualProduct}}" stepKey="clickVirtualProduct"/> + + <!-- Create virtual product with tier price for general group --> + <fillField selector="{{AdminProductFormSection.productName}}" userInput="{{virtualProductGeneralGroup.name}}" stepKey="fillProductName"/> + <fillField selector="{{AdminProductFormSection.productSku}}" userInput="{{virtualProductGeneralGroup.sku}}" stepKey="fillProductSku"/> + <fillField selector="{{AdminProductFormSection.productPrice}}" userInput="{{virtualProductGeneralGroup.price}}" stepKey="fillProductPrice"/> + <click selector="{{AdminProductFormSection.advancedPricingLink}}" stepKey="clickAdvancedPricingLink"/> + <click selector="{{AdminProductFormAdvancedPricingSection.customerGroupPriceAddButton}}" stepKey="clickCustomerGroupPriceAddButton"/> + <selectOption selector="{{AdminProductFormAdvancedPricingSection.productTierPriceWebsiteSelect('0')}}" userInput="{{tierPriceOnGeneralGroup.website}}" stepKey="selectProductTierPriceWebsite"/> + <selectOption selector="{{AdminProductFormAdvancedPricingSection.productTierPriceCustGroupSelect('0')}}" userInput="{{tierPriceOnGeneralGroup.customer_group}}" stepKey="selectProductTierPriceGroup"/> + <scrollTo selector="{{AdminProductFormAdvancedPricingSection.productTierPriceQtyInput('0')}}" x="50" y="0" stepKey="scrollToProductTierPriceQuantityInputTextBox"/> + <fillField selector="{{AdminProductFormAdvancedPricingSection.productTierPriceQtyInput('0')}}" userInput="{{tierPriceOnGeneralGroup.qty}}" stepKey="fillProductTierPriceQuantityInput"/> + <fillField selector="{{AdminProductFormAdvancedPricingSection.productTierPriceFixedPriceInput('0')}}" userInput="{{tierPriceOnGeneralGroup.price}}" stepKey="fillProductTierPriceFixedPrice"/> + <click selector="{{AdminProductFormAdvancedPricingSection.doneButton}}" stepKey="clickDoneButton"/> + <selectOption selector="{{AdminProductFormSection.productTaxClass}}" userInput="{{virtualProductGeneralGroup.productTaxClass}}" stepKey="selectProductTaxClass"/> + <fillField selector="{{AdminProductFormSection.productQuantity}}" userInput="{{virtualProductGeneralGroup.quantity}}" stepKey="fillProductQuantity"/> + <click selector="{{AdminProductFormSection.productStockStatus}}" stepKey="clickProductStockStatus"/> + <click selector="{{AdminProductFormSection.categoriesDropdown}}" stepKey="clickCategoriesDropDown"/> + <fillField selector="{{AdminProductFormSection.searchCategory}}" userInput="$$categoryEntity.name$$" stepKey="fillSearchCategory" /> + <click selector="{{AdminProductFormSection.selectCategory($$categoryEntity.name$$)}}" stepKey="clickOnCategory"/> + <click selector="{{AdminProductFormSection.done}}" stepKey="clickOnDoneAdvancedCategorySelect"/> + <selectOption selector="{{AdminProductFormSection.visibility}}" userInput="{{virtualProductGeneralGroup.visibility}}" stepKey="selectVisibility"/> + <click selector="{{AdminProductSEOSection.sectionHeader}}" stepKey="clickAdminProductSEOSectionHeader"/> + <fillField selector="{{AdminProductSEOSection.urlKeyInput}}" userInput="{{virtualProductGeneralGroup.urlKey}}" stepKey="fillUrlKeyInput"/> + <scrollToTopOfPage stepKey="scrollToTopOfAdminProductFormSection"/> + <click selector="{{AdminProductFormSection.save}}" stepKey="clickSaveButton"/> + <waitForPageLoad stepKey="waitForVirtualProductSaved"/> + + <!-- Verify we see success message --> + <see selector="{{AdminProductFormSection.successMessage}}" userInput="You saved the product." stepKey="seeAssertVirtualProductSuccessMessage"/> + + <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPage1"/> + <waitForPageLoad stepKey="waitForProductCatalogPage1"/> + <conditionalClick selector="{{AdminProductGridFilterSection.clearAll}}" dependentSelector="{{AdminProductGridFilterSection.clearAll}}" visible="true" stepKey="checkRetailCustomerTaxClass" /> + <fillField selector="{{AdminProductGridFilterSection.keywordSearch}}" userInput="{{virtualProductGeneralGroup.name}}" stepKey="fillVirtualProductName"/> + <click selector="{{AdminProductGridFilterSection.keywordSearchButton}}" stepKey="clickKeywordSearchButton"/> + <waitForPageLoad stepKey="waitForProductSearch"/> + <click selector="{{AdminProductGridFilterSection.nthRow('1')}}" stepKey="clickFirstRowToVerifyCreatedVirtualProduct"/> + <waitForPageLoad stepKey="waitUntilProductIsOpened"/> + + <!-- Verify we see created virtual product with tier price for general group(from the above step) in the product form page --> + <seeInField selector="{{AdminProductFormSection.productName}}" userInput="{{virtualProductGeneralGroup.name}}" stepKey="seeProductName"/> + <seeInField selector="{{AdminProductFormSection.productSku}}" userInput="{{virtualProductGeneralGroup.sku}}" stepKey="seeProductSku"/> + <seeInField selector="{{AdminProductFormSection.productPrice}}" userInput="{{virtualProductGeneralGroup.price}}" stepKey="seeProductPrice"/> + <click selector="{{AdminProductFormSection.advancedPricingLink}}" stepKey="clickAdvancedPricingLink1"/> + <seeOptionIsSelected selector="{{AdminProductFormAdvancedPricingSection.productTierPriceWebsiteSelect('0')}}" userInput="{{tierPriceOnGeneralGroup.website}}" stepKey="seeProductTierPriceWebsite"/> + <seeOptionIsSelected selector="{{AdminProductFormAdvancedPricingSection.productTierPriceCustGroupSelect('0')}}" userInput="{{tierPriceOnGeneralGroup.customer_group}}" stepKey="seeProductTierPriceGroup"/> + <seeInField selector="{{AdminProductFormAdvancedPricingSection.productTierPriceQtyInput('0')}}" userInput="{{tierPriceOnGeneralGroup.qty}}" stepKey="seeProductTierPriceQuantityInput"/> + <seeInField selector="{{AdminProductFormAdvancedPricingSection.productTierPriceFixedPriceInput('0')}}" userInput="{{tierPriceOnGeneralGroup.price}}" stepKey="seeProductTierPriceFixedPrice"/> + <click selector="{{AdminProductFormAdvancedPricingSection.advancedPricingCloseButton}}" stepKey="clickAdvancedPricingCloseButton"/> + <seeInField selector="{{AdminProductFormSection.productTaxClass}}" userInput="{{virtualProductGeneralGroup.productTaxClass}}" stepKey="seeProductTaxClass"/> + <seeInField selector="{{AdminProductFormSection.productQuantity}}" userInput="{{virtualProductGeneralGroup.quantity}}" stepKey="seeProductQuantity"/> + <seeInField selector="{{AdminProductFormSection.productStockStatus}}" userInput="{{virtualProductGeneralGroup.status}}" stepKey="seeProductStockStatus"/> + <click selector="{{AdminProductFormSection.categoriesDropdown}}" stepKey="clickCategoriesDropDownToVerify"/> + <grabMultiple selector="{{AdminProductFormSection.selectMultipleCategories}}" stepKey="selectedCategories" /> + <assertEquals stepKey="assertSelectedCategories"> + <actualResult type="variable">selectedCategories</actualResult> + <expectedResult type="array">[$$categoryEntity.name$$]</expectedResult> + </assertEquals> + <click selector="{{AdminProductFormSection.done}}" stepKey="clickOnDoneOnCategorySelect"/> + <seeInField selector="{{AdminProductFormSection.visibility}}" userInput="{{virtualProductGeneralGroup.visibility}}" stepKey="seeVisibility"/> + <scrollTo selector="{{AdminProductSEOSection.sectionHeader}}" x="0" y="-80" stepKey="scrollToAdminProductSEOSection1"/> + <click selector="{{AdminProductSEOSection.sectionHeader}}" stepKey="clickAdminProductSEOSection1"/> + <seeInField selector="{{AdminProductSEOSection.urlKeyInput}}" userInput="{{virtualProductGeneralGroup.urlKey}}" stepKey="seeUrlKey"/> + + <!--Verify customer see created virtual product on category page --> + <amOnPage url="{{StorefrontCategoryPage.url($$categoryEntity.name$$)}}" stepKey="openCategoryPage"/> + <waitForPageLoad stepKey="waitForCategoryPageToLoad"/> + <see selector="{{StorefrontCategoryMainSection.productLink}}" userInput="{{virtualProductGeneralGroup.name}}" stepKey="seeVirtualProductNameOnCategoryPage"/> + + <!-- Verify customer see created virtual product with tier price for general group(from above step) in storefront page with customer --> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginAsCustomer"> + <argument name="Customer" value="$$customer$$" /> + </actionGroup> + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="goToStorefront"/> + <waitForPageLoad stepKey="waitForStoreFrontProductPageLoad"/> + <fillField selector="{{StorefrontQuickSearchResultsSection.searchTextBox}}" userInput="{{virtualProductGeneralGroup.name}}" stepKey="fillVirtualProductNameInSearchTextBox"/> + <waitForPageLoad stepKey="waitForSearchTextBox"/> + <click selector="{{StorefrontQuickSearchResultsSection.searchTextBoxButton}}" stepKey="clickSearchTextBoxButton"/> + <waitForPageLoad stepKey="waitForSearch"/> + <see selector="{{StorefrontQuickSearchResultsSection.productLink}}" userInput="{{virtualProductGeneralGroup.name}}" stepKey="seeVirtualProductName"/> + <grabTextFrom selector="{{StorefrontQuickSearchResultsSection.asLowAsLabel}}" stepKey="tierPriceTextOnStorefrontPage"/> + + <!-- Verify customer see created virtual product with tier price --> + <assertEquals stepKey="assertTierPriceTextOnCategoryPage"> + <expectedResult type="string">As low as ${{tierPriceOnGeneralGroup.price}}</expectedResult> + <actualResult type="variable">tierPriceTextOnStorefrontPage</actualResult> + </assertEquals> + <click selector="{{StorefrontQuickSearchResultsSection.productLink}}" stepKey="openSearchedProduct"/> + <waitForPageLoad stepKey="waitForProductPageToBeLoaded"/> + <grabTextFrom selector="{{StorefrontProductInfoMainSection.tierPriceText}}" stepKey="tierPriceText"/> + <assertEquals stepKey="assertTierPriceTextOnProductPage"> + <expectedResult type="string">Buy {{tierPriceOnGeneralGroup.qty}} for ${{tierPriceOnGeneralGroup.price}} each and save 20%</expectedResult> + <actualResult type="variable">tierPriceText</actualResult> + </assertEquals> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithTierPriceTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithTierPriceTest.xml new file mode 100644 index 00000000000..6ef2569945f --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithTierPriceTest.xml @@ -0,0 +1,123 @@ +<?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="AdminCreateVirtualProductWithTierPriceTest"> + <annotations> + <stories value="Create virtual product"/> + <title value="Create virtual product with tier price"/> + <description value="Test log in to Create virtual product and Create virtual product with tier price"/> + <testCaseId value="MC-6032"/> + <severity value="CRITICAL"/> + <group value="catalog"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <createData entity="SimpleSubCategory" stepKey="categoryEntity"/> + </before> + <after> + <deleteData stepKey="deleteSimpleSubCategory" createDataKey="categoryEntity"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPage"/> + <waitForPageLoad stepKey="waitForProductCatalogPage"/> + <click selector="{{AdminProductGridActionSection.addProductToggle}}" stepKey="clickAddProductToggle"/> + <waitForPageLoad stepKey="waitForProductToggleToSelectProduct"/> + <click selector="{{AdminProductGridActionSection.addVirtualProduct}}" stepKey="clickVirtualProduct"/> + + <!-- Create virtual product with tier price --> + <fillField selector="{{AdminProductFormSection.productName}}" userInput="{{virtualProductBigQty.name}}" stepKey="fillProductName"/> + <fillField selector="{{AdminProductFormSection.productSku}}" userInput="{{virtualProductBigQty.sku}}" stepKey="fillProductSku"/> + <fillField selector="{{AdminProductFormSection.productPrice}}" userInput="{{virtualProductBigQty.price}}" stepKey="fillProductPrice"/> + <click selector="{{AdminProductFormSection.advancedPricingLink}}" stepKey="clickAdvancedPricingLink"/> + <click selector="{{AdminProductFormAdvancedPricingSection.customerGroupPriceAddButton}}" stepKey="clickCustomerGroupPriceAddButton"/> + <scrollTo selector="{{AdminProductFormAdvancedPricingSection.productTierPriceQtyInput('0')}}" x="50" y="0" stepKey="scrollToProductTierPriceQuantityInputTextBox"/> + <fillField selector="{{AdminProductFormAdvancedPricingSection.productTierPriceQtyInput('0')}}" userInput="{{tierPriceOnVirtualProduct.qty}}" stepKey="fillProductTierPriceQuantityInput"/> + <fillField selector="{{AdminProductFormAdvancedPricingSection.productTierPriceFixedPriceInput('0')}}" userInput="{{tierPriceOnVirtualProduct.price}}" stepKey="selectProductTierPriceFixedPrice"/> + <click selector="{{AdminProductFormAdvancedPricingSection.doneButton}}" stepKey="clickDoneButton"/> + <selectOption selector="{{AdminProductFormSection.productTaxClass}}" userInput="{{virtualProductBigQty.productTaxClass}}" stepKey="selectProductTaxClass"/> + <fillField selector="{{AdminProductFormSection.productQuantity}}" userInput="{{virtualProductBigQty.quantity}}" stepKey="fillProductQuantity"/> + <selectOption selector="{{AdminProductFormSection.stockStatus}}" userInput="{{virtualProductBigQty.status}}" stepKey="selectStockStatusInStock"/> + <click selector="{{AdminProductFormSection.categoriesDropdown}}" stepKey="clickCategoriesDropDown"/> + <fillField selector="{{AdminProductFormSection.searchCategory}}" userInput="$$categoryEntity.name$$" stepKey="fillSearchCategory" /> + <click selector="{{AdminProductFormSection.selectCategory($$categoryEntity.name$$)}}" stepKey="clickOnCategory"/> + <click selector="{{AdminProductFormSection.done}}" stepKey="clickOnDoneAdvancedCategorySelect"/> + <selectOption selector="{{AdminProductFormSection.visibility}}" userInput="{{virtualProductBigQty.visibility}}" stepKey="selectVisibility"/> + <click selector="{{AdminProductSEOSection.sectionHeader}}" stepKey="clickAdminProductSEOSection"/> + <fillField selector="{{AdminProductSEOSection.urlKeyInput}}" userInput="{{virtualProductBigQty.urlKey}}" stepKey="fillUrlKey"/> + <scrollToTopOfPage stepKey="scrollToTopOfAdminProductFormSection"/> + <click selector="{{AdminProductFormSection.save}}" stepKey="clickSaveButton"/> + <waitForPageLoad stepKey="waitForVirtualProductSaved"/> + + <!-- Verify we see success message --> + <see selector="{{AdminProductFormSection.successMessage}}" userInput="You saved the product." stepKey="seeAssertVirtualProductSuccessMessage"/> + + <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPage1"/> + <waitForPageLoad stepKey="waitForProductCatalogPage1"/> + <conditionalClick selector="{{AdminProductGridFilterSection.clearAll}}" dependentSelector="{{AdminProductGridFilterSection.clearAll}}" visible="true" stepKey="checkRetailCustomerTaxClass" /> + <fillField selector="{{AdminProductGridFilterSection.keywordSearch}}" userInput="{{virtualProductBigQty.name}}" stepKey="fillProductName1"/> + <click selector="{{AdminProductGridFilterSection.keywordSearchButton}}" stepKey="clickKeywordSearchButton"/> + <waitForPageLoad stepKey="waitForProductSearch"/> + <click selector="{{AdminProductGridFilterSection.nthRow('1')}}" stepKey="clickFirstRowToVerifyCreatedVirtualProduct"/> + <waitForPageLoad stepKey="waitUntilProductIsOpened" /> + + <!-- Verify we see created virtual product with tier price(from the above step) in the product form page --> + <seeInField selector="{{AdminProductFormSection.productName}}" userInput="{{virtualProductBigQty.name}}" stepKey="seeProductName"/> + <seeInField selector="{{AdminProductFormSection.productSku}}" userInput="{{virtualProductBigQty.sku}}" stepKey="seeProductSku"/> + <seeInField selector="{{AdminProductFormSection.productPrice}}" userInput="{{virtualProductBigQty.price}}" stepKey="seeProductPrice"/> + <click selector="{{AdminProductFormSection.advancedPricingLink}}" stepKey="clickAdvancedPricingLink1"/> + <seeInField selector="{{AdminProductFormAdvancedPricingSection.productTierPriceQtyInput('0')}}" userInput="{{tierPriceOnVirtualProduct.qty}}" stepKey="seeProductTierPriceQuantityInput"/> + <seeInField selector="{{AdminProductFormAdvancedPricingSection.productTierPriceFixedPriceInput('0')}}" userInput="{{tierPriceOnVirtualProduct.price}}" stepKey="seeProductTierPriceFixedPrice"/> + <click selector="{{AdminProductFormAdvancedPricingSection.advancedPricingCloseButton}}" stepKey="clickAdvancedPricingCloseButton"/> + <seeInField selector="{{AdminProductFormSection.productTaxClass}}" userInput="{{virtualProductBigQty.productTaxClass}}" stepKey="seeProductTaxClass"/> + <seeInField selector="{{AdminProductFormSection.productQuantity}}" userInput="{{virtualProductBigQty.quantity}}" stepKey="seeProductQuantity"/> + <seeInField selector="{{AdminProductFormSection.productStockStatus}}" userInput="{{virtualProductBigQty.status}}" stepKey="seeProductStockStatus"/> + <click selector="{{AdminProductFormSection.categoriesDropdown}}" stepKey="clickCategoriesDropDownToVerify"/> + <grabMultiple selector="{{AdminProductFormSection.selectMultipleCategories}}" stepKey="selectedCategories" /> + <assertEquals stepKey="assertSelectedCategories"> + <actualResult type="variable">selectedCategories</actualResult> + <expectedResult type="array">[$$categoryEntity.name$$]</expectedResult> + </assertEquals> + <click selector="{{AdminProductFormSection.done}}" stepKey="clickOnDoneOnCategorySelect"/> + <seeInField selector="{{AdminProductFormSection.visibility}}" userInput="{{virtualProductBigQty.visibility}}" stepKey="seeVisibility"/> + <scrollTo selector="{{AdminProductSEOSection.sectionHeader}}" x="0" y="-80" stepKey="scrollToAdminProductSEOSection1"/> + <click selector="{{AdminProductSEOSection.sectionHeader}}" stepKey="clickAdminProductSEOSection1"/> + <seeInField selector="{{AdminProductSEOSection.urlKeyInput}}" userInput="{{virtualProductBigQty.urlKey}}" stepKey="seeUrlKey"/> + + <!--Verify customer see created virtual product on category page --> + <amOnPage url="{{StorefrontCategoryPage.url($$categoryEntity.name$$)}}" stepKey="openCategoryPage"/> + <waitForPageLoad stepKey="waitForCategoryPageToLoad"/> + <see selector="{{StorefrontCategoryMainSection.productLink}}" userInput="{{virtualProductBigQty.name}}" stepKey="seeVirtualProductNameOnCategoryPage"/> + + <!-- Verify customer see created virtual product with tier price(from above step) on storefront page and is searchable by sku --> + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="goToStorefront"/> + <waitForPageLoad stepKey="waitForStoreFrontProductPageLoad"/> + <fillField selector="{{StorefrontQuickSearchResultsSection.searchTextBox}}" userInput="{{virtualProductBigQty.sku}}" stepKey="fillVirtualProductName"/> + <waitForPageLoad stepKey="waitForSearchTextBox"/> + <click selector="{{StorefrontQuickSearchResultsSection.searchTextBoxButton}}" stepKey="clickSearchTextBoxButton"/> + <waitForPageLoad stepKey="waitForSearch"/> + <see selector="{{StorefrontQuickSearchResultsSection.productLink}}" userInput="{{virtualProductBigQty.name}}" stepKey="seeVirtualProductName"/> + <grabTextFrom selector="{{StorefrontQuickSearchResultsSection.asLowAsLabel}}" stepKey="tierPriceTextOnStorefrontPage"/> + <assertEquals stepKey="assertTierPriceTextOnCategoryPage"> + <expectedResult type="string">As low as ${{tierPriceOnVirtualProduct.price}}</expectedResult> + <actualResult type="variable">tierPriceTextOnStorefrontPage</actualResult> + </assertEquals> + <click selector="{{StorefrontQuickSearchResultsSection.productLink}}" stepKey="openSearchedProduct"/> + <waitForPageLoad stepKey="waitForProductPageToBeLoaded" /> + + <!-- Verify customer see product tier price on product page --> + <grabTextFrom selector="{{StorefrontProductInfoMainSection.tierPriceText}}" stepKey="tierPriceText"/> + <assertEquals stepKey="assertTierPriceTextOnProductPage"> + <expectedResult type="string">Buy {{tierPriceOnVirtualProduct.qty}} for ${{tierPriceOnVirtualProduct.price}} each and save 10%</expectedResult> + <actualResult type="variable">tierPriceText</actualResult> + </assertEquals> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithoutManageStockTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithoutManageStockTest.xml new file mode 100644 index 00000000000..cb41b0292d3 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithoutManageStockTest.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="AdminCreateVirtualProductWithoutManageStockTest"> + <annotations> + <stories value="Create virtual product"/> + <title value="Create virtual product without manage stock"/> + <description value="Test log in to Create virtual product and Create virtual product without manage stock"/> + <testCaseId value="MC-6035"/> + <severity value="CRITICAL"/> + <group value="catalog"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <createData entity="SimpleSubCategory" stepKey="categoryEntity"/> + </before> + <after> + <deleteData stepKey="deleteSimpleSubCategory" createDataKey="categoryEntity"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPage"/> + <waitForPageLoad stepKey="waitForProductCatalogPage"/> + <click selector="{{AdminProductGridActionSection.addProductToggle}}" stepKey="clickAddProductToggle"/> + <waitForPageLoad stepKey="waitForProductToggleToSelectProduct"/> + <click selector="{{AdminProductGridActionSection.addVirtualProduct}}" stepKey="clickVirtualProduct"/> + + <!-- Create virtual product without manage stock --> + <fillField selector="{{AdminProductFormSection.productName}}" userInput="{{virtualProductWithoutManageStock.name}}" stepKey="fillProductName"/> + <fillField selector="{{AdminProductFormSection.productSku}}" userInput="{{virtualProductWithoutManageStock.sku}}" stepKey="fillProductSku"/> + <fillField selector="{{AdminProductFormSection.productPrice}}" userInput="{{virtualProductWithoutManageStock.price}}" stepKey="fillProductPrice"/> + <click selector="{{AdminProductFormSection.advancedPricingLink}}" stepKey="clickAdvancedPricingLink"/> + <fillField selector="{{AdminProductFormAdvancedPricingSection.specialPrice}}" userInput="{{virtualProductWithoutManageStock.special_price}}" stepKey="fillSpecialPrice"/> + <click selector="{{AdminProductFormAdvancedPricingSection.doneButton}}" stepKey="clickDoneButton"/> + <fillField selector="{{AdminProductFormSection.productQuantity}}" userInput="{{virtualProductWithoutManageStock.quantity}}" stepKey="fillProductQuantity"/> + <click selector="{{AdminProductFormSection.advancedInventoryLink}}" stepKey="clickAdvancedInventoryLink"/> + <click selector="{{AdminProductFormAdvancedInventorySection.manageStock}}" stepKey="clickManageStock"/> + <checkOption selector="{{AdminProductFormAdvancedInventorySection.useConfigSettings}}" stepKey="CheckUseConfigSettingsCheckBox"/> + <click selector="{{AdminProductFormAdvancedInventorySection.doneButton}}" stepKey="clickDoneButtonOnAdvancedInventorySection"/> + <click selector="{{AdminProductFormSection.categoriesDropdown}}" stepKey="clickCategoriesDropDown"/> + <fillField selector="{{AdminProductFormSection.searchCategory}}" userInput="$$categoryEntity.name$$" stepKey="fillSearchCategory" /> + <click selector="{{AdminProductFormSection.selectCategory($$categoryEntity.name$$)}}" stepKey="clickOnCategory"/> + <click selector="{{AdminProductFormSection.done}}" stepKey="clickOnDoneAdvancedCategorySelect"/> + <click selector="{{AdminProductSEOSection.sectionHeader}}" stepKey="clickAdminProductSEOSection"/> + <fillField selector="{{AdminProductSEOSection.urlKeyInput}}" userInput="{{virtualProductWithoutManageStock.urlKey}}" stepKey="fillUrlKey"/> + <scrollToTopOfPage stepKey="scrollToTopOfAdminProductFormSection"/> + <click selector="{{AdminProductFormSection.save}}" stepKey="clickSaveButton"/> + <waitForPageLoad stepKey="waitForVirtualProductSaved"/> + + <!-- Verify we see success message --> + <see selector="{{AdminProductFormSection.successMessage}}" userInput="You saved the product." stepKey="seeAssertVirtualProductSuccessMessage"/> + + <!-- Verify customer see created virtual product without manage stock on the storefront page --> + <amOnPage url="{{StorefrontProductPage.url(virtualProductWithoutManageStock.urlKey)}}" stepKey="goToProductPage"/> + <waitForPageLoad stepKey="waitForStoreFrontPageToLoad"/> + <see selector="{{StorefrontProductInfoMainSection.productName}}" userInput="{{virtualProductWithoutManageStock.name}}" stepKey="seeVirtualProductNameOnStoreFrontPage"/> + <see selector="{{StorefrontProductInfoMainSection.productSku}}" userInput="{{virtualProductWithoutManageStock.sku}}" stepKey="seeVirtualProductSku"/> + + <!-- Verify customer see product special price on the storefront page --> + <grabTextFrom selector="{{StorefrontProductInfoMainSection.specialPriceAmount}}" stepKey="specialPriceAmount"/> + <assertEquals stepKey="assertSpecialPriceTextOnProductPage"> + <expectedResult type="string">${{virtualProductWithoutManageStock.special_price}}</expectedResult> + <actualResult type="variable">specialPriceAmount</actualResult> + </assertEquals> + + <!-- Verify customer see product old price on the storefront page --> + <grabTextFrom selector="{{StorefrontProductInfoMainSection.oldPriceAmount}}" stepKey="oldPriceAmount"/> + <assertEquals stepKey="assertOldPriceTextOnProductPage"> + <expectedResult type="string">${{virtualProductWithoutManageStock.price}}</expectedResult> + <actualResult type="variable">oldPriceAmount</actualResult> + </assertEquals> + + <!-- Verify customer see product in stock status on the storefront page --> + <grabTextFrom selector="{{StorefrontProductInfoMainSection.productStockStatus}}" stepKey="productStockAvailableStatus"/> + <assertEquals stepKey="assertStockAvailableOnProductPage"> + <expectedResult type="string">{{virtualProductWithoutManageStock.storefrontStatus}}</expectedResult> + <actualResult type="variable">productStockAvailableStatus</actualResult> + </assertEquals> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteAttributeSetTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteAttributeSetTest.xml new file mode 100644 index 00000000000..4d28ccbd44d --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteAttributeSetTest.xml @@ -0,0 +1,55 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminDeleteAttributeSetTest"> + <annotations> + <features value="Catalog"/> + <title value="Delete Attribute Set"/> + <description value="Admin should be able to delete an attribute set"/> + <testCaseId value="MC-4413"/> + <severity value="CRITICAL"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="CatalogAttributeSet" stepKey="createAttributeSet"/> + <createData entity="SimpleProductWithCustomAttributeSet" stepKey="SimpleProductWithCustomAttributeSet"> + <requiredEntity createDataKey="createCategory"/> + <requiredEntity createDataKey="createAttributeSet"/> + </createData> + </before> + <after> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <amOnPage url="{{AdminProductAttributeSetGridPage.url}}" stepKey="goToAttributeSetsPage"/> + <fillField selector="{{AdminProductAttributeSetGridSection.filter}}" userInput="$$createAttributeSet.attribute_set_name$$" stepKey="filterByAttributeName"/> + <!-- Filter the grid to find created below attribute set --> + <click selector="{{AdminProductAttributeSetGridSection.searchBtn}}" stepKey="clickSearch"/> + <click selector="{{AdminProductAttributeSetGridSection.nthRow('1')}}" stepKey="clickFirstRow"/> + <!-- Delete attribute set and confirm the modal --> + <click selector="{{AdminProductAttributeSetSection.deleteBtn}}" stepKey="clickDelete"/> + <click selector="{{AdminProductAttributeSetSection.modalOk}}" stepKey="confirmDelete"/> + <waitForPageLoad stepKey="waitForDeleteToFinish"/> + <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="The attribute set has been removed." stepKey="deleteMessage"/> + <!-- Assert the attribute set is not in the grid --> + <fillField selector="{{AdminProductAttributeSetGridSection.filter}}" userInput="$$createAttributeSet.attribute_set_name$$" stepKey="filterByAttributeName2"/> + <click selector="{{AdminProductAttributeSetGridSection.searchBtn}}" stepKey="clickSearch2"/> + <see selector="{{AdminDataGridTableSection.dataGridEmpty}}" userInput="We couldn't find any records." stepKey="assertDataGridEmptyMessage"/> + <!-- Search for the product by sku and name on the product page --> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="goToAdminProductIndex"/> + <waitForPageLoad stepKey="waitForAdminProductIndex"/> + <actionGroup ref="filterProductGridBySkuAndName" stepKey="filerProductsBySkuAndName"> + <argument name="product" value="SimpleProductWithCustomAttributeSet"/> + </actionGroup> + <!-- Should not see the product --> + <see selector="{{AdminDataGridTableSection.dataGridEmpty}}" userInput="We couldn't find any records." stepKey="assertDataGridEmptyMessage2"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteProductAttributeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteProductAttributeTest.xml new file mode 100644 index 00000000000..54b83e034fb --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteProductAttributeTest.xml @@ -0,0 +1,64 @@ +<?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="DeleteProductAttributeTest"> + <annotations> + <features value="Catalog"/> + <title value="Delete Product Attribute"/> + <description value="Admin should able to delete a product attribute"/> + <testCaseId value="MC-10887"/> + <severity value="CRITICAL"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <createData entity="productAttributeWysiwyg" stepKey="createProductAttribute"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="deleteProductAttributeByAttributeCode" stepKey="deleteProductAttribute"> + <argument name="ProductAttributeCode" value="$$createProductAttribute.attribute_code$$"/> + </actionGroup> + <!-- Assert the product attribute is not in the grid by Attribute code --> + <actionGroup ref="filterProductAttributeByAttributeCode" stepKey="filterByAttributeCode"> + <argument name="ProductAttributeCode" value="$$createProductAttribute.attribute_code$$"/> + </actionGroup> + <see selector="{{AdminDataGridTableSection.dataGridEmpty}}" userInput="We couldn't find any records." stepKey="assertDataGridEmptyMessage"/> + <!--Assert the product attribute is not in the grid by Default Label --> + <actionGroup ref="filterProductAttributeByDefaultLabel" stepKey="filterByDefaultLabel"> + <argument name="productAttributeLabel" value="$$createProductAttribute.default_frontend_label$$"/> + </actionGroup> + <see selector="{{AdminDataGridTableSection.dataGridEmpty}}" userInput="We couldn't find any records." stepKey="assertDataGridEmptyMessage2"/> + <!--Go to the Catalog > Products page and create Simple Product --> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="amOnProductList"/> + <waitForPageLoad stepKey="waitForProductList"/> + <click selector="{{AdminProductGridActionSection.addProductToggle}}" stepKey="toggleAddProductBtn"/> + <click selector="{{AdminProductGridActionSection.addSimpleProduct}}" stepKey="chooseAddSimpleProduct"/> + <waitForPageLoad stepKey="waitForProductAdded"/> + <!-- Press Add Attribute button --> + <click selector="{{AdminProductFormSection.addAttributeBtn}}" stepKey="clickAddAttributeBtn"/> + <waitForPageLoad stepKey="waitForAttributeAdded"/> + <!-- Filter By Attribute Label on Add Attribute Page --> + <click selector="{{AdminProductFiltersSection.filter}}" stepKey="clickOnFilter"/> + <actionGroup ref="filterProductAttributeByAttributeLabel" stepKey="filterByAttributeLabel"> + <argument name="productAttributeLabel" value="$$createProductAttribute.default_frontend_label$$"/> + </actionGroup> + <see selector="{{AdminDataGridTableSection.dataGridEmpty}}" userInput="We couldn't find any records." stepKey="assertDataGridEmptyMessage3"/> + <!-- Filter By Attribute Code on Export > Products page --> + <amOnPage url="{{AdminExportIndexPage.url}}" stepKey="navigateToSystemExport"/> + <selectOption selector="{{AdminExportMainSection.entityType}}" userInput="Products" stepKey="selectProductsOption"/> + <waitForElementVisible selector="{{AdminExportMainSection.entityAttributes}}" stepKey="waitForElementVisible"/> + <click selector="{{AdminExportAttributeSection.resetFilter}}" stepKey="resetFilter"/> + <fillField selector="{{AdminExportAttributeSection.filterByAttributeCode}}" userInput="$$createProductAttribute.attribute_code$$" stepKey="setAttributeCode"/> + <waitForPageLoad stepKey="waitForUserInput"/> + <click selector="{{AdminExportAttributeSection.search}}" stepKey="searchForAttribute"/> + <see selector="{{AdminDataGridTableSection.dataGridEmpty}}" userInput="We couldn't find any records." stepKey="assertDataGridEmptyMessage4"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteProductWithCustomOptionTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteProductWithCustomOptionTest.xml new file mode 100644 index 00000000000..7f6a1333b72 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteProductWithCustomOptionTest.xml @@ -0,0 +1,54 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminDeleteProductWithCustomOptionTest"> + <annotations> + <features value="Catalog"/> + <stories value="Delete products"/> + <title value="Delete Product with Custom Option"/> + <description value="Admin should be able to delete a product with custom option"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-11015"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="_defaultProduct" stepKey="createSimpleProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <updateData createDataKey="createSimpleProduct" entity="productWithOptions2" stepKey="updateProductWithCustomOption"/> + </before> + <after> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="deleteProductUsingProductGrid" stepKey="deleteSimpleProductFilteredBySkuAndName"> + <argument name="product" value="$$createSimpleProduct$$"/> + </actionGroup> + <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="A total of 1 record(s) have been deleted." stepKey="deleteMessage"/> + <!--Verify product on product page --> + <amOnPage url="{{StorefrontProductPage.url($$createSimpleProduct.name$$)}}" stepKey="amOnSimpleProductPage"/> + <see selector="{{StorefrontProductInfoMainSection.productName}}" userInput="Whoops, our bad..." stepKey="seeWhoops"/> + <!-- Search for the product by sku --> + <fillField selector="{{StorefrontQuickSearchSection.searchPhrase}}" userInput="$$createSimpleProduct.sku$$" stepKey="fillSearchBarByProductSku"/> + <waitForPageLoad stepKey="waitForSearchButton"/> + <click selector="{{StorefrontQuickSearchSection.searchButton}}" stepKey="clickSearchButton"/> + <waitForPageLoad stepKey="waitForSearchResults"/> + <!-- Should not see any search results --> + <dontSee userInput="$$createSimpleProduct.sku$$" selector="{{StorefrontCatalogSearchMainSection.searchResults}}" stepKey="dontSeeProduct"/> + <see selector="{{StorefrontCatalogSearchMainSection.message}}" userInput="Your search returned no results." stepKey="seeCantFindProductOneMessage"/> + <!-- Go to the category page that we created in the before block --> + <amOnPage url="{{StorefrontCategoryPage.url($$createCategory.name$$)}}" stepKey="onCategoryPage"/> + <!-- Should not see the product --> + <dontSee userInput="$$createSimpleProduct.name$$" selector="{{StorefrontCategoryMainSection.productsList}}" stepKey="dontSeeProductInCategory"/> + <see selector="{{StorefrontCategoryMainSection.emptyProductMessage}}" userInput="We can't find products matching the selection." stepKey="seeEmptyProductMessage"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteRootCategoryAssignedToStoreTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteRootCategoryAssignedToStoreTest.xml new file mode 100644 index 00000000000..e4b269dff96 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteRootCategoryAssignedToStoreTest.xml @@ -0,0 +1,51 @@ +<?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="AdminDeleteRootCategoryAssignedToStoreTest"> + <annotations> + <stories value="Delete categories"/> + <title value="Cannot delete root category assigned to some store"/> + <description value="Login as admin and root category can not be deleted when category is assigned with any store."/> + <testCaseId value="MC-6050"/> + <severity value="CRITICAL"/> + <group value="Catalog"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminPanel"/> + <createData entity="NewRootCategory" stepKey="rootCategory" /> + </before> + <after> + <actionGroup ref="DeleteCustomStoreActionGroup" stepKey="deleteCreatedStore"> + <argument name="storeGroupName" value="customStore.code"/> + </actionGroup> + <deleteData createDataKey="rootCategory" stepKey="deleteRootCategory"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <amOnPage url="{{AdminSystemStorePage.url}}" stepKey="amOnAdminSystemStorePage"/> + <waitForPageLoad stepKey="waitForSystemStorePage"/> + <click selector="{{AdminStoresMainActionsSection.createStoreButton}}" stepKey="selectCreateStore"/> + <fillField userInput="{{customStore.name}}" selector="{{AdminNewStoreGroupSection.storeGrpNameTextField}}" stepKey="fillStoreName"/> + <fillField userInput="{{customStore.code}}" selector="{{AdminNewStoreGroupSection.storeGrpCodeTextField}}" stepKey="fillStoreCode"/> + <selectOption userInput="{{NewRootCategory.name}}" selector="{{AdminNewStoreGroupSection.storeRootCategoryDropdown}}" stepKey="selectStoreStatus"/> + <click selector="{{AdminStoresMainActionsSection.saveButton}}" stepKey="clickSaveStoreButton"/> + <see userInput="You saved the store." stepKey="seeSaveMessage"/> + + <!--Verify Delete Root Category can not be deleted--> + <amOnPage url="{{AdminCategoryPage.url}}" stepKey="openAdminCategoryIndexPage1"/> + <waitForPageLoad stepKey="waitForCategoryIndexPageToBeLoaded1"/> + <scrollToTopOfPage stepKey="scrollToTopOfPage2"/> + <click selector="{{AdminCategorySidebarTreeSection.expandAll}}" stepKey="expandToSeeAllCategories"/> + <click selector="{{AdminCategorySidebarTreeSection.categoryInTree(NewRootCategory.name))}}" stepKey="clickRootCategoryInTree"/> + + <!--Verify Delete button is not displayed--> + <dontSeeElement selector="{{AdminCategoryMainActionsSection.DeleteButton}}" stepKey="dontSeeDeleteButton"/> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteRootCategoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteRootCategoryTest.xml new file mode 100644 index 00000000000..e7ab14c7794 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteRootCategoryTest.xml @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminDeleteRootCategoryTest"> + <annotations> + <stories value="Delete categories"/> + <title value="Can delete a root category not assigned to any store"/> + <description value="Login as admin and delete a root category not assigned to any store"/> + <testCaseId value="MC-6048"/> + <severity value="CRITICAL"/> + <group value="Catalog"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminPanel"/> + <createData entity="NewRootCategory" stepKey="rootCategory" /> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Verify Created root Category--> + <amOnPage url="{{AdminCategoryPage.url}}" stepKey="openAdminCategoryIndexPage"/> + <waitForPageLoad stepKey="waitForCategoryIndexPageToBeLoaded"/> + <click selector="{{AdminCategorySidebarTreeSection.expandAll}}" stepKey="expandToSeeAllCategories"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + <seeElement selector="{{AdminCategoryBasicFieldSection.CategoryNameInput(NewRootCategory.name)}}" stepKey="seeRootCategory"/> + + <!--Delete Root Category--> + <deleteData createDataKey="rootCategory" stepKey="deleteRootCategory"/> + + <!--Verify Root Category is not listed in backend--> + <amOnPage url="{{AdminCategoryPage.url}}" stepKey="openAdminCategoryIndexPage1"/> + <waitForPageLoad stepKey="waitForCategoryIndexPageToBeLoaded1"/> + <click selector="{{AdminCategorySidebarTreeSection.expandAll}}" stepKey="expandToSeeAllCategories1"/> + <dontSee selector="{{AdminCategoryBasicFieldSection.CategoryNameInput}}" userInput="{{NewRootCategory.name}}" stepKey="dontSeeRootCategory"/> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteRootSubCategoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteRootSubCategoryTest.xml new file mode 100644 index 00000000000..6df571f403a --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteRootSubCategoryTest.xml @@ -0,0 +1,90 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminDeleteRootSubCategoryTest"> + <annotations> + <stories value="Delete categories"/> + <title value="Can delete a subcategory"/> + <description value="Login as admin and delete a root sub category"/> + <testCaseId value="MC-6049"/> + <severity value="CRITICAL"/> + <group value="Catalog"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminPanel"/> + <createData entity="NewRootCategory" stepKey="rootCategory" /> + <createData entity="SimpleRootSubCategory" stepKey="category"> + <requiredEntity createDataKey="rootCategory"/> + </createData> + </before> + <after> + <actionGroup ref="DeleteCustomStoreActionGroup" stepKey="deleteCreatedStore"> + <argument name="storeGroupName" value="customStore.code"/> + </actionGroup> + <deleteData createDataKey="rootCategory" stepKey="deleteRootCategory"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Create a Store--> + <amOnPage url="{{AdminSystemStorePage.url}}" stepKey="amOnAdminSystemStorePage"/> + <waitForPageLoad stepKey="waitForSystemStorePage"/> + <click selector="{{AdminStoresMainActionsSection.createStoreButton}}" stepKey="selectCreateStore"/> + <fillField userInput="{{customStore.name}}" selector="{{AdminNewStoreGroupSection.storeGrpNameTextField}}" stepKey="fillStoreName"/> + <fillField userInput="{{customStore.code}}" selector="{{AdminNewStoreGroupSection.storeGrpCodeTextField}}" stepKey="fillStoreCode"/> + <selectOption userInput="{{NewRootCategory.name}}" selector="{{AdminNewStoreGroupSection.storeRootCategoryDropdown}}" stepKey="selectStoreStatus"/> + <click selector="{{AdminStoresMainActionsSection.saveButton}}" stepKey="clickSaveStoreButton"/> + <see userInput="You saved the store." stepKey="seeSaveMessage"/> + + <!--Create a Store View--> + <click selector="{{AdminStoresMainActionsSection.createStoreViewButton}}" stepKey="selectCreateStoreView"/> + <click selector="{{AdminNewStoreSection.storeGrpDropdown}}" stepKey="clickDropDown"/> + <selectOption userInput="{{customStore.name}}" selector="{{AdminNewStoreSection.storeGrpDropdown}}" stepKey="selectStoreViewStatus"/> + <fillField userInput="{{customStore.name}}" selector="{{AdminNewStoreSection.storeNameTextField}}" stepKey="fillStoreViewName"/> + <fillField userInput="{{customStore.code}}" selector="{{AdminNewStoreSection.storeCodeTextField}}" stepKey="fillStoreViewCode"/> + <selectOption selector="{{AdminNewStoreSection.statusDropdown}}" userInput="Enabled" stepKey="enableStatus"/> + <click selector="{{AdminStoresMainActionsSection.saveButton}}" stepKey="clickSaveStoreViewButton"/> + <waitForElementVisible selector="{{AdminConfirmationModalSection.ok}}" stepKey="waitForModal" /> + <see selector="{{AdminConfirmationModalSection.title}}" userInput="Warning message" stepKey="seeWarning" /> + <click selector="{{AdminConfirmationModalSection.ok}}" stepKey="dismissModal" /> + <waitForElementNotVisible selector="{{AdminNewStoreViewActionsSection.loadingMask}}" stepKey="waitForElementVisible"/> + <see userInput="You saved the store view." stepKey="seeSaveMessage1"/> + + <!--Go To store front page--> + <amOnPage url="/{{NewRootCategory.name}}/{{SimpleRootSubCategory.name}}.html" stepKey="seeTheCategoryInStoreFrontPage"/> + <waitForPageLoad time="60" stepKey="waitForStoreFrontPageLoad"/> + + <!--Verify subcategory displayed in store front--> + <click selector="{{StorefrontFooterSection.switchStoreButton}}" stepKey="selectMainWebsite"/> + <click selector="{{StorefrontFooterSection.storeLink(customStore.name)}}" stepKey="selectMainWebsite1"/> + <waitForPageLoad stepKey="waitForCategoryToLoad"/> + <seeElement selector="{{StorefrontHeaderSection.NavigationCategoryByName(SimpleRootSubCategory.name)}}" stepKey="seeSubCategoryInStoreFront"/> + + <!--Delete SubCategory--> + <deleteData createDataKey="category" stepKey="deleteCategory"/> + + <!--Verify Sub Category is absent in backend --> + <amOnPage url="{{AdminCategoryPage.url}}" stepKey="openAdminCategoryIndexPage"/> + <waitForPageLoad stepKey="waitForCategoryIndexPageToBeLoaded"/> + <click selector="{{AdminCategorySidebarTreeSection.expandAll}}" stepKey="expandToSeeAllCategories2"/> + <dontSee selector="{{AdminCategorySidebarTreeSection.categoryInTree(SimpleRootSubCategory.name)}}" stepKey="dontSeeCategoryInTree"/> + + <!--Verify Sub Category is not present in Store Front--> + <amOnPage url="/{{NewRootCategory.name}}/{{SimpleSubCategory.name}}.html" stepKey="seeTheCategoryInStoreFrontPage1"/> + <waitForPageLoad time="60" stepKey="waitForStoreFrontPageLoad2"/> + <dontSeeElement selector="{{StorefrontHeaderSection.NavigationCategoryByName(SimpleSubCategory.name)}}" stepKey="dontSeeSubCategoryInStoreFront"/> + + <!--Verify in Category is not in Url Rewrite grid--> + <amOnPage url="{{AdminUrlRewriteIndexPage.url}}" stepKey="openUrlRewriteIndexPage"/> + <waitForPageLoad stepKey="waitForUrlRewritePageTopLoad"/> + <fillField selector="{{AdminUrlRewriteIndexSection.requestPathFilter}}" userInput="{{SimpleRootSubCategory.url_key}}" stepKey="fillRequestPath"/> + <click selector="{{AdminUrlRewriteIndexSection.searchButton}}" stepKey="clickOnSearchButton"/> + <see selector="{{AdminUrlRewriteIndexSection.emptyRecordMessage}}" userInput="We couldn't find any records." stepKey="seeEmptyRow"/> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteSimpleProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteSimpleProductTest.xml new file mode 100644 index 00000000000..7c460a3dfc5 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteSimpleProductTest.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="AdminDeleteSimpleProductTest"> + <annotations> + <features value="Catalog"/> + <stories value="Delete products"/> + <title value="Delete Simple Product"/> + <description value="Admin should be able to delete a simple product"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-11013"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="_defaultProduct" stepKey="createSimpleProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + </before> + <after> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="deleteProductUsingProductGrid" stepKey="deleteSimpleProductFilteredBySkuAndName"> + <argument name="product" value="$$createSimpleProduct$$"/> + </actionGroup> + <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="A total of 1 record(s) have been deleted." stepKey="deleteMessage"/> + <!--Verify product on Product Page --> + <amOnPage url="{{StorefrontProductPage.url($$createSimpleProduct.name$$)}}" stepKey="amOnSimpleProductPage"/> + <see selector="{{StorefrontProductInfoMainSection.productName}}" userInput="Whoops, our bad..." stepKey="seeWhoops"/> + <!-- Search for the product by sku --> + <fillField selector="{{StorefrontQuickSearchSection.searchPhrase}}" userInput="$$createSimpleProduct.sku$$" stepKey="fillSearchBarByProductSku"/> + <waitForPageLoad stepKey="waitForSearchButton"/> + <click selector="{{StorefrontQuickSearchSection.searchButton}}" stepKey="clickSearchButton"/> + <waitForPageLoad stepKey="waitForSearchResults"/> + <!-- Should not see any search results --> + <dontSee userInput="$$createSimpleProduct.sku$$" selector="{{StorefrontCatalogSearchMainSection.searchResults}}" stepKey="dontSeeProduct"/> + <see selector="{{StorefrontCatalogSearchMainSection.message}}" userInput="Your search returned no results." stepKey="seeCantFindProductOneMessage"/> + <!-- Go to the category page that we created in the before block --> + <amOnPage url="{{StorefrontCategoryPage.url($$createCategory.name$$)}}" stepKey="onCategoryPage"/> + <!-- Should not see the product --> + <dontSee userInput="$$createSimpleProduct.name$$" selector="{{StorefrontCategoryMainSection.productsList}}" stepKey="dontSeeProductInCategory"/> + <see selector="{{StorefrontCategoryMainSection.emptyProductMessage}}" userInput="We can't find products matching the selection." stepKey="seeEmptyProductMessage"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteVirtualProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteVirtualProductTest.xml new file mode 100644 index 00000000000..413d53d1c37 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteVirtualProductTest.xml @@ -0,0 +1,54 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminDeleteVirtualProductTest"> + <annotations> + <features value="Catalog"/> + <stories value="Delete products"/> + <title value="Delete Virtual Product"/> + <description value="Admin should be able to delete a virtual product"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-11014"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="defaultVirtualProduct" stepKey="createVirtualProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + </before> + <after> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <actionGroup ref="deleteProductUsingProductGrid" stepKey="deleteVirtualProductFilteredBySkuAndName"> + <argument name="product" value="$$createVirtualProduct$$"/> + </actionGroup> + <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="A total of 1 record(s) have been deleted." stepKey="deleteMessage"/> + <!--Verify product on product page --> + <amOnPage url="{{StorefrontProductPage.url($$createVirtualProduct.name$$)}}" stepKey="amOnVirtualProductPage"/> + <see selector="{{StorefrontProductInfoMainSection.productName}}" userInput="Whoops, our bad..." stepKey="seeWhoops"/> + <!-- Search for the product by sku --> + <fillField selector="{{StorefrontQuickSearchSection.searchPhrase}}" userInput="$$createVirtualProduct.sku$$" stepKey="fillSearchBarByProductSku"/> + <waitForPageLoad stepKey="waitForSearchButton"/> + <click selector="{{StorefrontQuickSearchSection.searchButton}}" stepKey="clickSearchButton"/> + <waitForPageLoad stepKey="waitForSearchResults"/> + <!-- Should not see any search results --> + <dontSee userInput="$$createVirtualProduct.sku$$" selector="{{StorefrontCatalogSearchMainSection.searchResults}}" stepKey="dontSeeProduct"/> + <see selector="{{StorefrontCatalogSearchMainSection.message}}" userInput="Your search returned no results." stepKey="seeCantFindProductOneMessage"/> + <!-- Go to the category page that we created in the before block --> + <amOnPage url="{{StorefrontCategoryPage.url($$createCategory.name$$)}}" stepKey="onCategoryPage"/> + <!-- Should not see the product --> + <dontSee userInput="$$createVirtualProduct.name$$" selector="{{StorefrontCategoryMainSection.productsList}}" stepKey="dontSeeProductInCategory"/> + <see selector="{{StorefrontCategoryMainSection.emptyProductMessage}}" userInput="We can't find products matching the selection." stepKey="seeEmptyProductMessage"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminFilteringCategoryProductsUsingScopeSelectorTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminFilteringCategoryProductsUsingScopeSelectorTest.xml index efceff6ffb1..5c434ecabf8 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminFilteringCategoryProductsUsingScopeSelectorTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminFilteringCategoryProductsUsingScopeSelectorTest.xml @@ -10,7 +10,7 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminFilteringCategoryProductsUsingScopeSelectorTest"> <annotations> - <features value="Catalog"/> + <stories value="Filtering Category Products"/> <title value="Filtering Category Products using scope selector"/> <description value="Filtering Category Products using scope selector"/> <severity value="MAJOR"/> @@ -19,19 +19,19 @@ </annotations> <before> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> - <!--Create website, Sore adn Store View--> - <actionGroup ref="AdminCreateWebsiteActionGroup" stepKey="adminCreateWebsite"> - <argument name="newWebsiteName" value="secondWebsite"/> - <argument name="websiteCode" value="second_website"/> + <!--Create website, Store and Store View--> + <actionGroup ref="AdminCreateWebsiteActionGroup" stepKey="createSecondWebsite"> + <argument name="newWebsiteName" value="{{secondCustomWebsite.name}}"/> + <argument name="websiteCode" value="{{secondCustomWebsite.code}}"/> </actionGroup> - <actionGroup ref="AdminCreateNewStoreGroupActionGroup" stepKey="adminCreateStore"> - <argument name="website" value="secondWebsite"/> - <argument name="storeGroupName" value="secondStore"/> - <argument name="storeGroupCode" value="second_store"/> + <actionGroup ref="AdminCreateNewStoreGroupActionGroup" stepKey="createSecondStoreGroup"> + <argument name="website" value="{{secondCustomWebsite.name}}"/> + <argument name="storeGroupName" value="{{SecondStoreGroupUnique.name}}"/> + <argument name="storeGroupCode" value="{{SecondStoreGroupUnique.code}}"/> </actionGroup> - <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="adminCreateStoreView"> - <argument name="StoreGroup" value="customStoreTierPrice"/> - <argument name="customStore" value="customStoreView"/> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createSecondStoreView"> + <argument name="StoreGroup" value="SecondStoreGroupUnique"/> + <argument name="customStore" value="SecondStoreUnique"/> </actionGroup> <!--Create Simple Product and Category --> @@ -60,9 +60,7 @@ <click selector="{{ProductInWebsitesSection.sectionHeader}}" stepKey="clickToOpenWebsiteSection"/> <waitForPageLoad stepKey="waitForToOpenedWebsiteSection"/> <uncheckOption selector="{{ProductInWebsitesSection.website('Main Website')}}" stepKey="uncheckWebsite"/> - <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="clickSaveProduct"/> - <see selector="{{AdminProductMessagesSection.successMessage}}" userInput="You saved the product." - stepKey="seeSuccessMessage"/> + <actionGroup ref="saveProductForm" stepKey="saveProduct1"/> <!-- Set filter to product name and product2 in website 2 only --> <actionGroup ref="SearchForProductOnBackendActionGroup" stepKey="searchForProduct2"> @@ -72,12 +70,10 @@ <argument name="product" value="$$createProduct2$$"/> </actionGroup> <actionGroup ref="SelectProductInWebsitesActionGroup" stepKey="selectProductInWebsites"> - <argument name="website" value="secondWebsite"/> + <argument name="website" value="{{secondCustomWebsite.name}}"/> </actionGroup> <uncheckOption selector="{{ProductInWebsitesSection.website('Main Website')}}" stepKey="uncheckWebsite1"/> - <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="clickSaveProduct1"/> - <see selector="{{AdminProductMessagesSection.successMessage}}" userInput="You saved the product." - stepKey="seeSuccessMessage1"/> + <actionGroup ref="saveProductForm" stepKey="saveProduct2"/> <!-- Set filter to product name and product12 assigned to both websites 1 and 2 --> <actionGroup ref="SearchForProductOnBackendActionGroup" stepKey="searchForProduct12"> @@ -87,15 +83,13 @@ <argument name="product" value="$$createProduct12$$"/> </actionGroup> <actionGroup ref="SelectProductInWebsitesActionGroup" stepKey="selectProductInWebsites1"> - <argument name="website" value="secondWebsite"/> + <argument name="website" value="{{secondCustomWebsite.name}}"/> </actionGroup> - <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="clickSaveProduct2"/> - <see selector="{{AdminProductMessagesSection.successMessage}}" userInput="You saved the product." - stepKey="seeSuccessMessage2"/> + <actionGroup ref="saveProductForm" stepKey="saveProduct3"/> </before> <after> <actionGroup ref="AdminDeleteWebsiteActionGroup" stepKey="deleteWebsite"> - <argument name="websiteName" value="secondWebsite"/> + <argument name="websiteName" value="{{secondCustomWebsite.name}}"/> </actionGroup> <actionGroup ref="ClearProductsFilterActionGroup" stepKey="clearProductsFilter"/> <deleteData createDataKey="createProduct0" stepKey="deleteProduct"/> @@ -107,7 +101,6 @@ </after> <!-- Step 1-2: Open Category page and Set scope selector to All Store Views--> <amOnPage url="{{AdminCategoryPage.url}}" stepKey="goToCategoryPage"/> - <waitForPageLoad stepKey="waitForCategoryPageLoad"/> <click selector="{{AdminCategorySidebarTreeSection.categoryInTree($$createCategory.name$$)}}" stepKey="clickCategoryName"/> <click selector="{{AdminCategoryProductsSection.sectionHeader}}" stepKey="openProductSection"/> @@ -126,17 +119,9 @@ <!-- Step 3: Set scope selector to Website1( Storeview for the Website 1) --> <scrollToTopOfPage stepKey="scrollToTopOfPage"/> - <click selector="{{AdminCategoryMainActionsSection.CategoryStoreViewDropdownToggle}}" - stepKey="clickStoresList"/> - <waitForPageLoad stepKey="waitForCategoryPageLoad1"/> - <click selector="{{AdminCategoryMainActionsSection.CategoryStoreViewOption('Default Store View')}}" - stepKey="clickStoreView"/> - <waitForElementVisible selector="{{AdminCategoryMainActionsSection.CategoryStoreViewModalAccept}}" - stepKey="waitForPopup1"/> - <click selector="{{AdminCategoryMainActionsSection.CategoryStoreViewModalAccept}}" stepKey="clickActionAccept"/> - <waitForElementNotVisible selector="{{AdminCategoryMainActionsSection.CategoryStoreViewModalAccept}}" - stepKey="waitForNotVisibleModalAccept"/> - <waitForPageLoad stepKey="waitForCategoryPageLoad2"/> + <actionGroup ref="AdminSwitchStoreViewActionGroup" stepKey="swichToDefaultStoreView"> + <argument name="storeView" value="_defaultStore.name"/> + </actionGroup> <grabTextFrom selector="{{AdminCategorySidebarTreeSection.categoryInTree($$createCategory.name$$)}}" stepKey="grabTextFromCategory1"/> <assertRegExp expected="/\(2\)$/" expectedType="string" actual="$grabTextFromCategory1" actualType="variable" @@ -154,18 +139,9 @@ <!-- Step 4: Set scope selector to Website2 ( StoreView for Website 2) --> <scrollToTopOfPage stepKey="scrollToTopOfPage1"/> - <click selector="{{AdminCategoryMainActionsSection.CategoryStoreViewDropdownToggle}}" - stepKey="clickStoresList1"/> - <waitForPageLoad stepKey="waitForCategoryPageLoad3"/> - <click selector="{{AdminCategoryMainActionsSection.CategoryStoreViewOption('secondStoreView')}}" - stepKey="clickStoreView1"/> - <waitForElementVisible selector="{{AdminCategoryMainActionsSection.CategoryStoreViewModalAccept}}" - stepKey="waitForPopup2"/> - <click selector="{{AdminCategoryMainActionsSection.CategoryStoreViewModalAccept}}" - stepKey="clickActionAccept1"/> - <waitForElementNotVisible selector="{{AdminCategoryMainActionsSection.CategoryStoreViewModalAccept}}" - stepKey="waitForNotVisibleModalAccept1"/> - <waitForPageLoad stepKey="waitForCategoryPageLoad4"/> + <actionGroup ref="AdminSwitchStoreViewActionGroup" stepKey="swichToSecondStoreView"> + <argument name="storeView" value="SecondStoreUnique.name"/> + </actionGroup> <click selector="{{AdminCategoryProductsSection.sectionHeader}}" stepKey="openProductSection2"/> <grabTextFrom selector="{{AdminCategorySidebarTreeSection.categoryInTree($$createCategory.name$$)}}" stepKey="grabTextFromCategory2"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassProductPriceUpdateTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassProductPriceUpdateTest.xml new file mode 100644 index 00000000000..d7607b4b269 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassProductPriceUpdateTest.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="AdminMassProductPriceUpdateTest"> + <annotations> + <stories value="Mass product update "/> + <features value="Catalog"/> + <title value="Mass update simple product price"/> + <description value="Login as admin and update mass product price"/> + <testCaseId value="MC-8510"/> + <severity value="CRITICAL"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminPanel"/> + <createData entity="defaultSimpleProduct" stepKey="simpleProduct1"/> + <createData entity="defaultSimpleProduct" stepKey="simpleProduct2"/> + </before> + <after> + <deleteData createDataKey="simpleProduct1" stepKey="deleteSimpleProduct1"/> + <deleteData createDataKey="simpleProduct2" stepKey="deleteSimpleProduct2"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Open Product Index Page--> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="navigateToProductIndex"/> + <waitForPageLoad stepKey="waitForProductIndexPageToLoad"/> + + <!--Search products using keyword --> + <actionGroup ref="searchProductGridByKeyword2" stepKey="searchByKeyword"> + <argument name="keyword" value="Testp"/> + </actionGroup> + + <!--Sort Products by ID in descending order--> + <actionGroup ref="sortProductsByIdDescending" stepKey="sortProductsByIdDescending"/> + + <!--Select products--> + <checkOption selector="{{AdminProductGridSection.productRowCheckboxBySku($$simpleProduct1.sku$$)}}" stepKey="selectFirstProduct"/> + <checkOption selector="{{AdminProductGridSection.productRowCheckboxBySku($$simpleProduct2.sku$$)}}" stepKey="selectSecondProduct"/> + + <!-- Update product price--> + <click selector="{{AdminProductGridSection.bulkActionDropdown}}" stepKey="clickDropdown"/> + <click selector="{{AdminProductGridSection.bulkActionOption('Update attributes')}}" stepKey="clickChangeStatus"/> + <waitForPageLoad stepKey="waitForProductAttributePageToLoad"/> + <scrollTo stepKey="scrollToPriceCheckBox" selector="{{AdminEditProductAttributesSection.ChangeAttributePriceToggle}}" x="0" y="-160"/> + <click selector="{{AdminEditProductAttributesSection.ChangeAttributePriceToggle}}" stepKey="selectPriceCheckBox"/> + <fillField stepKey="fillPrice" selector="{{AdminEditProductAttributesSection.AttributePrice}}" userInput="90.99"/> + <click stepKey="clickOnSaveButton" selector="{{AdminEditProductAttributesSection.Save}}"/> + <waitForPageLoad stepKey="waitForUpdatedProductToSave" /> + <see selector="{{AdminProductMessagesSection.successMessage}}" userInput="A total of 2 record(s) were updated." stepKey="seeAttributeUpateSuccessMsg"/> + + <!--Verify product name, sku and updated price--> + <click stepKey="openFirstProduct" selector="{{AdminProductGridSection.productRowBySku($$simpleProduct1.sku$$)}}"/> + <waitForPageLoad stepKey="waitForFirstProductToLoad"/> + <seeInField stepKey="seeFirstProductNameInField" selector="{{AdminProductFormSection.productName}}" userInput="$$simpleProduct1.name$$"/> + <seeInField stepKey="seeFirstProductSkuInField" selector="{{AdminProductFormSection.productSku}}" userInput="$$simpleProduct1.sku$$"/> + <seeInField stepKey="seeFirstProductPriceInField" selector="{{AdminProductFormSection.productPrice}}" userInput="90.99"/> + <click stepKey="clickOnBackButton" selector="{{AdminGridMainControls.back}}"/> + <waitForPageLoad stepKey="waitForProductsToLoad"/> + <click stepKey="openSecondProduct" selector="{{AdminProductGridSection.productRowBySku($$simpleProduct2.sku$$)}}"/> + <waitForPageLoad stepKey="waitForSecondProductToLoad"/> + <seeInField stepKey="seeSecondProductNameInField" selector="{{AdminProductFormSection.productName}}" userInput="$$simpleProduct2.name$$"/> + <seeInField stepKey="seeSecondProductSkuInField" selector="{{AdminProductFormSection.productSku}}" userInput="$$simpleProduct2.sku$$"/> + <seeInField stepKey="seeSecondProductPriceInField" selector="{{AdminProductFormSection.productPrice}}" userInput="90.99"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveAnchoredCategoryToDefaultCategoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveAnchoredCategoryToDefaultCategoryTest.xml new file mode 100644 index 00000000000..247711295a5 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveAnchoredCategoryToDefaultCategoryTest.xml @@ -0,0 +1,123 @@ +<?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="AdminMoveAnchoredCategoryToDefaultCategoryTest"> + <annotations> + <stories value="Move categories"/> + <title value="Move default anchored subcategory with anchored parent to default subcategory"/> + <description value="Login as admin,move anchored subcategory with anchored parent to default subcategory"/> + <testCaseId value="MC-6493"/> + <severity value="CRITICAL"/> + <group value="mtf_migrated"/> + <features value="Catalog"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminPanel"/> + <createData entity="_defaultCategory" stepKey="createDefaultCategory"/> + <createData entity="defaultSimpleProduct" stepKey="simpleProduct"/> + </before> + <after> + <deleteData createDataKey="createDefaultCategory" stepKey="deleteDefaultCategory"/> + <deleteData createDataKey="simpleProduct" stepKey="deleteSimpleProduct"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--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(_defaultCategory.name)}}" stepKey="selectCategory"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + + <!--Enable Anchor for _defaultCategory 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"/> + <waitForPageLoad stepKey="waitForSecondCategoryToSave"/> + <seeElement selector="{{AdminCategoryMessagesSection.SuccessMessage}}" stepKey="seeSuccessMessage"/> + + <!--Enable Anchor for FirstLevelSubCat Category--> + <click selector="{{AdminCategorySidebarActionSection.AddSubcategoryButton}}" stepKey="clickOnAddSubCategoryButton"/> + <fillField selector="{{AdminCategoryBasicFieldSection.CategoryNameInput}}" userInput="{{FirstLevelSubCat.name}}" stepKey="addSubCategoryName"/> + <scrollTo selector="{{CategoryDisplaySettingsSection.DisplaySettingTab}}" x="0" y="-80" stepKey="scrollToDisplaySetting1"/> + <click selector="{{CategoryDisplaySettingsSection.DisplaySettingTab}}" stepKey="selectDisplaySetting1"/> + <checkOption selector="{{CategoryDisplaySettingsSection.anchor}}" stepKey="enableAnchor1"/> + <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="saveSubCategory1"/> + <waitForPageLoad stepKey="waitForSecondCategoryToSave1"/> + <seeElement selector="{{AdminCategoryMessagesSection.SuccessMessage}}" stepKey="seeSuccessMessage1"/> + + <!--Enable Anchor for SimpleSubCategory Category and add products to the Category--> + <click selector="{{AdminCategorySidebarActionSection.AddSubcategoryButton}}" stepKey="clickOnAddSubCategoryButton1"/> + <fillField selector="{{AdminCategoryBasicFieldSection.CategoryNameInput}}" userInput="{{SimpleSubCategory.name}}" stepKey="addSubCategoryName1"/> + <scrollTo selector="{{CategoryDisplaySettingsSection.DisplaySettingTab}}" x="0" y="-80" stepKey="scrollToDisplaySetting2"/> + <click selector="{{CategoryDisplaySettingsSection.DisplaySettingTab}}" stepKey="selectDisplaySetting2"/> + <checkOption selector="{{CategoryDisplaySettingsSection.anchor}}" stepKey="enableAnchor2"/> + <scrollTo selector="{{AdminCategoryBasicFieldSection.productsInCategory}}" x="0" y="-80" stepKey="scrollToProductInCategory1"/> + <click selector="{{AdminCategoryBasicFieldSection.productsInCategory}}" stepKey="clickOnProductInCategory"/> + <fillField selector="{{AdminCategoryContentSection.productTableColumnName}}" userInput="$$simpleProduct.name$$" stepKey="selectProduct"/> + <click selector="{{AdminCategoryContentSection.productSearch}}" stepKey="clickSearchButton"/> + <click selector="{{AdminCategoryContentSection.productTableRow}}" stepKey="selectProductFromTableRow"/> + <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="saveSubCategory2"/> + <waitForPageLoad stepKey="waitForSecondCategoryToSave2"/> + <seeElement selector="{{AdminCategoryMessagesSection.SuccessMessage}}" stepKey="seeSuccessMessage2"/> + + <!--Open Category in store front page--> + <amOnPage url="/$$createDefaultCategory.name$$/{{FirstLevelSubCat.name}}/{{SimpleSubCategory.name}}.html" stepKey="seeTheCategoryInStoreFrontPage"/> + <waitForPageLoad stepKey="waitForStoreFrontPageLoad"/> + <seeElement selector="{{StorefrontHeaderSection.NavigationCategoryByName(_defaultCategory.name)}}" stepKey="seeDefaultCategoryOnStoreNavigationBar"/> + <dontSeeElement selector="{{StorefrontHeaderSection.NavigationCategoryByName(SimpleSubCategory.name)}}" stepKey="dontSeeSubCategoryOnStoreNavigationBar"/> + + <!--<Verify breadcrumbs in store front page--> + <grabMultiple selector="{{StorefrontNavigationSection.categoryBreadcrumbs}}" stepKey="breadcrumbs"/> + <assertEquals stepKey="verifyTheCategoryInStoreFrontPage"> + <expectedResult type="array">['Home', $$createDefaultCategory.name$$,{{FirstLevelSubCat.name}}, {{SimpleSubCategory.name}}]</expectedResult> + <actualResult type="variable">breadcrumbs</actualResult> + </assertEquals> + + <!--Verify Product displayed in category store front page--> + <click selector="{{StorefrontCategoryMainSection.productLink}}" stepKey="openSearchedProduct"/> + <waitForPageLoad stepKey="waitForProductToLoad1"/> + <see selector="{{StorefrontProductInfoMainSection.productName}}" userInput="{{defaultSimpleProduct.name}}" stepKey="assertProductName"/> + + <!--Open Category Page--> + <amOnPage url="{{AdminCategoryPage.url}}" stepKey="openAdminCategoryIndexPage1"/> + <waitForPageLoad stepKey="waitForPageToLoad1"/> + <click selector="{{AdminCategorySidebarTreeSection.expandAll}}" stepKey="clickOnExpandTree2"/> + <waitForPageLoad stepKey="waitForPageToLoad2"/> + + <!--Move SubCategory under Default Category--> + <dragAndDrop selector1="{{AdminCategorySidebarTreeSection.categoryInTree(SimpleSubCategory.name)}}" selector2="{{AdminCategorySidebarTreeSection.categoryInTree('Default Category')}}" stepKey="moveCategory"/> + <see selector="{{AdminCategoryModalSection.message}}" userInput="This operation can take a long time" stepKey="seeWarningMessage"/> + <click selector="{{AdminCategoryModalSection.ok}}" stepKey="clickOkButtonOnWarningPopup"/> + <waitForPageLoad stepKey="waitForPageToLoad3"/> + <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You moved the category." stepKey="seeSuccessMoveMessage"/> + <amOnPage url="/{{SimpleSubCategory.name}}.html" stepKey="seeTheCategoryInStoreFrontPage1"/> + <waitForPageLoad stepKey="waitForStoreFrontPageLoad1"/> + + <!--Verify breadcrumbs in store front page after the move--> + <grabMultiple selector="{{StorefrontNavigationSection.categoryBreadcrumbs}}" stepKey="breadcrumbsAfterMove"/> + <assertEquals stepKey="verifyBreadcrumbsInFrontPageAfterMove"> + <expectedResult type="array">['Home',{{SimpleSubCategory.name}}]</expectedResult> + <actualResult type="variable">breadcrumbsAfterMove</actualResult> + </assertEquals> + + <!--Open Category in store front--> + <amOnPage url="{{StorefrontCategoryPage.url(SimpleSubCategory.name)}}" stepKey="amOnCategoryPage"/> + <waitForPageLoad stepKey="waitForPageToBeLoaded"/> + <seeElement selector="{{StorefrontCategoryMainSection.CategoryTitle(SimpleSubCategory.name)}}" stepKey="seeCategoryInTitle"/> + <seeElement selector="{{StorefrontHeaderSection.NavigationCategoryByName(SimpleSubCategory.name)}}" stepKey="seeCategoryOnStoreNavigationBarAfterMove"/> + <click selector="{{StorefrontCategoryMainSection.productLink}}" stepKey="openSearchedProduct1"/> + <waitForPageLoad stepKey="waitForProductToLoad2"/> + + <!--Verify product name on Store Front--> + <see selector="{{StorefrontProductInfoMainSection.productName}}" userInput="{{defaultSimpleProduct.name}}" stepKey="assertProductNameAfterMove"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveCategoryAndCheckUrlRewritesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveCategoryAndCheckUrlRewritesTest.xml new file mode 100644 index 00000000000..ba6e6a43674 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveCategoryAndCheckUrlRewritesTest.xml @@ -0,0 +1,113 @@ +<?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="AdminMoveCategoryAndCheckUrlRewritesTest"> + <annotations> + <stories value="Move categories"/> + <title value="URL Rewrites for subcategories during creation and move"/> + <description value="Login as admin, move category from one to another and check category url rewrites"/> + <testCaseId value="MC-6494"/> + <features value="Catalog"/> + <severity value="CRITICAL"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminPanel"/> + <createData entity="_defaultCategory" stepKey="createDefaultCategory"/> + </before> + <after> + <deleteData createDataKey="createDefaultCategory" stepKey="deleteCategory"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--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(_defaultCategory.name)}}" stepKey="selectCategory"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + + <!--Create second level category--> + <click selector="{{AdminCategorySidebarActionSection.AddSubcategoryButton}}" stepKey="clickOnAddSubCategoryButton"/> + <fillField selector="{{AdminCategoryBasicFieldSection.CategoryNameInput}}" userInput="{{SubCategory.name}}" stepKey="addSubCategoryName"/> + <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="saveSubCategory1"/> + <waitForPageLoad stepKey="waitForSecondCategoryToSave1"/> + <seeElement selector="{{AdminCategoryMessagesSection.SuccessMessage}}" stepKey="seeSuccessMessage1"/> + + <!--Create third level category under second level category--> + <click selector="{{AdminCategorySidebarActionSection.AddSubcategoryButton}}" stepKey="clickOnAddSubCategoryButton1"/> + <fillField selector="{{AdminCategoryBasicFieldSection.CategoryNameInput}}" userInput="{{SimpleSubCategory.name}}" stepKey="addSubCategoryName1"/> + <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="saveSubCategory2"/> + <waitForPageLoad stepKey="waitForSecondCategoryToSave2"/> + <seeElement selector="{{AdminCategoryMessagesSection.SuccessMessage}}" stepKey="seeSuccessMessage2"/> + <grabFromCurrentUrl stepKey="categoryId" regex="#\/([0-9]*)?\/$#" /> + + <!--Open Url Rewrite Page--> + <amOnPage url="{{AdminUrlRewriteIndexPage.url}}" stepKey="openUrlRewriteIndexPage"/> + <waitForPageLoad stepKey="waitForUrlRewritePage"/> + + <!--Search third level category Redirect Path, Target Path and Redirect Type--> + <fillField selector="{{AdminUrlRewriteIndexSection.requestPathFilter}}" userInput="{{SimpleSubCategory.name_lwr}}" stepKey="fillRedirectPathFilter"/> + <click selector="{{AdminUrlRewriteIndexSection.searchButton}}" stepKey="clickOnSearchButton"/> + <waitForPageLoad stepKey="waitForPageToLoad0"/> + + <!--Verify Category RedirectType--> + <see stepKey="verifyTheRedirectType" selector="{{AdminUrlRewriteIndexSection.redirectTypeColumn('1')}}" userInput="No" /> + + <!--Verify Redirect Path --> + <see selector="{{AdminUrlRewriteIndexSection.requestPathColumn('1')}}" userInput="{{_defaultCategory.name_lwr}}2/{{SubCategory.name_lwr}}/{{SimpleSubCategory.name_lwr}}.html" stepKey="verifyTheRedirectPath"/> + + <!--Verify Category Target Path--> + <see stepKey="verifyTheTargetPath" selector="{{AdminUrlRewriteIndexSection.targetPathColumn('1')}}" userInput="catalog/category/view/id/{$categoryId}"/> + + <!--Open Category Page --> + <amOnPage url="{{AdminCategoryPage.url}}" stepKey="openAdminCategoryIndexPage1"/> + <waitForPageLoad stepKey="waitForPageToLoad1"/> + <click selector="{{AdminCategorySidebarTreeSection.expandAll}}" stepKey="clickOnExpandTree2"/> + <waitForPageLoad stepKey="waitForPageToLoad2"/> + + <!--Move the third level category under first level category --> + <dragAndDrop selector1="{{AdminCategorySidebarTreeSection.categoryInTree(SimpleSubCategory.name)}}" selector2="{{AdminCategorySidebarTreeSection.categoryInTree(_defaultCategory.name)}}" stepKey="moveCategory"/> + <see selector="{{AdminCategoryModalSection.message}}" userInput="This operation can take a long time" stepKey="seeWarningMessage"/> + <click selector="{{AdminCategoryModalSection.ok}}" stepKey="clickOkButtonOnWarningPopup"/> + <waitForPageLoad stepKey="waitForPageToLoad3"/> + <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You moved the category." stepKey="seeSuccessMoveMessage"/> + + <!--Open Url Rewrite page --> + <amOnPage url="{{AdminUrlRewriteIndexPage.url}}" stepKey="openUrlRewriteIndexPage1"/> + <waitForPageLoad stepKey="waitForUrlRewritePage1"/> + <fillField selector="{{AdminUrlRewriteIndexSection.requestPathFilter}}" userInput="{{_defaultCategory.name_lwr}}2/{{SimpleSubCategory.name_lwr}}.html" stepKey="fillCategoryUrlKey1"/> + <click selector="{{AdminUrlRewriteIndexSection.searchButton}}" stepKey="clickOnSearchButton1"/> + <waitForPageLoad stepKey="waitForPageToLoad4"/> + + <!--Verify new Redirect Path after move --> + <see stepKey="verifyTheRequestPathAfterMove" selector="{{AdminUrlRewriteIndexSection.requestPathColumn('1')}}" userInput="{{_defaultCategory.name_lwr}}2/{{SimpleSubCategory.name_lwr}}.html" /> + + <!--Verify new Target Path after move --> + <see stepKey="verifyTheTargetPathAfterMove" selector="{{AdminUrlRewriteIndexSection.targetPathColumn('1')}}" userInput="catalog/category/view/id/{$categoryId}" /> + + <!--Verify new RedirectType after move --> + <see stepKey="verifyTheRedirectTypeAfterMove" selector="{{AdminUrlRewriteIndexSection.redirectTypeColumn('1')}}" userInput="No" /> + + <!--Verify before move Redirect Path displayed with associated Target Path and Redirect Type--> + <fillField selector="{{AdminUrlRewriteIndexSection.requestPathFilter}}" userInput="{{SimpleSubCategory.name_lwr}}" stepKey="fillCategoryUrlKey2"/> + <click selector="{{AdminUrlRewriteIndexSection.searchButton}}" stepKey="clickOnSearchButton2"/> + <waitForPageLoad stepKey="waitForPageToLoad5"/> + <see stepKey="verifyTheRedirectTypeAfterMove1" selector="{{AdminUrlRewriteIndexSection.redirectTypeColumn('1')}}" userInput="Permanent (301)" /> + <see stepKey="verifyTheRequestPathAfterMove1" selector="{{AdminUrlRewriteIndexSection.requestPathColumn('1')}}" userInput="{{_defaultCategory.name_lwr}}2/{{SubCategory.name_lwr}}/{{SimpleSubCategory.name_lwr}}.html" /> + <see stepKey="verifyTheTargetPathAfterMove1" selector="{{AdminUrlRewriteIndexSection.targetPathColumn('1')}}" userInput="{{_defaultCategory.name_lwr}}2/{{SimpleSubCategory.name_lwr}}.html" /> + + <!--Verify before move Redirect Path directs to the category page--> + <amOnPage url="{{_defaultCategory.name_lwr}}2/{{SubCategory.name_lwr}}/{{SimpleSubCategory.name_lwr}}.html" stepKey="openCategoryStoreFrontPage"/> + <waitForPageLoad stepKey="waitForCategoryPageToLoad"/> + <seeElement selector="{{StorefrontHeaderSection.NavigationCategoryByName(_defaultCategory.name)}}" stepKey="seeCategoryOnStoreNavigationBar"/> + <seeElement selector="{{StorefrontCategoryMainSection.CategoryTitle(SimpleSubCategory.name)}}" stepKey="seeCategoryInTitle"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveCategoryFromParentAnchoredCategoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveCategoryFromParentAnchoredCategoryTest.xml new file mode 100644 index 00000000000..d17078d794b --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveCategoryFromParentAnchoredCategoryTest.xml @@ -0,0 +1,117 @@ +<?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="AdminMoveCategoryFromParentAnchoredCategoryTest"> + <annotations> + <stories value="Move categories"/> + <title value="Move default subcategory with anchored parent to default subcategory"/> + <description value="Login as admin,move subcategory with anchored parent to default subcategory"/> + <testCaseId value="MC-6492"/> + <severity value="CRITICAL"/> + <group value="mtf_migrated"/> + <features value="Catalog"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminPanel"/> + <createData entity="defaultSimpleProduct" stepKey="simpleProduct"/> + <createData entity="_defaultCategory" stepKey="createDefaultCategory"/> + </before> + <after> + <deleteData createDataKey="simpleProduct" stepKey="deleteSimpleProduct"/> + <deleteData createDataKey="createDefaultCategory" stepKey="deleteDefaultCategory"/> + <actionGroup ref="DeleteCategory" stepKey="deleteCategory"> + <argument name="categoryEntity" value="SimpleSubCategory"/> + </actionGroup> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--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(_defaultCategory.name)}}" stepKey="selectCategory"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + + <!--Enable Anchor for _defaultCategory 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"/> + + <!--Create a Subcategory under _defaultCategory category--> + <click selector="{{AdminCategorySidebarActionSection.AddSubcategoryButton}}" stepKey="clickOnAddSubCategoryButton"/> + <fillField selector="{{AdminCategoryBasicFieldSection.CategoryNameInput}}" userInput="{{SimpleSubCategory.name}}" stepKey="addSubCategoryName"/> + + <!--Add a product to SimpleSubCategory category--> + <scrollTo selector="{{AdminCategoryBasicFieldSection.productsInCategory}}" x="0" y="-80" stepKey="scrollToProductInCategory"/> + <click selector="{{AdminCategoryBasicFieldSection.productsInCategory}}" stepKey="clickOnProductInCategory"/> + <fillField selector="{{AdminCategoryContentSection.productTableColumnName}}" userInput="$$simpleProduct.name$$" stepKey="selectProduct"/> + <click selector="{{AdminCategoryContentSection.productSearch}}" stepKey="clickSearchButton"/> + <click selector="{{AdminCategoryContentSection.productTableRow}}" stepKey="selectProductFromTableRow"/> + <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="saveSubCategory1"/> + <waitForPageLoad stepKey="waitForSecondCategoryToSave"/> + <seeElement selector="{{AdminCategoryMessagesSection.SuccessMessage}}" stepKey="seeSuccessMessage"/> + + <!--Verify category displayed in store front page--> + <amOnPage url="/$$createDefaultCategory.name$$/{{SimpleSubCategory.name}}.html" stepKey="seeTheCategoryInStoreFrontPage"/> + <waitForPageLoad stepKey="waitForStoreFrontPageLoad"/> + <seeElement selector="{{StorefrontHeaderSection.NavigationCategoryByName(_defaultCategory.name)}}" stepKey="seeDefaultCategoryOnStoreNavigationBar"/> + <dontSeeElement selector="{{StorefrontHeaderSection.NavigationCategoryByName(SimpleSubCategory.name)}}" stepKey="dontSeeSubCategoryOnStoreNavigationBar"/> + + <!--Check category breadcrumbs in store front page--> + <grabMultiple selector="{{StorefrontNavigationSection.categoryBreadcrumbs}}" stepKey="breadcrumbs"/> + <assertEquals stepKey="verifyTheCategoryInStoreFrontPage"> + <expectedResult type="array">['Home', $$createDefaultCategory.name$$,{{SimpleSubCategory.name}}]</expectedResult> + <actualResult type="variable">breadcrumbs</actualResult> + </assertEquals> + + <!--Verify Product displayed in category store front page--> + <click selector="{{StorefrontCategoryMainSection.productLink}}" stepKey="openSearchedProduct"/> + <waitForPageLoad stepKey="waitForProductToLoad1"/> + <see selector="{{StorefrontProductInfoMainSection.productName}}" userInput="{{defaultSimpleProduct.name}}" stepKey="assertProductName"/> + + <!--Open Category Page--> + <amOnPage url="{{AdminCategoryPage.url}}" stepKey="openAdminCategoryIndexPage1"/> + <waitForPageLoad stepKey="waitForPageToLoad1"/> + <click selector="{{AdminCategorySidebarTreeSection.expandAll}}" stepKey="clickOnExpandTree2"/> + <waitForPageLoad stepKey="waitForPageToLoad2"/> + + <!--Move SubCategory under Default Category--> + <dragAndDrop selector1="{{AdminCategorySidebarTreeSection.categoryInTree(SimpleSubCategory.name)}}" selector2="{{AdminCategorySidebarTreeSection.categoryInTree('Default Category')}}" stepKey="moveCategory"/> + <see selector="{{AdminCategoryModalSection.message}}" userInput="This operation can take a long time" stepKey="seeWarningMessage"/> + <click selector="{{AdminCategoryModalSection.ok}}" stepKey="clickOkButtonOnWarningPopup"/> + <waitForPageLoad stepKey="waitForPageToLoad3"/> + <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You moved the category." stepKey="seeSuccessMoveMessage"/> + + <!--Open category in store front page--> + <amOnPage url="/{{SimpleSubCategory.name}}.html" stepKey="seeTheCategoryInStoreFrontPage1"/> + <waitForPageLoad stepKey="waitForStoreFrontPageLoad1"/> + + <!--Verify breadcrumbs after the move in store front page--> + <grabMultiple selector="{{StorefrontNavigationSection.categoryBreadcrumbs}}" stepKey="breadcrumbsAfterMove"/> + <assertEquals stepKey="verifyBreadcrumbsInFrontPageAfterMove"> + <expectedResult type="array">['Home',{{SimpleSubCategory.name}}]</expectedResult> + <actualResult type="variable">breadcrumbsAfterMove</actualResult> + </assertEquals> + + <!--Open category store front page --> + <amOnPage url="{{StorefrontCategoryPage.url(SimpleSubCategory.name)}}" stepKey="amOnCategoryPage"/> + <waitForPageLoad stepKey="waitForPageToBeLoaded"/> + + <!--Verify Category in store front--> + <seeElement selector="{{StorefrontCategoryMainSection.CategoryTitle(SimpleSubCategory.name)}}" stepKey="seeCategoryInTitle"/> + <seeElement selector="{{StorefrontHeaderSection.NavigationCategoryByName(SimpleSubCategory.name)}}" stepKey="seeSubCategoryOnStoreNavigationBarAfterMove"/> + <click selector="{{StorefrontCategoryMainSection.productLink}}" stepKey="openSearchedProduct1"/> + <waitForPageLoad stepKey="waitForProductToLoad2"/> + + <!--Verify product name on Store Front--> + <see selector="{{StorefrontProductInfoMainSection.productName}}" userInput="{{defaultSimpleProduct.name}}" stepKey="assertProductNameAfterMove"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveCategoryToAnotherPositionInCategoryTreeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveCategoryToAnotherPositionInCategoryTreeTest.xml new file mode 100644 index 00000000000..9831f73e078 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveCategoryToAnotherPositionInCategoryTreeTest.xml @@ -0,0 +1,116 @@ +<?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="AdminMoveCategoryToAnotherPositionInCategoryTreeTest"> + <annotations> + <stories value="Move categories"/> + <title value="Move Category to Another Position in Category Tree"/> + <description value="Test log in to Move Category and Move Category to Another Position in Category Tree"/> + <testCaseId value="MC-13612"/> + <severity value="CRITICAL"/> + <group value="catalog"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminPanel"/> + <createData entity="_defaultCategory" stepKey="createDefaultCategory"/> + </before> + <after> + <deleteData createDataKey="createDefaultCategory" stepKey="deleteDefaultCategory"/> + <actionGroup ref="DeleteCategory" stepKey="SecondLevelSubCat"> + <argument name="categoryEntity" value="SecondLevelSubCat"/> + </actionGroup> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Open Category Page --> + <amOnPage url="{{AdminCategoryPage.url}}" stepKey="openAdminCategoryIndexPage"/> + <waitForPageLoad stepKey="waitForPageToLoaded"/> + <click selector="{{AdminCategorySidebarTreeSection.expandAll}}" stepKey="clickExpandTree"/> + <waitForPageLoad stepKey="waitForCategoryToLoad"/> + <click selector="{{AdminCategorySidebarTreeSection.categoryInTree(_defaultCategory.name)}}" stepKey="selectCategory"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + <!-- Create three level deep sub Category --> + <click selector="{{AdminCategorySidebarActionSection.AddSubcategoryButton}}" stepKey="clickAddSubCategoryButton"/> + <fillField selector="{{AdminCategoryBasicFieldSection.CategoryNameInput}}" userInput="{{FirstLevelSubCat.name}}" stepKey="fillSubCategoryName"/> + <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="saveFirstLevelSubCategory"/> + <waitForPageLoad stepKey="waitForFirstLevelCategoryToSave"/> + <seeElement selector="{{AdminCategoryMessagesSection.SuccessMessage}}" stepKey="seeSuccessMessage"/> + <click selector="{{AdminCategorySidebarActionSection.AddSubcategoryButton}}" stepKey="clickOnAddSubCategoryButtonAgain"/> + <fillField selector="{{AdminCategoryBasicFieldSection.CategoryNameInput}}" userInput="{{SecondLevelSubCat.name}}" stepKey="fillSecondLevelSubCategoryName"/> + <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="saveSecondLevelSubCategory"/> + <waitForPageLoad stepKey="waitForSecondLevelCategoryToSave"/> + <seeElement selector="{{AdminCategoryMessagesSection.SuccessMessage}}" stepKey="seeSaveSuccessMessage"/> + <grabFromCurrentUrl stepKey="categoryId" regex="#\/([0-9]*)?\/$#" /> + + <!-- Move Category to another position in category tree, but click cancel button --> + <dragAndDrop selector1="{{AdminCategorySidebarTreeSection.categoryInTree(SecondLevelSubCat.name)}}" selector2="{{AdminCategorySidebarTreeSection.categoryInTree('Default Category')}}" stepKey="moveCategory"/> + <see selector="{{AdminCategoryModalSection.message}}" userInput="This operation can take a long time" stepKey="seeWarningMessage"/> + <click selector="{{AdminCategoryModalSection.cancel}}" stepKey="clickCancelButtonOnWarningPopup"/> + <!-- Verify Category in store front page after clicking cancel button --> + <amOnPage url="/$$createDefaultCategory.name$$/{{FirstLevelSubCat.name}}/{{SecondLevelSubCat.name}}.html" stepKey="seeTheCategoryInStoreFrontPage"/> + <waitForPageLoad stepKey="waitForStoreFrontPageLoad"/> + <seeElement selector="{{StorefrontHeaderSection.NavigationCategoryByName(_defaultCategory.name)}}" stepKey="seeDefaultCategoryOnStoreNavigationBar"/> + <dontSeeElement selector="{{StorefrontHeaderSection.NavigationCategoryByName(SimpleSubCategory.name)}}" stepKey="dontSeeSubCategoryOnStoreNavigationBar"/> + <!-- Verify breadcrumbs in store front page after clicking cancel button --> + <grabMultiple selector="{{StorefrontNavigationSection.categoryBreadcrumbs}}" stepKey="breadcrumbs"/> + <assertEquals stepKey="verifyTheCategoryInStoreFrontPage"> + <expectedResult type="array">['Home', $$createDefaultCategory.name$$,{{FirstLevelSubCat.name}},{{SecondLevelSubCat.name}}]</expectedResult> + <actualResult type="variable">breadcrumbs</actualResult> + </assertEquals> + + <!-- Move Category to another position in category tree and click ok button--> + <amOnPage url="{{AdminCategoryPage.url}}" stepKey="openTheAdminCategoryIndexPage"/> + <waitForPageLoad stepKey="waitTillPageLoad"/> + <click selector="{{AdminCategorySidebarTreeSection.expandAll}}" stepKey="clickOnExpandTree"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <dragAndDrop selector1="{{AdminCategorySidebarTreeSection.categoryInTree(SecondLevelSubCat.name)}}" selector2="{{AdminCategorySidebarTreeSection.categoryInTree('Default Category')}}" stepKey="DragCategory"/> + <see selector="{{AdminCategoryModalSection.message}}" userInput="This operation can take a long time" stepKey="seeWarningMessageForOneMoreTime"/> + <click selector="{{AdminCategoryModalSection.ok}}" stepKey="clickOkButtonOnWarningPopup"/> + <waitForPageLoad stepKey="waitTheForPageToLoad"/> + <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You moved the category." stepKey="seeSuccessMoveMessage"/> + <amOnPage url="/{{SimpleSubCategory.name}}.html" stepKey="seeCategoryNameInStoreFrontPage"/> + <waitForPageLoad stepKey="waitForStoreFrontPageToLoad"/> + <!-- Verify Category in store front after moving category to another position in category tree --> + <amOnPage url="{{StorefrontCategoryPage.url(SecondLevelSubCat.name)}}" stepKey="amOnCategoryPage"/> + <waitForPageLoad stepKey="waitForPageToBeLoaded"/> + <seeElement selector="{{StorefrontCategoryMainSection.CategoryTitle(SecondLevelSubCat.name)}}" stepKey="seeCategoryInTitle"/> + <seeElement selector="{{StorefrontHeaderSection.NavigationCategoryByName(SecondLevelSubCat.name)}}" stepKey="seeCategoryOnStoreNavigationBarAfterMove"/> + <!-- Verify breadcrumbs in store front page after moving category to another position in category tree --> + <click selector="{{StorefrontHeaderSection.NavigationCategoryByName(SecondLevelSubCat.name)}}" stepKey="clickCategoryOnNavigation"/> + <waitForPageLoad stepKey="waitForCategoryLoad"/> + <grabMultiple selector="{{StorefrontNavigationSection.categoryBreadcrumbs}}" stepKey="breadcrumbsAfterMove"/> + <assertEquals stepKey="verifyBreadcrumbsInFrontPageAfterMove"> + <expectedResult type="array">['Home',{{SecondLevelSubCat.name}}]</expectedResult> + <actualResult type="variable">breadcrumbsAfterMove</actualResult> + </assertEquals> + + <!-- Open Url Rewrite page and see the url rewrite for the moved category --> + <amOnPage url="{{AdminUrlRewriteIndexPage.url}}" stepKey="openUrlRewriteIndexPage"/> + <waitForPageLoad stepKey="waitForUrlRewritePageLoad"/> + <fillField selector="{{AdminUrlRewriteIndexSection.requestPathFilter}}" userInput="{{SecondLevelSubCat.name_lwr}}.html" stepKey="fillCategoryUrlKey"/> + <click selector="{{AdminUrlRewriteIndexSection.searchButton}}" stepKey="clickOnSearchButton"/> + <waitForPageLoad stepKey="waitForUrlPageToLoad"/> + <!-- Verify new Redirect Path after move --> + <see selector="{{AdminUrlRewriteIndexSection.requestPathColumn('2')}}" userInput="{{SecondLevelSubCat.name_lwr}}.html" stepKey="verifyTheRequestPathAfterMove"/> + <!-- Verify new Target Path after move --> + <see selector="{{AdminUrlRewriteIndexSection.targetPathColumn('2')}}" userInput="catalog/category/view/id/{$categoryId}" stepKey="verifyTheTargetPathAfterMove"/> + <!-- Verify new RedirectType after move --> + <see selector="{{AdminUrlRewriteIndexSection.redirectTypeColumn('2')}}" userInput="No" stepKey="verifyTheRedirectTypeAfterMove"/> + <!-- Verify before move Redirect Path displayed with associated Target Path and Redirect Type--> + <fillField selector="{{AdminUrlRewriteIndexSection.requestPathFilter}}" userInput="{{SecondLevelSubCat.name_lwr}}" stepKey="fillTheCategoryUrlKey"/> + <click selector="{{AdminUrlRewriteIndexSection.searchButton}}" stepKey="clickOnSearchButton2"/> + <waitForPageLoad stepKey="waitForSearch"/> + <see selector="{{AdminUrlRewriteIndexSection.redirectTypeColumn('1')}}" userInput="Permanent (301)" stepKey="verifyTheRedirectTypeBeforeMove"/> + <see selector="{{AdminUrlRewriteIndexSection.requestPathColumn('1')}}" userInput="{{_defaultCategory.name_lwr}}2/{{FirstLevelSubCat.name_lwr}}/{{SecondLevelSubCat.name_lwr}}.html" stepKey="verifyTheRequestPathBeforeMove"/> + <see selector="{{AdminUrlRewriteIndexSection.targetPathColumn('1')}}" userInput="{{SecondLevelSubCat.name_lwr}}.html" stepKey="verifyTheTargetPathBeforeMove"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminNavigateMultipleUpSellProductsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminNavigateMultipleUpSellProductsTest.xml new file mode 100644 index 00000000000..bcd4ca85312 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminNavigateMultipleUpSellProductsTest.xml @@ -0,0 +1,183 @@ +<?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="AdminNavigateMultipleUpSellProductsTest"> + <annotations> + <stories value="Up Sell products"/> + <title value="Promote Multiple Products (Simple, Configurable) as Up-Sell Products"/> + <description value="Login as admin and add simple and configurable Products as Up-Sell products"/> + <testCaseId value="MC-8902"/> + <severity value="CRITICAL"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <!--Create Simple Products--> + <createData entity="SimpleSubCategory" stepKey="createCategory1"/> + <createData entity="SimpleProduct" stepKey="createSimpleProduct"> + <requiredEntity createDataKey="createCategory1"/> + </createData> + <createData entity="SimpleSubCategory" stepKey="createCategory2"/> + <createData entity="SimpleProduct" stepKey="createSimpleProduct1"> + <requiredEntity createDataKey="createCategory2"/> + </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="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"/> + </createData> + <createData entity="ApiSimpleTwo" stepKey="createConfigChildProduct2"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption2"/> + </createData> + <createData entity="ConfigurableProductTwoOptions" stepKey="createConfigProductOption"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption1"/> + <requiredEntity createDataKey="getConfigAttributeOption2"/> + </createData> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddChild1"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigChildProduct1"/> + </createData> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddChild2"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigChildProduct2"/> + </createData> + + <!--Login as admin--> + <actionGroup ref="LoginAsAdmin" stepKey="login"/> + </before> + <after> + <!--Logout as admin--> + <actionGroup ref="logout" stepKey="logout"/> + + <!--Delete created data--> + <deleteData createDataKey="createConfigProduct" stepKey="deleteConfigProduct"/> + <deleteData createDataKey="createSimpleProduct1" stepKey="deleteSimpleProduct1"/> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createCategory1" stepKey="deleteSubCategory1"/> + <deleteData createDataKey="createCategory2" stepKey="deleteCategory2"/> + <deleteData createDataKey="createConfigChildProduct2" stepKey="deletecreateConfigChildProduct2"/> + <deleteData createDataKey="createConfigChildProduct1" stepKey="deletecreateConfigChildProduct1"/> + <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteConfigProductAttribute"/> + </after> + + <!--Open Product Index Page--> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="navigateToProductIndex"/> + <waitForPageLoad stepKey="waitForProductIndexPageToLoad"/> + + <!--Select SimpleProduct --> + <actionGroup ref="filterProductGridBySku" stepKey="findCreatedProduct"> + <argument name="product" value="$$createSimpleProduct$$"/> + </actionGroup> + <click stepKey="openFirstProduct" selector="{{AdminProductGridSection.productRowBySku($$createSimpleProduct.sku$$)}}"/> + <waitForPageLoad stepKey="waitForProductToLoad"/> + + <!--Add SimpleProduct1 and ConfigProduct as Up sell products--> + <click stepKey="clickOnRelatedProducts" selector="{{AdminProductFormRelatedUpSellCrossSellSection.relatedProductsHeader}}"/> + <click stepKey="clickOnAddUpSellProducts" selector="{{AdminProductFormRelatedUpSellCrossSellSection.addUpSellProduct}}"/> + <actionGroup ref="filterProductGridBySku2" stepKey="filterProduct"> + <argument name="sku" value="$$createSimpleProduct1.sku$$"/> + </actionGroup> + <waitForPageLoad stepKey="waitForTheProductToLoad"/> + <checkOption selector="{{AdminAddProductsToGroupPanel.firstCheckbox}}" stepKey="selectTheSimpleProduct2"/> + <click stepKey="addSelectedProduct" selector="{{AdminAddRelatedProductsModalSection.AddUpSellProductsButton}}"/> + <waitForPageLoad stepKey="waitForProductToBeAdded"/> + <click stepKey="clickOnAddUpSellProductsButton" selector="{{AdminProductFormRelatedUpSellCrossSellSection.addUpSellProduct}}"/> + <actionGroup ref="filterProductGridBySku2" stepKey="filterConfigurableProduct"> + <argument name="sku" value="$$createConfigProduct.sku$$"/> + </actionGroup> + <waitForPageLoad stepKey="waitForTheConfigProductToLoad"/> + <checkOption selector="{{AdminAddProductsToGroupPanel.firstCheckbox}}" stepKey="selectTheConfigProduct"/> + <click stepKey="addSelectedProductButton" selector="{{AdminAddRelatedProductsModalSection.AddUpSellProductsButton}}"/> + <waitForPageLoad stepKey="waitForConfigProductToBeAdded"/> + <click stepKey="clickOnRelatedProducts1" selector="{{AdminProductFormRelatedUpSellCrossSellSection.relatedProductsHeader}}"/> + <click stepKey="clickOnSaveButton" selector="{{AdminProductFormActionSection.saveButton}}"/> + <waitForPageLoad stepKey="waitForLoading1"/> + <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the product." stepKey="messageYouSavedTheProductIsShown"/> + + <!--Go to Product Index Page --> + <click stepKey="clickOnBackButton" selector="{{AdminGridMainControls.back}}"/> + <waitForPageLoad stepKey="waitForProductsToBeLoaded"/> + + <!--Select Configurable Product--> + <actionGroup ref="filterProductGridBySku" stepKey="findConfigProduct"> + <argument name="product" value="$$createConfigProduct$$"/> + </actionGroup> + <click stepKey="openConfigProduct" selector="{{AdminProductGridSection.productRowBySku($$createConfigProduct.sku$$)}}"/> + <waitForPageLoad stepKey="waitForConfigProductToLoad"/> + + <!--Add SimpleProduct1 as Up Sell Product--> + <click stepKey="clickOnRelatedProductsHeader" selector="{{AdminProductFormRelatedUpSellCrossSellSection.relatedProductsHeader}}"/> + <click stepKey="clickOnAddUpSellProductsButton1" selector="{{AdminProductFormRelatedUpSellCrossSellSection.addUpSellProduct}}"/> + <actionGroup ref="filterProductGridBySku2" stepKey="filterSimpleProduct2"> + <argument name="sku" value="$$createSimpleProduct1.sku$$"/> + </actionGroup> + <waitForPageLoad stepKey="waitForTheSimpleProduct2ToBeLoaded"/> + <checkOption selector="{{AdminAddProductsToGroupPanel.firstCheckbox}}" stepKey="selectSimpleProduct1"/> + <click stepKey="addSimpleProduct2" selector="{{AdminAddRelatedProductsModalSection.AddUpSellProductsButton}}"/> + <waitForPageLoad stepKey="waitForSimpleProductToBeAdded"/> + <scrollTo selector="{{AdminProductFormActionSection.saveButton}}" stepKey="scrollToTheSaveButton"/> + <click stepKey="clickOnSaveButton1" selector="{{AdminProductFormActionSection.saveButton}}"/> + <waitForPageLoad stepKey="waitForLoading2"/> + <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the product." stepKey="messageYouSavedTheProductIsShown1"/> + <waitForPageLoad stepKey="waitForUpdatesTobeSaved1"/> + + <!--Go to SimpleProduct store front page--> + <amOnPage url="$$createSimpleProduct.sku$$.html" stepKey="goToSimpleProductFrontPage"/> + <waitForPageLoad stepKey="waitForProduct"/> + <see stepKey="seeProductName" userInput="$$createSimpleProduct.sku$$" selector="{{StorefrontProductInfoMainSection.productName}}"/> + <scrollTo stepKey="scrollToTheUpSellHeading" selector="{{StorefrontProductUpSellProductsSection.upSellHeading}}"/> + + <!--Verify Up Sell Products displayed in SimpleProduct page--> + <see stepKey="seeTheUpSellHeading" selector="{{StorefrontProductUpSellProductsSection.upSellHeading}}" userInput="We found other products you might like!"/> + <see stepKey="seeSimpleProduct1" selector="{{StorefrontProductUpSellProductsSection.upSellProducts}}" userInput="$$createSimpleProduct1.name$$"/> + <see stepKey="seeConfigProduct" selector="{{StorefrontProductUpSellProductsSection.upSellProducts}}" userInput="$$createConfigProduct.name$$"/> + + <!--Go to Config Product store front page--> + <amOnPage url="$$createConfigProduct.sku$$.html" stepKey="goToConfigProductFrontPage"/> + <waitForPageLoad stepKey="waitForConfigProductToBeLoaded"/> + <scrollTo stepKey="scrollToTheUpSellHeading1" selector="{{StorefrontProductUpSellProductsSection.upSellHeading}}"/> + + <!--Verify Up Sell Products displayed in ConfigProduct page--> + <see stepKey="seeTheUpSellHeading1" selector="{{StorefrontProductUpSellProductsSection.upSellHeading}}" userInput="We found other products you might like!"/> + <see stepKey="seeSimpleProduct2" selector="{{StorefrontProductUpSellProductsSection.upSellProducts}}" userInput="$$createSimpleProduct1.name$$"/> + + <!--Go to SimpleProduct1 store front page--> + <amOnPage url="$$createSimpleProduct1.sku$$.html" stepKey="goToSimpleProduct1FrontPage"/> + <waitForPageLoad stepKey="waitForSimpleProduct1ToBeLoaded"/> + + <!--Verify No Up Sell Products displayed in SimplProduct1 page--> + <dontSee stepKey="dontSeeTheUpSellHeading1" selector="{{StorefrontProductUpSellProductsSection.upSellHeading}}" userInput="We found other products you might like!"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductImageAssignmentForMultipleStoresTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductImageAssignmentForMultipleStoresTest.xml new file mode 100644 index 00000000000..37fbf01a6b9 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductImageAssignmentForMultipleStoresTest.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="AdminProductImageAssignmentForMultipleStoresTest"> + <annotations> + <features value="Catalog"/> + <stories value="Product image assignment for multiple stores"/> + <title value="Product image assignment for multiple stores"/> + <description value="Product image assignment for multiple stores"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-58718"/> + <group value="product"/> + <group value="WYSIWYGDisabled"/> + <skip> + <issueId value="MC-13841"/> + </skip> + </annotations> + <before> + <!-- Login Admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <!-- Create Store View English --> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createStoreViewEn"> + <argument name="customStore" value="customStoreEN"/> + </actionGroup> + <!-- Create Store View France --> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createStoreViewFr"> + <argument name="customStore" value="customStoreFR"/> + </actionGroup> + <!-- Create Category and Simple Product --> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="_defaultProduct" stepKey="createSimpleProduct"> + <requiredEntity createDataKey="createCategory"/> + <field key="price">100</field> + </createData> + </before> + <after> + <!-- Delete Store View English --> + <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteStoreViewEn"> + <argument name="customStore" value="customStoreEN"/> + </actionGroup> + <!-- Delete Store View France --> + <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteStoreViewFr"> + <argument name="customStore" value="customStoreFR"/> + </actionGroup> + <!-- Clear Filter Store --> + <actionGroup ref="clearFiltersAdminDataGrid" stepKey="resetFiltersOnStorePage"/> + <!-- Delete Category and Simple Product --> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteProduct"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <!-- Clear Filter Product --> + <actionGroup ref="AdminClearFiltersActionGroup" stepKey="clearProductFilters"/> + <!-- Logout Admin --> + <actionGroup ref="logout" stepKey="logoutOfAdmin"/> + </after> + <!-- Search Product and Open Edit --> + <actionGroup ref="SearchForProductOnBackendActionGroup" stepKey="searchProduct"> + <argument name="product" value="$$createSimpleProduct$$"/> + </actionGroup> + <actionGroup ref="OpenEditProductOnBackendActionGroup" stepKey="openEditProduct"> + <argument name="product" value="$$createSimpleProduct$$"/> + </actionGroup> + + <!-- Switch to the English store view --> + <actionGroup ref="AdminSwitchStoreViewActionGroup" stepKey="switchStoreViewEnglishProduct"> + <argument name="storeView" value="customStoreEN.name"/> + </actionGroup> + + <!-- Upload Image English --> + <actionGroup ref="addProductImage" stepKey="uploadImageEnglish"/> + <actionGroup ref="saveProductForm" stepKey="saveProduct1"/> + + <!-- Switch to the French store view --> + <actionGroup ref="AdminSwitchStoreViewActionGroup" stepKey="switchStoreViewFrenchProduct"> + <argument name="storeView" value="customStoreFR.name"/> + </actionGroup> + + <!-- Upload Image French --> + <actionGroup ref="addProductImage" stepKey="uploadImageFrench"> + <argument name="image" value="Magento3"/> + </actionGroup> + <actionGroup ref="AdminAssignImageRolesActionGroup" stepKey="assignImageRole1"> + <argument name="image" value="Magento3"/> + </actionGroup> + <actionGroup ref="saveProductForm" stepKey="saveProduct2"/> + + <!-- Switch to the All store view --> + <actionGroup ref="AdminSwitchToAllStoreViewActionGroup" stepKey="switchAllStoreViewProduct"/> + + <!-- Upload Image All Store View --> + <actionGroup ref="addProductImage" stepKey="uploadImageAllStoreView"> + <argument name="image" value="TestImageNew"/> + </actionGroup> + <actionGroup ref="AdminAssignImageRolesActionGroup" stepKey="assignImageRole"> + <argument name="image" value="TestImageNew"/> + </actionGroup> + + <!-- Change any product data product description --> + <click selector="{{AdminProductContentSection.sectionHeader}}" stepKey="openDescriptionDropDown"/> + <fillField selector="{{AdminProductContentSection.descriptionTextArea}}" userInput="This is the long description" stepKey="fillLongDescription"/> + <fillField selector="{{AdminProductContentSection.shortDescriptionTextArea}}" userInput="This is the short description" stepKey="fillShortDescription"/> + <actionGroup ref="saveProductForm" stepKey="saveProduct"/> + + <!-- Go to Product Page and see Default Store View--> + <amOnPage url="{{StorefrontProductPage.url($$createSimpleProduct.custom_attributes[url_key]$$)}}" stepKey="goToDefaultStorefrontProductPage"/> + <seeElement selector="{{StorefrontProductMediaSection.productImageActive(TestImageNew.filename)}}" stepKey="seeActiveImageDefault"/> + + <!-- English Switch Store View and see English Store View --> + <actionGroup ref="StorefrontSwitchStoreViewActionGroup" stepKey="switchStoreViewEnglish"> + <argument name="storeView" value="customStoreEN"/> + </actionGroup> + <click selector="{{StorefrontHeaderSection.NavigationCategoryByName($$createCategory.name$$)}}" stepKey="openCategoryPage"/> + <waitForPageLoad time="30" stepKey="waitForCategoryPage"/> + <seeElement selector="{{StorefrontCategoryProductSection.ProductImageBySrc(ProductImage.fileName)}}" stepKey="seeThumb"/> + <click selector="{{StorefrontCategoryProductSection.ProductTitleByName($$createSimpleProduct.name$$)}}" stepKey="openProductPage"/> + <waitForPageLoad time="30" stepKey="waitForProductPage"/> + <seeElement selector="{{StorefrontProductMediaSection.productImageActive(ProductImage.filename)}}" stepKey="seeActiveImageEnglish"/> + + <!-- Switch France Store View and see France Store View --> + <actionGroup ref="StorefrontSwitchStoreViewActionGroup" stepKey="switchStoreViewFrance"> + <argument name="storeView" value="customStoreFR"/> + </actionGroup> + <click selector="{{StorefrontHeaderSection.NavigationCategoryByName($$createCategory.name$$)}}" stepKey="openCategoryPage1"/> + <waitForPageLoad time="30" stepKey="waitForCategoryPage1"/> + <seeElement selector="{{StorefrontCategoryProductSection.ProductImageBySrc(Magento3.fileName)}}" stepKey="seeThumb1"/> + <click selector="{{StorefrontCategoryProductSection.ProductTitleByName($$createSimpleProduct.name$$)}}" stepKey="openProductPage1"/> + <waitForPageLoad time="30" stepKey="waitForProductPage1"/> + <seeElement selector="{{StorefrontProductMediaSection.productImageActive(Magento3.filename)}}" stepKey="seeActiveImageFrance"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveDefaultVideoSimpleProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveDefaultVideoSimpleProductTest.xml index 1bd218d18c2..876eedb9347 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveDefaultVideoSimpleProductTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveDefaultVideoSimpleProductTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminRemoveDefaultVideoSimpleProductTest"> <annotations> <features value="Catalog"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveDefaultVideoVirtualProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveDefaultVideoVirtualProductTest.xml index e6d3978cad7..8b3b38d0ece 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveDefaultVideoVirtualProductTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveDefaultVideoVirtualProductTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminRemoveDefaultVideoVirtualProductTest" extends="AdminRemoveDefaultVideoSimpleProductTest"> <annotations> <features value="Catalog"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminTierPriceNotAvailableForProductOptionsWithoutTierPriceTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminTierPriceNotAvailableForProductOptionsWithoutTierPriceTest.xml new file mode 100644 index 00000000000..3086f4398e0 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminTierPriceNotAvailableForProductOptionsWithoutTierPriceTest.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="AdminTierPriceNotAvailableForProductOptionsWithoutTierPriceTest"> + <annotations> + <features value="Catalog"/> + <title value="Check that 'trie price' block not available for simple product from options without 'trie price'"/> + <description value="Check that 'trie price' block not available for simple product from options without 'trie price'"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-97050"/> + <useCaseId value="MAGETWO-96842"/> + <group value="catalog"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + + <!--Create category--> + <createData entity="SimpleSubCategory" stepKey="createCategory"/> + + <!-- 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 created data--> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createConfigProduct" stepKey="deleteConfigProduct"/> + <deleteData createDataKey="createConfigChildProduct1" stepKey="deleteConfigChildProduct1"/> + <deleteData createDataKey="createConfigChildProduct2" stepKey="deleteConfigChildProduct2"/> + <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteConfigProductAttribute"/> + + <actionGroup ref="logout" stepKey="logoutOfAdmin"/> + </after> + + <!--Go to storefront product page an check price box css--> + <amOnPage url="{{StorefrontProductPage.url($$createConfigProduct.custom_attributes[url_key]$$)}}" stepKey="navigateToSimpleProductPage"/> + <waitForPageLoad stepKey="waitForStoreFrontLoad"/> + <selectOption selector="{{StorefrontProductInfoMainSection.productAttributeOptionsSelectButton}}" userInput="$$getConfigAttributeOption1.value$$" stepKey="selectOption"/> + <grabAttributeFrom selector="{{StorefrontProductInfoMainSection.productPrice}}" userInput="class" stepKey="grabGrabPriceClass"/> + <assertNotContains actual="$grabGrabPriceClass" expected=".price-box .price-tier_price" expectedType="string" stepKey="assertNotEquals"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryAndCheckDefaultUrlKeyOnStoreViewTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryAndCheckDefaultUrlKeyOnStoreViewTest.xml new file mode 100644 index 00000000000..d8d462f850f --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryAndCheckDefaultUrlKeyOnStoreViewTest.xml @@ -0,0 +1,77 @@ +<?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="AdminUpdateCategoryAndCheckDefaultUrlKeyOnStoreViewTest"> + <annotations> + <stories value="Update categories"/> + <title value="Update category, check default URL key on the custom store view"/> + <description value="Login as admin and update category and check default URL Key on custom store view"/> + <testCaseId value="MC-6063"/> + <severity value="CRITICAL"/> + <group value="Catalog"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminPanel"/> + <createData entity="NewRootCategory" stepKey="rootCategory"/> + <createData entity="SimpleRootSubCategory" stepKey="category"> + <requiredEntity createDataKey="rootCategory"/> + </createData> + </before> + <after> + <actionGroup ref="DeleteCustomStoreActionGroup" stepKey="deleteCustomStore"> + <argument name="storeGroupName" value="customStore.name"/> + </actionGroup> + <deleteData createDataKey="rootCategory" stepKey="deleteRootCategory"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Open Store Page --> + <amOnPage url="{{AdminSystemStorePage.url}}" stepKey="amOnAdminSystemStorePage"/> + <waitForPageLoad stepKey="waitForSystemStorePage"/> + + <!--Create Custom Store --> + <click selector="{{AdminStoresMainActionsSection.createStoreButton}}" stepKey="selectCreateStore"/> + <fillField userInput="{{customStore.name}}" selector="{{AdminNewStoreGroupSection.storeGrpNameTextField}}" stepKey="fillStoreName"/> + <fillField userInput="{{customStore.code}}" selector="{{AdminNewStoreGroupSection.storeGrpCodeTextField}}" stepKey="fillStoreCode"/> + <selectOption userInput="{{NewRootCategory.name}}" selector="{{AdminNewStoreGroupSection.storeRootCategoryDropdown}}" stepKey="selectStoreStatus"/> + <click selector="{{AdminStoresMainActionsSection.saveButton}}" stepKey="clickSaveStoreButton"/> + + <!--Create Store View--> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createStoreView"> + <argument name="StoreGroup" value="customStore"/> + <argument name="customStore" value="customStore"/> + </actionGroup> + + <!--Update Category--> + <amOnPage url="{{AdminCategoryPage.url}}" stepKey="openAdminCategoryIndexPage"/> + <waitForPageLoad stepKey="waitForPageToLoaded"/> + <click selector="{{AdminCategorySidebarTreeSection.expandAll}}" stepKey="clickOnExpandTree"/> + <click selector="{{AdminCategorySidebarTreeSection.categoryInTree(SimpleRootSubCategory.name)}}" stepKey="selectCategory"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + <fillField selector="{{AdminCategoryBasicFieldSection.CategoryNameInput}}" userInput="{{_defaultCategory.name}}" stepKey="updateCategoryName"/> + <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="saveUpdatedCategory"/> + <waitForPageLoad stepKey="waitForCateforyToSave"/> + <seeElement selector="{{AdminCategoryMessagesSection.SuccessMessage}}" stepKey="seeSuccessMessage"/> + <waitForPageLoad stepKey="waitForPageToLoad1"/> + <scrollTo selector="{{AdminCategorySEOSection.SectionHeader}}" x="0" y="-80" stepKey="scrollToSearchEngineOptimization1"/> + <click selector="{{AdminCategorySEOSection.SectionHeader}}" stepKey="selectSearchEngineOptimization1"/> + <waitForPageLoad stepKey="waitForPageToLoad2"/> + <seeInField selector="{{AdminCategorySEOSection.UrlKeyInput}}" stepKey="seeCategoryUrlKey" userInput="{{SimpleRootSubCategory.name_lwr}}2" /> + <!--Open Category in Store Front Page--> + <amOnPage url="/{{NewRootCategory.name}}/{{_defaultCategory.name}}.html" stepKey="seeTheCategoryInStoreFront"/> + <waitForPageLoad stepKey="waitForCategoryPageToLoad"/> + <click selector="{{StorefrontFooterSection.switchStoreButton}}" stepKey="clickSwitchStoreButtonOnDefaultStore"/> + <click selector="{{StorefrontFooterSection.storeLink(customStore.name)}}" stepKey="selectSecondStoreToSwitchOn"/> + <seeElement selector="{{StorefrontHeaderSection.NavigationCategoryByName(_defaultCategory.name)}}" stepKey="seeUpdatedCatergoryInStoreFront"/> + <click selector="{{StorefrontHeaderSection.NavigationCategoryByName(_defaultCategory.name)}}" stepKey="selectCategoryOnStoreFront"/> + <waitForPageLoad stepKey="waitForProductToLoad"/> + <seeElement selector="{{StorefrontCategoryMainSection.CategoryTitle(_defaultCategory.name)}}" stepKey="seeTheUpdatedCategoryTitle"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryAndMakeInactiveTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryAndMakeInactiveTest.xml new file mode 100644 index 00000000000..479249ca678 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryAndMakeInactiveTest.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="AdminUpdateCategoryAndMakeInactiveTest"> + <annotations> + <stories value="Update categories"/> + <title value="Update category, make inactive"/> + <description value="Login as admin and update category and make it Inactive"/> + <testCaseId value="MC-6060"/> + <severity value="CRITICAL"/> + <group value="Catalog"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminPanel"/> + <createData entity="_defaultCategory" stepKey="createDefaultCategory"/> + </before> + <after> + <deleteData createDataKey="createDefaultCategory" stepKey="deleteCreatedCategory"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Open category page--> + <amOnPage url="{{AdminCategoryPage.url}}" stepKey="openAdminCategoryIndexPage"/> + <waitForPageLoad stepKey="waitForPageToLoaded"/> + + <!--Update category and make category inactive--> + <click selector="{{AdminCategorySidebarTreeSection.expandAll}}" stepKey="clickOnExpandTree"/> + <click selector="{{AdminCategorySidebarTreeSection.categoryInTree(_defaultCategory.name)}}" stepKey="selectCreatedCategory"/> + <click selector="{{AdminCategoryBasicFieldSection.enableCategoryLabel}}" stepKey="disableCategory"/> + <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="clickSaveButton"/> + <waitForPageLoad stepKey="waitForCategorySaved"/> + <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the category." stepKey="assertSuccessMessage"/> + <see selector="{{AdminCategoryContentSection.categoryPageTitle}}" userInput="{{_defaultCategory.name}}" stepKey="seePageTitle" /> + <dontSeeCheckboxIsChecked selector="{{AdminCategoryBasicFieldSection.EnableCategory}}" stepKey="dontCategoryIsChecked"/> + + <!--Verify Inactive Category is store front page--> + <amOnPage url="{{StorefrontCategoryPage.url(_defaultCategory.name)}}" stepKey="amOnCategoryPage"/> + <waitForPageLoad stepKey="waitForPageToBeLoaded"/> + <dontSeeElement selector="{{StorefrontHeaderSection.NavigationCategoryByName(_defaultCategory.name)}}" stepKey="dontSeeCategoryOnStoreNavigationBar"/> + <waitForPageLoad time="15" stepKey="wait"/> + + <!--Verify Inactive Category in category page --> + <amOnPage url="{{AdminCategoryPage.url}}" stepKey="openAdminCategoryIndexPage1"/> + <waitForPageLoad stepKey="waitForPageToLoaded1"/> + <click selector="{{AdminCategorySidebarTreeSection.expandAll}}" stepKey="clickOnExpandTree1"/> + <seeElement selector="{{AdminCategoryContentSection.categoryInTree(_defaultCategory.name)}}" stepKey="assertCategoryInTree" /> + <click selector="{{AdminCategorySidebarTreeSection.categoryInTree(_defaultCategory.name)}}" stepKey="selectCreatedCategory1"/> + <see selector="{{AdminCategoryContentSection.categoryPageTitle}}" userInput="{{_defaultCategory.name}}" stepKey="seeCategoryPageTitle1" /> + <dontSeeCheckboxIsChecked selector="{{AdminCategoryBasicFieldSection.EnableCategory}}" stepKey="assertCategoryIsInactive"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryNameWithStoreViewTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryNameWithStoreViewTest.xml new file mode 100644 index 00000000000..2cb4a6b6dd4 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryNameWithStoreViewTest.xml @@ -0,0 +1,82 @@ +<?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="AdminUpdateCategoryNameWithStoreViewTest"> + <annotations> + <stories value="Update categories"/> + <title value="Update category, with custom store view"/> + <description value="Login as admin and update category name with custom Store View"/> + <testCaseId value="MC-6061"/> + <severity value="CRITICAL"/> + <group value="Catalog"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminPanel"/> + <createData entity="NewRootCategory" stepKey="rootCategory"/> + <createData entity="SimpleRootSubCategory" stepKey="category"> + <requiredEntity createDataKey="rootCategory"/> + </createData> + </before> + <after> + <actionGroup ref="DeleteCustomStoreActionGroup" stepKey="deleteCustomStore"> + <argument name="storeGroupName" value="customStore.name"/> + </actionGroup> + <deleteData createDataKey="rootCategory" stepKey="deleteRootCategory"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Open store page --> + <amOnPage url="{{AdminSystemStorePage.url}}" stepKey="amOnAdminSystemStorePage"/> + <waitForPageLoad stepKey="waitForSystemStorePage"/> + + <!--Create Custom Store --> + <click selector="{{AdminStoresMainActionsSection.createStoreButton}}" stepKey="selectCreateStore"/> + <fillField userInput="{{customStore.name}}" selector="{{AdminNewStoreGroupSection.storeGrpNameTextField}}" stepKey="fillStoreName"/> + <fillField userInput="{{customStore.code}}" selector="{{AdminNewStoreGroupSection.storeGrpCodeTextField}}" stepKey="fillStoreCode"/> + <selectOption userInput="{{NewRootCategory.name}}" selector="{{AdminNewStoreGroupSection.storeRootCategoryDropdown}}" stepKey="selectStoreStatus"/> + <click selector="{{AdminStoresMainActionsSection.saveButton}}" stepKey="clickSaveStoreButton"/> + + <!--Create Store View--> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createStoreView"> + <argument name="StoreGroup" value="customStore"/> + <argument name="customStore" value="customStore"/> + </actionGroup> + + <!--Verify created SubCAtegory is present on Store Front --> + <amOnPage url="/{{NewRootCategory.name}}/{{SimpleRootSubCategory.name}}.html" stepKey="seeTheCategoryInStoreFront"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + <click selector="{{StorefrontFooterSection.switchStoreButton}}" stepKey="ClickSwitchStoreButtonOnDefaultStore"/> + <click selector="{{StorefrontFooterSection.storeLink(customStore.name)}}" stepKey="SelectSecondStoreToSwitchOn"/> + <seeElement selector="{{StorefrontHeaderSection.NavigationCategoryByName(SimpleRootSubCategory.name)}}" stepKey="seeCatergoryInStoreFront"/> + + <!--Open Category Page--> + <amOnPage url="{{AdminCategoryPage.url}}" stepKey="openAdminCategoryIndexPage"/> + <waitForPageLoad stepKey="waitForPageToLoaded"/> + + <!--Update Category--> + <click selector="{{AdminCategorySidebarTreeSection.expandAll}}" stepKey="expandToSeeAllCategories"/> + <click selector="{{AdminCategorySidebarTreeSection.categoryInTreeUnderRoot(SimpleRootSubCategory.name)}}" stepKey="clickOnSubcategoryIsUndeRootCategory"/> + <waitForPageLoad stepKey="waitForPageToLoad1"/> + <fillField selector="{{AdminCategoryBasicFieldSection.CategoryNameInput}}" userInput="{{_defaultCategory.name}}" stepKey="updateCategoryName"/> + <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="saveUpdatedCategory"/> + <waitForPageLoad stepKey="waitForCateforyToSave"/> + <seeElement selector="{{AdminCategoryMessagesSection.SuccessMessage}}" stepKey="seeSuccessMessage"/> + + <!--Verify the Category is not present in Store Front--> + <amOnPage url="/{{NewRootCategory.name}}/{{SimpleRootSubCategory.name}}.html" stepKey="seeTheCategoryInStoreFront1"/> + <waitForPageLoad stepKey="waitForPageToLoaded2"/> + <dontSeeElement selector="{{StorefrontHeaderSection.NavigationCategoryByName(SimpleRootSubCategory.name)}}" stepKey="dontSeeCatergoryInStoreFront"/> + + <!--Verify the Updated Category is present in Store Front--> + <amOnPage url="/{{NewRootCategory.name}}/{{_defaultCategory.name}}.html" stepKey="seeTheUpdatedCategoryInStoreFront"/> + <waitForPageLoad stepKey="waitForPageToLoaded3"/> + <seeElement selector="{{StorefrontHeaderSection.NavigationCategoryByName(_defaultCategory.name)}}" stepKey="seeUpdatedCatergoryInStoreFront"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryUrlKeyWithStoreViewTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryUrlKeyWithStoreViewTest.xml new file mode 100644 index 00000000000..e7c4a8a093e --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryUrlKeyWithStoreViewTest.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="AdminUpdateCategoryUrlKeyWithStoreViewTest"> + <annotations> + <stories value="Update categories"/> + <title value="Update category, URL key with custom store view"/> + <description value="Login as admin and update category URL Key with store view"/> + <testCaseId value="MC-6062"/> + <severity value="CRITICAL"/> + <group value="Catalog"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminPanel"/> + <createData entity="NewRootCategory" stepKey="rootCategory"/> + <createData entity="SimpleRootSubCategory" stepKey="category"> + <requiredEntity createDataKey="rootCategory"/> + </createData> + </before> + <after> + <actionGroup ref="DeleteCustomStoreActionGroup" stepKey="deleteCustomStore"> + <argument name="storeGroupName" value="customStore.name"/> + </actionGroup> + <deleteData createDataKey="rootCategory" stepKey="deleteRootCategory"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Open Store Page --> + <amOnPage url="{{AdminSystemStorePage.url}}" stepKey="amOnAdminSystemStorePage"/> + <waitForPageLoad stepKey="waitForSystemStorePage"/> + + <!--Create Custom Store --> + <click selector="{{AdminStoresMainActionsSection.createStoreButton}}" stepKey="selectCreateStore"/> + <fillField userInput="{{customStore.name}}" selector="{{AdminNewStoreGroupSection.storeGrpNameTextField}}" stepKey="fillStoreName"/> + <fillField userInput="{{customStore.code}}" selector="{{AdminNewStoreGroupSection.storeGrpCodeTextField}}" stepKey="fillStoreCode"/> + <selectOption userInput="{{NewRootCategory.name}}" selector="{{AdminNewStoreGroupSection.storeRootCategoryDropdown}}" stepKey="selectStoreStatus"/> + <click selector="{{AdminStoresMainActionsSection.saveButton}}" stepKey="clickSaveStoreButton"/> + + <!--Create Store View--> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createStoreView"> + <argument name="StoreGroup" value="customStore"/> + <argument name="customStore" value="customStore"/> + </actionGroup> + + <!--Verify Category in Store View--> + <amOnPage url="/{{NewRootCategory.name}}/{{SimpleRootSubCategory.name}}.html" stepKey="seeTheCategoryInStoreFront"/> + <waitForPageLoad stepKey="waitForSystemStorePage1"/> + <click selector="{{StorefrontFooterSection.switchStoreButton}}" stepKey="ClickSwitchStoreButtonOnDefaultStore"/> + <click selector="{{StorefrontFooterSection.storeLink(customStore.name)}}" stepKey="SelectSecondStoreToSwitchOn"/> + <seeElement selector="{{StorefrontHeaderSection.NavigationCategoryByName(SimpleRootSubCategory.name)}}" stepKey="seeCatergoryInStoreFront"/> + <click selector="{{StorefrontHeaderSection.NavigationCategoryByName(SimpleRootSubCategory.name)}}" stepKey="selectCategory"/> + <waitForPageLoad stepKey="waitForProductToLoad"/> + + <!--Update URL Key--> + <amOnPage url="{{AdminCategoryPage.url}}" stepKey="openAdminCategoryIndexPage"/> + <waitForPageLoad stepKey="waitForPageToLoaded2"/> + <click selector="{{AdminCategorySidebarTreeSection.expandAll}}" stepKey="clickOnExpandTree"/> + <click selector="{{AdminCategorySidebarTreeSection.categoryInTree(SimpleRootSubCategory.name)}}" stepKey="selectCategory1"/> + <scrollTo selector="{{AdminCategorySEOSection.SectionHeader}}" stepKey="scrollToSearchEngineOptimization"/> + <click selector="{{AdminCategorySEOSection.SectionHeader}}" stepKey="openSeoSection"/> + <clearField selector="{{AdminCategorySEOSection.UrlKeyInput}}" stepKey="clearUrlKeyField"/> + <fillField selector="{{AdminCategorySEOSection.UrlKeyInput}}" userInput="newurlkey" stepKey="enterURLKey"/> + <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="saveCategoryAfterFirstSeoUpdate"/> + <seeElement selector="{{AdminCategoryMessagesSection.SuccessMessage}}" stepKey="assertSuccessMessage"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + + <!--Open Category Store Front Page--> + <amOnPage url="/{{NewRootCategory.name}}/{{SimpleRootSubCategory.name}}.html" stepKey="seeTheCategoryInStoreFront1"/> + <waitForPageLoad stepKey="waitForSystemStorePage3"/> + <seeElement selector="{{StorefrontHeaderSection.NavigationCategoryByName(SimpleRootSubCategory.name)}}" stepKey="seeCategoryOnNavigation1"/> + <click selector="{{StorefrontHeaderSection.NavigationCategoryByName(SimpleRootSubCategory.name)}}" stepKey="selectCategory2"/> + <waitForPageLoad stepKey="waitForProductToLoad1"/> + + <!--Verify Updated URLKey is present--> + <seeInCurrentUrl stepKey="verifyUpdatedUrlKey" url="newurlkey.html"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryWithInactiveIncludeInMenuTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryWithInactiveIncludeInMenuTest.xml new file mode 100644 index 00000000000..3fea9c0eed7 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryWithInactiveIncludeInMenuTest.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="AdminUpdateCategoryWithInactiveIncludeInMenuTest"> + <annotations> + <stories value="Update categories"/> + <title value="Update category, name description urlkey metatitle exclude from menu"/> + <description value="Login as admin and update category name, description, urlKey, metatitle and exclude from menu"/> + <testCaseId value="MC-6058"/> + <severity value="CRITICAL"/> + <group value="Catalog"/> + <group value="mtf_migrated"/> + <group value="WYSIWYGDisabled"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminPanel"/> + <createData entity="_defaultCategory" stepKey="createDefaultCategory"/> + </before> + <after> + <deleteData createDataKey="createDefaultCategory" stepKey="deleteCategory"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Open Category Page--> + <amOnPage url="{{AdminCategoryPage.url}}" stepKey="openAdminCategoryIndexPage"/> + <waitForPageLoad stepKey="waitForPageToLoaded"/> + + <!--Update Category name,description, urlKey, meta title and disable Include in Menu--> + <click selector="{{AdminCategorySidebarTreeSection.expandAll}}" stepKey="clickOnExpandTree"/> + <click selector="{{AdminCategorySidebarTreeSection.categoryInTree(_defaultCategory.name)}}" stepKey="selectCreatedCategory"/> + <fillField selector="{{AdminCategoryBasicFieldSection.CategoryNameInput}}" userInput="{{SimpleRootSubCategory.name}}" stepKey="fillCategoryName"/> + <checkOption selector="{{AdminCategoryBasicFieldSection.EnableCategory}}" stepKey="enableCategory"/> + <click selector="{{AdminCategoryBasicFieldSection.includeInMenuLabel}}" stepKey="disableIncludeInMenu"/> + <scrollTo selector="{{AdminCategoryContentSection.sectionHeader}}" x="0" y="-80" stepKey="scrollToContent"/> + <click selector="{{AdminCategoryContentSection.sectionHeader}}" stepKey="selectContent"/> + <fillField selector="{{AdminCategoryContentSection.description}}" userInput="Updated category Description Fields" stepKey="fillUpdatedDescription"/> + <scrollTo selector="{{AdminCategorySEOSection.SectionHeader}}" x="0" y="-80" stepKey="scrollToSearchEngineOptimization"/> + <click selector="{{AdminCategorySEOSection.SectionHeader}}" stepKey="selectSearchEngineOptimization"/> + <fillField selector="{{AdminCategorySEOSection.UrlKeyInput}}" userInput="{{SimpleRootSubCategory.url_key}}" stepKey="fillUpdatedUrlKey"/> + <fillField selector="{{AdminCategorySEOSection.MetaTitleInput}}" userInput="{{SimpleRootSubCategory.name}}" stepKey="fillUpdatedMetaTitle"/> + <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="clickSaveButton"/> + <waitForPageLoad stepKey="waitForCategorySaved"/> + <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the category." stepKey="assertSuccessMessage"/> + + <!--Open UrlRewrite Page--> + <amOnPage url="{{AdminUrlRewriteIndexPage.url}}" stepKey="openUrlRewriteIndexPage"/> + <waitForPageLoad stepKey="waitForUrlRewritePage"/> + + <!--Verify Updated Category UrlKey--> + <fillField selector="{{AdminUrlRewriteIndexSection.requestPathFilter}}" userInput="{{SimpleRootSubCategory.url_key}}" stepKey="fillUpdatedCategoryUrlKey"/> + <click selector="{{AdminUrlRewriteIndexSection.searchButton}}" stepKey="clickOnSearchButton"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + <see stepKey="seeCategoryUrlKey" selector="{{AdminUrlRewriteIndexSection.requestPathColumn('1')}}" userInput="{{SimpleRootSubCategory.url_key}}.html" /> + <!--Verify Updated Category UrlKey directs to category Store Front--> + <amOnPage url="{{SimpleRootSubCategory.url_key}}.html" stepKey="seeTheCategoryInStoreFrontPage"/> + <waitForPageLoad time="60" stepKey="waitForStoreFrontPageLoad"/> + <seeElement selector="{{StorefrontCategoryMainSection.CategoryTitle(SimpleRootSubCategory.name)}}" stepKey="seeUpdatedCategoryInStoreFrontPage"/> + + <!--Verify Updated fields in Category Page--> + <amOnPage url="{{AdminCategoryPage.url}}" stepKey="openAdminCategoryIndexPage1"/> + <waitForPageLoad stepKey="waitForPageToLoaded1"/> + <click selector="{{AdminCategorySidebarTreeSection.expandAll}}" stepKey="clickOnExpandTree1"/> + <click selector="{{AdminCategorySidebarTreeSection.categoryInTree(SimpleRootSubCategory.name)}}" stepKey="selectCreatedCategory1"/> + <waitForPageLoad stepKey="waitForCategoryPageToLoad"/> + <see selector="{{AdminCategoryContentSection.categoryPageTitle}}" userInput="{{SimpleRootSubCategory.name}}" stepKey="seeUpdatedCategoryTitle"/> + <dontSeeCheckboxIsChecked selector="{{AdminCategoryBasicFieldSection.includeInMenuLabel}}" stepKey="verifyInactiveIncludeInMenu"/> + <seeInField selector="{{AdminCategoryBasicFieldSection.CategoryNameInput}}" userInput="{{SimpleRootSubCategory.name}}" stepKey="seeUpdatedCategoryName"/> + <scrollTo selector="{{AdminCategoryContentSection.sectionHeader}}" x="0" y="-80" stepKey="scrollToContent1"/> + <click selector="{{AdminCategoryContentSection.sectionHeader}}" stepKey="selectContent1"/> + <scrollTo selector="{{AdminCategoryContentSection.description}}" stepKey="scrollToDescription1"/> + <seeInField stepKey="seeUpdatedDiscription" selector="{{AdminCategoryContentSection.description}}" userInput="Updated category Description Fields"/> + <scrollTo selector="{{AdminCategorySEOSection.SectionHeader}}" x="0" y="-80" stepKey="scrollToSearchEngineOptimization1"/> + <click selector="{{AdminCategorySEOSection.SectionHeader}}" stepKey="selectSearchEngineOptimization1"/> + <seeInField stepKey="seeUpdatedUrlKey" selector="{{AdminCategorySEOSection.UrlKeyInput}}" userInput="{{SimpleRootSubCategory.url_key}}"/> + <seeInField stepKey="seeUpdatedMetaTitleInput" selector="{{AdminCategorySEOSection.MetaTitleInput}}" userInput="{{SimpleRootSubCategory.name}}"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryWithProductsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryWithProductsTest.xml new file mode 100644 index 00000000000..1cb01ac11cb --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryWithProductsTest.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="AdminUpdateCategoryWithProductsTest"> + <annotations> + <stories value="Update categories"/> + <title value="Update category, sort products by default sorting"/> + <description value="Login as admin, update category and sort products"/> + <testCaseId value="MC-6059"/> + <severity value="CRITICAL"/> + <group value="Catalog"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminPanel"/> + <createData entity="defaultSimpleProduct" stepKey="simpleProduct" /> + <createData entity="_defaultCategory" stepKey="createCategory"/> + </before> + <after> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="simpleProduct" stepKey="deleteSimpleProduct"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Open Category Page--> + <amOnPage url="{{AdminCategoryPage.url}}" stepKey="openAdminCategoryIndexPage"/> + <waitForPageLoad stepKey="waitForPageToLoaded"/> + <click selector="{{AdminCategorySidebarTreeSection.expandAll}}" stepKey="clickOnExpandTree"/> + <click selector="{{AdminCategorySidebarTreeSection.categoryInTree(_defaultCategory.name)}}" stepKey="selectCreatedCategory"/> + <checkOption selector="{{AdminCategoryBasicFieldSection.EnableCategory}}" stepKey="enableCategory"/> + + <!--Update Product Display Setting--> + <scrollTo selector="{{CategoryDisplaySettingsSection.DisplaySettingTab}}" x="0" y="-80" stepKey="scrollToDisplaySetting"/> + <click selector="{{CategoryDisplaySettingsSection.DisplaySettingTab}}" stepKey="selectDisplaySetting"/> + <scrollToTopOfPage stepKey="scfrollToTop"/> + <click selector="{{CategoryDisplaySettingsSection.productListCheckBox}}" stepKey="enableTheAvailableProductList"/> + <selectOption selector="{{CategoryDisplaySettingsSection.productList}}" parameterArray="['Product Name', 'Price']" stepKey="selectPrice"/> + <scrollTo selector="{{CategoryDisplaySettingsSection.defaultProductLisCheckBox}}" x="0" y="-80" stepKey="scrollToDefaultProductList"/> + <click selector="{{CategoryDisplaySettingsSection.defaultProductLisCheckBox}}" stepKey="enableTheDefaultProductList"/> + <selectOption selector="{{CategoryDisplaySettingsSection.defaultProductList}}" userInput="name" stepKey="selectProductName"/> + + <!--Add Products in Category--> + <scrollTo selector="{{AdminCategoryBasicFieldSection.productsInCategory}}" x="0" y="-80" stepKey="scrollToProductInCategory"/> + <click selector="{{AdminCategoryBasicFieldSection.productsInCategory}}" stepKey="clickOnProductInCategory"/> + <scrollToTopOfPage stepKey="scrollOnTopOfPage"/> + <click selector="{{CatalogProductsSection.resetFilter}}" stepKey="clickOnResetFilter"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + <fillField selector="{{AdminCategoryContentSection.productTableColumnName}}" userInput="$$simpleProduct.name$$" stepKey="selectProduct1"/> + <click selector="{{AdminCategoryContentSection.productSearch}}" stepKey="clickSearchButton"/> + <waitForPageLoad stepKey="waitFroPageToLoad1"/> + <scrollTo selector="{{AdminCategoryContentSection.productTableRow}}" stepKey="scrollToTableRow"/> + <click selector="{{AdminCategoryContentSection.productTableRow}}" stepKey="selectProduct1FromTableRow"/> + <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="clickSaveButton"/> + <waitForPageLoad stepKey="waitForCategorySaved"/> + <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the category." stepKey="assertSuccessMessage"/> + <waitForPageLoad stepKey="waitForPageTitleToBeSaved"/> + + <!--Verify Category Title--> + <see selector="{{AdminCategoryContentSection.categoryPageTitle}}" userInput="{{_defaultCategory.name}}" stepKey="seePageTitle" /> + + <!--Verify Category in store front page--> + <amOnPage url="{{StorefrontCategoryPage.url(_defaultCategory.name)}}" stepKey="seeDefaultProductPage"/> + <waitForPageLoad stepKey="waitForPageToBeLoaded"/> + <seeElement selector="{{StorefrontHeaderSection.NavigationCategoryByName(_defaultCategory.name)}}" stepKey="seeCategoryOnNavigation"/> + <click selector="{{StorefrontHeaderSection.NavigationCategoryByName(_defaultCategory.name)}}" stepKey="selectCategory"/> + <waitForPageLoad stepKey="waitForProductToLoad"/> + + <!--Verify Product in Category--> + <seeElement stepKey="seeProductsInCategory" selector="{{StorefrontCategoryMainSection.productLink}}"/> + <click selector="{{StorefrontCategoryMainSection.productLink}}" stepKey="openSearchedProduct"/> + <waitForPageLoad stepKey="waitForProductToLoad1"/> + + <!--Verify product name and price on Store Front--> + <see selector="{{StorefrontProductInfoMainSection.productName}}" userInput="{{defaultSimpleProduct.name}}" stepKey="assertProductName"/> + <see selector="{{StorefrontProductInfoMainSection.productPrice}}" userInput="{{defaultSimpleProduct.price}}" stepKey="assertProductPrice"/> + </test> +</tests> + diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryAndAddProductsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryAndAddProductsTest.xml new file mode 100644 index 00000000000..8872ea98eb5 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryAndAddProductsTest.xml @@ -0,0 +1,103 @@ +<?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="AdminUpdateFlatCategoryAndAddProductsTest"> + <annotations> + <stories value="Update category"/> + <title value="Flat Catalog - Assign Simple Product to Category"/> + <description value="Login as admin, update flat category by adding a simple product"/> + <testCaseId value="MC-11012"/> + <severity value="CRITICAL"/> + <group value="Catalog"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminPanel"/> + <!-- Create Simple Product --> + <createData entity="SimpleSubCategory" stepKey="category"/> + <!-- Create category --> + <createData entity="defaultSimpleProduct" stepKey="createSimpleProduct"/> + <!-- Create First StoreView --> + <actionGroup ref="CreateStoreView" stepKey="createCustomStoreViewEn"> + <argument name="storeView" value="customStoreEN"/> + </actionGroup> + <!-- Create Second StoreView --> + <actionGroup ref="CreateStoreView" stepKey="createCustomStoreViewFr"> + <argument name="storeView" value="customStoreFR"/> + </actionGroup> + <!--Run full reindex and clear caches --> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <!--Enable Flat Catalog Category --> + <magentoCLI stepKey="setFlatCatalogCategory" command="config:set catalog/frontend/flat_catalog_category 1"/> + <!--Open Index Management Page and Select Index mode "Update by Schedule" --> + <magentoCLI stepKey="setIndexerMode" command="indexer:set-mode" arguments="schedule" /> + <!-- Run cron twice --> + <magentoCLI command="cron:run" stepKey="runCron1"/> + <magentoCLI command="cron:run" stepKey="runCron2"/> + </before> + <after> + <magentoCLI stepKey="setFlatCatalogCategory" command="config:set catalog/frontend/flat_catalog_category 0 "/> + <magentoCLI stepKey="setIndexersMode" command="indexer:set-mode" arguments="realtime" /> + <magentoCLI stepKey="indexerReindex" command="indexer:reindex" /> + <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteStoreViewEn"> + <argument name="customStore" value="customStoreEN"/> + </actionGroup> + <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteStoreViewFr"> + <argument name="customStore" value="customStoreFR"/> + </actionGroup> + <deleteData createDataKey="category" stepKey="deleteCategory"/> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!-- Select Created Category--> + <magentoCLI command="indexer:reindex" stepKey="reindexBeforeFlow"/> + <amOnPage url="{{AdminCategoryPage.url}}" stepKey="openAdminCategoryIndexPage"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + <click selector="{{AdminCategorySidebarTreeSection.expandAll}}" stepKey="clickOnExpandTree"/> + <click selector="{{AdminCategorySidebarTreeSection.categoryInTree(SimpleSubCategory.name)}}" stepKey="selectCreatedCategory"/> + <waitForPageLoad stepKey="waitForTheCategoryPageToLoaded"/> + <!--Add Products in Category--> + <scrollTo selector="{{AdminCategoryBasicFieldSection.productsInCategory}}" x="0" y="-80" stepKey="scrollToProductInCategory"/> + <click selector="{{AdminCategoryBasicFieldSection.productsInCategory}}" stepKey="clickOnProductInCategory"/> + <scrollToTopOfPage stepKey="scrollOnTopOfPage"/> + <conditionalClick selector="{{CatalogProductsSection.resetFilter}}" dependentSelector="{{CatalogProductsSection.resetFilter}}" visible="true" stepKey="clickOnResetFilter"/> + <waitForPageLoad stepKey="waitForProductsToLoad"/> + <fillField selector="{{AdminCategoryContentSection.productTableColumnName}}" userInput="$$createSimpleProduct.name$$" stepKey="selectProduct"/> + <click selector="{{AdminCategoryContentSection.productSearch}}" stepKey="clickSearchButton"/> + <waitForPageLoad stepKey="waitFroPageToLoad1"/> + <scrollTo selector="{{AdminCategoryContentSection.productTableRow}}" stepKey="scrollToTableRow"/> + <click selector="{{AdminCategoryContentSection.productTableRow}}" stepKey="selectProductFromTableRow"/> + <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="saveSubCategory"/> + <waitForPageLoad stepKey="waitForSecondCategoryToSave"/> + <seeElement selector="{{AdminCategoryMessagesSection.SuccessMessage}}" stepKey="seeSuccessMessage"/> + <!--Open Index Management Page and verify flat categoryIndex status--> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + <!--Open Index Management Page --> + <amOnPage url="{{AdminIndexManagementPage.url}}" stepKey="openIndexManagementPage"/> + <waitForPageLoad stepKey="waitForIndexPageToLoad"/> + <see stepKey="seeCategoryIndexStatus" selector="{{AdminIndexManagementSection.indexerStatus('Category Flat Data')}}" userInput="Ready"/> + <!--Verify Product In Store Front--> + <amOnPage url="$$createSimpleProduct.name$$.html" stepKey="goToStorefrontPage"/> + <waitForPageLoad stepKey="waitForPageToBeLoaded"/> + <!--Verify product and category is visible in First Store View --> + <click stepKey="selectStoreSwitcher" selector="{{StorefrontHeaderSection.storeViewSwitcher}}"/> + <click stepKey="selectFirstStoreView" selector="{{StorefrontHeaderSection.storeViewList(customStoreEN.name)}}"/> + <waitForPageLoad stepKey="waitForFirstStoreView"/> + <seeElement selector="{{StorefrontHeaderSection.NavigationCategoryByName($$category.name$$)}}" stepKey="seeCategoryOnNavigation"/> + <see selector="{{StorefrontProductInfoMainSection.productName}}" userInput="{{defaultSimpleProduct.name}}" stepKey="assertProductName"/> + <!--Verify product and category is visible in Second Store View --> + <click stepKey="selectStoreSwitcher1" selector="{{StorefrontHeaderSection.storeViewSwitcher}}"/> + <click stepKey="selectSecondStoreView" selector="{{StorefrontHeaderSection.storeViewList(customStoreFR.name)}}"/> + <waitForPageLoad stepKey="waitForSecondStoreView"/> + <seeElement selector="{{StorefrontHeaderSection.NavigationCategoryByName($$category.name$$)}}" stepKey="seeCategoryOnNavigation1"/> + <see selector="{{StorefrontProductInfoMainSection.productName}}" userInput="{{defaultSimpleProduct.name}}" stepKey="seeProductName"/> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryIncludeInNavigationTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryIncludeInNavigationTest.xml new file mode 100644 index 00000000000..55273033706 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryIncludeInNavigationTest.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="AdminUpdateFlatCategoryAndIncludeInMenuTest"> + <annotations> + <stories value="Update category"/> + <title value="Flat Catalog - Update Category, Include in Navigation Menu"/> + <description value="Login as admin and update flat category by enabling Include in Menu"/> + <testCaseId value="MC-11011"/> + <severity value="CRITICAL"/> + <group value="Catalog"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminPanel"/> + <!--Create category--> + <createData entity="CatNotIncludeInMenu" stepKey="createCategory"/> + <!-- Create First StoreView --> + <actionGroup ref="CreateStoreView" stepKey="createCustomStoreViewEn"> + <argument name="storeView" value="customStoreEN"/> + </actionGroup> + <!-- Create Second StoreView --> + <actionGroup ref="CreateStoreView" stepKey="createCustomStoreViewFr"> + <argument name="storeView" value="customStoreFR"/> + </actionGroup> + <!--Run full reindex and clear caches --> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + <!--Enable Flat Catalog Category --> + <magentoCLI stepKey="setFlatCatalogCategory" command="config:set catalog/frontend/flat_catalog_category 1"/> + <!--Open Index Management Page and Select Index mode "Update by Schedule" --> + <magentoCLI stepKey="setIndexerMode" command="indexer:set-mode" arguments="schedule" /> + <!-- Run cron twice --> + <magentoCLI command="cron:run" stepKey="runCron1"/> + <magentoCLI command="cron:run" stepKey="runCron2"/> + </before> + <after> + <magentoCLI stepKey="setFlatCatalogCategory" command="config:set catalog/frontend/flat_catalog_category 0 "/> + <magentoCLI stepKey="setIndexersMode" command="indexer:set-mode" arguments="realtime" /> + <magentoCLI stepKey="indexerReindex" command="indexer:reindex" /> + <deleteData stepKey="deleteCategory" createDataKey="createCategory"/> + <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteStoreViewEn"> + <argument name="customStore" value="customStoreEN"/> + </actionGroup> + <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteStoreViewFr"> + <argument name="customStore" value="customStoreFR"/> + </actionGroup> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!--Verify Category is not listed in navigation menu--> + <amOnPage url="/{{CatNotIncludeInMenu.name_lwr}}.html" stepKey="openCategoryPage"/> + <waitForPageLoad time="60" stepKey="waitForPageToBeLoaded"/> + <dontSee selector="{{StorefrontHeaderSection.NavigationCategoryByName(CatNotIncludeInMenu.name)}}" stepKey="dontSeeCategoryOnNavigation"/> + <!-- Select created category and enable Include In Menu option--> + <amOnPage url="{{AdminCategoryPage.url}}" stepKey="openAdminCategoryIndexPage"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + <click selector="{{AdminCategorySidebarTreeSection.expandAll}}" stepKey="clickOnExpandTree"/> + <click selector="{{AdminCategorySidebarTreeSection.categoryInTree(CatNotIncludeInMenu.name)}}" stepKey="selectCreatedCategory"/> + <waitForPageLoad stepKey="waitForCategoryPageToLoad"/> + <click selector="{{AdminCategoryBasicFieldSection.includeInMenuLabel}}" stepKey="enableIncludeInMenuOption"/> + <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="saveSubCategory"/> + <waitForPageLoad stepKey="waitForSecondCategoryToSave"/> + <seeElement selector="{{AdminCategoryMessagesSection.SuccessMessage}}" stepKey="seeSuccessMessage"/> + <!--Run full reindex and clear caches --> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + <!--Open Index Management Page --> + <amOnPage url="{{AdminIndexManagementPage.url}}" stepKey="openIndexManagementPage"/> + <waitForPageLoad stepKey="waitForIndexPageToBeLoaded"/> + <see stepKey="seeIndexStatus" selector="{{AdminIndexManagementSection.indexerStatus('Category Flat Data')}}" userInput="Ready"/> + <!--Verify Category In Store Front--> + <amOnPage url="/$$createCategory.name$$.html" stepKey="openCategoryPage1"/> + <waitForPageLoad stepKey="waitForCategoryStoreFrontPageToLoad"/> + <!--Verify category is visible in First Store View --> + <click stepKey="selectStoreSwitcher" selector="{{StorefrontHeaderSection.storeViewSwitcher}}"/> + <click stepKey="selectForstStoreView" selector="{{StorefrontHeaderSection.storeViewList(customStoreEN.name)}}"/> + <waitForPageLoad stepKey="waitForFirstStoreView"/> + <seeElement selector="{{StorefrontHeaderSection.NavigationCategoryByName($$createCategory.name$$)}}" stepKey="seeCategoryOnNavigation"/> + <!--Verify category is visible in Second Store View --> + <click stepKey="selectStoreSwitcher1" selector="{{StorefrontHeaderSection.storeViewSwitcher}}"/> + <click stepKey="selectSecondStoreView" selector="{{StorefrontHeaderSection.storeViewList(customStoreFR.name)}}"/> + <waitForPageLoad stepKey="waitForSecondstoreView"/> + <seeElement selector="{{StorefrontHeaderSection.NavigationCategoryByName($$createCategory.name$$)}}" stepKey="seeCategoryOnNavigation1"/> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryNameAndDescriptionTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryNameAndDescriptionTest.xml new file mode 100644 index 00000000000..fcbc0cb2052 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryNameAndDescriptionTest.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="AdminUpdateFlatCategoryNameAndDescriptionTest"> + <annotations> + <stories value="Update category"/> + <title value="Flat Catalog - Update Category Name and Description"/> + <description value="Login as admin and update flat category name and description"/> + <testCaseId value="MC-11010"/> + <severity value="CRITICAL"/> + <group value="Catalog"/> + <group value="mtf_migrated"/> + <group value="WYSIWYGDisabled"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminPanel"/> + <!--Create category--> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <!-- Create First StoreView --> + <actionGroup ref="CreateStoreView" stepKey="createCustomStoreViewEn"> + <argument name="storeView" value="customStoreEN"/> + </actionGroup> + <!-- Create Second StoreView --> + <actionGroup ref="CreateStoreView" stepKey="createCustomStoreViewFr"> + <argument name="storeView" value="customStoreFR"/> + </actionGroup> + <!--Run full reindex and clear caches --> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + <!--Enable Flat Catalog Category --> + <magentoCLI stepKey="setFlatCatalogCategory" command="config:set catalog/frontend/flat_catalog_category 1"/> + <!--Open Index Management Page and Select Index mode "Update by Schedule" --> + <magentoCLI stepKey="setIndexerMode" command="indexer:set-mode" arguments="schedule" /> + <!-- Run cron twice --> + <magentoCLI command="cron:run" stepKey="runCron1"/> + <magentoCLI command="cron:run" stepKey="runCron2"/> + </before> + <after> + <magentoCLI stepKey="setFlatCatalogCategory" command="config:set catalog/frontend/flat_catalog_category 0 "/> + <magentoCLI stepKey="setIndexerMode" command="indexer:set-mode" arguments="realtime" /> + <magentoCLI stepKey="indexerReindex" command="indexer:reindex" /> + <deleteData stepKey="deleteCategory" createDataKey="createCategory" /> + <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteStoreViewEn"> + <argument name="customStore" value="customStoreEN"/> + </actionGroup> + <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteStoreViewFr"> + <argument name="customStore" value="customStoreFR"/> + </actionGroup> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!-- Select Created Category--> + <amOnPage url="{{AdminCategoryPage.url}}" stepKey="openAdminCategoryIndexPage"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + <click selector="{{AdminCategorySidebarTreeSection.expandAll}}" stepKey="clickOnExpandTree"/> + <click selector="{{AdminCategorySidebarTreeSection.categoryInTree(_defaultCategory.name)}}" stepKey="selectCreatedCategory"/> + <waitForPageLoad stepKey="waitForPageToLoaded"/> + <!--Update Category Name and Description --> + <fillField selector="{{AdminCategoryBasicFieldSection.CategoryNameInput}}" userInput="{{SimpleSubCategory.name}}" stepKey="addSubCategoryName"/> + <scrollTo selector="{{AdminCategoryContentSection.sectionHeader}}" x="0" y="-80" stepKey="scrollToContent"/> + <click selector="{{AdminCategoryContentSection.sectionHeader}}" stepKey="selectContent"/> + <fillField selector="{{AdminCategoryContentSection.description}}" userInput="Updated category Description Fields" stepKey="fillUpdatedDescription"/> + <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="saveSubCategory"/> + <waitForPageLoad stepKey="waitForSecondCategoryToSave"/> + <seeElement selector="{{AdminCategoryMessagesSection.SuccessMessage}}" stepKey="seeSuccessMessage"/> + <!--Run full reindex and clear caches --> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + <!--Open Index Management Page --> + <amOnPage url="{{AdminIndexManagementPage.url}}" stepKey="openIndexManagementPage"/> + <waitForPageLoad stepKey="waitForIndexPageToLoad"/> + <see stepKey="seeIndexStatus" selector="{{AdminIndexManagementSection.indexerStatus('Category Flat Data')}}" userInput="READY"/> + <!--Verify Category In Store Front--> + <amOnPage url="{{SimpleSubCategory.name}}.html" stepKey="goToStorefrontPage"/> + <waitForPageLoad stepKey="waitForPageToBeLoaded"/> + <!--Verify category is visible in First Store View --> + <click stepKey="selectStoreSwitcher" selector="{{StorefrontHeaderSection.storeViewSwitcher}}"/> + <click stepKey="selectFirstStoreView" selector="{{StorefrontHeaderSection.storeViewList(customStoreEN.name)}}"/> + <waitForPageLoad stepKey="waitForFirstStoreView"/> + <seeElement selector="{{StorefrontHeaderSection.NavigationCategoryByName(SimpleSubCategory.name)}}" stepKey="seeCategoryOnNavigation"/> + <!--Verify category is visible in Second Store View --> + <click stepKey="selectStoreSwitcher1" selector="{{StorefrontHeaderSection.storeViewSwitcher}}"/> + <click stepKey="selectSecondStoreView" selector="{{StorefrontHeaderSection.storeViewList(customStoreFR.name)}}"/> + <waitForPageLoad stepKey="waitForSecondStoreView"/> + <seeElement selector="{{StorefrontHeaderSection.NavigationCategoryByName(SimpleSubCategory.name)}}" stepKey="seeCategoryOnNavigation1"/> + <!-- Verify Updated Category Name and description on Category Page--> + <amOnPage url="{{AdminCategoryPage.url}}" stepKey="openAdminCategoryIndexPage1"/> + <waitForPageLoad stepKey="waitForCategoryPageToLoad"/> + <click selector="{{AdminCategorySidebarTreeSection.expandAll}}" stepKey="clickOnExpandTree1"/> + <click selector="{{AdminCategorySidebarTreeSection.categoryInTree(SimpleSubCategory.name)}}" stepKey="selectUpdatedCategory"/> + <waitForPageLoad stepKey="waitForUpdatedCategoryPageToLoad"/> + <seeInField selector="{{AdminCategoryBasicFieldSection.CategoryNameInput}}" userInput="{{SimpleSubCategory.name}}" stepKey="seeUpdatedSubCategoryName"/> + <scrollTo selector="{{AdminCategoryContentSection.sectionHeader}}" x="0" y="-80" stepKey="scrollToContent1"/> + <click selector="{{AdminCategoryContentSection.sectionHeader}}" stepKey="selectContent1"/> + <seeInField selector="{{AdminCategoryContentSection.description}}" userInput="Updated category Description Fields" stepKey="seeUpdatedDescription"/> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateTopCategoryUrlWithNoRedirectTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateTopCategoryUrlWithNoRedirectTest.xml new file mode 100644 index 00000000000..4dea6663e61 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateTopCategoryUrlWithNoRedirectTest.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="AdminUpdateTopCategoryUrlWithNoRedirectTest"> + <annotations> + <stories value="Update category"/> + <title value="Update top category url and do not create redirect"/> + <description value="Login as admin and update top category url and do not create redirect"/> + <testCaseId value="MC-6056"/> + <severity value="CRITICAL"/> + <group value="Catalog"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <!-- Create three level nested category --> + <createData entity="_defaultCategory" stepKey="createDefaultCategory"/> + <createData entity="Two_nested_categories" stepKey="createTwoLevelNestedCategories"> + <requiredEntity createDataKey="createDefaultCategory"/> + </createData> + <createData entity="Three_nested_categories" stepKey="createThreeLevelNestedCategories"> + <requiredEntity createDataKey="createTwoLevelNestedCategories"/> + </createData> + + <!-- Login as admin --> + <actionGroup ref="LoginAsAdmin" stepKey="login"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logout"/> + + <deleteData createDataKey="createThreeLevelNestedCategories" stepKey="deleteThreeNestedCategories"/> + <deleteData createDataKey="createTwoLevelNestedCategories" stepKey="deleteTwoLevelNestedCategory"/> + <deleteData createDataKey="createDefaultCategory" stepKey="deleteDefaultCategory"/> + </after> + + <!-- Open Category page --> + <amOnPage url="{{AdminCategoryPage.url}}" stepKey="openAdminCategoryIndexPage"/> + <waitForPageLoad stepKey="waitForPageToLoaded"/> + + <!-- Open 3rd Level category --> + <click selector="{{AdminCategorySidebarTreeSection.expandAll}}" stepKey="clickOnExpandTree"/> + <waitForPageLoad stepKey="waitForCategoryToLoad"/> + <click selector="{{AdminCategorySidebarTreeSection.categoryInTree($$createThreeLevelNestedCategories.name$$)}}" stepKey="selectCategory"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + + <!--Update category UrlKey and uncheck permanent redirect for old URL --> + <scrollTo selector="{{AdminCategorySEOSection.SectionHeader}}" x="0" y="-80" stepKey="scrollToSearchEngineOptimization1"/> + <click selector="{{AdminCategorySEOSection.SectionHeader}}" stepKey="selectSearchEngineOptimization"/> + <waitForPageLoad stepKey="waitForPageToLoad1"/> + <fillField selector="{{AdminCategorySEOSection.UrlKeyInput}}" userInput="updatedurl" stepKey="updateUrlKey"/> + <uncheckOption selector="{{AdminCategorySEOSection.UrlKeyRedirectCheckbox}}" stepKey="uncheckPermanentRedirectCheckBox"/> + <click selector="{{AdminCategorySEOSection.SectionHeader}}" stepKey="selectSearchEngineOptimization1"/> + <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="saveUpdatedCategory"/> + <waitForPageLoad stepKey="waitForCategoryToSave"/> + <seeElement selector="{{AdminCategoryMessagesSection.SuccessMessage}}" stepKey="seeSuccessMessage"/> + + <!-- Get Category Id --> + <grabFromCurrentUrl stepKey="categoryId" regex="#\/([0-9]*)?\/$#"/> + + <!-- Open Url Rewrite Page --> + <amOnPage url="{{AdminUrlRewriteIndexPage.url}}" stepKey="openUrlRewriteIndexPage"/> + <waitForPageLoad stepKey="waitForUrlRewritePage"/> + + <!-- Verify third level category's Redirect Path, Target Path and Redirect Type after the URL Update --> + <click selector="{{AdminUrlRewriteIndexSection.resetButton}}" stepKey="clickOnResetButton"/> + <waitForPageLoad stepKey="waitForPageToLoad0"/> + <fillField selector="{{AdminUrlRewriteIndexSection.requestPathFilter}}" userInput="updatedurl" stepKey="fillUpdatedUrlInRedirectPathFilter"/> + <click selector="{{AdminUrlRewriteIndexSection.searchButton}}" stepKey="clickOnSearchButton"/> + <waitForPageLoad stepKey="waitForPageToLoad2"/> + <see stepKey="seeTheRedirectType" selector="{{AdminUrlRewriteIndexSection.redirectTypeColumn('1')}}" userInput="No" /> + <see stepKey="seeTheTargetPath" selector="{{AdminUrlRewriteIndexSection.targetPathColumn('1')}}" userInput="catalog/category/view/id/{$categoryId}"/> + <see selector="{{AdminUrlRewriteIndexSection.requestPathColumn('1')}}" userInput="$$createDefaultCategory.name$$/$$createTwoLevelNestedCategories.name$$/updatedurl.html" stepKey="seeTheRedirectPath"/> + + <!-- Verify third level category's old URL path doesn't show redirect path--> + <click selector="{{AdminUrlRewriteIndexSection.resetButton}}" stepKey="clickOnResetButton1"/> + <waitForPageLoad stepKey="waitForPageToLoad3"/> + <fillField selector="{{AdminUrlRewriteIndexSection.requestPathFilter}}" userInput="{{Three_nested_categories.name_lwr}}" stepKey="fillOldUrlInRedirectPathFilter"/> + <click selector="{{AdminUrlRewriteIndexSection.searchButton}}" stepKey="clickOnSearchButton1"/> + <waitForPageLoad stepKey="waitForPageToLoad4"/> + <see stepKey="seeEmptyRecodsMessage" selector="{{AdminUrlRewriteIndexSection.emptyRecords}}" userInput="We couldn't find any records."/> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateTopCategoryUrlWithRedirectTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateTopCategoryUrlWithRedirectTest.xml new file mode 100644 index 00000000000..ee1ed5f97ed --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateTopCategoryUrlWithRedirectTest.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="AdminUpdateTopCategoryUrlWithRedirectTest"> + <annotations> + <stories value="Update category"/> + <title value="Update top category url and create redirect"/> + <description value="Login as admin and update top category url and create redirect"/> + <testCaseId value="MC-6057"/> + <severity value="CRITICAL"/> + <group value="Catalog"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <!-- Login as admin --> + <actionGroup ref="LoginAsAdmin" stepKey="login"/> + <!-- Create three level nested category --> + <createData entity="_defaultCategory" stepKey="createDefaultCategory"/> + <createData entity="Two_nested_categories" stepKey="createTwoLevelNestedCategories"> + <requiredEntity createDataKey="createDefaultCategory"/> + </createData> + <createData entity="Three_nested_categories" stepKey="createThreeLevelNestedCategories"> + <requiredEntity createDataKey="createTwoLevelNestedCategories"/> + </createData> + </before> + <after> + <deleteData createDataKey="createThreeLevelNestedCategories" stepKey="deleteThreeNestedCategories"/> + <deleteData createDataKey="createTwoLevelNestedCategories" stepKey="deleteTwoLevelNestedCategory"/> + <deleteData createDataKey="createDefaultCategory" stepKey="deleteDefaultCategory"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Open Category page --> + <amOnPage url="{{AdminCategoryPage.url}}" stepKey="openAdminCategoryIndexPage"/> + <waitForPageLoad stepKey="waitForPageToLoaded"/> + + <!-- Open 3rd Level category --> + <click selector="{{AdminCategorySidebarTreeSection.expandAll}}" stepKey="clickOnExpandTree"/> + <waitForPageLoad stepKey="waitForCategoryToLoad"/> + <click selector="{{AdminCategorySidebarTreeSection.categoryInTree($$createThreeLevelNestedCategories.name$$)}}" stepKey="selectCategory"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + + <!--Update category UrlKey and check permanent redirect for old URL --> + <scrollTo selector="{{AdminCategorySEOSection.SectionHeader}}" x="0" y="-80" stepKey="scrollToSearchEngineOptimization1"/> + <click selector="{{AdminCategorySEOSection.SectionHeader}}" stepKey="selectSearchEngineOptimization"/> + <waitForPageLoad stepKey="waitForPageToLoad1"/> + <fillField selector="{{AdminCategorySEOSection.UrlKeyInput}}" userInput="updateredirecturl" stepKey="updateUrlKey"/> + <checkOption selector="{{AdminCategorySEOSection.UrlKeyRedirectCheckbox}}" stepKey="checkPermanentRedirectCheckBox"/> + <click selector="{{AdminCategorySEOSection.SectionHeader}}" stepKey="selectSearchEngineOptimization1"/> + <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="saveUpdatedCategory"/> + <waitForPageLoad stepKey="waitForCategoryToSave"/> + <seeElement selector="{{AdminCategoryMessagesSection.SuccessMessage}}" stepKey="seeSuccessMessage"/> + + <!-- Get Category ID --> + <grabFromCurrentUrl stepKey="categoryId" regex="#\/([0-9]*)?\/$#"/> + + <!-- Open Url Rewrite Page --> + <amOnPage url="{{AdminUrlRewriteIndexPage.url}}" stepKey="openUrlRewriteIndexPage"/> + <waitForPageLoad stepKey="waitForUrlRewritePage"/> + + <!-- Verify third level category's Redirect Path, Target Path and Redirect Type after the URL update --> + <click selector="{{AdminUrlRewriteIndexSection.resetButton}}" stepKey="clickOnResetButton"/> + <waitForPageLoad stepKey="waitForPageToLoad2"/> + <fillField selector="{{AdminUrlRewriteIndexSection.requestPathFilter}}" userInput="updateredirecturl" stepKey="fillUpdatedURLInRedirectPathFilter"/> + <click selector="{{AdminUrlRewriteIndexSection.searchButton}}" stepKey="clickOnSearchButton"/> + <waitForPageLoad stepKey="waitForPageToLoad3"/> + <see selector="{{AdminUrlRewriteIndexSection.redirectTypeColumn('1')}}" userInput="No" stepKey="seeTheRedirectType"/> + <see selector="{{AdminUrlRewriteIndexSection.targetPathColumn('1')}}" userInput="catalog/category/view/id/{$categoryId}" stepKey="seeTheTargetPath"/> + <see selector="{{AdminUrlRewriteIndexSection.requestPathColumn('1')}}" userInput="$$createDefaultCategory.name$$/$$createTwoLevelNestedCategories.name$$/updateredirecturl.html" stepKey="seeTheRedirectPath"/> + + <!-- Verify third level category's Redirect path, Target Path and Redirect type for old URL --> + <click selector="{{AdminUrlRewriteIndexSection.resetButton}}" stepKey="clickOnResetButton1"/> + <waitForPageLoad stepKey="waitForPageToLoad4"/> + <fillField selector="{{AdminUrlRewriteIndexSection.requestPathFilter}}" userInput="$$createThreeLevelNestedCategories.name$$" stepKey="fillOldUrlInRedirectPathFilter"/> + <click selector="{{AdminUrlRewriteIndexSection.searchButton}}" stepKey="clickOnSearchButton1"/> + <waitForPageLoad stepKey="waitForPageToLoad5"/> + <see selector="{{AdminUrlRewriteIndexSection.requestPathColumn('1')}}" userInput="$$createDefaultCategory.name$$/$$createTwoLevelNestedCategories.name$$/$$createThreeLevelNestedCategories.name$$.html" stepKey="seeTheRedirectPathForOldUrl"/> + <see selector="{{AdminUrlRewriteIndexSection.targetPathColumn('1')}}" userInput="$$createDefaultCategory.name$$/$$createTwoLevelNestedCategories.name$$/updateredirecturl.html" stepKey="seeTheTargetPathForOldUrl"/> + <see selector="{{AdminUrlRewriteIndexSection.redirectTypeColumn('1')}}" userInput="Permanent (301)" stepKey="seeTheRedirectTypeForOldUrl"/> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockVisibleInCategoryOnlyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockVisibleInCategoryOnlyTest.xml new file mode 100644 index 00000000000..9bdc93e61e4 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockVisibleInCategoryOnlyTest.xml @@ -0,0 +1,155 @@ +<?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="AdminUpdateVirtualProductWithRegularPriceInStockVisibleInCategoryOnlyTest"> + <annotations> + <stories value="Update Virtual Product"/> + <title value="Update Virtual Product with Regular Price (In Stock) Visible in Category Only"/> + <description value="Test log in to Update Virtual Product and Update Virtual Product with Regular Price (In Stock) Visible in Category Only"/> + <testCaseId value="MC-6495"/> + <severity value="CRITICAL"/> + <group value="catalog"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <actionGroup ref = "LoginAsAdmin" stepKey="loginAsAdmin"/> + <createData entity="SimpleSubCategory" stepKey="initialCategoryEntity"/> + <createData entity="defaultVirtualProduct" stepKey="initialVirtualProduct"> + <requiredEntity createDataKey="initialCategoryEntity"/> + </createData> + <createData entity="SimpleSubCategory" stepKey="categoryEntity"/> + </before> + <after> + <deleteData stepKey="deleteSimpleSubCategory" createDataKey="initialCategoryEntity"/> + <deleteData stepKey="deleteSimpleSubCategory2" createDataKey="categoryEntity"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Search default virtual product in the grid page --> + <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPage1"/> + <waitForPageLoad stepKey="waitForProductCatalogPage1"/> + <conditionalClick selector="{{AdminProductGridFilterSection.clearAll}}" dependentSelector="{{AdminProductGridFilterSection.clearAll}}" visible="true" stepKey="clickClearAllFilter" /> + <fillField selector="{{AdminProductGridFilterSection.keywordSearch}}" userInput="$$initialVirtualProduct.name$$" stepKey="fillVirtualProductNameInKeywordSearch"/> + <click selector="{{AdminProductGridFilterSection.keywordSearchButton}}" stepKey="clickKeywordSearchButton"/> + <waitForPageLoad stepKey="waitForProductSearch"/> + <click selector="{{AdminProductGridFilterSection.nthRow('1')}}" stepKey="clickFirstRowToVerifyCreatedVirtualProduct"/> + <waitForPageLoad stepKey="waitUntilProductIsOpened"/> + + <!-- Update virtual product with regular price --> + <fillField selector="{{AdminProductFormSection.productName}}" userInput="{{updateVirtualProductRegularPrice.name}}" stepKey="fillProductName"/> + <fillField selector="{{AdminProductFormSection.productSku}}" userInput="{{updateVirtualProductRegularPrice.sku}}" stepKey="fillProductSku"/> + <fillField selector="{{AdminProductFormSection.productPrice}}" userInput="{{updateVirtualProductRegularPrice.price}}" stepKey="fillProductPrice"/> + <!-- Press enter to validate advanced pricing link --> + <pressKey selector="{{AdminProductFormSection.productPrice}}" parameterArray="[\Facebook\WebDriver\WebDriverKeys::ENTER]" stepKey="pressEnterKey"/> + <click selector="{{AdminProductFormSection.advancedPricingLink}}" stepKey="clickAdvancedPricingLink"/> + <click selector="{{AdminProductFormAdvancedPricingSection.customerGroupPriceAddButton}}" stepKey="clickCustomerGroupPriceAddButton"/> + <scrollTo selector="{{AdminProductFormAdvancedPricingSection.productTierPriceQtyInput('0')}}" x="50" y="0" stepKey="scrollToProductTierPriceQuantityInputTextBox"/> + <selectOption selector="{{AdminProductFormAdvancedPricingSection.productTierPriceWebsiteSelect('0')}}" userInput="{{tierPriceOnVirtualProduct.website}}" stepKey="selectProductTierPriceWebsiteInput"/> + <selectOption selector="{{AdminProductFormAdvancedPricingSection.productTierPriceCustGroupSelect('0')}}" userInput="{{tierPriceOnVirtualProduct.customer_group}}" stepKey="selectProductTierPriceCustomerGroupInput"/> + <fillField selector="{{AdminProductFormAdvancedPricingSection.productTierPriceQtyInput('0')}}" userInput="{{tierPriceOnVirtualProduct.qty}}" stepKey="fillProductTierPriceQuantityInput"/> + <fillField selector="{{AdminProductFormAdvancedPricingSection.productTierPriceFixedPriceInput('0')}}" userInput="{{tierPriceOnVirtualProduct.price}}" stepKey="selectProductTierPriceFixedPrice"/> + <click selector="{{AdminProductFormAdvancedPricingSection.doneButton}}" stepKey="clickDoneButton"/> + <selectOption selector="{{AdminProductFormSection.productTaxClass}}" userInput="{{updateVirtualProductRegularPrice.productTaxClass}}" stepKey="selectProductStockClass"/> + <fillField selector="{{AdminProductFormSection.productQuantity}}" userInput="{{updateVirtualProductRegularPrice.quantity}}" stepKey="fillProductQuantity"/> + <selectOption selector="{{AdminProductFormSection.stockStatus}}" userInput="{{updateVirtualProductRegularPrice.status}}" stepKey="selectStockStatusInStock"/> + <click selector="{{AdminProductFormSection.categoriesDropdown}}" stepKey="clickCategoriesDropDown"/> + <fillField selector="{{AdminProductFormSection.searchCategory}}" userInput="$$initialCategoryEntity.name$$" stepKey="fillSearchForInitialCategory" /> + <click selector="{{AdminProductFormSection.selectCategory($$initialCategoryEntity.name$$)}}" stepKey="unselectInitialCategory"/> + <fillField selector="{{AdminProductFormSection.searchCategory}}" userInput="$$categoryEntity.name$$" stepKey="fillSearchCategory" /> + <click selector="{{AdminProductFormSection.selectCategory($$categoryEntity.name$$)}}" stepKey="clickOnCategory"/> + <click selector="{{AdminProductFormSection.done}}" stepKey="clickOnDoneAdvancedCategorySelect"/> + <selectOption selector="{{AdminProductFormSection.visibility}}" userInput="{{updateVirtualProductRegularPrice.visibility}}" stepKey="selectVisibility"/> + <click selector="{{AdminProductSEOSection.sectionHeader}}" stepKey="clickAdminProductSEOSection"/> + <fillField selector="{{AdminProductSEOSection.urlKeyInput}}" userInput="{{updateVirtualProductRegularPrice.urlKey}}" stepKey="fillUrlKey"/> + <scrollToTopOfPage stepKey="scrollToTopOfAdminProductFormSection"/> + <click selector="{{AdminProductFormSection.save}}" stepKey="clickSaveButton"/> + <waitForPageLoad stepKey="waitForVirtualProductSaved"/> + <!-- Verify we see success message --> + <see selector="{{AdminProductFormSection.successMessage}}" userInput="You saved the product." stepKey="seeAssertVirtualProductSaveSuccessMessage"/> + + <!-- Search updated virtual product(from above step) in the grid page --> + <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPageToSearchUpdatedVirtualProduct"/> + <waitForPageLoad stepKey="waitForProductCatalogPageToLoad"/> + <conditionalClick selector="{{AdminProductGridFilterSection.clearAll}}" dependentSelector="{{AdminProductGridFilterSection.clearAll}}" visible="true" stepKey="clickClearAll"/> + <click selector="{{AdminProductGridFilterSection.filters}}" stepKey="clickFiltersButton"/> + <fillField selector="{{AdminProductGridFilterSection.nameFilter}}" userInput="{{updateVirtualProductRegularPrice.name}}" stepKey="fillVirtualProductNameInNameFilter"/> + <fillField selector="{{AdminProductGridFilterSection.skuFilter}}" userInput="{{updateVirtualProductRegularPrice.sku}}" stepKey="fillVirtualProductSku"/> + <click selector="{{AdminProductGridFilterSection.applyFilters}}" stepKey="clickApplyFiltersButton"/> + <click selector="{{AdminProductGridFilterSection.nthRow('1')}}" stepKey="clickFirstRowToVerifyUpdatedVirtualProductVisibleInGrid"/> + <waitForPageLoad stepKey="waitUntilVirtualProductPageIsOpened"/> + + <!-- Verify we see created virtual product with tier price(from the above step) in the product form page --> + <seeInField selector="{{AdminProductFormSection.productName}}" userInput="{{updateVirtualProductRegularPrice.name}}" stepKey="seeProductName"/> + <seeInField selector="{{AdminProductFormSection.productSku}}" userInput="{{updateVirtualProductRegularPrice.sku}}" stepKey="seeProductSku"/> + <seeInField selector="{{AdminProductFormSection.productPrice}}" userInput="{{updateVirtualProductRegularPrice.price}}" stepKey="seeProductPrice"/> + <click selector="{{AdminProductFormSection.advancedPricingLink}}" stepKey="clickAdvancedPricingLink1"/> + <seeInField selector="{{AdminProductFormAdvancedPricingSection.productTierPriceWebsiteSelect('0')}}" userInput="{{tierPriceOnVirtualProduct.website}}" stepKey="seeProductTierPriceWebsiteInput"/> + <seeInField selector="{{AdminProductFormAdvancedPricingSection.productTierPriceCustGroupSelect('0')}}" userInput="{{tierPriceOnVirtualProduct.customer_group}}" stepKey="seeProductTierPriceCustomerGroupInput"/> + <seeInField selector="{{AdminProductFormAdvancedPricingSection.productTierPriceQtyInput('0')}}" userInput="{{tierPriceOnVirtualProduct.qty}}" stepKey="seeProductTierPriceQuantityInput"/> + <seeInField selector="{{AdminProductFormAdvancedPricingSection.productTierPriceFixedPriceInput('0')}}" userInput="{{tierPriceOnVirtualProduct.price}}" stepKey="seeProductTierPriceFixedPrice"/> + <click selector="{{AdminProductFormAdvancedPricingSection.advancedPricingCloseButton}}" stepKey="clickAdvancedPricingCloseButton"/> + <seeInField selector="{{AdminProductFormSection.productTaxClass}}" userInput="{{updateVirtualProductRegularPrice.productTaxClass}}" stepKey="seeProductTaxClass"/> + <seeInField selector="{{AdminProductFormSection.productQuantity}}" userInput="{{updateVirtualProductRegularPrice.quantity}}" stepKey="seeProductQuantity"/> + <seeInField selector="{{AdminProductFormSection.productStockStatus}}" userInput="{{updateVirtualProductRegularPrice.status}}" stepKey="seeProductStockStatus"/> + <click selector="{{AdminProductFormSection.categoriesDropdown}}" stepKey="clickCategoriesDropDownToVerify"/> + <grabMultiple selector="{{AdminProductFormSection.selectMultipleCategories}}" stepKey="selectedCategories" /> + <assertEquals stepKey="assertSelectedCategories"> + <actualResult type="variable">selectedCategories</actualResult> + <expectedResult type="array">[$$categoryEntity.name$$]</expectedResult> + </assertEquals> + <click selector="{{AdminProductFormSection.done}}" stepKey="clickOnDoneOnCategorySelect"/> + <seeInField selector="{{AdminProductFormSection.visibility}}" userInput="{{updateVirtualProductRegularPrice.visibility}}" stepKey="seeVisibility"/> + <scrollTo selector="{{AdminProductSEOSection.sectionHeader}}" x="0" y="-80" stepKey="scrollToAdminProductSEOSection1"/> + <click selector="{{AdminProductSEOSection.sectionHeader}}" stepKey="clickAdminProductSEOSection1"/> + <seeInField selector="{{AdminProductSEOSection.urlKeyInput}}" userInput="{{updateVirtualProductRegularPrice.urlKey}}" stepKey="seeUrlKey"/> + + <!-- Verify customer don't see updated virtual product link on storefront page and is searchable by sku --> + <amOnPage url="{{StorefrontProductPage.url(updateVirtualProductRegularPrice.urlKey)}}" stepKey="goToProductPage"/> + <waitForPageLoad stepKey="waitForStoreFrontProductPageLoad"/> + <fillField selector="{{StorefrontQuickSearchResultsSection.searchTextBox}}" userInput="{{updateVirtualProductRegularPrice.sku}}" stepKey="fillVirtualProductSkuOnStorefrontPage"/> + <waitForPageLoad stepKey="waitForSearchTextBox"/> + <click selector="{{StorefrontQuickSearchResultsSection.searchTextBoxButton}}" stepKey="clickSearchTextBoxButton"/> + <waitForPageLoad stepKey="waitForSearch"/> + <dontSee selector="{{StorefrontQuickSearchResultsSection.productLink}}" userInput="{{updateVirtualProductRegularPrice.name}}" stepKey="dontSeeVirtualProductName"/> + + <!-- Verify customer see updated virtual product in category page --> + <amOnPage url="{{StorefrontCategoryPage.url($$categoryEntity.name$$)}}" stepKey="openCategoryPage"/> + <waitForPageLoad stepKey="waitForCategoryPageLoad"/> + <see selector="{{StorefrontCategoryMainSection.productLink}}" userInput="{{updateVirtualProductRegularPrice.name}}" stepKey="seeVirtualProductLinkOnCategoryPage"/> + <grabTextFrom selector="{{StorefrontCategoryMainSection.asLowAs}}" stepKey="tierPriceTextOnCategoryPage"/> + <assertEquals stepKey="assertTierPriceTextOnCategoryPage"> + <expectedResult type="string">As low as ${{tierPriceOnVirtualProduct.price}}</expectedResult> + <actualResult type="variable">tierPriceTextOnCategoryPage</actualResult> + </assertEquals> + + <!-- Verify customer see updated virtual product and tier price on product page --> + <amOnPage url="{{StorefrontProductPage.url(updateVirtualProductRegularPrice.urlKey)}}" stepKey="goToStorefrontProductPage"/> + <waitForPageLoad stepKey="waitForStoreFrontProductPageToLoad"/> + <see selector="{{StorefrontProductInfoMainSection.productName}}" userInput="{{updateVirtualProductRegularPrice.name}}" stepKey="seeVirtualProductNameOnStoreFrontPage"/> + <see selector="{{StorefrontProductInfoMainSection.productPrice}}" userInput="{{updateVirtualProductRegularPrice.price}}" stepKey="seeVirtualProductPriceOnStoreFrontPage"/> + <see selector="{{StorefrontProductInfoMainSection.productSku}}" userInput="{{updateVirtualProductRegularPrice.sku}}" stepKey="seeVirtualProductSku"/> + <grabTextFrom selector="{{StorefrontProductInfoMainSection.productStockStatus}}" stepKey="productStockAvailableStatus"/> + <grabTextFrom selector="{{StorefrontProductInfoMainSection.tierPriceText}}" stepKey="tierPriceText"/> + <assertEquals stepKey="assertTierPriceTextOnProductPage"> + <expectedResult type="string">Buy {{tierPriceOnVirtualProduct.qty}} for ${{tierPriceOnVirtualProduct.price}} each and save 10%</expectedResult> + <actualResult type="variable">tierPriceText</actualResult> + </assertEquals> + <assertEquals stepKey="assertStockAvailableOnProductPage"> + <expectedResult type="string">{{updateVirtualProductRegularPrice.storefrontStatus}}</expectedResult> + <actualResult type="variable">productStockAvailableStatus</actualResult> + </assertEquals> + <grabTextFrom selector="{{StorefrontProductInfoMainSection.productPrice}}" stepKey="productPriceAmount"/> + <assertEquals stepKey="assertOldPriceTextOnProductPage"> + <expectedResult type="string">${{updateVirtualProductRegularPrice.price}}</expectedResult> + <actualResult type="variable">productPriceAmount</actualResult> + </assertEquals> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockWithCustomOptionsVisibleInSearchOnlyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockWithCustomOptionsVisibleInSearchOnlyTest.xml new file mode 100644 index 00000000000..d67d5b36109 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockWithCustomOptionsVisibleInSearchOnlyTest.xml @@ -0,0 +1,249 @@ +<?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="AdminUpdateVirtualProductWithRegularPriceInStockWithCustomOptionsVisibleInSearchOnlyTest"> + <annotations> + <stories value="Update Virtual Product"/> + <title value="Update Virtual Product with Regular Price (In Stock) with Custom Options Visible in Search Only"/> + <description value="Test log in to Update Virtual Product and Update Virtual Product with Regular Price (In Stock) with Custom Options Visible in Search Only"/> + <testCaseId value="MC-6641"/> + <severity value="CRITICAL"/> + <group value="catalog"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <actionGroup ref = "LoginAsAdmin" stepKey="loginAsAdmin"/> + <createData entity="SimpleSubCategory" stepKey="initialCategoryEntity"/> + <createData entity="defaultVirtualProduct" stepKey="initialVirtualProduct"> + <requiredEntity createDataKey="initialCategoryEntity"/> + </createData> + <createData entity="SimpleSubCategory" stepKey="categoryEntity"/> + </before> + <after> + <deleteData stepKey="deleteSimpleSubCategory" createDataKey="initialCategoryEntity"/> + <deleteData stepKey="deleteSimpleSubCategory2" createDataKey="categoryEntity"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Search default virtual product in the grid page --> + <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPage1"/> + <waitForPageLoad stepKey="waitForProductCatalogPage1"/> + <conditionalClick selector="{{AdminProductGridFilterSection.clearAll}}" dependentSelector="{{AdminProductGridFilterSection.clearAll}}" visible="true" stepKey="clickClearAllFilter" /> + <fillField selector="{{AdminProductGridFilterSection.keywordSearch}}" userInput="$$initialVirtualProduct.name$$" stepKey="fillVirtualProductNameInKeywordSearch"/> + <click selector="{{AdminProductGridFilterSection.keywordSearchButton}}" stepKey="clickKeywordSearchButton"/> + <waitForPageLoad stepKey="waitForProductSearch"/> + <click selector="{{AdminProductGridFilterSection.nthRow('1')}}" stepKey="clickFirstRowToVerifyCreatedVirtualProduct"/> + <waitForPageLoad stepKey="waitUntilProductIsOpened"/> + + <!-- Update virtual product with regular price(in stock) --> + <fillField selector="{{AdminProductFormSection.productName}}" userInput="{{updateVirtualProductRegularPriceInStock.name}}" stepKey="fillProductName"/> + <fillField selector="{{AdminProductFormSection.productSku}}" userInput="{{updateVirtualProductRegularPriceInStock.sku}}" stepKey="fillProductSku"/> + <fillField selector="{{AdminProductFormSection.productPrice}}" userInput="{{updateVirtualProductRegularPriceInStock.price}}" stepKey="fillProductPrice"/> + <selectOption selector="{{AdminProductFormSection.productTaxClass}}" userInput="{{updateVirtualProductRegularPriceInStock.productTaxClass}}" stepKey="selectProductTaxClass"/> + <fillField selector="{{AdminProductFormSection.productQuantity}}" userInput="{{updateVirtualProductRegularPriceInStock.quantity}}" stepKey="fillProductQuantity"/> + <selectOption selector="{{AdminProductFormSection.stockStatus}}" userInput="{{updateVirtualProductRegularPriceInStock.status}}" stepKey="selectStockStatusInStock"/> + <click selector="{{AdminProductFormSection.categoriesDropdown}}" stepKey="clickCategoriesDropDown"/> + <fillField selector="{{AdminProductFormSection.searchCategory}}" userInput="$$initialCategoryEntity.name$$" stepKey="fillSearchForInitialCategory" /> + <click selector="{{AdminProductFormSection.selectCategory($$initialCategoryEntity.name$$)}}" stepKey="unselectInitialCategory"/> + <fillField selector="{{AdminProductFormSection.searchCategory}}" userInput="$$categoryEntity.name$$" stepKey="fillSearchCategory" /> + <click selector="{{AdminProductFormSection.selectCategory($$categoryEntity.name$$)}}" stepKey="clickOnCategory"/> + <click selector="{{AdminProductFormSection.done}}" stepKey="clickOnDoneAdvancedCategorySelect"/> + <selectOption selector="{{AdminProductFormSection.visibility}}" userInput="{{updateVirtualProductRegularPriceInStock.visibility}}" stepKey="selectVisibility"/> + <click selector="{{AdminProductSEOSection.sectionHeader}}" stepKey="clickAdminProductSEOSection"/> + <fillField selector="{{AdminProductSEOSection.urlKeyInput}}" userInput="{{updateVirtualProductRegularPriceInStock.urlKey}}" stepKey="fillUrlKey"/> + <click selector="{{AdminProductCustomizableOptionsSection.checkIfCustomizableOptionsTabOpen}}" stepKey="clickAdminProductCustomizableOption"/> + <!-- Create virtual product with customizable options dataSet1 --> + <click selector="{{AdminProductCustomizableOptionsSection.addOptionBtn}}" stepKey="clickAddOptionButton"/> + <waitForPageLoad stepKey="waitForFirstOption"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.optionTitleInput('0')}}" userInput="{{virtualProductCustomizableOption1.title}}" stepKey="fillOptionTitleForFirstDataSet"/> + <click selector="{{AdminProductCustomizableOptionsSection.optionTypeDropDown('1')}}" stepKey="selectOptionTypeDropDownFirstDataSet"/> + <click selector="{{AdminProductCustomizableOptionsSection.optionTypeItem('1', virtualProductCustomizableOption1.type)}}" stepKey="selectOptionFieldFromDropDownForFirstDataSet"/> + <checkOption selector="{{AdminProductCustomizableOptionsSection.requiredCheckBox('0')}}" stepKey="checkRequiredCheckBoxForFirstDataSet"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.optionPrice('0')}}" userInput="{{virtualProductCustomizableOption1.option_0_price}}" stepKey="fillOptionPriceForFirstDataSet"/> + <selectOption selector="{{AdminProductCustomizableOptionsSection.optionPriceType('0')}}" userInput="{{virtualProductCustomizableOption1.option_0_price_type}}" stepKey="selectOptionPriceTypeForFirstDataSet"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.optionSku('0')}}" userInput="{{virtualProductCustomizableOption1.option_0_sku}}" stepKey="fillOptionSkuForFirstDataSet"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.maxCharactersInput('0')}}" userInput="{{virtualProductCustomizableOption1.option_0_max_characters}}" stepKey="fillOptionMaxCharactersForFirstDataSet"/> + <!--Create virtual product with customizable options dataSet2 --> + <click selector="{{AdminProductCustomizableOptionsSection.addOptionBtn}}" stepKey="clickAddOptionButtonForSecondDataSet"/> + <waitForPageLoad stepKey="waitForSecondDataSetToLoad"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.optionTitleInput('1')}}" userInput="{{virtualProductCustomizableOption2.title}}" stepKey="fillOptionTitleForSecondDataSet"/> + <click selector="{{AdminProductCustomizableOptionsSection.optionTypeDropDown('2')}}" stepKey="selectOptionTypeDropDownSecondDataSet"/> + <click selector="{{AdminProductCustomizableOptionsSection.optionTypeItem('2', virtualProductCustomizableOption2.type)}}" stepKey="selectOptionFieldFromDropDownForSecondDataSet"/> + <checkOption selector="{{AdminProductCustomizableOptionsSection.requiredCheckBox('1')}}" stepKey="checkRequiredCheckBoxForSecondDataSet"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.optionPrice('1')}}" userInput="{{virtualProductCustomizableOption2.option_0_price}}" stepKey="fillOptionPriceForSecondDataSet"/> + <selectOption selector="{{AdminProductCustomizableOptionsSection.optionPriceType('1')}}" userInput="{{virtualProductCustomizableOption2.option_0_price_type}}" stepKey="selectOptionPriceTypeForSecondDataSet"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.optionSku('1')}}" userInput="{{virtualProductCustomizableOption2.option_0_sku}}" stepKey="fillOptionSkuForSecondDataSet"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.maxCharactersInput('1')}}" userInput="{{virtualProductCustomizableOption2.option_0_max_characters}}" stepKey="fillOptionMaxCharactersForSecondDataSet"/> + <!-- Create virtual product with customizable options dataSet3 --> + <click selector="{{AdminProductCustomizableOptionsSection.addOptionBtn}}" stepKey="clickAddOptionButtonForThirdSetOfData"/> + <waitForPageLoad stepKey="waitForThirdSetOfDataToLoad"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.optionTitleInput('2')}}" userInput="{{virtualProductCustomizableOption3.title}}" stepKey="fillOptionTitleForThirdDataSet"/> + <click selector="{{AdminProductCustomizableOptionsSection.optionTypeDropDown('3')}}" stepKey="selectOptionTypeDropDownForThirdDataSet"/> + <click selector="{{AdminProductCustomizableOptionsSection.optionTypeItem('3', virtualProductCustomizableOption3.type)}}" stepKey="selectOptionFieldFromDropDownForThirdDataSet"/> + <checkOption selector="{{AdminProductCustomizableOptionsSection.requiredCheckBox('2')}}" stepKey="checkRequiredCheckBoxForThirdDataSet"/> + <click selector="{{AdminProductCustomizableOptionsSection.addValue}}" stepKey="clickAddOptionButtonForThirdDataSetToAddFirstRow"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.fillOptionValueTitle(virtualProductCustomizableOption3.title,'0')}}" userInput="{{virtualProductCustomizableOption3.option_0_title}}" stepKey="fillOptionTitleForThirdDataSetFirstRow"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.fillOptionValuePrice(virtualProductCustomizableOption3.title,'0')}}" userInput="{{virtualProductCustomizableOption3.option_0_price}}" stepKey="fillOptionPriceForThirdDataSetFirstRow"/> + <selectOption selector="{{AdminProductCustomizableOptionsSection.clickSelectPriceType(virtualProductCustomizableOption3.title,'0')}}" userInput="{{virtualProductCustomizableOption3.option_0_price_type}}" stepKey="selectOptionPriceTypeForThirdDataSetFirstRow"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.fillOptionValueSku(virtualProductCustomizableOption3.title,'0')}}" userInput="{{virtualProductCustomizableOption3.option_0_sku}}" stepKey="fillOptionSkuForThirdDataSetFirstRow"/> + <click selector="{{AdminProductCustomizableOptionsSection.addValue}}" stepKey="clickAddOptionButtonForThirdDataSetToAddSecondRow"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.fillOptionValueTitle(virtualProductCustomizableOption3.title,'1')}}" userInput="{{virtualProductCustomizableOption3.option_1_title}}" stepKey="fillOptionTitleForThirdDataSetSecondRow"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.fillOptionValuePrice(virtualProductCustomizableOption3.title,'1')}}" userInput="{{virtualProductCustomizableOption3.option_1_price}}" stepKey="fillOptionPriceForThirdDataSetSecondRow"/> + <selectOption selector="{{AdminProductCustomizableOptionsSection.clickSelectPriceType(virtualProductCustomizableOption3.title,'1')}}" userInput="{{virtualProductCustomizableOption3.option_1_price_type}}" stepKey="selectOptionPriceTypeForThirdDataSetSecondRow"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.fillOptionValueSku(virtualProductCustomizableOption3.title,'1')}}" userInput="{{virtualProductCustomizableOption3.option_1_sku}}" stepKey="fillOptionSkuForThirdDataSetSecondRow"/> + <!-- Create virtual product with customizable options dataSet4 --> + <scrollToTopOfPage stepKey="scrollToAddOptionButton"/> + <click selector="{{AdminProductCustomizableOptionsSection.addOptionBtn}}" stepKey="clickAddOptionButtonForFourthDataSet"/> + <waitForPageLoad stepKey="waitForFourthDataSetToLoad"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.optionTitleInput('3')}}" userInput="{{virtualProductCustomizableOption4.title}}" stepKey="fillOptionTitleForFourthDataSet"/> + <click selector="{{AdminProductCustomizableOptionsSection.optionTypeDropDown('4')}}" stepKey="selectOptionTypeDropDownForFourthSetOfData"/> + <click selector="{{AdminProductCustomizableOptionsSection.optionTypeItem('4', virtualProductCustomizableOption4.type)}}" stepKey="selectOptionFieldFromDropDownForFourthDataSet"/> + <checkOption selector="{{AdminProductCustomizableOptionsSection.requiredCheckBox('3')}}" stepKey="checkRequiredCheckBoxForFourthDataSet"/> + <click selector="{{AdminProductCustomizableOptionsSection.addValue}}" stepKey="clickAddOptionButtonForFourthDataSetToAddFirstRow"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.fillOptionValueTitle(virtualProductCustomizableOption4.title,'0')}}" userInput="{{virtualProductCustomizableOption4.option_0_title}}" stepKey="fillOptionTitleForFourthDataSetFirstRow"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.fillOptionValuePrice(virtualProductCustomizableOption4.title,'0')}}" userInput="{{virtualProductCustomizableOption4.option_0_price}}" stepKey="fillOptionPriceForFourthDataSetFirstRow"/> + <selectOption selector="{{AdminProductCustomizableOptionsSection.clickSelectPriceType(virtualProductCustomizableOption4.title,'0')}}" userInput="{{virtualProductCustomizableOption4.option_0_price_type}}" stepKey="selectOptionPriceTypeForFourthDataSetFirstRow"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.fillOptionValueSku(virtualProductCustomizableOption4.title,'0')}}" userInput="{{virtualProductCustomizableOption4.option_0_sku}}" stepKey="fillOptionSkuForFourthDataSetFirstRow"/> + <click selector="{{AdminProductCustomizableOptionsSection.addValue}}" stepKey="clickAddOptionButtonForFourthDataSetToAddSecondRow"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.fillOptionValueTitle(virtualProductCustomizableOption4.title,'1')}}" userInput="{{virtualProductCustomizableOption4.option_1_title}}" stepKey="fillOptionTitleForFourthDataSetSecondRow"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.fillOptionValuePrice(virtualProductCustomizableOption4.title,'1')}}" userInput="{{virtualProductCustomizableOption4.option_1_price}}" stepKey="fillOptionPriceForFourthDataSetSecondRow"/> + <selectOption selector="{{AdminProductCustomizableOptionsSection.clickSelectPriceType(virtualProductCustomizableOption4.title,'1')}}" userInput="{{virtualProductCustomizableOption4.option_1_price_type}}" stepKey="selectOptionPriceTypeForFourthDataSetSecondRow"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.fillOptionValueSku(virtualProductCustomizableOption4.title,'1')}}" userInput="{{virtualProductCustomizableOption4.option_1_sku}}" stepKey="fillOptionSkuForFourthDataSetSecondRow"/> + <scrollToTopOfPage stepKey="scrollToTopOfAdminProductFormSection"/> + <click selector="{{AdminProductFormSection.save}}" stepKey="clickSaveButton"/> + <waitForPageLoad stepKey="waitForVirtualProductSaved"/> + <!-- Verify we see success message --> + <see selector="{{AdminProductFormSection.successMessage}}" userInput="You saved the product." stepKey="seeAssertVirtualProductSaveSuccessMessage"/> + + <!-- Search updated virtual product(from above step) in the grid page --> + <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPageToSearchUpdatedVirtualProduct"/> + <waitForPageLoad stepKey="waitForProductCatalogPageToLoad"/> + <conditionalClick selector="{{AdminProductGridFilterSection.clearAll}}" dependentSelector="{{AdminProductGridFilterSection.clearAll}}" visible="true" stepKey="clickClearAll"/> + <click selector="{{AdminProductGridFilterSection.filters}}" stepKey="clickFiltersButton"/> + <fillField selector="{{AdminProductGridFilterSection.nameFilter}}" userInput="{{updateVirtualProductRegularPriceInStock.name}}" stepKey="fillVirtualProductNameInNameFilter"/> + <fillField selector="{{AdminProductGridFilterSection.skuFilter}}" userInput="{{updateVirtualProductRegularPriceInStock.sku}}" stepKey="fillVirtualProductSku"/> + <click selector="{{AdminProductGridFilterSection.applyFilters}}" stepKey="clickApplyFiltersButton"/> + <click selector="{{AdminProductGridFilterSection.nthRow('1')}}" stepKey="clickFirstRowToVerifyUpdatedVirtualProductVisibleInGrid"/> + <waitForPageLoad stepKey="waitUntilVirtualProductPageIsOpened"/> + + <!-- Verify we customer see updated virtual product in the product form page --> + <seeInField selector="{{AdminProductFormSection.productName}}" userInput="{{updateVirtualProductRegularPriceInStock.name}}" stepKey="seeProductName"/> + <seeInField selector="{{AdminProductFormSection.productSku}}" userInput="{{updateVirtualProductRegularPriceInStock.sku}}" stepKey="seeProductSku"/> + <seeInField selector="{{AdminProductFormSection.productPrice}}" userInput="{{updateVirtualProductRegularPriceInStock.price}}" stepKey="seeProductPrice"/> + <seeInField selector="{{AdminProductFormSection.productTaxClass}}" userInput="{{updateVirtualProductRegularPriceInStock.productTaxClass}}" stepKey="seeProductTaxClass"/> + <seeInField selector="{{AdminProductFormSection.productQuantity}}" userInput="{{updateVirtualProductRegularPriceInStock.quantity}}" stepKey="seeProductQuantity"/> + <seeInField selector="{{AdminProductFormSection.productStockStatus}}" userInput="{{updateVirtualProductRegularPriceInStock.status}}" stepKey="seeProductStockStatus"/> + <click selector="{{AdminProductFormSection.categoriesDropdown}}" stepKey="clickCategoriesDropDownToVerify"/> + <grabMultiple selector="{{AdminProductFormSection.selectMultipleCategories}}" stepKey="selectedCategories" /> + <assertEquals stepKey="assertSelectedCategories"> + <actualResult type="variable">selectedCategories</actualResult> + <expectedResult type="array">[$$categoryEntity.name$$]</expectedResult> + </assertEquals> + <click selector="{{AdminProductFormSection.done}}" stepKey="clickOnDoneOnCategorySelect"/> + <seeInField selector="{{AdminProductFormSection.visibility}}" userInput="{{updateVirtualProductRegularPriceInStock.visibility}}" stepKey="seeVisibility"/> + <scrollTo selector="{{AdminProductSEOSection.sectionHeader}}" x="0" y="-80" stepKey="scrollToAdminProductSEOSection1"/> + <click selector="{{AdminProductSEOSection.sectionHeader}}" stepKey="clickAdminProductSEOSection1"/> + <seeInField selector="{{AdminProductSEOSection.urlKeyInput}}" userInput="{{updateVirtualProductRegularPriceInStock.urlKey}}" stepKey="seeUrlKey"/> + <click selector="{{AdminProductCustomizableOptionsSection.checkIfCustomizableOptionsTabOpen}}" stepKey="clickAdminProductCustomizableOptionToSeeValues"/> + <!-- Create virtual product with customizable options dataSet1 --> + <click selector="{{AdminProductCustomizableOptionsSection.addOptionBtn}}" stepKey="clickAddOptionButton1"/> + <waitForPageLoad stepKey="waitForFirstOptionToLoad"/> + <seeInField selector="{{AdminProductCustomizableOptionsSection.optionTitleInput('0')}}" userInput="{{virtualProductCustomizableOption1.title}}" stepKey="seeOptionTitleForFirstDataSet"/> + <click selector="{{AdminProductCustomizableOptionsSection.optionTypeDropDown('1')}}" stepKey="selectOptionTypeDropDownFirstDataSet1"/> + <click selector="{{AdminProductCustomizableOptionsSection.optionTypeItem('1', virtualProductCustomizableOption1.type)}}" stepKey="selectOptionFieldFromDropDownForFirstDataSet1"/> + <checkOption selector="{{AdminProductCustomizableOptionsSection.requiredCheckBox('0')}}" stepKey="checkRequiredCheckBoxForFirstDataSet1"/> + <seeInField selector="{{AdminProductCustomizableOptionsSection.optionPrice('0')}}" userInput="{{virtualProductCustomizableOption1.option_0_price}}" stepKey="seeOptionPriceForFirstDataSet"/> + <seeOptionIsSelected selector="{{AdminProductCustomizableOptionsSection.optionPriceType('0')}}" userInput="{{virtualProductCustomizableOption1.option_0_price_type}}" stepKey="selectOptionPriceTypeForFirstDataSet1"/> + <seeInField selector="{{AdminProductCustomizableOptionsSection.optionSku('0')}}" userInput="{{virtualProductCustomizableOption1.option_0_sku}}" stepKey="seeOptionSkuForFirstDataSet"/> + <seeInField selector="{{AdminProductCustomizableOptionsSection.maxCharactersInput('0')}}" userInput="{{virtualProductCustomizableOption1.option_0_max_characters}}" stepKey="seeOptionMaxCharactersForFirstDataSet"/> + <!--Create virtual product with customizable options dataSet2 --> + <click selector="{{AdminProductCustomizableOptionsSection.addOptionBtn}}" stepKey="clickAddOptionButtonForSecondDataSetToSeeFields"/> + <waitForPageLoad stepKey="waitForTheSecondDataSetToLoad"/> + <seeInField selector="{{AdminProductCustomizableOptionsSection.optionTitleInput('1')}}" userInput="{{virtualProductCustomizableOption2.title}}" stepKey="seeOptionTitleForSecondDataSet"/> + <click selector="{{AdminProductCustomizableOptionsSection.optionTypeDropDown('2')}}" stepKey="selectOptionTypeDropDownSecondDataSet2"/> + <click selector="{{AdminProductCustomizableOptionsSection.optionTypeItem('2', virtualProductCustomizableOption2.type)}}" stepKey="selectOptionFieldFromDropDownForSecondDataSet2"/> + <checkOption selector="{{AdminProductCustomizableOptionsSection.requiredCheckBox('1')}}" stepKey="checkRequiredCheckBoxForTheSecondDataSet"/> + <seeInField selector="{{AdminProductCustomizableOptionsSection.optionPrice('1')}}" userInput="{{virtualProductCustomizableOption2.option_0_price}}" stepKey="seeOptionPriceForSecondDataSet"/> + <seeOptionIsSelected selector="{{AdminProductCustomizableOptionsSection.optionPriceType('1')}}" userInput="{{virtualProductCustomizableOption2.option_0_price_type}}" stepKey="selectOptionPriceTypeForTheSecondDataSet"/> + <seeInField selector="{{AdminProductCustomizableOptionsSection.optionSku('1')}}" userInput="{{virtualProductCustomizableOption2.option_0_sku}}" stepKey="seeOptionSkuForSecondDataSet"/> + <seeInField selector="{{AdminProductCustomizableOptionsSection.maxCharactersInput('1')}}" userInput="{{virtualProductCustomizableOption2.option_0_max_characters}}" stepKey="seeOptionMaxCharactersForSecondDataSet"/> + <!-- Create virtual product with customizable options dataSet3 --> + <click selector="{{AdminProductCustomizableOptionsSection.addOptionBtn}}" stepKey="clickAddOptionButtonForTheThirdSetOfData"/> + <waitForPageLoad stepKey="waitForTheThirdSetOfDataToLoad"/> + <seeInField selector="{{AdminProductCustomizableOptionsSection.optionTitleInput('2')}}" userInput="{{virtualProductCustomizableOption3.title}}" stepKey="seeOptionTitleForThirdDataSet"/> + <click selector="{{AdminProductCustomizableOptionsSection.optionTypeDropDown('3')}}" stepKey="selectOptionTypeDropDownForTheThirdDataSet"/> + <click selector="{{AdminProductCustomizableOptionsSection.optionTypeItem('3', virtualProductCustomizableOption3.type)}}" stepKey="selectOptionFieldFromDropDownForTheThirdDataSet"/> + <checkOption selector="{{AdminProductCustomizableOptionsSection.requiredCheckBox('2')}}" stepKey="checkRequiredCheckBoxForTheThirdDataSet"/> + <seeInField selector="{{AdminProductCustomizableOptionsSection.fillOptionValueTitle(virtualProductCustomizableOption3.title,'0')}}" userInput="{{virtualProductCustomizableOption3.option_0_title}}" stepKey="seeOptionTitleForThirdDataSetFirstRow"/> + <seeInField selector="{{AdminProductCustomizableOptionsSection.fillOptionValuePrice(virtualProductCustomizableOption3.title,'0')}}" userInput="{{virtualProductCustomizableOption3.option_0_price}}" stepKey="seeOptionPriceForThirdDataSetFirstRow"/> + <seeOptionIsSelected selector="{{AdminProductCustomizableOptionsSection.clickSelectPriceType(virtualProductCustomizableOption3.title,'0')}}" userInput="{{virtualProductCustomizableOption3.option_0_price_type}}" stepKey="selectOptionPriceTypeForTheThirdDataSetFirstRow"/> + <seeInField selector="{{AdminProductCustomizableOptionsSection.fillOptionValueSku(virtualProductCustomizableOption3.title,'0')}}" userInput="{{virtualProductCustomizableOption3.option_0_sku}}" stepKey="seeOptionSkuForThirdDataSetFirstRow"/> + <seeInField selector="{{AdminProductCustomizableOptionsSection.fillOptionValueTitle(virtualProductCustomizableOption3.title,'1')}}" userInput="{{virtualProductCustomizableOption3.option_1_title}}" stepKey="seeOptionTitleForThirdDataSetSecondRow"/> + <seeInField selector="{{AdminProductCustomizableOptionsSection.fillOptionValuePrice(virtualProductCustomizableOption3.title,'1')}}" userInput="{{virtualProductCustomizableOption3.option_1_price}}" stepKey="seeOptionPriceForThirdDataSetSecondRow"/> + <selectOption selector="{{AdminProductCustomizableOptionsSection.clickSelectPriceType(virtualProductCustomizableOption3.title,'1')}}" userInput="{{virtualProductCustomizableOption3.option_1_price_type}}" stepKey="selectOptionPriceTypeForTheThirdDataSetSecondRow"/> + <seeInField selector="{{AdminProductCustomizableOptionsSection.fillOptionValueSku(virtualProductCustomizableOption3.title,'1')}}" userInput="{{virtualProductCustomizableOption3.option_1_sku}}" stepKey="seeOptionSkuForThirdDataSetSecondRow"/> + <!-- Create virtual product with customizable options dataSet4 --> + <scrollToTopOfPage stepKey="scrollToTheAddOptionButton"/> + <click selector="{{AdminProductCustomizableOptionsSection.addOptionBtn}}" stepKey="clickAddOptionButtonForTheFourthDataSet"/> + <waitForPageLoad stepKey="waitForTheFourthDataSetToLoad"/> + <fillField selector="{{AdminProductCustomizableOptionsSection.optionTitleInput('3')}}" userInput="{{virtualProductCustomizableOption4.title}}" stepKey="fillOptionTitleForTheFourthDataSet"/> + <click selector="{{AdminProductCustomizableOptionsSection.optionTypeDropDown('4')}}" stepKey="selectOptionTypeDropDownForTheFourthSetOfData"/> + <click selector="{{AdminProductCustomizableOptionsSection.optionTypeItem('4', virtualProductCustomizableOption4.type)}}" stepKey="selectOptionFieldFromDropDownForTheFourthDataSet"/> + <checkOption selector="{{AdminProductCustomizableOptionsSection.requiredCheckBox('3')}}" stepKey="checkRequiredCheckBoxForTheFourthDataSet"/> + <seeInField selector="{{AdminProductCustomizableOptionsSection.fillOptionValueTitle(virtualProductCustomizableOption4.title,'0')}}" userInput="{{virtualProductCustomizableOption4.option_0_title}}" stepKey="seeOptionTitleForFourthDataSetFirstRow"/> + <seeInField selector="{{AdminProductCustomizableOptionsSection.fillOptionValuePrice(virtualProductCustomizableOption4.title,'0')}}" userInput="{{virtualProductCustomizableOption4.option_0_price}}" stepKey="seeOptionPriceForFourthDataSetFirstRow"/> + <seeOptionIsSelected selector="{{AdminProductCustomizableOptionsSection.clickSelectPriceType(virtualProductCustomizableOption4.title,'0')}}" userInput="{{virtualProductCustomizableOption4.option_0_price_type}}" stepKey="selectOptionPriceTypeForTheFourthDataSetFirstRow"/> + <seeInField selector="{{AdminProductCustomizableOptionsSection.fillOptionValueSku(virtualProductCustomizableOption4.title,'0')}}" userInput="{{virtualProductCustomizableOption4.option_0_sku}}" stepKey="seeOptionSkuForFourthDataSetFirstRow"/> + <seeInField selector="{{AdminProductCustomizableOptionsSection.fillOptionValueTitle(virtualProductCustomizableOption4.title,'1')}}" userInput="{{virtualProductCustomizableOption4.option_1_title}}" stepKey="seeOptionTitleForFourthDataSetSecondRow"/> + <seeInField selector="{{AdminProductCustomizableOptionsSection.fillOptionValuePrice(virtualProductCustomizableOption4.title,'1')}}" userInput="{{virtualProductCustomizableOption4.option_1_price}}" stepKey="seeOptionPriceForFourthDataSetSecondRow"/> + <selectOption selector="{{AdminProductCustomizableOptionsSection.clickSelectPriceType(virtualProductCustomizableOption4.title,'1')}}" userInput="{{virtualProductCustomizableOption4.option_1_price_type}}" stepKey="selectOptionPriceTypeForTheFourthDataSetSecondRow"/> + <seeInField selector="{{AdminProductCustomizableOptionsSection.fillOptionValueSku(virtualProductCustomizableOption4.title,'1')}}" userInput="{{virtualProductCustomizableOption4.option_1_sku}}" stepKey="seeOptionSkuForFourthDataSetSecondRow"/> + + <!--Verify customer don't see updated virtual product link on category page --> + <amOnPage url="{{StorefrontCategoryPage.url($$categoryEntity.name$$)}}" stepKey="openCategoryPage"/> + <waitForPageLoad stepKey="waitForCategoryPageLoad"/> + <dontSee selector="{{StorefrontCategoryMainSection.productLink}}" userInput="{{updateVirtualProductRegularPriceInStock.name}}" stepKey="dontSeeVirtualProductNameOnCategoryPage"/> + + <!-- Verify customer see updated virtual product (from the above step) on the storefront page --> + <amOnPage url="{{StorefrontProductPage.url(updateVirtualProductRegularPriceInStock.urlKey)}}" stepKey="goToProductPage"/> + <waitForPageLoad stepKey="waitForStorefrontProductPageLoad"/> + <see selector="{{StorefrontProductInfoMainSection.productName}}" userInput="{{updateVirtualProductRegularPriceInStock.name}}" stepKey="seeVirtualProductNameOnStoreFrontPage"/> + <see selector="{{StorefrontProductInfoMainSection.productPrice}}" userInput="{{updateVirtualProductRegularPriceInStock.price}}" stepKey="seeVirtualProductPriceOnStoreFrontPage"/> + <see selector="{{StorefrontProductInfoMainSection.productSku}}" userInput="{{updateVirtualProductRegularPriceInStock.sku}}" stepKey="seeVirtualProductSku"/> + <grabTextFrom selector="{{StorefrontProductInfoMainSection.productStockStatus}}" stepKey="productStockAvailableStatus"/> + <assertEquals stepKey="assertStockAvailableOnProductPage"> + <expectedResult type="string">{{updateVirtualProductRegularPriceInStock.storefrontStatus}}</expectedResult> + <actualResult type="variable">productStockAvailableStatus</actualResult> + </assertEquals> + <grabTextFrom selector="{{StorefrontProductInfoMainSection.productPrice}}" stepKey="productPriceAmount"/> + <assertEquals stepKey="assertOldPriceTextOnProductPage"> + <expectedResult type="string">${{updateVirtualProductRegularPriceInStock.price}}</expectedResult> + <actualResult type="variable">productPriceAmount</actualResult> + </assertEquals> + <!--Verify we customer see customizable options are Required --> + <seeElement selector="{{StorefrontProductInfoMainSection.requiredCustomInput(virtualProductCustomizableOption1.title)}}" stepKey="verifyFistCustomOptionIsRequired" /> + <seeElement selector="{{StorefrontProductInfoMainSection.requiredCustomInput(virtualProductCustomizableOption2.title)}}" stepKey="verifySecondCustomOptionIsRequired" /> + <seeElement selector="{{StorefrontProductInfoMainSection.requiredCustomSelect(virtualProductCustomizableOption3.title)}}" stepKey="verifyThirdCustomOptionIsRequired" /> + <seeElement selector="{{StorefrontProductInfoMainSection.requiredCustomSelect(virtualProductCustomizableOption4.title)}}" stepKey="verifyFourthCustomOptionIsRequired" /> + <!--Verify customer see customizable option titles and prices --> + <grabMultiple selector="{{StorefrontProductInfoMainSection.allCustomOptionLabels}}" stepKey="allCustomOptionLabels" /> + <assertEquals stepKey="verifyLabels"> + <actualResult type="variable">allCustomOptionLabels</actualResult> + <expectedResult type="array">[{{virtualProductCustomizableOption1.title}} + ${{virtualProductCustomizableOption1.option_0_price}}, {{virtualProductCustomizableOption2.title}} + ${{virtualProductCustomizableOption2.option_0_price}}, {{virtualProductCustomizableOption3.title}}, {{virtualProductCustomizableOption4.title}}]</expectedResult> + </assertEquals> + <grabAttributeFrom userInput="for" selector="{{StorefrontProductInfoMainSection.customOptionLabel(virtualProductCustomizableOption4.title)}}" stepKey="fourthOptionId" /> + <grabMultiple selector="{{StorefrontProductInfoMainSection.customSelectOptions({$fourthOptionId})}}" stepKey="grabFourthOptions" /> + <assertEquals stepKey="assertFourthSelectOptions"> + <actualResult type="variable">grabFourthOptions</actualResult> + <expectedResult type="array">['-- Please Select --', {{virtualProductCustomizableOption4.option_0_title}} +$12.01, {{virtualProductCustomizableOption4.option_1_title}} +$20.02]</expectedResult> + </assertEquals> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceOutOfStockVisibleInCategoryAndSearchTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceOutOfStockVisibleInCategoryAndSearchTest.xml new file mode 100644 index 00000000000..a2a4f658602 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceOutOfStockVisibleInCategoryAndSearchTest.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="AdminUpdateVirtualProductWithRegularPriceOutOfStockVisibleInCategoryAndSearchTest"> + <annotations> + <stories value="Update Virtual Product"/> + <title value="Update Virtual Product with Regular Price (Out of Stock) Visible in Category and Search"/> + <description value="Test log in to Update Virtual Product and Update Virtual Product with Regular Price (Out of Stock) Visible in Category and Search"/> + <testCaseId value="MC-7433"/> + <severity value="CRITICAL"/> + <group value="catalog"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <actionGroup ref = "LoginAsAdmin" stepKey="loginAsAdmin"/> + <createData entity="SimpleSubCategory" stepKey="initialCategoryEntity"/> + <createData entity="defaultVirtualProduct" stepKey="initialVirtualProduct"> + <requiredEntity createDataKey="initialCategoryEntity"/> + </createData> + <createData entity="SimpleSubCategory" stepKey="categoryEntity"/> + </before> + <after> + <deleteData stepKey="deleteSimpleSubCategory" createDataKey="initialCategoryEntity"/> + <deleteData stepKey="deleteSimpleSubCategory2" createDataKey="categoryEntity"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Search default virtual product in the grid page --> + <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPage1"/> + <waitForPageLoad stepKey="waitForProductCatalogPage1"/> + <conditionalClick selector="{{AdminProductGridFilterSection.clearAll}}" dependentSelector="{{AdminProductGridFilterSection.clearAll}}" visible="true" stepKey="clickClearAllFilter" /> + <fillField selector="{{AdminProductGridFilterSection.keywordSearch}}" userInput="$$initialVirtualProduct.name$$" stepKey="fillVirtualProductNameInKeywordSearch"/> + <click selector="{{AdminProductGridFilterSection.keywordSearchButton}}" stepKey="clickKeywordSearchButton"/> + <waitForPageLoad stepKey="waitForProductSearch"/> + <click selector="{{AdminProductGridFilterSection.nthRow('1')}}" stepKey="clickFirstRowToVerifyCreatedVirtualProduct"/> + <waitForPageLoad stepKey="waitUntilProductIsOpened"/> + + <!-- Update virtual product with regular price(out of stock) --> + <fillField selector="{{AdminProductFormSection.productName}}" userInput="{{updateVirtualProductRegularPrice5OutOfStock.name}}" stepKey="fillProductName"/> + <fillField selector="{{AdminProductFormSection.productSku}}" userInput="{{updateVirtualProductRegularPrice5OutOfStock.sku}}" stepKey="fillProductSku"/> + <fillField selector="{{AdminProductFormSection.productPrice}}" userInput="{{updateVirtualProductRegularPrice5OutOfStock.price}}" stepKey="fillProductPrice"/> + <selectOption selector="{{AdminProductFormSection.productTaxClass}}" userInput="{{updateVirtualProductRegularPrice5OutOfStock.productTaxClass}}" stepKey="selectProductStockClass"/> + <selectOption selector="{{AdminProductFormSection.stockStatus}}" userInput="{{updateVirtualProductRegularPrice5OutOfStock.status}}" stepKey="selectStockStatusInStock"/> + <selectOption selector="{{AdminProductFormSection.visibility}}" userInput="{{updateVirtualProductRegularPrice5OutOfStock.visibility}}" stepKey="selectVisibility"/> + <click selector="{{AdminProductSEOSection.sectionHeader}}" stepKey="clickAdminProductSEOSection"/> + <fillField selector="{{AdminProductSEOSection.urlKeyInput}}" userInput="{{updateVirtualProductRegularPrice5OutOfStock.urlKey}}" stepKey="fillUrlKey"/> + <scrollToTopOfPage stepKey="scrollToTopOfAdminProductFormSection"/> + <click selector="{{AdminProductFormSection.save}}" stepKey="clickSaveButton"/> + <waitForPageLoad stepKey="waitForVirtualProductSaved"/> + <!-- Verify we see success message --> + <see selector="{{AdminProductFormSection.successMessage}}" userInput="You saved the product." stepKey="seeAssertVirtualProductSaveSuccessMessage"/> + + <!-- Search updated virtual product(from above step) in the grid page --> + <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPageToSearchUpdatedVirtualProduct"/> + <waitForPageLoad stepKey="waitForProductCatalogPageToLoad"/> + <conditionalClick selector="{{AdminProductGridFilterSection.clearAll}}" dependentSelector="{{AdminProductGridFilterSection.clearAll}}" visible="true" stepKey="clickClearAll"/> + <click selector="{{AdminProductGridFilterSection.filters}}" stepKey="clickFiltersButton"/> + <fillField selector="{{AdminProductGridFilterSection.nameFilter}}" userInput="{{updateVirtualProductRegularPrice5OutOfStock.name}}" stepKey="fillVirtualProductNameInNameFilter"/> + <fillField selector="{{AdminProductGridFilterSection.skuFilter}}" userInput="{{updateVirtualProductRegularPrice5OutOfStock.sku}}" stepKey="fillVirtualProductSku"/> + <click selector="{{AdminProductGridFilterSection.applyFilters}}" stepKey="clickApplyFiltersButton"/> + <click selector="{{AdminProductGridFilterSection.nthRow('1')}}" stepKey="clickFirstRowToVerifyUpdatedVirtualProductVisibleInGrid"/> + <waitForPageLoad stepKey="waitUntilVirtualProductPageIsOpened"/> + + <!-- Verify customer see updated virtual product with regular price(out of stock) in the product form page --> + <seeInField selector="{{AdminProductFormSection.productName}}" userInput="{{updateVirtualProductRegularPrice5OutOfStock.name}}" stepKey="seeProductName"/> + <seeInField selector="{{AdminProductFormSection.productSku}}" userInput="{{updateVirtualProductRegularPrice5OutOfStock.sku}}" stepKey="seeProductSku"/> + <seeInField selector="{{AdminProductFormSection.productPrice}}" userInput="{{updateVirtualProductRegularPrice5OutOfStock.price}}" stepKey="seeProductPrice"/> + <seeInField selector="{{AdminProductFormSection.productTaxClass}}" userInput="{{updateVirtualProductRegularPrice5OutOfStock.productTaxClass}}" stepKey="seeProductTaxClass"/> + <seeInField selector="{{AdminProductFormSection.productStockStatus}}" userInput="{{updateVirtualProductRegularPrice5OutOfStock.status}}" stepKey="seeProductStockStatus"/> + <seeInField selector="{{AdminProductFormSection.visibility}}" userInput="{{updateVirtualProductRegularPrice5OutOfStock.visibility}}" stepKey="seeVisibility"/> + <scrollTo selector="{{AdminProductSEOSection.sectionHeader}}" x="0" y="-80" stepKey="scrollToAdminProductSEOSection1"/> + <click selector="{{AdminProductSEOSection.sectionHeader}}" stepKey="clickAdminProductSEOSection1"/> + <seeInField selector="{{AdminProductSEOSection.urlKeyInput}}" userInput="{{updateVirtualProductRegularPrice5OutOfStock.urlKey}}" stepKey="seeUrlKey"/> + + <!--Verify customer see updated virtual product with regular price(out of stock) on product storefront page --> + <amOnPage url="{{StorefrontProductPage.url(updateVirtualProductRegularPrice5OutOfStock.urlKey)}}" stepKey="goToProductPage"/> + <waitForPageLoad stepKey="waitForStorefrontProductPageLoad"/> + <see selector="{{StorefrontProductInfoMainSection.productName}}" userInput="{{updateVirtualProductRegularPrice5OutOfStock.name}}" stepKey="seeVirtualProductNameOnStoreFrontPage"/> + <see selector="{{StorefrontProductInfoMainSection.productPrice}}" userInput="{{updateVirtualProductRegularPrice5OutOfStock.price}}" stepKey="seeVirtualProductPriceOnStoreFrontPage"/> + <see selector="{{StorefrontProductInfoMainSection.productSku}}" userInput="{{updateVirtualProductRegularPrice5OutOfStock.sku}}" stepKey="seeVirtualProductSku"/> + <grabTextFrom selector="{{StorefrontProductInfoMainSection.productStockStatus}}" stepKey="productStockAvailableStatus"/> + <assertEquals stepKey="assertStockAvailableOnProductPage"> + <expectedResult type="string">{{updateVirtualProductRegularPrice5OutOfStock.storefrontStatus}}</expectedResult> + <actualResult type="variable">productStockAvailableStatus</actualResult> + </assertEquals> + <grabTextFrom selector="{{StorefrontProductInfoMainSection.productPrice}}" stepKey="productPriceAmount"/> + <assertEquals stepKey="assertOldPriceTextOnProductPage"> + <expectedResult type="string">${{updateVirtualProductRegularPrice5OutOfStock.price}}</expectedResult> + <actualResult type="variable">productPriceAmount</actualResult> + </assertEquals> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceOutOfStockVisibleInCategoryOnlyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceOutOfStockVisibleInCategoryOnlyTest.xml new file mode 100644 index 00000000000..e64022b3116 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceOutOfStockVisibleInCategoryOnlyTest.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="AdminUpdateVirtualProductWithRegularPriceOutOfStockVisibleInCategoryOnlyTest"> + <annotations> + <stories value="Update Virtual Product"/> + <title value="Update Virtual Product with Regular Price (Out of Stock) Visible in Category Only"/> + <description value="Test log in to Update Virtual Product and Update Virtual Product with Regular Price (Out of Stock) Visible in Category Only"/> + <testCaseId value="MC-6503"/> + <severity value="CRITICAL"/> + <group value="catalog"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <actionGroup ref = "LoginAsAdmin" stepKey="loginAsAdmin"/> + <createData entity="SimpleSubCategory" stepKey="initialCategoryEntity"/> + <createData entity="defaultVirtualProduct" stepKey="initialVirtualProduct"> + <requiredEntity createDataKey="initialCategoryEntity"/> + </createData> + <createData entity="SimpleSubCategory" stepKey="categoryEntity"/> + </before> + <after> + <deleteData stepKey="deleteSimpleSubCategory" createDataKey="initialCategoryEntity"/> + <deleteData stepKey="deleteSimpleSubCategory2" createDataKey="categoryEntity"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Search default virtual product in the grid page --> + <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPage1"/> + <waitForPageLoad stepKey="waitForProductCatalogPage1"/> + <conditionalClick selector="{{AdminProductGridFilterSection.clearAll}}" dependentSelector="{{AdminProductGridFilterSection.clearAll}}" visible="true" stepKey="clickclearAllFilter" /> + <fillField selector="{{AdminProductGridFilterSection.keywordSearch}}" userInput="$$initialVirtualProduct.name$$" stepKey="fillVirtualProductNameInKeywordSearch"/> + <click selector="{{AdminProductGridFilterSection.keywordSearchButton}}" stepKey="clickKeywordSearchButton"/> + <waitForPageLoad stepKey="waitForProductSearch"/> + <click selector="{{AdminProductGridFilterSection.nthRow('1')}}" stepKey="clickFirstRowToVerifyCreatedVirtualProduct"/> + <waitForPageLoad stepKey="waitUntilProductIsOpened"/> + + <!-- Update virtual product with regular price(out of stock) --> + <fillField selector="{{AdminProductFormSection.productName}}" userInput="{{updateVirtualProductRegularPrice5OutOfStock.name}}" stepKey="fillProductName"/> + <fillField selector="{{AdminProductFormSection.productSku}}" userInput="{{updateVirtualProductRegularPrice5OutOfStock.sku}}" stepKey="fillProductSku"/> + <fillField selector="{{AdminProductFormSection.productPrice}}" userInput="{{updateVirtualProductRegularPrice5OutOfStock.price}}" stepKey="fillProductPrice"/> + <selectOption selector="{{AdminProductFormSection.productTaxClass}}" userInput="{{updateVirtualProductRegularPrice5OutOfStock.productTaxClass}}" stepKey="selectProductStockClass"/> + <selectOption selector="{{AdminProductFormSection.stockStatus}}" userInput="{{updateVirtualProductRegularPrice5OutOfStock.status}}" stepKey="selectStockStatusInStock"/> + <click selector="{{AdminProductFormSection.categoriesDropdown}}" stepKey="clickCategoriesDropDown"/> + <fillField selector="{{AdminProductFormSection.searchCategory}}" userInput="$$initialCategoryEntity.name$$" stepKey="fillSearchForInitialCategory" /> + <click selector="{{AdminProductFormSection.selectCategory($$initialCategoryEntity.name$$)}}" stepKey="unselectInitialCategory"/> + <fillField selector="{{AdminProductFormSection.searchCategory}}" userInput="$$categoryEntity.name$$" stepKey="fillSearchCategory" /> + <click selector="{{AdminProductFormSection.selectCategory($$categoryEntity.name$$)}}" stepKey="clickOnCategory"/> + <click selector="{{AdminProductFormSection.done}}" stepKey="clickOnDoneAdvancedCategorySelect"/> + <selectOption selector="{{AdminProductFormSection.visibility}}" userInput="{{updateVirtualProductRegularPrice5OutOfStock.visibility}}" stepKey="selectVisibility"/> + <click selector="{{AdminProductSEOSection.sectionHeader}}" stepKey="clickAdminProductSEOSection"/> + <fillField selector="{{AdminProductSEOSection.urlKeyInput}}" userInput="{{updateVirtualProductRegularPrice5OutOfStock.urlKey}}" stepKey="fillUrlKey"/> + <scrollToTopOfPage stepKey="scrollToTopOfAdminProductFormSection"/> + <click selector="{{AdminProductFormSection.save}}" stepKey="clickSaveButton"/> + <waitForPageLoad stepKey="waitForVirtualProductSaved"/> + <!-- Verify we see success message --> + <see selector="{{AdminProductFormSection.successMessage}}" userInput="You saved the product." stepKey="seeAssertVirtualProductSaveSuccessMessage"/> + + <!-- Search updated virtual product(from above step) in the grid page --> + <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPageToSearchUpdatedVirtualProduct"/> + <waitForPageLoad stepKey="waitForProductCatalogPageToLoad"/> + <conditionalClick selector="{{AdminProductGridFilterSection.clearAll}}" dependentSelector="{{AdminProductGridFilterSection.clearAll}}" visible="true" stepKey="clickClearAll"/> + <click selector="{{AdminProductGridFilterSection.filters}}" stepKey="clickFiltersButton"/> + <fillField selector="{{AdminProductGridFilterSection.nameFilter}}" userInput="{{updateVirtualProductRegularPrice5OutOfStock.name}}" stepKey="fillVirtualProductNameInNameFilter"/> + <fillField selector="{{AdminProductGridFilterSection.skuFilter}}" userInput="{{updateVirtualProductRegularPrice5OutOfStock.sku}}" stepKey="fillVirtualProductSku"/> + <click selector="{{AdminProductGridFilterSection.applyFilters}}" stepKey="clickApplyFiltersButton"/> + <click selector="{{AdminProductGridFilterSection.nthRow('1')}}" stepKey="clickFirstRowToVerifyUpdatedVirtualProductVisibleInGrid"/> + <waitForPageLoad stepKey="waitUntilVirtualProductPageIsOpened"/> + + <!-- Verify we see updated virtual product with regular price(from the above step) in the product form page --> + <seeInField selector="{{AdminProductFormSection.productName}}" userInput="{{updateVirtualProductRegularPrice5OutOfStock.name}}" stepKey="seeProductName"/> + <seeInField selector="{{AdminProductFormSection.productSku}}" userInput="{{updateVirtualProductRegularPrice5OutOfStock.sku}}" stepKey="seeProductSku"/> + <seeInField selector="{{AdminProductFormSection.productPrice}}" userInput="{{updateVirtualProductRegularPrice5OutOfStock.price}}" stepKey="seeProductPrice"/> + <seeInField selector="{{AdminProductFormSection.productTaxClass}}" userInput="{{updateVirtualProductRegularPrice5OutOfStock.productTaxClass}}" stepKey="seeProductTaxClass"/> + <seeInField selector="{{AdminProductFormSection.productStockStatus}}" userInput="{{updateVirtualProductRegularPrice5OutOfStock.status}}" stepKey="seeProductStockStatus"/> + <click selector="{{AdminProductFormSection.categoriesDropdown}}" stepKey="clickCategoriesDropDownToVerify"/> + <grabMultiple selector="{{AdminProductFormSection.selectMultipleCategories}}" stepKey="selectedCategories" /> + <assertEquals stepKey="assertSelectedCategories"> + <actualResult type="variable">selectedCategories</actualResult> + <expectedResult type="array">[$$categoryEntity.name$$]</expectedResult> + </assertEquals> + <click selector="{{AdminProductFormSection.done}}" stepKey="clickOnDoneOnCategorySelect"/> + <seeInField selector="{{AdminProductFormSection.visibility}}" userInput="{{updateVirtualProductRegularPrice5OutOfStock.visibility}}" stepKey="seeVisibility"/> + <scrollTo selector="{{AdminProductSEOSection.sectionHeader}}" x="0" y="-80" stepKey="scrollToAdminProductSEOSection1"/> + <click selector="{{AdminProductSEOSection.sectionHeader}}" stepKey="clickAdminProductSEOSection1"/> + <seeInField selector="{{AdminProductSEOSection.urlKeyInput}}" userInput="{{updateVirtualProductRegularPrice5OutOfStock.urlKey}}" stepKey="seeUrlKey"/> + + <!--Verify customer don't see updated virtual product link(from above step) on category page --> + <amOnPage url="{{StorefrontCategoryPage.url($$categoryEntity.name$$)}}" stepKey="openCategoryPage"/> + <waitForPageLoad stepKey="waitForCategoryPageLoad"/> + <dontSee selector="{{StorefrontCategoryMainSection.productLink}}" userInput="{{updateVirtualProductRegularPrice5OutOfStock.name}}" stepKey="dontseeVirtualProductNameOnCategoryPage"/> + + <!--Verify customer see updated virtual product (from above step) on product storefront page --> + <amOnPage url="{{StorefrontProductPage.url(updateVirtualProductRegularPrice5OutOfStock.urlKey)}}" stepKey="goToProductPage"/> + <waitForPageLoad stepKey="waitForStorefrontProductPageLoad"/> + <see selector="{{StorefrontProductInfoMainSection.productName}}" userInput="{{updateVirtualProductRegularPrice5OutOfStock.name}}" stepKey="seeVirtualProductNameOnStoreFrontPage"/> + <see selector="{{StorefrontProductInfoMainSection.productPrice}}" userInput="{{updateVirtualProductRegularPrice5OutOfStock.price}}" stepKey="seeVirtualProductPriceOnStoreFrontPage"/> + <see selector="{{StorefrontProductInfoMainSection.productSku}}" userInput="{{updateVirtualProductRegularPrice5OutOfStock.sku}}" stepKey="seeVirtualProductSku"/> + <grabTextFrom selector="{{StorefrontProductInfoMainSection.productStockStatus}}" stepKey="productStockAvailableStatus"/> + <assertEquals stepKey="assertStockAvailableOnProductPage"> + <expectedResult type="string">{{updateVirtualProductRegularPrice5OutOfStock.storefrontStatus}}</expectedResult> + <actualResult type="variable">productStockAvailableStatus</actualResult> + </assertEquals> + <grabTextFrom selector="{{StorefrontProductInfoMainSection.productPrice}}" stepKey="productPriceAmount"/> + <assertEquals stepKey="assertOldPriceTextOnProductPage"> + <expectedResult type="string">${{updateVirtualProductRegularPrice5OutOfStock.price}}</expectedResult> + <actualResult type="variable">productPriceAmount</actualResult> + </assertEquals> + + <!--Verify customer don't see updated virtual product link(from above step) on magento storefront page and is searchable by sku --> + <amOnPage url="{{StorefrontProductPage.url(updateVirtualProductRegularPrice5OutOfStock.urlKey)}}" stepKey="goToMagentoStorefrontPage"/> + <waitForPageLoad stepKey="waitForStoreFrontProductPageLoad"/> + <fillField selector="{{StorefrontQuickSearchResultsSection.searchTextBox}}" userInput="{{updateVirtualProductRegularPrice5OutOfStock.sku}}" stepKey="fillVirtualProductName"/> + <waitForPageLoad stepKey="waitForSearchTextBox"/> + <click selector="{{StorefrontQuickSearchResultsSection.searchTextBoxButton}}" stepKey="clickSearchTextBoxButton"/> + <waitForPageLoad stepKey="waitForSearch"/> + <dontSee selector="{{StorefrontQuickSearchResultsSection.productLink}}" userInput="{{updateVirtualProductRegularPrice5OutOfStock.name}}" stepKey="dontSeeVirtualProductName"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceOutOfStockVisibleInSearchOnlyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceOutOfStockVisibleInSearchOnlyTest.xml new file mode 100644 index 00000000000..aa3184994da --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceOutOfStockVisibleInSearchOnlyTest.xml @@ -0,0 +1,112 @@ +<?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="AdminUpdateVirtualProductWithRegularPriceOutOfStockVisibleInSearchOnlyTest"> + <annotations> + <stories value="Update Virtual Product"/> + <title value="Update Virtual Product with Regular Price (Out of Stock) Visible in Search Only"/> + <description value="Test log in to Update Virtual Product and Update Virtual Product with Regular Price (Out of Stock) Visible in Search Only"/> + <testCaseId value="MC-6498"/> + <severity value="CRITICAL"/> + <group value="catalog"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <actionGroup ref = "LoginAsAdmin" stepKey="loginAsAdmin"/> + <createData entity="SimpleSubCategory" stepKey="initialCategoryEntity"/> + <createData entity="defaultVirtualProduct" stepKey="initialVirtualProduct"> + <requiredEntity createDataKey="initialCategoryEntity"/> + </createData> + </before> + <after> + <deleteData stepKey="deleteSimpleSubCategory" createDataKey="initialCategoryEntity"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Search default virtual product in the grid page --> + <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPage1"/> + <waitForPageLoad stepKey="waitForProductCatalogPage1"/> + <conditionalClick selector="{{AdminProductGridFilterSection.clearAll}}" dependentSelector="{{AdminProductGridFilterSection.clearAll}}" visible="true" stepKey="clickClearAllFilter" /> + <fillField selector="{{AdminProductGridFilterSection.keywordSearch}}" userInput="$$initialVirtualProduct.name$$" stepKey="fillVirtualProductNameInKeywordSearch"/> + <click selector="{{AdminProductGridFilterSection.keywordSearchButton}}" stepKey="clickKeywordSearchButton"/> + <waitForPageLoad stepKey="waitForProductSearch"/> + <click selector="{{AdminProductGridFilterSection.nthRow('1')}}" stepKey="clickFirstRowToVerifyCreatedVirtualProduct"/> + <waitForPageLoad stepKey="waitUntilProductIsOpened"/> + + <!-- Update virtual product with regular price(out of stock) --> + <fillField selector="{{AdminProductFormSection.productName}}" userInput="{{updateVirtualProductRegularPrice99OutOfStock.name}}" stepKey="fillProductName"/> + <fillField selector="{{AdminProductFormSection.productSku}}" userInput="{{updateVirtualProductRegularPrice99OutOfStock.sku}}" stepKey="fillProductSku"/> + <fillField selector="{{AdminProductFormSection.productPrice}}" userInput="{{updateVirtualProductRegularPrice99OutOfStock.price}}" stepKey="fillProductPrice"/> + <selectOption selector="{{AdminProductFormSection.productTaxClass}}" userInput="{{updateVirtualProductRegularPrice99OutOfStock.productTaxClass}}" stepKey="selectProductStockClass"/> + <selectOption selector="{{AdminProductFormSection.stockStatus}}" userInput="{{updateVirtualProductRegularPrice99OutOfStock.status}}" stepKey="selectStockStatusInStock"/> + <selectOption selector="{{AdminProductFormSection.visibility}}" userInput="{{updateVirtualProductRegularPrice99OutOfStock.visibility}}" stepKey="selectVisibility"/> + <click selector="{{AdminProductSEOSection.sectionHeader}}" stepKey="clickAdminProductSEOSection"/> + <fillField selector="{{AdminProductSEOSection.urlKeyInput}}" userInput="{{updateVirtualProductRegularPrice99OutOfStock.urlKey}}" stepKey="fillUrlKey"/> + <scrollToTopOfPage stepKey="scrollToTopOfAdminProductFormSection"/> + <click selector="{{AdminProductFormSection.save}}" stepKey="clickSaveButton"/> + <waitForPageLoad stepKey="waitForVirtualProductSaved"/> + <!-- Verify we see success message --> + <see selector="{{AdminProductFormSection.successMessage}}" userInput="You saved the product." stepKey="seeAssertVirtualProductSaveSuccessMessage"/> + + <!-- Search updated virtual product(from above step) in the grid --> + <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPageToSearchUpdatedVirtualProduct"/> + <waitForPageLoad stepKey="waitForProductCatalogPageToLoad"/> + <conditionalClick selector="{{AdminProductGridFilterSection.clearAll}}" dependentSelector="{{AdminProductGridFilterSection.clearAll}}" visible="true" stepKey="clickClearAll"/> + <click selector="{{AdminProductGridFilterSection.filters}}" stepKey="clickFiltersButton"/> + <fillField selector="{{AdminProductGridFilterSection.nameFilter}}" userInput="{{updateVirtualProductRegularPrice99OutOfStock.name}}" stepKey="fillVirtualProductNameInNameFilter"/> + <fillField selector="{{AdminProductGridFilterSection.skuFilter}}" userInput="{{updateVirtualProductRegularPrice99OutOfStock.sku}}" stepKey="fillVirtualProductSku"/> + <click selector="{{AdminProductGridFilterSection.applyFilters}}" stepKey="clickApplyFiltersButton"/> + <click selector="{{AdminProductGridFilterSection.nthRow('1')}}" stepKey="clickFirstRowToVerifyUpdatedVirtualProductVisibleInGrid"/> + <waitForPageLoad stepKey="waitUntilVirtualProductPageIsOpened"/> + + <!-- Verify customer see updated virtual product with regular price in the product form page --> + <seeInField selector="{{AdminProductFormSection.productName}}" userInput="{{updateVirtualProductRegularPrice99OutOfStock.name}}" stepKey="seeProductName"/> + <seeInField selector="{{AdminProductFormSection.productSku}}" userInput="{{updateVirtualProductRegularPrice99OutOfStock.sku}}" stepKey="seeProductSku"/> + <seeInField selector="{{AdminProductFormSection.productPrice}}" userInput="{{updateVirtualProductRegularPrice99OutOfStock.price}}" stepKey="seeProductPrice"/> + <seeInField selector="{{AdminProductFormSection.productTaxClass}}" userInput="{{updateVirtualProductRegularPrice99OutOfStock.productTaxClass}}" stepKey="seeProductTaxClass"/> + <seeInField selector="{{AdminProductFormSection.productStockStatus}}" userInput="{{updateVirtualProductRegularPrice99OutOfStock.status}}" stepKey="seeProductStockStatus"/> + <seeInField selector="{{AdminProductFormSection.visibility}}" userInput="{{updateVirtualProductRegularPrice99OutOfStock.visibility}}" stepKey="seeVisibility"/> + <scrollTo selector="{{AdminProductSEOSection.sectionHeader}}" x="0" y="-80" stepKey="scrollToAdminProductSEOSection1"/> + <click selector="{{AdminProductSEOSection.sectionHeader}}" stepKey="clickAdminProductSEOSection1"/> + <seeInField selector="{{AdminProductSEOSection.urlKeyInput}}" userInput="{{updateVirtualProductRegularPrice99OutOfStock.urlKey}}" stepKey="seeUrlKey"/> + + <!--Verify customer see updated virtual product on storefront page by url key --> + <amOnPage url="{{StorefrontProductPage.url(updateVirtualProductRegularPrice99OutOfStock.urlKey)}}" stepKey="goToProductPage"/> + <waitForPageLoad stepKey="waitForStorefrontProductPageLoad"/> + <see selector="{{StorefrontProductInfoMainSection.productName}}" userInput="{{updateVirtualProductRegularPrice99OutOfStock.name}}" stepKey="seeVirtualProductNameOnStoreFrontPage"/> + <see selector="{{StorefrontProductInfoMainSection.productPrice}}" userInput="{{updateVirtualProductRegularPrice99OutOfStock.price}}" stepKey="seeVirtualProductPriceOnStoreFrontPage"/> + <see selector="{{StorefrontProductInfoMainSection.productSku}}" userInput="{{updateVirtualProductRegularPrice99OutOfStock.sku}}" stepKey="seeVirtualProductSku"/> + <grabTextFrom selector="{{StorefrontProductInfoMainSection.productStockStatus}}" stepKey="productStockAvailableStatus"/> + <assertEquals stepKey="assertStockAvailableOnProductPage"> + <expectedResult type="string">{{updateVirtualProductRegularPrice99OutOfStock.storefrontStatus}}</expectedResult> + <actualResult type="variable">productStockAvailableStatus</actualResult> + </assertEquals> + <grabTextFrom selector="{{StorefrontProductInfoMainSection.productPrice}}" stepKey="productPriceAmount"/> + <assertEquals stepKey="assertOldPriceTextOnProductPage"> + <expectedResult type="string">${{updateVirtualProductRegularPrice99OutOfStock.price}}</expectedResult> + <actualResult type="variable">productPriceAmount</actualResult> + </assertEquals> + + <!--Verify customer don't see updated virtual product link on magento storefront page --> + <amOnPage url="{{StorefrontProductPage.url(updateVirtualProductRegularPrice99OutOfStock.urlKey)}}" stepKey="goToMagentoStorefrontPage"/> + <waitForPageLoad stepKey="waitForStoreFrontProductPageLoad"/> + <fillField selector="{{StorefrontQuickSearchResultsSection.searchTextBox}}" userInput="{{updateVirtualProductRegularPrice99OutOfStock.sku}}" stepKey="fillVirtualProductName"/> + <waitForPageLoad stepKey="waitForSearchTextBox"/> + <click selector="{{StorefrontQuickSearchResultsSection.searchTextBoxButton}}" stepKey="clickSearchTextBoxButton"/> + <waitForPageLoad stepKey="waitForSearch"/> + <dontSee selector="{{StorefrontQuickSearchResultsSection.productLink}}" userInput="{{updateVirtualProductRegularPrice99OutOfStock.name}}" stepKey="dontSeeVirtualProductLinkOnStorefrontPage"/> + + <!--Verify customer don't see updated virtual product link on category page --> + <amOnPage url="{{StorefrontCategoryPage.url($$initialCategoryEntity.name$$)}}" stepKey="openCategoryPage"/> + <waitForPageLoad stepKey="waitForCategoryPageLoad"/> + <dontSee selector="{{StorefrontCategoryMainSection.productLink}}" userInput="{{updateVirtualProductRegularPrice99OutOfStock.name}}" stepKey="dontSeeVirtualProductLinkOnCategoryPage"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithSpecialPriceInStockVisibleInCategoryAndSearchTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithSpecialPriceInStockVisibleInCategoryAndSearchTest.xml new file mode 100644 index 00000000000..9b6a56d6f81 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithSpecialPriceInStockVisibleInCategoryAndSearchTest.xml @@ -0,0 +1,143 @@ +<?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="AdminUpdateVirtualProductWithSpecialPriceInStockVisibleInCategoryAndSearchTest"> + <annotations> + <stories value="Update Virtual Product"/> + <title value="Update Virtual Product with Special Price (In Stock) Visible in Category and Search"/> + <description value="Test log in to Update Virtual Product and Update Virtual Product with Special Price (In Stock) Visible in Category and Search"/> + <testCaseId value="MC-6496"/> + <severity value="CRITICAL"/> + <group value="catalog"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <actionGroup ref = "LoginAsAdmin" stepKey="loginAsAdmin"/> + <createData entity="SimpleSubCategory" stepKey="initialCategoryEntity"/> + <createData entity="defaultVirtualProduct" stepKey="initialVirtualProduct"> + <requiredEntity createDataKey="initialCategoryEntity"/> + </createData> + <createData entity="SimpleSubCategory" stepKey="categoryEntity"/> + </before> + <after> + <deleteData stepKey="deleteSimpleSubCategory" createDataKey="initialCategoryEntity"/> + <deleteData stepKey="deleteSimpleSubCategory2" createDataKey="categoryEntity"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Search default virtual product in the grid page --> + <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPage"/> + <waitForPageLoad stepKey="waitForProductCatalogPage"/> + <conditionalClick selector="{{AdminProductGridFilterSection.clearAll}}" dependentSelector="{{AdminProductGridFilterSection.clearAll}}" visible="true" stepKey="clickClearAllFilter" /> + <fillField selector="{{AdminProductGridFilterSection.keywordSearch}}" userInput="$$initialVirtualProduct.name$$" stepKey="fillVirtualProductNameInKeywordSearch"/> + <click selector="{{AdminProductGridFilterSection.keywordSearchButton}}" stepKey="clickKeywordSearchButton"/> + <waitForPageLoad stepKey="waitForProductSearch"/> + <click selector="{{AdminProductGridFilterSection.nthRow('1')}}" stepKey="clickFirstRowToVerifyCreatedVirtualProduct"/> + <waitForPageLoad stepKey="waitUntilProductIsOpened"/> + + <!-- Update virtual product with special price --> + <fillField selector="{{AdminProductFormSection.productName}}" userInput="{{updateVirtualProductSpecialPrice.name}}" stepKey="fillProductName"/> + <fillField selector="{{AdminProductFormSection.productSku}}" userInput="{{updateVirtualProductSpecialPrice.sku}}" stepKey="fillProductSku"/> + <fillField selector="{{AdminProductFormSection.productPrice}}" userInput="{{updateVirtualProductSpecialPrice.price}}" stepKey="fillProductPrice"/> + <!-- Press enter to validate advanced pricing link --> + <pressKey selector="{{AdminProductFormSection.productPrice}}" parameterArray="[\Facebook\WebDriver\WebDriverKeys::ENTER]" stepKey="pressEnterKey"/> + <click selector="{{AdminProductFormSection.advancedPricingLink}}" stepKey="clickAdvancedPricingLink"/> + <fillField selector="{{AdminProductFormAdvancedPricingSection.specialPrice}}" userInput="{{updateVirtualProductSpecialPrice.special_price}}" stepKey="fillSpecialPrice"/> + <click selector="{{AdminProductFormAdvancedPricingSection.doneButton}}" stepKey="clickDoneButton"/> + <selectOption selector="{{AdminProductFormSection.productTaxClass}}" userInput="{{updateVirtualProductSpecialPrice.productTaxClass}}" stepKey="selectProductStockClass"/> + <fillField selector="{{AdminProductFormSection.productQuantity}}" userInput="{{updateVirtualProductSpecialPrice.quantity}}" stepKey="fillProductQuantity"/> + <selectOption selector="{{AdminProductFormSection.stockStatus}}" userInput="{{updateVirtualProductSpecialPrice.status}}" stepKey="selectStockStatusInStock"/> + <click selector="{{AdminProductFormSection.categoriesDropdown}}" stepKey="clickCategoriesDropDown"/> + <fillField selector="{{AdminProductFormSection.searchCategory}}" userInput="$$initialCategoryEntity.name$$" stepKey="fillSearchForInitialCategory" /> + <click selector="{{AdminProductFormSection.selectCategory($$initialCategoryEntity.name$$)}}" stepKey="unselectInitialCategory"/> + <fillField selector="{{AdminProductFormSection.searchCategory}}" userInput="$$categoryEntity.name$$" stepKey="fillSearchCategory" /> + <click selector="{{AdminProductFormSection.selectCategory($$categoryEntity.name$$)}}" stepKey="clickOnCategory"/> + <click selector="{{AdminProductFormSection.done}}" stepKey="clickOnDoneAdvancedCategorySelect"/> + <selectOption selector="{{AdminProductFormSection.visibility}}" userInput="{{updateVirtualProductSpecialPrice.visibility}}" stepKey="selectVisibility"/> + <click selector="{{AdminProductSEOSection.sectionHeader}}" stepKey="clickAdminProductSEOSection"/> + <fillField selector="{{AdminProductSEOSection.urlKeyInput}}" userInput="{{updateVirtualProductSpecialPrice.urlKey}}" stepKey="fillUrlKey"/> + <scrollToTopOfPage stepKey="scrollToTopOfAdminProductFormSection"/> + <click selector="{{AdminProductFormSection.save}}" stepKey="clickSaveButton"/> + <waitForPageLoad stepKey="waitForVirtualProductSaved"/> + <!-- Verify we see success message --> + <see selector="{{AdminProductFormSection.successMessage}}" userInput="You saved the product." stepKey="seeAssertVirtualProductSaveSuccessMessage"/> + + <!-- Search updated virtual product(from above step) in the grid page --> + <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPageToSearchUpdatedVirtualProduct"/> + <waitForPageLoad stepKey="waitForProductCatalogPageToLoad"/> + <conditionalClick selector="{{AdminProductGridFilterSection.clearAll}}" dependentSelector="{{AdminProductGridFilterSection.clearAll}}" visible="true" stepKey="clickClearAll"/> + <click selector="{{AdminProductGridFilterSection.filters}}" stepKey="clickFiltersButton"/> + <fillField selector="{{AdminProductGridFilterSection.nameFilter}}" userInput="{{updateVirtualProductSpecialPrice.name}}" stepKey="fillVirtualProductNameInNameFilter"/> + <fillField selector="{{AdminProductGridFilterSection.skuFilter}}" userInput="{{updateVirtualProductSpecialPrice.sku}}" stepKey="fillVirtualProductSku"/> + <click selector="{{AdminProductGridFilterSection.applyFilters}}" stepKey="clickApplyFiltersButton"/> + <click selector="{{AdminProductGridFilterSection.nthRow('1')}}" stepKey="clickFirstRowToVerifyUpdatedVirtualProductVisibleInGrid"/> + <waitForPageLoad stepKey="waitUntilVirtualProductPageIsOpened"/> + <!-- Verify customer see updated virtual product with special price(from the above step) in the product form page --> + <seeInField selector="{{AdminProductFormSection.productName}}" userInput="{{updateVirtualProductSpecialPrice.name}}" stepKey="seeProductName"/> + <seeInField selector="{{AdminProductFormSection.productSku}}" userInput="{{updateVirtualProductSpecialPrice.sku}}" stepKey="seeProductSku"/> + <seeInField selector="{{AdminProductFormSection.productPrice}}" userInput="{{updateVirtualProductSpecialPrice.price}}" stepKey="seeProductPrice"/> + <click selector="{{AdminProductFormSection.advancedPricingLink}}" stepKey="clickAdvancedPricingLink1"/> + <seeInField selector="{{AdminProductFormAdvancedPricingSection.specialPrice}}" userInput="{{updateVirtualProductSpecialPrice.special_price}}" stepKey="seeSpecialPrice"/> + <click selector="{{AdminProductFormAdvancedPricingSection.advancedPricingCloseButton}}" stepKey="clickAdvancedPricingCloseButton"/> + <seeInField selector="{{AdminProductFormSection.productTaxClass}}" userInput="{{updateVirtualProductSpecialPrice.productTaxClass}}" stepKey="seeProductTaxClass"/> + <seeInField selector="{{AdminProductFormSection.productQuantity}}" userInput="{{updateVirtualProductSpecialPrice.quantity}}" stepKey="seeProductQuantity"/> + <see selector="{{AdminProductFormSection.productStockStatus}}" userInput="{{updateVirtualProductSpecialPrice.status}}" stepKey="seeProductStockStatus"/> + <click selector="{{AdminProductFormSection.categoriesDropdown}}" stepKey="clickCategoriesDropDownToVerify"/> + <grabMultiple selector="{{AdminProductFormSection.selectMultipleCategories}}" stepKey="selectedCategories" /> + <assertEquals stepKey="assertSelectedCategories"> + <actualResult type="variable">selectedCategories</actualResult> + <expectedResult type="array">[$$categoryEntity.name$$]</expectedResult> + </assertEquals> + <click selector="{{AdminProductFormSection.done}}" stepKey="clickOnDoneOnCategorySelect"/> + <see selector="{{AdminProductFormSection.visibility}}" userInput="{{updateVirtualProductSpecialPrice.visibility}}" stepKey="seeVisibility"/> + <scrollTo selector="{{AdminProductSEOSection.sectionHeader}}" x="0" y="-80" stepKey="scrollToAdminProductSEOSection1"/> + <click selector="{{AdminProductSEOSection.sectionHeader}}" stepKey="clickAdminProductSEOSection1"/> + <seeInField selector="{{AdminProductSEOSection.urlKeyInput}}" userInput="{{updateVirtualProductSpecialPrice.urlKey}}" stepKey="seeUrlKey"/> + + <!--Verify customer see updated virtual product link on category page --> + <amOnPage url="{{StorefrontCategoryPage.url($$categoryEntity.name$$)}}" stepKey="openCategoryPage"/> + <waitForPageLoad stepKey="waitForCategoryPageLoad"/> + <see selector="{{StorefrontCategoryMainSection.productLink}}" userInput="{{updateVirtualProductSpecialPrice.name}}" stepKey="seeVirtualProductNameOnCategoryPage"/> + + <!-- Verify customer see updated virtual product on the magento storefront page and is searchable by sku --> + <amOnPage url="{{StorefrontProductPage.url(updateVirtualProductSpecialPrice.urlKey)}}" stepKey="goToProductPage"/> + <waitForPageLoad stepKey="waitForStoreFrontProductPageLoad"/> + <fillField selector="{{StorefrontQuickSearchResultsSection.searchTextBox}}" userInput="{{updateVirtualProductSpecialPrice.sku}}" stepKey="fillVirtualProductName"/> + <waitForPageLoad stepKey="waitForSearchTextBox"/> + <click selector="{{StorefrontQuickSearchResultsSection.searchTextBoxButton}}" stepKey="clickSearchTextBoxButton"/> + <waitForPageLoad stepKey="waitForSearch"/> + <see selector="{{StorefrontQuickSearchResultsSection.productLink}}" userInput="{{updateVirtualProductSpecialPrice.name}}" stepKey="seeVirtualProductName"/> + + <!--Verify customer see updated virtual product with special price(from above step) on product storefront page by url key --> + <amOnPage url="{{StorefrontProductPage.url(updateVirtualProductSpecialPrice.urlKey)}}" stepKey="goToProductStorefrontPage"/> + <waitForPageLoad stepKey="waitForStorefrontProductPageLoad"/> + <see selector="{{StorefrontProductInfoMainSection.productName}}" userInput="{{updateVirtualProductSpecialPrice.name}}" stepKey="seeVirtualProductNameOnStoreFrontPage"/> + <see selector="{{StorefrontProductInfoMainSection.productSku}}" userInput="{{updateVirtualProductSpecialPrice.sku}}" stepKey="seeVirtualProductSku"/> + <!-- Verify customer see virtual product special price on the storefront page --> + <grabTextFrom selector="{{StorefrontProductInfoMainSection.specialPriceAmount}}" stepKey="specialPriceAmount"/> + <assertEquals stepKey="assertSpecialPriceTextOnProductPage"> + <expectedResult type="string">${{updateVirtualProductSpecialPrice.special_price}}</expectedResult> + <actualResult type="variable">specialPriceAmount</actualResult> + </assertEquals> + <!-- Verify customer see virtual product old price on the storefront page --> + <grabTextFrom selector="{{StorefrontProductInfoMainSection.oldPriceAmount}}" stepKey="oldPriceAmount"/> + <assertEquals stepKey="assertOldPriceTextOnProductPage"> + <expectedResult type="string">${{updateVirtualProductSpecialPrice.price}}</expectedResult> + <actualResult type="variable">oldPriceAmount</actualResult> + </assertEquals> + <!-- Verify customer see virtual product in stock status on the storefront page --> + <grabTextFrom selector="{{StorefrontProductInfoMainSection.productStockStatus}}" stepKey="productStockAvailableStatus"/> + <assertEquals stepKey="assertStockAvailableOnProductPage"> + <expectedResult type="string">{{updateVirtualProductSpecialPrice.storefrontStatus}}</expectedResult> + <actualResult type="variable">productStockAvailableStatus</actualResult> + </assertEquals> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithSpecialPriceOutOfStockVisibleInCategoryAndSearchTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithSpecialPriceOutOfStockVisibleInCategoryAndSearchTest.xml new file mode 100644 index 00000000000..920a0a494ba --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithSpecialPriceOutOfStockVisibleInCategoryAndSearchTest.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="UpdateVirtualProductWithSpecialPriceOutOfStockVisibleInCategoryAndSearchTest"> + <annotations> + <stories value="Update Virtual Product"/> + <title value="Update Virtual Product with Special Price (Out of Stock) Visible in Category and Search"/> + <description value="Test log in to Update Virtual Product and Update Virtual Product with Special Price (Out of Stock) Visible in Category and Search"/> + <testCaseId value="MC-6505"/> + <severity value="CRITICAL"/> + <group value="catalog"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <actionGroup ref = "LoginAsAdmin" stepKey="loginAsAdmin"/> + <createData entity="SimpleSubCategory" stepKey="initialCategoryEntity"/> + <createData entity="defaultVirtualProduct" stepKey="initialVirtualProduct"> + <requiredEntity createDataKey="initialCategoryEntity"/> + </createData> + <createData entity="SimpleSubCategory" stepKey="categoryEntity"/> + </before> + <after> + <deleteData stepKey="deleteSimpleSubCategory" createDataKey="initialCategoryEntity"/> + <deleteData stepKey="deleteSimpleSubCategory2" createDataKey="categoryEntity"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Search default virtual product in the grid page --> + <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPage1"/> + <waitForPageLoad stepKey="waitForProductCatalogPage1"/> + <conditionalClick selector="{{AdminProductGridFilterSection.clearAll}}" dependentSelector="{{AdminProductGridFilterSection.clearAll}}" visible="true" stepKey="clickClearAllFilter" /> + <fillField selector="{{AdminProductGridFilterSection.keywordSearch}}" userInput="$$initialVirtualProduct.name$$" stepKey="fillVirtualProductNameInKeywordSearch"/> + <click selector="{{AdminProductGridFilterSection.keywordSearchButton}}" stepKey="clickKeywordSearchButton"/> + <waitForPageLoad stepKey="waitForProductSearch"/> + <click selector="{{AdminProductGridFilterSection.nthRow('1')}}" stepKey="clickFirstRowToVerifyCreatedVirtualProduct"/> + <waitForPageLoad stepKey="waitUntilProductIsOpened"/> + + <!-- Update virtual product with special price(out of stock) --> + <fillField selector="{{AdminProductFormSection.productName}}" userInput="{{updateVirtualProductSpecialPriceOutOfStock.name}}" stepKey="fillProductName"/> + <fillField selector="{{AdminProductFormSection.productSku}}" userInput="{{updateVirtualProductSpecialPriceOutOfStock.sku}}" stepKey="fillProductSku"/> + <fillField selector="{{AdminProductFormSection.productPrice}}" userInput="{{updateVirtualProductSpecialPriceOutOfStock.price}}" stepKey="fillProductPrice"/> + <!-- Press enter to validate advanced pricing link --> + <pressKey selector="{{AdminProductFormSection.productPrice}}" parameterArray="[\Facebook\WebDriver\WebDriverKeys::ENTER]" stepKey="pressEnterKey"/> + <click selector="{{AdminProductFormSection.advancedPricingLink}}" stepKey="clickAdvancedPricingLink"/> + <fillField selector="{{AdminProductFormAdvancedPricingSection.specialPrice}}" userInput="{{updateVirtualProductSpecialPriceOutOfStock.special_price}}" stepKey="fillSpecialPrice"/> + <click selector="{{AdminProductFormAdvancedPricingSection.doneButton}}" stepKey="clickDoneButton"/> + <selectOption selector="{{AdminProductFormSection.productTaxClass}}" userInput="{{updateVirtualProductSpecialPriceOutOfStock.productTaxClass}}" stepKey="selectProductStockClass"/> + <selectOption selector="{{AdminProductFormSection.stockStatus}}" userInput="{{updateVirtualProductSpecialPriceOutOfStock.status}}" stepKey="selectStockStatusInStock"/> + <click selector="{{AdminProductFormSection.categoriesDropdown}}" stepKey="clickCategoriesDropDown"/> + <fillField selector="{{AdminProductFormSection.searchCategory}}" userInput="$$initialCategoryEntity.name$$" stepKey="fillSearchForInitialCategory" /> + <click selector="{{AdminProductFormSection.selectCategory($$initialCategoryEntity.name$$)}}" stepKey="unselectInitialCategory"/> + <fillField selector="{{AdminProductFormSection.searchCategory}}" userInput="$$categoryEntity.name$$" stepKey="fillSearchCategory" /> + <click selector="{{AdminProductFormSection.selectCategory($$categoryEntity.name$$)}}" stepKey="clickOnCategory"/> + <click selector="{{AdminProductFormSection.done}}" stepKey="clickOnDoneAdvancedCategorySelect"/> + <selectOption selector="{{AdminProductFormSection.visibility}}" userInput="{{updateVirtualProductSpecialPriceOutOfStock.visibility}}" stepKey="selectVisibility"/> + <click selector="{{AdminProductSEOSection.sectionHeader}}" stepKey="clickAdminProductSEOSection"/> + <fillField selector="{{AdminProductSEOSection.urlKeyInput}}" userInput="{{updateVirtualProductSpecialPriceOutOfStock.urlKey}}" stepKey="fillUrlKey"/> + <scrollToTopOfPage stepKey="scrollToTopOfAdminProductFormSection"/> + <click selector="{{AdminProductFormSection.save}}" stepKey="clickSaveButton"/> + <waitForPageLoad stepKey="waitForVirtualProductSaved"/> + <!-- Verify we see success message --> + <see selector="{{AdminProductFormSection.successMessage}}" userInput="You saved the product." stepKey="seeAssertVirtualProductSaveSuccessMessage"/> + + <!-- Search updated virtual product with special price(out of stock) in the grid --> + <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPageToSearchUpdatedVirtualProduct"/> + <waitForPageLoad stepKey="waitForProductCatalogPageToLoad"/> + <conditionalClick selector="{{AdminProductGridFilterSection.clearAll}}" dependentSelector="{{AdminProductGridFilterSection.clearAll}}" visible="true" stepKey="clickClearAll"/> + <click selector="{{AdminProductGridFilterSection.filters}}" stepKey="clickFiltersButton"/> + <fillField selector="{{AdminProductGridFilterSection.nameFilter}}" userInput="{{updateVirtualProductSpecialPriceOutOfStock.name}}" stepKey="fillVirtualProductNameInNameFilter"/> + <fillField selector="{{AdminProductGridFilterSection.skuFilter}}" userInput="{{updateVirtualProductSpecialPriceOutOfStock.sku}}" stepKey="fillVirtualProductSku"/> + <click selector="{{AdminProductGridFilterSection.applyFilters}}" stepKey="clickApplyFiltersButton"/> + <click selector="{{AdminProductGridFilterSection.nthRow('1')}}" stepKey="clickFirstRowToVerifyUpdatedVirtualProductVisibleInGrid"/> + <waitForPageLoad stepKey="waitUntilVirtualProductPageIsOpened"/> + <!-- Verify customer see updated virtual product with special price(out of stock) in the product form page --> + <seeInField selector="{{AdminProductFormSection.productName}}" userInput="{{updateVirtualProductSpecialPriceOutOfStock.name}}" stepKey="seeProductName"/> + <seeInField selector="{{AdminProductFormSection.productSku}}" userInput="{{updateVirtualProductSpecialPriceOutOfStock.sku}}" stepKey="seeProductSku"/> + <seeInField selector="{{AdminProductFormSection.productPrice}}" userInput="{{updateVirtualProductSpecialPriceOutOfStock.price}}" stepKey="seeProductPrice"/> + <click selector="{{AdminProductFormSection.advancedPricingLink}}" stepKey="clickAdvancedPricingLink1"/> + <seeInField selector="{{AdminProductFormAdvancedPricingSection.specialPrice}}" userInput="{{updateVirtualProductSpecialPriceOutOfStock.special_price}}" stepKey="seeSpecialPrice"/> + <click selector="{{AdminProductFormAdvancedPricingSection.advancedPricingCloseButton}}" stepKey="clickAdvancedPricingCloseButton"/> + <seeInField selector="{{AdminProductFormSection.productTaxClass}}" userInput="{{updateVirtualProductSpecialPriceOutOfStock.productTaxClass}}" stepKey="seeProductTaxClass"/> + <see selector="{{AdminProductFormSection.productStockStatus}}" userInput="{{updateVirtualProductSpecialPriceOutOfStock.status}}" stepKey="seeProductStockStatus"/> + <click selector="{{AdminProductFormSection.categoriesDropdown}}" stepKey="clickCategoriesDropDownToVerify"/> + <grabMultiple selector="{{AdminProductFormSection.selectMultipleCategories}}" stepKey="selectedCategories" /> + <assertEquals stepKey="assertSelectedCategories"> + <actualResult type="variable">selectedCategories</actualResult> + <expectedResult type="array">[$$categoryEntity.name$$]</expectedResult> + </assertEquals> + <click selector="{{AdminProductFormSection.done}}" stepKey="clickOnDoneOnCategorySelect"/> + <see selector="{{AdminProductFormSection.visibility}}" userInput="{{updateVirtualProductSpecialPriceOutOfStock.visibility}}" stepKey="seeVisibility"/> + <scrollTo selector="{{AdminProductSEOSection.sectionHeader}}" x="0" y="-80" stepKey="scrollToAdminProductSEOSection1"/> + <click selector="{{AdminProductSEOSection.sectionHeader}}" stepKey="clickAdminProductSEOSection1"/> + <seeInField selector="{{AdminProductSEOSection.urlKeyInput}}" userInput="{{updateVirtualProductSpecialPriceOutOfStock.urlKey}}" stepKey="seeUrlKey"/> + + <!--Verify customer see updated virtual product with special price(out of stock) on product storefront page --> + <amOnPage url="{{StorefrontProductPage.url(updateVirtualProductSpecialPriceOutOfStock.urlKey)}}" stepKey="goToProductPage"/> + <waitForPageLoad stepKey="waitForStorefrontProductPageLoad"/> + <see selector="{{StorefrontProductInfoMainSection.productName}}" userInput="{{updateVirtualProductSpecialPriceOutOfStock.name}}" stepKey="seeVirtualProductNameOnStoreFrontPage"/> + <see selector="{{StorefrontProductInfoMainSection.productPrice}}" userInput="{{updateVirtualProductSpecialPriceOutOfStock.price}}" stepKey="seeVirtualProductPriceOnStoreFrontPage"/> + <see selector="{{StorefrontProductInfoMainSection.productSku}}" userInput="{{updateVirtualProductSpecialPriceOutOfStock.sku}}" stepKey="seeVirtualProductSku"/> + <grabTextFrom selector="{{StorefrontProductInfoMainSection.productStockStatus}}" stepKey="productStockAvailableStatus"/> + <assertEquals stepKey="assertStockAvailableOnProductPage"> + <expectedResult type="string">{{updateVirtualProductSpecialPriceOutOfStock.storefrontStatus}}</expectedResult> + <actualResult type="variable">productStockAvailableStatus</actualResult> + </assertEquals> + <grabTextFrom selector="{{StorefrontProductInfoMainSection.oldPriceAmount}}" stepKey="productPriceAmount"/> + <assertEquals stepKey="assertOldPriceTextOnProductPage"> + <expectedResult type="string">${{updateVirtualProductSpecialPriceOutOfStock.price}}</expectedResult> + <actualResult type="variable">productPriceAmount</actualResult> + </assertEquals> + <!--Verify customer see virtual product with special price on the storefront page--> + <grabTextFrom selector="{{StorefrontProductInfoMainSection.specialPriceAmount}}" stepKey="specialPriceAmount"/> + <assertEquals stepKey="assertSpecialPriceTextOnProductPage"> + <expectedResult type="string">${{updateVirtualProductSpecialPriceOutOfStock.special_price}}</expectedResult> + <actualResult type="variable">specialPriceAmount</actualResult> + </assertEquals> + + <!--Verify customer don't see updated virtual product link on magento storefront page and is searchable by sku --> + <amOnPage url="{{StorefrontProductPage.url(updateVirtualProductSpecialPriceOutOfStock.urlKey)}}" stepKey="goToMagentoStorefrontPage"/> + <waitForPageLoad stepKey="waitForStoreFrontProductPageLoad"/> + <fillField selector="{{StorefrontQuickSearchResultsSection.searchTextBox}}" userInput="{{updateVirtualProductSpecialPriceOutOfStock.sku}}" stepKey="fillVirtualProductName"/> + <waitForPageLoad stepKey="waitForSearchTextBox"/> + <click selector="{{StorefrontQuickSearchResultsSection.searchTextBoxButton}}" stepKey="clickSearchTextBoxButton"/> + <waitForPageLoad stepKey="waitForSearch"/> + <dontSee selector="{{StorefrontQuickSearchResultsSection.productLink}}" userInput="{{updateVirtualProductSpecialPriceOutOfStock.name}}" stepKey="dontSeeVirtualProductNameOnStorefrontPage"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceInStockVisibleInCategoryAndSearchTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceInStockVisibleInCategoryAndSearchTest.xml new file mode 100644 index 00000000000..d4ec5e410d9 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceInStockVisibleInCategoryAndSearchTest.xml @@ -0,0 +1,155 @@ +<?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="UpdateVirtualProductWithTierPriceInStockVisibleInCategoryAndSearchTest"> + <annotations> + <stories value="Update Virtual Product"/> + <title value="Update Virtual Product with Tier Price (In Stock) Visible in Category and Search"/> + <description value="Test log in to Update Virtual Product and Update Virtual Product with Tier Price (In Stock) Visible in Category and Search"/> + <testCaseId value="MC-6504"/> + <severity value="CRITICAL"/> + <group value="catalog"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <actionGroup ref = "LoginAsAdmin" stepKey="loginAsAdmin"/> + <createData entity="SimpleSubCategory" stepKey="initialCategoryEntity"/> + <createData entity="defaultVirtualProduct" stepKey="initialVirtualProduct"> + <requiredEntity createDataKey="initialCategoryEntity"/> + </createData> + <createData entity="SimpleSubCategory" stepKey="categoryEntity"/> + </before> + <after> + <deleteData stepKey="deleteSimpleSubCategory" createDataKey="initialCategoryEntity"/> + <deleteData stepKey="deleteSimpleSubCategory2" createDataKey="categoryEntity"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Search default virtual product in the grid --> + <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPage1"/> + <waitForPageLoad stepKey="waitForProductCatalogPage1"/> + <conditionalClick selector="{{AdminProductGridFilterSection.clearAll}}" dependentSelector="{{AdminProductGridFilterSection.clearAll}}" visible="true" stepKey="clickClearAllFilter" /> + <fillField selector="{{AdminProductGridFilterSection.keywordSearch}}" userInput="$$initialVirtualProduct.name$$" stepKey="fillVirtualProductNameInKeywordSearch"/> + <click selector="{{AdminProductGridFilterSection.keywordSearchButton}}" stepKey="clickKeywordSearchButton"/> + <waitForPageLoad stepKey="waitForProductSearch"/> + <click selector="{{AdminProductGridFilterSection.nthRow('1')}}" stepKey="clickFirstRowToVerifyCreatedVirtualProduct"/> + <waitForPageLoad stepKey="waitUntilProductIsOpened"/> + + <!-- Update virtual product with tier price --> + <fillField selector="{{AdminProductFormSection.productName}}" userInput="{{updateVirtualProductTierPriceInStock.name}}" stepKey="fillProductName"/> + <fillField selector="{{AdminProductFormSection.productSku}}" userInput="{{updateVirtualProductTierPriceInStock.sku}}" stepKey="fillProductSku"/> + <fillField selector="{{AdminProductFormSection.productPrice}}" userInput="{{updateVirtualProductTierPriceInStock.price}}" stepKey="fillProductPrice"/> + <!-- Press enter to validate advanced pricing link --> + <pressKey selector="{{AdminProductFormSection.productPrice}}" parameterArray="[\Facebook\WebDriver\WebDriverKeys::ENTER]" stepKey="pressEnterKey"/> + <click selector="{{AdminProductFormSection.advancedPricingLink}}" stepKey="clickAdvancedPricingLink"/> + <click selector="{{AdminProductFormAdvancedPricingSection.customerGroupPriceAddButton}}" stepKey="clickCustomerGroupPriceAddButton"/> + <scrollTo selector="{{AdminProductFormAdvancedPricingSection.productTierPriceQtyInput('0')}}" x="50" y="0" stepKey="scrollToProductTierPriceQuantityInputTextBox"/> + <selectOption selector="{{AdminProductFormAdvancedPricingSection.productTierPriceWebsiteSelect('0')}}" userInput="{{tierPriceOnVirtualProduct.website}}" stepKey="selectProductTierPriceWebsiteInput"/> + <selectOption selector="{{AdminProductFormAdvancedPricingSection.productTierPriceCustGroupSelect('0')}}" userInput="{{tierPriceOnVirtualProduct.customer_group}}" stepKey="selectProductTierPriceCustomerGroupInput"/> + <fillField selector="{{AdminProductFormAdvancedPricingSection.productTierPriceQtyInput('0')}}" userInput="{{tierPriceOnVirtualProduct.qty}}" stepKey="fillProductTierPriceQuantityInput"/> + <fillField selector="{{AdminProductFormAdvancedPricingSection.productTierPriceFixedPriceInput('0')}}" userInput="{{tierPriceOnVirtualProduct.price}}" stepKey="selectProductTierPriceFixedPrice"/> + <click selector="{{AdminProductFormAdvancedPricingSection.doneButton}}" stepKey="clickDoneButton"/> + <selectOption selector="{{AdminProductFormSection.productTaxClass}}" userInput="{{updateVirtualProductTierPriceInStock.productTaxClass}}" stepKey="selectProductStockClass"/> + <fillField selector="{{AdminProductFormSection.productQuantity}}" userInput="{{updateVirtualProductTierPriceInStock.quantity}}" stepKey="fillProductQuantity"/> + <selectOption selector="{{AdminProductFormSection.stockStatus}}" userInput="{{updateVirtualProductTierPriceInStock.status}}" stepKey="selectStockStatusInStock"/> + <click selector="{{AdminProductFormSection.categoriesDropdown}}" stepKey="clickCategoriesDropDown"/> + <fillField selector="{{AdminProductFormSection.searchCategory}}" userInput="$$initialCategoryEntity.name$$" stepKey="fillSearchForInitialCategory" /> + <click selector="{{AdminProductFormSection.selectCategory($$initialCategoryEntity.name$$)}}" stepKey="unselectInitialCategory"/> + <fillField selector="{{AdminProductFormSection.searchCategory}}" userInput="$$categoryEntity.name$$" stepKey="fillSearchCategory" /> + <click selector="{{AdminProductFormSection.selectCategory($$categoryEntity.name$$)}}" stepKey="clickOnCategory"/> + <click selector="{{AdminProductFormSection.done}}" stepKey="clickOnDoneAdvancedCategorySelect"/> + <selectOption selector="{{AdminProductFormSection.visibility}}" userInput="{{updateVirtualProductTierPriceInStock.visibility}}" stepKey="selectVisibility"/> + <click selector="{{AdminProductSEOSection.sectionHeader}}" stepKey="clickAdminProductSEOSection"/> + <fillField selector="{{AdminProductSEOSection.urlKeyInput}}" userInput="{{updateVirtualProductTierPriceInStock.urlKey}}" stepKey="fillUrlKey"/> + <scrollToTopOfPage stepKey="scrollToTopOfAdminProductFormSection"/> + <click selector="{{AdminProductFormSection.save}}" stepKey="clickSaveButton"/> + <waitForPageLoad stepKey="waitForVirtualProductSaved"/> + <!-- Verify we see success message --> + <see selector="{{AdminProductFormSection.successMessage}}" userInput="You saved the product." stepKey="seeAssertVirtualProductSaveSuccessMessage"/> + + <!-- Search updated virtual product(from above step) in the grid --> + <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPageToSearchUpdatedVirtualProduct"/> + <waitForPageLoad stepKey="waitForProductCatalogPageToLoad"/> + <conditionalClick selector="{{AdminProductGridFilterSection.clearAll}}" dependentSelector="{{AdminProductGridFilterSection.clearAll}}" visible="true" stepKey="clickClearAll"/> + <click selector="{{AdminProductGridFilterSection.filters}}" stepKey="clickFiltersButton"/> + <fillField selector="{{AdminProductGridFilterSection.nameFilter}}" userInput="{{updateVirtualProductTierPriceInStock.name}}" stepKey="fillVirtualProductNameInNameFilter"/> + <fillField selector="{{AdminProductGridFilterSection.skuFilter}}" userInput="{{updateVirtualProductTierPriceInStock.sku}}" stepKey="fillVirtualProductSku"/> + <click selector="{{AdminProductGridFilterSection.applyFilters}}" stepKey="clickApplyFiltersButton"/> + <click selector="{{AdminProductGridFilterSection.nthRow('1')}}" stepKey="clickFirstRowToVerifyUpdatedVirtualProductVisibleInGrid"/> + <waitForPageLoad stepKey="waitUntilVirtualProductPageIsOpened"/> + + <!-- Verify customer see updated virtual product with tier price(from the above step) in the product form page --> + <seeInField selector="{{AdminProductFormSection.productName}}" userInput="{{updateVirtualProductTierPriceInStock.name}}" stepKey="seeProductName"/> + <seeInField selector="{{AdminProductFormSection.productSku}}" userInput="{{updateVirtualProductTierPriceInStock.sku}}" stepKey="seeProductSku"/> + <seeInField selector="{{AdminProductFormSection.productPrice}}" userInput="{{updateVirtualProductTierPriceInStock.price}}" stepKey="seeProductPrice"/> + <click selector="{{AdminProductFormSection.advancedPricingLink}}" stepKey="clickAdvancedPricingLink1"/> + <seeInField selector="{{AdminProductFormAdvancedPricingSection.productTierPriceWebsiteSelect('0')}}" userInput="{{tierPriceOnVirtualProduct.website}}" stepKey="seeProductTierPriceWebsiteInput"/> + <seeInField selector="{{AdminProductFormAdvancedPricingSection.productTierPriceCustGroupSelect('0')}}" userInput="{{tierPriceOnVirtualProduct.customer_group}}" stepKey="seeProductTierPriceCustomerGroupInput"/> + <seeInField selector="{{AdminProductFormAdvancedPricingSection.productTierPriceQtyInput('0')}}" userInput="{{tierPriceOnVirtualProduct.qty}}" stepKey="seeProductTierPriceQuantityInput"/> + <seeInField selector="{{AdminProductFormAdvancedPricingSection.productTierPriceFixedPriceInput('0')}}" userInput="{{tierPriceOnVirtualProduct.price}}" stepKey="seeProductTierPriceFixedPrice"/> + <click selector="{{AdminProductFormAdvancedPricingSection.advancedPricingCloseButton}}" stepKey="clickAdvancedPricingCloseButton"/> + <seeInField selector="{{AdminProductFormSection.productTaxClass}}" userInput="{{updateVirtualProductTierPriceInStock.productTaxClass}}" stepKey="seeProductTaxClass"/> + <seeInField selector="{{AdminProductFormSection.productQuantity}}" userInput="{{updateVirtualProductTierPriceInStock.quantity}}" stepKey="seeProductQuantity"/> + <seeInField selector="{{AdminProductFormSection.productStockStatus}}" userInput="{{updateVirtualProductTierPriceInStock.status}}" stepKey="seeProductStockStatus"/> + <click selector="{{AdminProductFormSection.categoriesDropdown}}" stepKey="clickCategoriesDropDownToVerify"/> + <grabMultiple selector="{{AdminProductFormSection.selectMultipleCategories}}" stepKey="selectedCategories" /> + <assertEquals stepKey="assertSelectedCategories"> + <actualResult type="variable">selectedCategories</actualResult> + <expectedResult type="array">[$$categoryEntity.name$$]</expectedResult> + </assertEquals> + <click selector="{{AdminProductFormSection.done}}" stepKey="clickOnDoneOnCategorySelect"/> + <seeInField selector="{{AdminProductFormSection.visibility}}" userInput="{{updateVirtualProductTierPriceInStock.visibility}}" stepKey="seeVisibility"/> + <scrollTo selector="{{AdminProductSEOSection.sectionHeader}}" x="0" y="-80" stepKey="scrollToAdminProductSEOSection1"/> + <click selector="{{AdminProductSEOSection.sectionHeader}}" stepKey="clickAdminProductSEOSection1"/> + <seeInField selector="{{AdminProductSEOSection.urlKeyInput}}" userInput="{{updateVirtualProductTierPriceInStock.urlKey}}" stepKey="seeUrlKey"/> + + <!--Verify customer see updated virtual product link on category page --> + <amOnPage url="{{StorefrontCategoryPage.url($$categoryEntity.name$$)}}" stepKey="openCategoryPage"/> + <waitForPageLoad stepKey="waitForCategoryPageLoad"/> + <see selector="{{StorefrontCategoryMainSection.productLink}}" userInput="{{updateVirtualProductTierPriceInStock.name}}" stepKey="seeVirtualProductLinkOnCategoryPage"/> + + <!--Verify customer see updated virtual product with tier price on product storefront page --> + <amOnPage url="{{StorefrontProductPage.url(updateVirtualProductTierPriceInStock.urlKey)}}" stepKey="goToProductPage"/> + <waitForPageLoad stepKey="waitForStorefrontProductPageLoad"/> + <see selector="{{StorefrontProductInfoMainSection.productName}}" userInput="{{updateVirtualProductTierPriceInStock.name}}" stepKey="seeVirtualProductNameOnStoreFrontPage"/> + <see selector="{{StorefrontProductInfoMainSection.productPrice}}" userInput="{{updateVirtualProductTierPriceInStock.price}}" stepKey="seeVirtualProductPriceOnStoreFrontPage"/> + <see selector="{{StorefrontProductInfoMainSection.productSku}}" userInput="{{updateVirtualProductTierPriceInStock.sku}}" stepKey="seeVirtualProductSku"/> + <grabTextFrom selector="{{StorefrontProductInfoMainSection.productStockStatus}}" stepKey="productStockAvailableStatus"/> + <assertEquals stepKey="assertStockAvailableOnProductPage"> + <expectedResult type="string">{{updateVirtualProductTierPriceInStock.storefrontStatus}}</expectedResult> + <actualResult type="variable">productStockAvailableStatus</actualResult> + </assertEquals> + <grabTextFrom selector="{{StorefrontProductInfoMainSection.productPrice}}" stepKey="productPriceAmount"/> + <assertEquals stepKey="assertOldPriceTextOnProductPage"> + <expectedResult type="string">${{updateVirtualProductTierPriceInStock.price}}</expectedResult> + <actualResult type="variable">productPriceAmount</actualResult> + </assertEquals> + <grabTextFrom selector="{{StorefrontProductInfoMainSection.tierPriceText}}" stepKey="tierPriceText"/> + <assertEquals stepKey="assertTierPriceTextOnProductPage"> + <expectedResult type="string">Buy {{tierPriceOnVirtualProduct.qty}} for ${{tierPriceOnVirtualProduct.price}} each and save 38%</expectedResult> + <actualResult type="variable">tierPriceText</actualResult> + </assertEquals> + + <!--Verify customer see updated virtual product link on magento storefront page and is searchable by sku --> + <amOnPage url="{{StorefrontProductPage.url(updateVirtualProductTierPriceInStock.urlKey)}}" stepKey="goToMagentoStorefrontPage"/> + <waitForPageLoad stepKey="waitForStoreFrontProductPageLoad"/> + <fillField selector="{{StorefrontQuickSearchResultsSection.searchTextBox}}" userInput="{{updateVirtualProductTierPriceInStock.sku}}" stepKey="fillVirtualProductName"/> + <waitForPageLoad stepKey="waitForSearchTextBox"/> + <click selector="{{StorefrontQuickSearchResultsSection.searchTextBoxButton}}" stepKey="clickSearchTextBoxButton"/> + <waitForPageLoad stepKey="waitForSearch"/> + <see selector="{{StorefrontQuickSearchResultsSection.productLink}}" userInput="{{updateVirtualProductTierPriceInStock.name}}" stepKey="seeVirtualProductName"/> + <grabTextFrom selector="{{StorefrontQuickSearchResultsSection.asLowAsLabel}}" stepKey="tierPriceTextOnStorefrontPage"/> + <assertEquals stepKey="assertTierPriceTextOnCategoryPage"> + <expectedResult type="string">As low as ${{tierPriceOnVirtualProduct.price}}</expectedResult> + <actualResult type="variable">tierPriceTextOnStorefrontPage</actualResult> + </assertEquals> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceInStockVisibleInCategoryOnlyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceInStockVisibleInCategoryOnlyTest.xml new file mode 100644 index 00000000000..717d710b4a2 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceInStockVisibleInCategoryOnlyTest.xml @@ -0,0 +1,151 @@ +<?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="AdminUpdateVirtualProductWithTierPriceInStockVisibleInCategoryOnlyTest"> + <annotations> + <stories value="Update Virtual Product"/> + <title value="Update Virtual Product with Tier Price (In Stock) Visible in Category Only"/> + <description value="Test log in to Update Virtual Product and Update Virtual Product with Tier Price (In Stock) Visible in Category Only"/> + <testCaseId value="MC-7508"/> + <severity value="CRITICAL"/> + <group value="catalog"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <actionGroup ref = "LoginAsAdmin" stepKey="loginAsAdmin"/> + <createData entity="SimpleSubCategory" stepKey="initialCategoryEntity"/> + <createData entity="defaultVirtualProduct" stepKey="initialVirtualProduct"> + <requiredEntity createDataKey="initialCategoryEntity"/> + </createData> + <createData entity="SimpleSubCategory" stepKey="categoryEntity"/> + </before> + <after> + <deleteData stepKey="deleteSimpleSubCategory" createDataKey="initialCategoryEntity"/> + <deleteData stepKey="deleteSimpleSubCategory2" createDataKey="categoryEntity"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Search default virtual product in the grid page --> + <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPage1"/> + <waitForPageLoad stepKey="waitForProductCatalogPage1"/> + <conditionalClick selector="{{AdminProductGridFilterSection.clearAll}}" dependentSelector="{{AdminProductGridFilterSection.clearAll}}" visible="true" stepKey="clickClearAllFilter" /> + <fillField selector="{{AdminProductGridFilterSection.keywordSearch}}" userInput="$$initialVirtualProduct.name$$" stepKey="fillVirtualProductNameInKeywordSearch"/> + <click selector="{{AdminProductGridFilterSection.keywordSearchButton}}" stepKey="clickKeywordSearchButton"/> + <waitForPageLoad stepKey="waitForProductSearch"/> + <click selector="{{AdminProductGridFilterSection.nthRow('1')}}" stepKey="clickFirstRowToVerifyCreatedVirtualProduct"/> + <waitForPageLoad stepKey="waitUntilProductIsOpened"/> + + <!-- Update virtual product with tier price(in stock) --> + <fillField selector="{{AdminProductFormSection.productName}}" userInput="{{updateVirtualProductWithTierPriceInStock.name}}" stepKey="fillProductName"/> + <fillField selector="{{AdminProductFormSection.productSku}}" userInput="{{updateVirtualProductWithTierPriceInStock.sku}}" stepKey="fillProductSku"/> + <fillField selector="{{AdminProductFormSection.productPrice}}" userInput="{{updateVirtualProductWithTierPriceInStock.price}}" stepKey="fillProductPrice"/> + <!-- Press enter to validate advanced pricing link --> + <pressKey selector="{{AdminProductFormSection.productPrice}}" parameterArray="[\Facebook\WebDriver\WebDriverKeys::ENTER]" stepKey="pressEnterKey"/> + <click selector="{{AdminProductFormSection.advancedPricingLink}}" stepKey="clickAdvancedPricingLink"/> + <click selector="{{AdminProductFormAdvancedPricingSection.customerGroupPriceAddButton}}" stepKey="clickCustomerGroupPriceAddButton"/> + <scrollTo selector="{{AdminProductFormAdvancedPricingSection.productTierPriceQtyInput('0')}}" x="50" y="0" stepKey="scrollToProductTierPriceQuantityInputTextBox"/> + <selectOption selector="{{AdminProductFormAdvancedPricingSection.productTierPriceWebsiteSelect('0')}}" userInput="{{tierPriceOnVirtualProduct.website}}" stepKey="selectProductTierPriceWebsiteInput"/> + <selectOption selector="{{AdminProductFormAdvancedPricingSection.productTierPriceCustGroupSelect('0')}}" userInput="{{tierPriceOnVirtualProduct.customer_group}}" stepKey="selectProductTierPriceCustomerGroupInput"/> + <fillField selector="{{AdminProductFormAdvancedPricingSection.productTierPriceQtyInput('0')}}" userInput="{{tierPriceOnVirtualProduct.qty}}" stepKey="fillProductTierPriceQuantityInput"/> + <fillField selector="{{AdminProductFormAdvancedPricingSection.productTierPriceFixedPriceInput('0')}}" userInput="{{tierPriceOnVirtualProduct.price}}" stepKey="selectProductTierPriceFixedPrice"/> + <click selector="{{AdminProductFormAdvancedPricingSection.doneButton}}" stepKey="clickDoneButton"/> + <selectOption selector="{{AdminProductFormSection.productTaxClass}}" userInput="{{updateVirtualProductWithTierPriceInStock.productTaxClass}}" stepKey="selectProductStockClass"/> + <fillField selector="{{AdminProductFormSection.productQuantity}}" userInput="{{updateVirtualProductWithTierPriceInStock.quantity}}" stepKey="fillProductQuantity"/> + <selectOption selector="{{AdminProductFormSection.stockStatus}}" userInput="{{updateVirtualProductWithTierPriceInStock.status}}" stepKey="selectStockStatusInStock"/> + <click selector="{{AdminProductFormSection.categoriesDropdown}}" stepKey="clickCategoriesDropDown"/> + <fillField selector="{{AdminProductFormSection.searchCategory}}" userInput="$$initialCategoryEntity.name$$" stepKey="fillSearchForInitialCategory" /> + <click selector="{{AdminProductFormSection.selectCategory($$initialCategoryEntity.name$$)}}" stepKey="unselectInitialCategory"/> + <fillField selector="{{AdminProductFormSection.searchCategory}}" userInput="$$categoryEntity.name$$" stepKey="fillSearchCategory" /> + <click selector="{{AdminProductFormSection.selectCategory($$categoryEntity.name$$)}}" stepKey="clickOnCategory"/> + <click selector="{{AdminProductFormSection.done}}" stepKey="clickOnDoneAdvancedCategorySelect"/> + <selectOption selector="{{AdminProductFormSection.visibility}}" userInput="{{updateVirtualProductWithTierPriceInStock.visibility}}" stepKey="selectVisibility"/> + <click selector="{{AdminProductSEOSection.sectionHeader}}" stepKey="clickAdminProductSEOSection"/> + <fillField selector="{{AdminProductSEOSection.urlKeyInput}}" userInput="{{updateVirtualProductWithTierPriceInStock.urlKey}}" stepKey="fillUrlKey"/> + <scrollToTopOfPage stepKey="scrollToTopOfAdminProductFormSection"/> + <click selector="{{AdminProductFormSection.save}}" stepKey="clickSaveButton"/> + <waitForPageLoad stepKey="waitForVirtualProductSaved"/> + <!-- Verify we see success message --> + <see selector="{{AdminProductFormSection.successMessage}}" userInput="You saved the product." stepKey="seeAssertVirtualProductSaveSuccessMessage"/> + + <!-- Search updated virtual product(from above step) in the grid --> + <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPageToSearchUpdatedVirtualProduct"/> + <waitForPageLoad stepKey="waitForProductCatalogPageToLoad"/> + <conditionalClick selector="{{AdminProductGridFilterSection.clearAll}}" dependentSelector="{{AdminProductGridFilterSection.clearAll}}" visible="true" stepKey="clickClearAll"/> + <click selector="{{AdminProductGridFilterSection.filters}}" stepKey="clickFiltersButton"/> + <fillField selector="{{AdminProductGridFilterSection.nameFilter}}" userInput="{{updateVirtualProductWithTierPriceInStock.name}}" stepKey="fillVirtualProductNameInNameFilter"/> + <fillField selector="{{AdminProductGridFilterSection.skuFilter}}" userInput="{{updateVirtualProductWithTierPriceInStock.sku}}" stepKey="fillVirtualProductSku"/> + <click selector="{{AdminProductGridFilterSection.applyFilters}}" stepKey="clickApplyFiltersButton"/> + <click selector="{{AdminProductGridFilterSection.nthRow('1')}}" stepKey="clickFirstRowToVerifyUpdatedVirtualProductVisibleInGrid"/> + <waitForPageLoad stepKey="waitUntilVirtualProductPageIsOpened"/> + + <!-- Verify customer see updated virtual product with tier price(from the above step) in the product form page --> + <seeInField selector="{{AdminProductFormSection.productName}}" userInput="{{updateVirtualProductWithTierPriceInStock.name}}" stepKey="seeProductName"/> + <seeInField selector="{{AdminProductFormSection.productSku}}" userInput="{{updateVirtualProductWithTierPriceInStock.sku}}" stepKey="seeProductSku"/> + <seeInField selector="{{AdminProductFormSection.productPrice}}" userInput="{{updateVirtualProductWithTierPriceInStock.price}}" stepKey="seeProductPrice"/> + <click selector="{{AdminProductFormSection.advancedPricingLink}}" stepKey="clickAdvancedPricingLink1"/> + <seeInField selector="{{AdminProductFormAdvancedPricingSection.productTierPriceWebsiteSelect('0')}}" userInput="{{tierPriceOnVirtualProduct.website}}" stepKey="seeProductTierPriceWebsiteInput"/> + <seeInField selector="{{AdminProductFormAdvancedPricingSection.productTierPriceCustGroupSelect('0')}}" userInput="{{tierPriceOnVirtualProduct.customer_group}}" stepKey="seeProductTierPriceCustomerGroupInput"/> + <seeInField selector="{{AdminProductFormAdvancedPricingSection.productTierPriceQtyInput('0')}}" userInput="{{tierPriceOnVirtualProduct.qty}}" stepKey="seeProductTierPriceQuantityInput"/> + <seeInField selector="{{AdminProductFormAdvancedPricingSection.productTierPriceFixedPriceInput('0')}}" userInput="{{tierPriceOnVirtualProduct.price}}" stepKey="seeProductTierPriceFixedPrice"/> + <click selector="{{AdminProductFormAdvancedPricingSection.advancedPricingCloseButton}}" stepKey="clickAdvancedPricingCloseButton"/> + <seeInField selector="{{AdminProductFormSection.productTaxClass}}" userInput="{{updateVirtualProductWithTierPriceInStock.productTaxClass}}" stepKey="seeProductTaxClass"/> + <seeInField selector="{{AdminProductFormSection.productQuantity}}" userInput="{{updateVirtualProductWithTierPriceInStock.quantity}}" stepKey="seeProductQuantity"/> + <seeInField selector="{{AdminProductFormSection.productStockStatus}}" userInput="{{updateVirtualProductWithTierPriceInStock.status}}" stepKey="seeProductStockStatus"/> + <click selector="{{AdminProductFormSection.categoriesDropdown}}" stepKey="clickCategoriesDropDownToVerify"/> + <grabMultiple selector="{{AdminProductFormSection.selectMultipleCategories}}" stepKey="selectedCategories" /> + <assertEquals stepKey="assertSelectedCategories"> + <actualResult type="variable">selectedCategories</actualResult> + <expectedResult type="array">[$$categoryEntity.name$$]</expectedResult> + </assertEquals> + <click selector="{{AdminProductFormSection.done}}" stepKey="clickOnDoneOnCategorySelect"/> + <seeInField selector="{{AdminProductFormSection.visibility}}" userInput="{{updateVirtualProductWithTierPriceInStock.visibility}}" stepKey="seeVisibility"/> + <scrollTo selector="{{AdminProductSEOSection.sectionHeader}}" x="0" y="-80" stepKey="scrollToAdminProductSEOSection1"/> + <click selector="{{AdminProductSEOSection.sectionHeader}}" stepKey="clickAdminProductSEOSection1"/> + <seeInField selector="{{AdminProductSEOSection.urlKeyInput}}" userInput="{{updateVirtualProductWithTierPriceInStock.urlKey}}" stepKey="seeUrlKey"/> + + <!--Verify customer see updated virtual product link on category page --> + <amOnPage url="{{StorefrontCategoryPage.url($$categoryEntity.name$$)}}" stepKey="openCategoryPage"/> + <waitForPageLoad stepKey="waitForCategoryPageLoad"/> + <see selector="{{StorefrontCategoryMainSection.productLink}}" userInput="{{updateVirtualProductWithTierPriceInStock.name}}" stepKey="seeVirtualProductNameOnCategoryPage"/> + + <!--Verify customer see updated virtual product with tier price(from above step) on product storefront page --> + <amOnPage url="{{StorefrontProductPage.url(updateVirtualProductWithTierPriceInStock.urlKey)}}" stepKey="goToProductPage"/> + <waitForPageLoad stepKey="waitForStorefrontProductPageLoad"/> + <see selector="{{StorefrontProductInfoMainSection.productName}}" userInput="{{updateVirtualProductWithTierPriceInStock.name}}" stepKey="seeVirtualProductNameOnStoreFrontPage"/> + <see selector="{{StorefrontProductInfoMainSection.productPrice}}" userInput="{{updateVirtualProductWithTierPriceInStock.price}}" stepKey="seeVirtualProductPriceOnStoreFrontPage"/> + <see selector="{{StorefrontProductInfoMainSection.productSku}}" userInput="{{updateVirtualProductWithTierPriceInStock.sku}}" stepKey="seeVirtualProductSku"/> + <grabTextFrom selector="{{StorefrontProductInfoMainSection.productStockStatus}}" stepKey="productStockAvailableStatus"/> + <assertEquals stepKey="assertStockAvailableOnProductPage"> + <expectedResult type="string">{{updateVirtualProductWithTierPriceInStock.storefrontStatus}}</expectedResult> + <actualResult type="variable">productStockAvailableStatus</actualResult> + </assertEquals> + <grabTextFrom selector="{{StorefrontProductInfoMainSection.productPrice}}" stepKey="productPriceAmount"/> + <assertEquals stepKey="assertOldPriceTextOnProductPage"> + <expectedResult type="string">${{updateVirtualProductWithTierPriceInStock.price}}</expectedResult> + <actualResult type="variable">productPriceAmount</actualResult> + </assertEquals> + <!-- Verify customer see product tier price on product page --> + <grabTextFrom selector="{{StorefrontProductInfoMainSection.tierPriceText}}" stepKey="tierPriceText"/> + <assertEquals stepKey="assertTierPriceTextOnProductPage"> + <expectedResult type="string">Buy {{tierPriceOnVirtualProduct.qty}} for ${{tierPriceOnVirtualProduct.price}} each and save 10%</expectedResult> + <actualResult type="variable">tierPriceText</actualResult> + </assertEquals> + + <!--Verify customer don't see updated virtual product link on magento storefront page and is searchable by sku --> + <amOnPage url="{{StorefrontProductPage.url(updateVirtualProductWithTierPriceInStock.urlKey)}}" stepKey="goToMagentoStorefrontPage"/> + <waitForPageLoad stepKey="waitForStoreFrontProductPageLoad"/> + <fillField selector="{{StorefrontQuickSearchResultsSection.searchTextBox}}" userInput="{{updateVirtualProductTierPriceInStock.sku}}" stepKey="fillVirtualProductName"/> + <waitForPageLoad stepKey="waitForSearchTextBox"/> + <click selector="{{StorefrontQuickSearchResultsSection.searchTextBoxButton}}" stepKey="clickSearchTextBoxButton"/> + <waitForPageLoad stepKey="waitForSearch"/> + <dontSee selector="{{StorefrontQuickSearchResultsSection.productLink}}" userInput="{{updateVirtualProductWithTierPriceInStock.name}}" stepKey="dontSeeVirtualProductName"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceOutOfStockVisibleInCategoryAndSearchTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceOutOfStockVisibleInCategoryAndSearchTest.xml new file mode 100644 index 00000000000..703a4e24cdc --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceOutOfStockVisibleInCategoryAndSearchTest.xml @@ -0,0 +1,151 @@ +<?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="AdminUpdateVirtualProductWithTierPriceOutOfStockVisibleInCategoryAndSearchTest"> + <annotations> + <stories value="Update Virtual Product"/> + <title value="Update Virtual Product with Tier Price (Out of Stock) Visible in Category and Search"/> + <description value="Test log in to Update Virtual Product and Update Virtual Product with Tier Price (Out of Stock) Visible in Category and Search"/> + <testCaseId value="MC-6499"/> + <severity value="CRITICAL"/> + <group value="catalog"/> + <group value="mtf_migrated"/> + </annotations> + + <before> + <actionGroup ref = "LoginAsAdmin" stepKey="loginAsAdmin"/> + <createData entity="SimpleSubCategory" stepKey="initialCategoryEntity"/> + <createData entity="defaultVirtualProduct" stepKey="initialVirtualProduct"> + <requiredEntity createDataKey="initialCategoryEntity"/> + </createData> + <createData entity="SimpleSubCategory" stepKey="categoryEntity"/> + </before> + <after> + <deleteData stepKey="deleteSimpleSubCategory" createDataKey="initialCategoryEntity"/> + <deleteData stepKey="deleteSimpleSubCategory2" createDataKey="categoryEntity"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Search default virtual product in the grid page --> + <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPage1"/> + <waitForPageLoad stepKey="waitForProductCatalogPage1"/> + <conditionalClick selector="{{AdminProductGridFilterSection.clearAll}}" dependentSelector="{{AdminProductGridFilterSection.clearAll}}" visible="true" stepKey="clickClearAllFilter" /> + <fillField selector="{{AdminProductGridFilterSection.keywordSearch}}" userInput="$$initialVirtualProduct.name$$" stepKey="fillVirtualProductNameInKeywordSearch"/> + <click selector="{{AdminProductGridFilterSection.keywordSearchButton}}" stepKey="clickKeywordSearchButton"/> + <waitForPageLoad stepKey="waitForProductSearch"/> + <click selector="{{AdminProductGridFilterSection.nthRow('1')}}" stepKey="clickFirstRowToVerifyCreatedVirtualProduct"/> + <waitForPageLoad stepKey="waitUntilProductIsOpened"/> + + <!-- Update virtual product with tier price(out of stock) --> + <fillField selector="{{AdminProductFormSection.productName}}" userInput="{{updateVirtualTierPriceOutOfStock.name}}" stepKey="fillProductName"/> + <fillField selector="{{AdminProductFormSection.productSku}}" userInput="{{updateVirtualTierPriceOutOfStock.sku}}" stepKey="fillProductSku"/> + <fillField selector="{{AdminProductFormSection.productPrice}}" userInput="{{updateVirtualTierPriceOutOfStock.price}}" stepKey="fillProductPrice"/> + <!-- Press enter to validate advanced pricing link --> + <pressKey selector="{{AdminProductFormSection.productPrice}}" parameterArray="[\Facebook\WebDriver\WebDriverKeys::ENTER]" stepKey="pressEnterKey"/> + <click selector="{{AdminProductFormSection.advancedPricingLink}}" stepKey="clickAdvancedPricingLink"/> + <click selector="{{AdminProductFormAdvancedPricingSection.customerGroupPriceAddButton}}" stepKey="clickCustomerGroupPriceAddButton"/> + <scrollTo selector="{{AdminProductFormAdvancedPricingSection.productTierPriceQtyInput('0')}}" x="50" y="0" stepKey="scrollToProductTierPriceQuantityInputTextBox"/> + <selectOption selector="{{AdminProductFormAdvancedPricingSection.productTierPriceWebsiteSelect('0')}}" userInput="{{tierPriceOnVirtualProduct.website}}" stepKey="selectProductTierPriceWebsiteInput"/> + <selectOption selector="{{AdminProductFormAdvancedPricingSection.productTierPriceCustGroupSelect('0')}}" userInput="{{tierPriceOnVirtualProduct.customer_group}}" stepKey="selectProductTierPriceCustomerGroupInput"/> + <fillField selector="{{AdminProductFormAdvancedPricingSection.productTierPriceQtyInput('0')}}" userInput="{{tierPriceOnVirtualProduct.qty}}" stepKey="fillProductTierPriceQuantityInput"/> + <fillField selector="{{AdminProductFormAdvancedPricingSection.productTierPriceFixedPriceInput('0')}}" userInput="{{tierPriceOnVirtualProduct.price}}" stepKey="selectProductTierPriceFixedPrice"/> + <click selector="{{AdminProductFormAdvancedPricingSection.doneButton}}" stepKey="clickDoneButton"/> + <selectOption selector="{{AdminProductFormSection.productTaxClass}}" userInput="{{updateVirtualTierPriceOutOfStock.productTaxClass}}" stepKey="selectProductStockClass"/> + <fillField selector="{{AdminProductFormSection.productQuantity}}" userInput="{{updateVirtualTierPriceOutOfStock.quantity}}" stepKey="fillProductQuantity"/> + <selectOption selector="{{AdminProductFormSection.stockStatus}}" userInput="{{updateVirtualTierPriceOutOfStock.status}}" stepKey="selectStockStatusInStock"/> + <click selector="{{AdminProductFormSection.categoriesDropdown}}" stepKey="clickCategoriesDropDown"/> + <fillField selector="{{AdminProductFormSection.searchCategory}}" userInput="$$initialCategoryEntity.name$$" stepKey="fillSearchForInitialCategory" /> + <click selector="{{AdminProductFormSection.selectCategory($$initialCategoryEntity.name$$)}}" stepKey="unselectInitialCategory"/> + <fillField selector="{{AdminProductFormSection.searchCategory}}" userInput="$$categoryEntity.name$$" stepKey="fillSearchCategory" /> + <click selector="{{AdminProductFormSection.selectCategory($$categoryEntity.name$$)}}" stepKey="clickOnCategory"/> + <click selector="{{AdminProductFormSection.done}}" stepKey="clickOnDoneAdvancedCategorySelect"/> + <selectOption selector="{{AdminProductFormSection.visibility}}" userInput="{{updateVirtualTierPriceOutOfStock.visibility}}" stepKey="selectVisibility"/> + <click selector="{{AdminProductSEOSection.sectionHeader}}" stepKey="clickAdminProductSEOSection"/> + <fillField selector="{{AdminProductSEOSection.urlKeyInput}}" userInput="{{updateVirtualTierPriceOutOfStock.urlKey}}" stepKey="fillUrlKey"/> + <scrollToTopOfPage stepKey="scrollToTopOfAdminProductFormSection"/> + <click selector="{{AdminProductFormSection.save}}" stepKey="clickSaveButton"/> + <waitForPageLoad stepKey="waitForVirtualProductSaved"/> + <!-- Verify we see success message --> + <see selector="{{AdminProductFormSection.successMessage}}" userInput="You saved the product." stepKey="seeAssertVirtualProductSaveSuccessMessage"/> + + <!-- Search updated virtual product(from above step) in the grid page --> + <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPageToSearchUpdatedVirtualProduct"/> + <waitForPageLoad stepKey="waitForProductCatalogPageToLoad"/> + <conditionalClick selector="{{AdminProductGridFilterSection.clearAll}}" dependentSelector="{{AdminProductGridFilterSection.clearAll}}" visible="true" stepKey="clickClearAll"/> + <click selector="{{AdminProductGridFilterSection.filters}}" stepKey="clickFiltersButton"/> + <fillField selector="{{AdminProductGridFilterSection.nameFilter}}" userInput="{{updateVirtualTierPriceOutOfStock.name}}" stepKey="fillVirtualProductNameInNameFilter"/> + <fillField selector="{{AdminProductGridFilterSection.skuFilter}}" userInput="{{updateVirtualTierPriceOutOfStock.sku}}" stepKey="fillVirtualProductSku"/> + <click selector="{{AdminProductGridFilterSection.applyFilters}}" stepKey="clickApplyFiltersButton"/> + <click selector="{{AdminProductGridFilterSection.nthRow('1')}}" stepKey="clickFirstRowToVerifyUpdatedVirtualProductVisibleInGrid"/> + <waitForPageLoad stepKey="waitUntilVirtualProductPageIsOpened"/> + + <!-- Verify we customer see updated virtual product with tier price(from the above step) in the product form page --> + <seeInField selector="{{AdminProductFormSection.productName}}" userInput="{{updateVirtualTierPriceOutOfStock.name}}" stepKey="seeProductName"/> + <seeInField selector="{{AdminProductFormSection.productSku}}" userInput="{{updateVirtualTierPriceOutOfStock.sku}}" stepKey="seeProductSku"/> + <seeInField selector="{{AdminProductFormSection.productPrice}}" userInput="{{updateVirtualTierPriceOutOfStock.price}}" stepKey="seeProductPrice"/> + <click selector="{{AdminProductFormSection.advancedPricingLink}}" stepKey="clickAdvancedPricingLink1"/> + <seeInField selector="{{AdminProductFormAdvancedPricingSection.productTierPriceWebsiteSelect('0')}}" userInput="{{tierPriceOnVirtualProduct.website}}" stepKey="seeProductTierPriceWebsiteInput"/> + <seeInField selector="{{AdminProductFormAdvancedPricingSection.productTierPriceCustGroupSelect('0')}}" userInput="{{tierPriceOnVirtualProduct.customer_group}}" stepKey="seeProductTierPriceCustomerGroupInput"/> + <seeInField selector="{{AdminProductFormAdvancedPricingSection.productTierPriceQtyInput('0')}}" userInput="{{tierPriceOnVirtualProduct.qty}}" stepKey="seeProductTierPriceQuantityInput"/> + <seeInField selector="{{AdminProductFormAdvancedPricingSection.productTierPriceFixedPriceInput('0')}}" userInput="{{tierPriceOnVirtualProduct.price}}" stepKey="seeProductTierPriceFixedPrice"/> + <click selector="{{AdminProductFormAdvancedPricingSection.advancedPricingCloseButton}}" stepKey="clickAdvancedPricingCloseButton"/> + <seeInField selector="{{AdminProductFormSection.productTaxClass}}" userInput="{{updateVirtualTierPriceOutOfStock.productTaxClass}}" stepKey="seeProductTaxClass"/> + <seeInField selector="{{AdminProductFormSection.productQuantity}}" userInput="{{updateVirtualTierPriceOutOfStock.quantity}}" stepKey="seeProductQuantity"/> + <seeInField selector="{{AdminProductFormSection.productStockStatus}}" userInput="{{updateVirtualTierPriceOutOfStock.status}}" stepKey="seeProductStockStatus"/> + <click selector="{{AdminProductFormSection.categoriesDropdown}}" stepKey="clickCategoriesDropDownToVerify"/> + <grabMultiple selector="{{AdminProductFormSection.selectMultipleCategories}}" stepKey="selectedCategories" /> + <assertEquals stepKey="assertSelectedCategories"> + <actualResult type="variable">selectedCategories</actualResult> + <expectedResult type="array">[$$categoryEntity.name$$]</expectedResult> + </assertEquals> + <click selector="{{AdminProductFormSection.done}}" stepKey="clickOnDoneOnCategorySelect"/> + <seeInField selector="{{AdminProductFormSection.visibility}}" userInput="{{updateVirtualTierPriceOutOfStock.visibility}}" stepKey="seeVisibility"/> + <scrollTo selector="{{AdminProductSEOSection.sectionHeader}}" x="0" y="-80" stepKey="scrollToAdminProductSEOSection1"/> + <click selector="{{AdminProductSEOSection.sectionHeader}}" stepKey="clickAdminProductSEOSection1"/> + <seeInField selector="{{AdminProductSEOSection.urlKeyInput}}" userInput="{{updateVirtualTierPriceOutOfStock.urlKey}}" stepKey="seeUrlKey"/> + + <!--Verify customer don't see updated virtual product link on category page --> + <amOnPage url="{{StorefrontCategoryPage.url($$categoryEntity.name$$)}}" stepKey="openCategoryPage"/> + <waitForPageLoad stepKey="waitForCategoryPageLoad"/> + <dontSee selector="{{StorefrontCategoryMainSection.productLink}}" userInput="{{updateVirtualTierPriceOutOfStock.name}}" stepKey="dontSeeVirtualProductNameOnCategoryPage"/> + + <!--Verify customer see updated virtual product with tier price(from above step) on product storefront page --> + <amOnPage url="{{StorefrontProductPage.url(updateVirtualTierPriceOutOfStock.urlKey)}}" stepKey="goToProductPage"/> + <waitForPageLoad stepKey="waitForStorefrontProductPageLoad"/> + <see selector="{{StorefrontProductInfoMainSection.productName}}" userInput="{{updateVirtualTierPriceOutOfStock.name}}" stepKey="seeVirtualProductNameOnStoreFrontPage"/> + <see selector="{{StorefrontProductInfoMainSection.productPrice}}" userInput="{{updateVirtualTierPriceOutOfStock.price}}" stepKey="seeVirtualProductPriceOnStoreFrontPage"/> + <see selector="{{StorefrontProductInfoMainSection.productSku}}" userInput="{{updateVirtualTierPriceOutOfStock.sku}}" stepKey="seeVirtualProductSku"/> + <grabTextFrom selector="{{StorefrontProductInfoMainSection.productStockStatus}}" stepKey="productStockAvailableStatus"/> + <assertEquals stepKey="assertStockAvailableOnProductPage"> + <expectedResult type="string">{{updateVirtualTierPriceOutOfStock.storefrontStatus}}</expectedResult> + <actualResult type="variable">productStockAvailableStatus</actualResult> + </assertEquals> + <grabTextFrom selector="{{StorefrontProductInfoMainSection.productPrice}}" stepKey="productPriceAmount"/> + <assertEquals stepKey="assertOldPriceTextOnProductPage"> + <expectedResult type="string">${{updateVirtualTierPriceOutOfStock.price}}</expectedResult> + <actualResult type="variable">productPriceAmount</actualResult> + </assertEquals> + <!-- Verify customer see product tier price on product page --> + <grabTextFrom selector="{{StorefrontProductInfoMainSection.tierPriceText}}" stepKey="tierPriceText"/> + <assertEquals stepKey="assertTierPriceTextOnProductPage"> + <expectedResult type="string">Buy {{tierPriceOnVirtualProduct.qty}} for ${{tierPriceOnVirtualProduct.price}} each and save 51%</expectedResult> + <actualResult type="variable">tierPriceText</actualResult> + </assertEquals> + + <!--Verify customer don't see updated virtual product link on magento storefront page and is searchable by sku --> + <amOnPage url="{{StorefrontProductPage.url(updateVirtualTierPriceOutOfStock.urlKey)}}" stepKey="goToMagentoStorefrontPage"/> + <waitForPageLoad stepKey="waitForStoreFrontProductPageLoad"/> + <fillField selector="{{StorefrontQuickSearchResultsSection.searchTextBox}}" userInput="{{updateVirtualTierPriceOutOfStock.sku}}" stepKey="fillVirtualProductName"/> + <waitForPageLoad stepKey="waitForSearchTextBox"/> + <click selector="{{StorefrontQuickSearchResultsSection.searchTextBoxButton}}" stepKey="clickSearchTextBoxButton"/> + <waitForPageLoad stepKey="waitForSearch"/> + <dontSee selector="{{StorefrontQuickSearchResultsSection.productLink}}" userInput="{{updateVirtualTierPriceOutOfStock.name}}" stepKey="dontSeeVirtualProductName"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdvanceCatalogSearchVirtualProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdvanceCatalogSearchVirtualProductTest.xml index 0eb8f566875..84c3f81ef6d 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdvanceCatalogSearchVirtualProductTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdvanceCatalogSearchVirtualProductTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdvanceCatalogSearchVirtualProductByNameTest" extends="AdvanceCatalogSearchSimpleProductByNameTest"> <annotations> <features value="Catalog"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/CheckTierPricingOfProductsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/CheckTierPricingOfProductsTest.xml index 170da829143..cee40241185 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/CheckTierPricingOfProductsTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/CheckTierPricingOfProductsTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="CheckTierPricingOfProductsTest"> <annotations> <features value="Shopping Cart"/> @@ -108,6 +108,10 @@ </actionGroup> <actionGroup ref="ClearProductsFilterActionGroup" stepKey="ClearProductsFilterActionGroup"/> + <!--Flush cache--> + <magentoCLI command="cache:flush" stepKey="cleanCache"/> + + <!--Edit customer info--> <actionGroup ref="OpenEditCustomerFromAdminActionGroup" stepKey="OpenEditCustomerFrom"> <argument name="customer" value="$$customer$$"/> @@ -318,13 +322,17 @@ <deleteData createDataKey="category" stepKey="deleteCategory"/> <deleteData createDataKey="customer" stepKey="deleteCustomer"/> <createData entity="DefaultConfigCatalogPrice" stepKey="defaultConfigCatalogPrice"/> - <actionGroup ref="AdminDeleteWebsiteActionGroup" stepKey="DeleteWebsite"> - <argument name="websiteName" value="secondWebsite"/> - </actionGroup> <actionGroup ref="DeleteCartPriceRuleByName" stepKey="cleanUpRule"> <argument name="ruleName" value="ship"/> </actionGroup> + <actionGroup ref="AdminDeleteWebsiteActionGroup" stepKey="DeleteWebsite"> + <argument name="websiteName" value="secondWebsite"/> + </actionGroup> <actionGroup ref="logout" stepKey="logout"/> + + <!--Do reindex and flush cache--> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> </after> </test> </tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/ProductAvailableAfterEnablingSubCategoriesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/ProductAvailableAfterEnablingSubCategoriesTest.xml new file mode 100644 index 00000000000..3dd55a9dfee --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/ProductAvailableAfterEnablingSubCategoriesTest.xml @@ -0,0 +1,55 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="ProductAvailableAfterEnablingSubCategoriesTest"> + <annotations> + <features value="Catalog"/> + <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"/> + <testCaseId value="MAGETWO-97370"/> + <useCaseId value="MAGETWO-96846"/> + <group value="Catalog"/> + </annotations> + <before> + <createData entity="ApiCategory" stepKey="createCategory"/> + <createData entity="SubCategoryWithParent" stepKey="simpleSubCategory"> + <requiredEntity createDataKey="createCategory"/> + <field key="is_active">false</field> + </createData> + <createData entity="ApiSimpleProduct" stepKey="createSimpleProduct"> + <requiredEntity createDataKey="simpleSubCategory"/> + </createData> + + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <amOnPage url="$$createCategory.name$$.html" stepKey="goToCategoryStorefront2"/> + <waitForPageLoad stepKey="waitForCategoryStorefront"/> + <dontSeeElement selector="{{StorefrontCategoryProductSection.ProductImageByName($$createSimpleProduct.name$$)}}" stepKey="dontSeeCreatedProduct"/> + <amOnPage url="{{AdminCategoryPage.url}}" stepKey="onCategoryIndexPage"/> + <waitForPageLoad stepKey="waitForCategoryPageLoadAddProducts"/> + <click selector="{{AdminCategorySidebarTreeSection.expandAll}}" stepKey="expandAll"/> + <click selector="{{AdminCategorySidebarTreeSection.categoryInTree($$simpleSubCategory.name$$)}}" stepKey="clickOnCreatedSimpleSubCategoryBeforeDelete"/> + <waitForPageLoad stepKey="AdminCategoryEditPageLoad"/> + <click selector="{{AdminCategoryBasicFieldSection.enableCategoryLabel}}" stepKey="EnableCategory"/> + <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="saveCategoryWithProducts"/> + <waitForPageLoad stepKey="waitForCategorySaved"/> + <see userInput="You saved the category." stepKey="seeSuccessMessage"/> + <amOnPage url="$$createCategory.name$$.html" stepKey="goToCategoryStorefront"/> + <waitForPageLoad stepKey="waitForCategoryStorefrontPage"/> + <seeElement selector="{{StorefrontCategoryProductSection.ProductImageByName($$createSimpleProduct.name$$)}}" stepKey="seeCreatedProduct"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductCustomOptionsDifferentStoreViewsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductCustomOptionsDifferentStoreViewsTest.xml index 9c9f09f807e..df4803bcd79 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductCustomOptionsDifferentStoreViewsTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductCustomOptionsDifferentStoreViewsTest.xml @@ -52,6 +52,10 @@ <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> + <!-- Reset Product filter --> + + <actionGroup ref="ClearProductsFilterActionGroup" stepKey="clearProductsFilter"/> + <!-- Delete Store View EN --> <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteStoreView1"> @@ -186,6 +190,7 @@ <amOnPage url="{{AdminOrdersPage.url}}" stepKey="onOrdersPage"/> <waitForPageLoad stepKey="waitForPageLoadOrdersPage"/> + <conditionalClick selector="{{AdminDataGridHeaderSection.clearFilters}}" dependentSelector="{{AdminDataGridHeaderSection.clearFilters}}" visible="true" stepKey="clearFilters" /> <fillField selector="{{AdminOrdersGridSection.search}}" userInput="{$grabOrderNumber}" stepKey="fillOrderNum"/> <click selector="{{AdminOrdersGridSection.submitSearch}}" stepKey="submitSearchOrderNum"/> <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskToDisappearOnSearch"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptions.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsTest.xml similarity index 93% rename from app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptions.xml rename to app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsTest.xml index 86755cb602e..951afa2ddb6 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptions.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsTest.xml @@ -8,7 +8,7 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StorefrontPurchaseProductWithCustomOptions"> + <test name="StorefrontPurchaseProductWithCustomOptionsTest"> <annotations> <features value="Catalog"/> <stories value="Purchase a product with Custom Options of different types"/> @@ -17,8 +17,6 @@ <severity value="CRITICAL"/> <testCaseId value="MAGETWO-61717"/> <group value="Catalog"/> - <!-- skip due to MAGETWO-97424 --> - <group value="skip"/> </annotations> <before> <createData entity="Simple_US_Customer" stepKey="createCustomer"/> @@ -55,6 +53,10 @@ <seeElement selector="{{StorefrontProductInfoMainSection.productAttributeOptionsMultiselect(ProductOptionMultiSelect.title, ProductOptionValueMultiSelect1.price)}}" stepKey="checkMultiSelectProductOption"/> <seeElement selector="{{StorefrontProductInfoMainSection.productAttributeOptionsData(ProductOptionDate.title, ProductOptionDate.price)}}" stepKey="checkDataProductOption"/> + <!--Generate year--> + <generateDate date="Now" format="Y" stepKey="year"/> + <generateDate date="Now" format="y" stepKey="shortYear"/> + <!-- Adding items to the checkout --> <fillField userInput="OptionField" selector="{{StorefrontProductInfoMainSection.productOptionFieldInput(ProductOptionField.title)}}" stepKey="fillProductOptionInputField"/> @@ -66,10 +68,10 @@ <selectOption userInput="{{ProductOptionValueMultiSelect1.price}}" selector="{{StorefrontProductInfoMainSection.productOptionSelect(ProductOptionMultiSelect.title)}}" stepKey="selectProductOptionMultiSelect"/> <selectOption userInput="01" selector="{{StorefrontProductInfoMainSection.productOptionDataMonth(ProductOptionDate.title)}}" stepKey="selectProductOptionDate"/> <selectOption userInput="01" selector="{{StorefrontProductInfoMainSection.productOptionDataDay(ProductOptionDate.title)}}" stepKey="selectProductOptionDate1"/> - <selectOption userInput="2018" selector="{{StorefrontProductInfoMainSection.productOptionDataYear(ProductOptionDate.title)}}" stepKey="selectProductOptionDate2"/> + <selectOption userInput="$year" selector="{{StorefrontProductInfoMainSection.productOptionDataYear(ProductOptionDate.title)}}" stepKey="selectProductOptionDate2"/> <selectOption userInput="01" selector="{{StorefrontProductInfoMainSection.productOptionDateAndTimeMonth(ProductOptionDateTime.title)}}" stepKey="selectProductOptionDateAndTimeMonth"/> <selectOption userInput="01" selector="{{StorefrontProductInfoMainSection.productOptionDateAndTimeDay(ProductOptionDateTime.title)}}" stepKey="selectProductOptionDateAndTimeDay"/> - <selectOption userInput="2018" selector="{{StorefrontProductInfoMainSection.productOptionDateAndTimeYear(ProductOptionDateTime.title)}}" stepKey="selectProductOptionDateAndTimeYear"/> + <selectOption userInput="$year" selector="{{StorefrontProductInfoMainSection.productOptionDateAndTimeYear(ProductOptionDateTime.title)}}" stepKey="selectProductOptionDateAndTimeYear"/> <selectOption userInput="01" selector="{{StorefrontProductInfoMainSection.productOptionDateAndTimeHour(ProductOptionDateTime.title)}}" stepKey="selectProductOptionDateAndTimeHour"/> <selectOption userInput="00" selector="{{StorefrontProductInfoMainSection.productOptionDateAndTimeMinute(ProductOptionDateTime.title)}}" stepKey="selectProductOptionDateAndTimeMinute"/> <selectOption userInput="01" selector="{{StorefrontProductInfoMainSection.productOptionTimeHour(ProductOptionTime.title)}}" stepKey="selectProductOptionTimeHour"/> @@ -102,8 +104,8 @@ <see selector="{{CheckoutPaymentSection.ProductOptionsActiveByProductItemName($createProduct.name$)}}" userInput="{{ProductOptionValueRadioButtons1.title}}" stepKey="seeProductOptionValueRadioButtons1Input1"/> <see selector="{{CheckoutPaymentSection.ProductOptionsActiveByProductItemName($createProduct.name$)}}" userInput="{{ProductOptionValueCheckbox.title}}" stepKey="seeProductOptionValueCheckboxInput1" /> <see selector="{{CheckoutPaymentSection.ProductOptionsActiveByProductItemName($createProduct.name$)}}" userInput="{{ProductOptionValueMultiSelect1.title}}" stepKey="seeproductAttributeOptionsMultiselect1Input1" /> - <see selector="{{CheckoutPaymentSection.ProductOptionsActiveByProductItemName($createProduct.name$)}}" userInput="Jan 1, 2018" stepKey="seeProductOptionDateAndTimeInput" /> - <see selector="{{CheckoutPaymentSection.ProductOptionsActiveByProductItemName($createProduct.name$)}}" userInput="1/1/18, 1:00 AM" stepKey="seeProductOptionDataInput" /> + <see selector="{{CheckoutPaymentSection.ProductOptionsActiveByProductItemName($createProduct.name$)}}" userInput="Jan 1, $year" stepKey="seeProductOptionDateAndTimeInput" /> + <see selector="{{CheckoutPaymentSection.ProductOptionsActiveByProductItemName($createProduct.name$)}}" userInput="1/1/$shortYear, 1:00 AM" stepKey="seeProductOptionDataInput" /> <see selector="{{CheckoutPaymentSection.ProductOptionsActiveByProductItemName($createProduct.name$)}}" userInput="1:00 AM" stepKey="seeProductOptionTimeInput" /> <!--Select shipping method--> <actionGroup ref="CheckoutSelectFlatRateShippingMethodActionGroup" stepKey="selectFlatRateShippingMethod"/> @@ -140,8 +142,8 @@ <see selector="{{AdminOrderItemsOrderedSection.productNameOptions}}" userInput="{{ProductOptionValueRadioButtons1.title}}" stepKey="seeAdminOrderProductOptionValueRadioButton1"/> <see selector="{{AdminOrderItemsOrderedSection.productNameOptions}}" userInput="{{ProductOptionValueCheckbox.title}}" stepKey="seeAdminOrderProductOptionValueCheckbox" /> <see selector="{{AdminOrderItemsOrderedSection.productNameOptions}}" userInput="{{ProductOptionValueMultiSelect1.title}}" stepKey="seeAdminOrderproductAttributeOptionsMultiselect1" /> - <see selector="{{AdminOrderItemsOrderedSection.productNameOptions}}" userInput="Jan 1, 2018" stepKey="seeAdminOrderProductOptionDateAndTime" /> - <see selector="{{AdminOrderItemsOrderedSection.productNameOptions}}" userInput="1/1/18, 1:00 AM" stepKey="seeAdminOrderProductOptionData" /> + <see selector="{{AdminOrderItemsOrderedSection.productNameOptions}}" userInput="Jan 1, $year" stepKey="seeAdminOrderProductOptionDateAndTime" /> + <see selector="{{AdminOrderItemsOrderedSection.productNameOptions}}" userInput="1/1/$shortYear, 1:00 AM" stepKey="seeAdminOrderProductOptionData" /> <see selector="{{AdminOrderItemsOrderedSection.productNameOptions}}" userInput="1:00 AM" stepKey="seeAdminOrderProductOptionTime" /> <!-- Reorder and Checking the correctness of displayed custom options for user parameters on Order and correctness of displayed price Subtotal--> @@ -158,8 +160,8 @@ <see selector="{{AdminOrderItemsOrderedSection.productNameOptions}}" userInput="{{ProductOptionValueRadioButtons1.title}}" stepKey="seeAdminOrderProductOptionValueRadioButton11"/> <see selector="{{AdminOrderItemsOrderedSection.productNameOptions}}" userInput="{{ProductOptionValueCheckbox.title}}" stepKey="seeAdminOrderProductOptionValueCheckbox1" /> <see selector="{{AdminOrderItemsOrderedSection.productNameOptions}}" userInput="{{ProductOptionValueMultiSelect1.title}}" stepKey="seeAdminOrderproductAttributeOptionsMultiselect11" /> - <see selector="{{AdminOrderItemsOrderedSection.productNameOptions}}" userInput="Jan 1, 2018" stepKey="seeAdminOrderProductOptionDateAndTime1" /> - <see selector="{{AdminOrderItemsOrderedSection.productNameOptions}}" userInput="1/1/18, 1:00 AM" stepKey="seeAdminOrderProductOptionData1" /> + <see selector="{{AdminOrderItemsOrderedSection.productNameOptions}}" userInput="Jan 1, $year" stepKey="seeAdminOrderProductOptionDateAndTime1" /> + <see selector="{{AdminOrderItemsOrderedSection.productNameOptions}}" userInput="1/1/$shortYear, 1:00 AM" stepKey="seeAdminOrderProductOptionData1" /> <see selector="{{AdminOrderItemsOrderedSection.productNameOptions}}" userInput="1:00 AM" stepKey="seeAdminOrderProductOptionTime1" /> <see selector="{{AdminOrderTotalSection.subTotal}}" userInput="{$finalProductPrice}" stepKey="seeOrderSubTotal"/> @@ -176,8 +178,8 @@ <see selector="{{StorefrontCustomerOrderSection.productCustomOptions($createProduct.name$, ProductOptionRadiobutton.title, ProductOptionValueRadioButtons1.title)}}" userInput="{{ProductOptionValueRadioButtons1.title}}" stepKey="seeStorefontOrderProductOptionValueRadioButtons11"/> <see selector="{{StorefrontCustomerOrderSection.productCustomOptions($createProduct.name$, ProductOptionCheckbox.title, ProductOptionValueCheckbox.title)}}" userInput="{{ProductOptionValueCheckbox.title}}" stepKey="seeStorefontOrderProductOptionValueCheckbox1" /> <see selector="{{StorefrontCustomerOrderSection.productCustomOptions($createProduct.name$, ProductOptionMultiSelect.title, ProductOptionValueMultiSelect1.title)}}" userInput="{{ProductOptionValueMultiSelect1.title}}" stepKey="seeStorefontOrderproductAttributeOptionsMultiselect11" /> - <see selector="{{StorefrontCustomerOrderSection.productCustomOptions($createProduct.name$, ProductOptionDate.title, 'Jan 1, 2018')}}" userInput="Jan 1, 2018" stepKey="seeStorefontOrderProductOptionDateAndTime1" /> - <see selector="{{StorefrontCustomerOrderSection.productCustomOptions($createProduct.name$, ProductOptionDateTime.title, '1/1/18, 1:00 AM')}}" userInput="1/1/18, 1:00 AM" stepKey="seeStorefontOrderProductOptionData1" /> + <see selector="{{StorefrontCustomerOrderSection.productCustomOptions($createProduct.name$, ProductOptionDate.title, 'Jan 1, $year')}}" userInput="Jan 1, $year" stepKey="seeStorefontOrderProductOptionDateAndTime1" /> + <see selector="{{StorefrontCustomerOrderSection.productCustomOptions($createProduct.name$, ProductOptionDateTime.title, '1/1/$shortYear, 1:00 AM')}}" userInput="1/1/$shortYear, 1:00 AM" stepKey="seeStorefontOrderProductOptionData1" /> <see selector="{{StorefrontCustomerOrderSection.productCustomOptions($createProduct.name$, ProductOptionTime.title, '1:00 AM')}}" userInput="1:00 AM" stepKey="seeStorefontOrderProductOptionTime1" /> <!-- Delete product and category --> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontSpecialPriceForDifferentTimezonesForWebsitesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontSpecialPriceForDifferentTimezonesForWebsitesTest.xml new file mode 100644 index 00000000000..268e18d2b4e --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontSpecialPriceForDifferentTimezonesForWebsitesTest.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="StorefrontSpecialPriceForDifferentTimezonesForWebsitesTest"> + <annotations> + <features value="Catalog"/> + <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"/> + <testCaseId value="MAGETWO-97508"/> + <useCaseId value="MAGETWO-96847"/> + <group value="Catalog"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="login"/> + + <!--Create product--> + <createData entity="SimpleProduct2" stepKey="createProduct"/> + + <!--Create customer--> + <createData entity="Simple_US_Customer" stepKey="createCustomer"/> + </before> + <after> + <!--Delete create data--> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> + <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> + + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Set timezone for default config--> + <amOnPage url="{{GeneralConfigurationPage.url}}" stepKey="goToGeneralConfig"/> + <waitForPageLoad stepKey="waitForConfigPage"/> + <conditionalClick selector="{{LocaleOptionsSection.sectionHeader}}" dependentSelector="{{LocaleOptionsSection.timezone}}" visible="false" stepKey="openLocaleSection"/> + <grabValueFrom selector="{{LocaleOptionsSection.timezone}}" stepKey="originalTimezone"/> + <selectOption selector="{{LocaleOptionsSection.timezone}}" userInput="Central European Standard Time (Europe/Paris)" stepKey="setTimezone"/> + <click selector="{{AdminMainActionsSection.save}}" stepKey="saveConfig"/> + + <!--Set timezone for Main Website--> + <amOnPage url="{{GeneralConfigurationPage.url}}" stepKey="goToGeneralConfig1"/> + <waitForPageLoad stepKey="waitForConfigPage1"/> + <actionGroup ref="AdminSwitchWebsiteActionGroup" stepKey="AdminSwitchStoreViewActionGroup"> + <argument name="website" value="_defaultWebsite"/> + </actionGroup> + <conditionalClick selector="{{LocaleOptionsSection.sectionHeader}}" dependentSelector="{{LocaleOptionsSection.timezone}}" visible="false" stepKey="openLocaleSection1"/> + <uncheckOption selector="{{LocaleOptionsSection.useDefault}}" stepKey="uncheckUseDefault"/> + <grabValueFrom selector="{{LocaleOptionsSection.timezone}}" stepKey="originalTimezone1"/> + <selectOption selector="{{LocaleOptionsSection.timezone}}" userInput="Greenwich Mean Time (Africa/Abidjan)" stepKey="setTimezone1"/> + <click selector="{{AdminMainActionsSection.save}}" stepKey="saveConfig1"/> + + <!--Set special price to created product--> + <amOnPage url="{{AdminProductEditPage.url($$createProduct.id$$)}}" stepKey="openAdminEditPage"/> + <actionGroup ref="AddSpecialPriceToProductActionGroup" stepKey="setSpecialPriceToCreatedProduct"> + <argument name="price" value="15"/> + </actionGroup> + <actionGroup ref="saveProductForm" stepKey="saveProductForm"/> + + <!--Login to storefront from customer and check price--> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="logInFromCustomer"> + <argument name="Customer" value="$$createCustomer$$"/> + </actionGroup> + + <!--Go to the product page and check special price--> + <amOnPage url="{{StorefrontProductPage.url($$createProduct.name$$)}}" stepKey="amOnSimpleProductPage"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <grabTextFrom selector="{{StorefrontProductInfoMainSection.specialPriceValue}}" stepKey="grabSpecialPrice"/> + <assertEquals expected='$15.00' expectedType="string" actual="$grabSpecialPrice" stepKey="assertSpecialPrice"/> + + <!--Reset timezone--> + <amOnPage url="{{GeneralConfigurationPage.url}}" stepKey="goToGeneralConfigReset"/> + <waitForPageLoad stepKey="waitForConfigPageReset"/> + <conditionalClick selector="{{LocaleOptionsSection.sectionHeader}}" dependentSelector="{{LocaleOptionsSection.timezone}}" visible="false" stepKey="openLocaleSectionReset"/> + <selectOption selector="{{LocaleOptionsSection.timezone}}" userInput="$originalTimezone" stepKey="resetTimezone"/> + <click selector="{{AdminMainActionsSection.save}}" stepKey="saveConfigReset"/> + + <!--Reset timezone--> + <amOnPage url="{{GeneralConfigurationPage.url}}" stepKey="goToGeneralConfigReset1"/> + <waitForPageLoad stepKey="waitForConfigPageReset1"/> + <actionGroup ref="AdminSwitchWebsiteActionGroup" stepKey="AdminSwitchStoreViewActionGroup1"> + <argument name="website" value="_defaultWebsite"/> + </actionGroup> + <conditionalClick selector="{{LocaleOptionsSection.sectionHeader}}" dependentSelector="{{LocaleOptionsSection.timezone}}" visible="false" stepKey="openLocaleSectionReset1"/> + <uncheckOption selector="{{LocaleOptionsSection.useDefault}}" stepKey="uncheckUseDefault1"/> + <selectOption selector="{{LocaleOptionsSection.timezone}}" userInput="$originalTimezone" stepKey="resetTimezone1"/> + <click selector="{{AdminMainActionsSection.save}}" stepKey="saveConfigReset1"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/TieredPricingAndQuantityIncrementsWorkWithDecimalinventoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/TieredPricingAndQuantityIncrementsWorkWithDecimalinventoryTest.xml index f283a040ced..4d7c97b2645 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/TieredPricingAndQuantityIncrementsWorkWithDecimalinventoryTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/TieredPricingAndQuantityIncrementsWorkWithDecimalinventoryTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="TieredPricingAndQuantityIncrementsWorkWithDecimalinventoryTest"> <annotations> <features value="Catalog"/> @@ -84,4 +84,4 @@ <click selector="{{CheckoutCartProductSection.updateShoppingCartButton}}" stepKey="clickOnUpdateShoppingCartButton"/> <seeInField userInput="5.5" selector="{{CheckoutCartProductSection.ProductQuantityByName(('$$createPreReqSimpleProduct.name$$'))}}" stepKey="seeInField2"/> </test> -</tests> \ No newline at end of file +</tests> diff --git a/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Rss/NotifyStockTest.php b/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Rss/NotifyStockTest.php index 1dd866f1fe2..da35d845468 100644 --- a/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Rss/NotifyStockTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Rss/NotifyStockTest.php @@ -96,7 +96,12 @@ public function testGetRssData() $this->urlBuilder->expects($this->once())->method('getUrl') ->with('catalog/product/edit', ['id' => 1, '_secure' => true, '_nosecret' => true]) ->will($this->returnValue('http://magento.com/catalog/product/edit/id/1')); - $this->assertEquals($this->rssFeed, $this->block->getRssData()); + + $data = $this->block->getRssData(); + $this->assertTrue(is_string($data['title'])); + $this->assertTrue(is_string($data['description'])); + $this->assertTrue(is_string($data['entries'][0]['description'])); + $this->assertEquals($this->rssFeed, $data); } public function testGetCacheLifetime() diff --git a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Category/RefreshPathTest.php b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Category/RefreshPathTest.php index 45de62e218c..adf00333721 100644 --- a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Category/RefreshPathTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Category/RefreshPathTest.php @@ -66,8 +66,8 @@ private function setObjectProperty($object, string $propertyName, $value) : void */ public function testExecute() : void { - $value = ['id' => 3, 'path' => '1/2/3', 'parentId' => 2]; - $result = '{"id":3,"path":"1/2/3","parentId":"2"}'; + $value = ['id' => 3, 'path' => '1/2/3', 'parentId' => 2, 'level' => 2]; + $result = '{"id":3,"path":"1/2/3","parentId":"2","level":"2"}'; $requestMock = $this->getMockForAbstractClass(\Magento\Framework\App\RequestInterface::class); diff --git a/app/code/Magento/Catalog/Test/Unit/Model/CategoryListTest.php b/app/code/Magento/Catalog/Test/Unit/Model/CategoryListTest.php index b8b76524099..f78c0ad9249 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/CategoryListTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/CategoryListTest.php @@ -93,7 +93,7 @@ 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('getItems')->willReturn([$categoryFirst, $categorySecond]); $this->collectionProcessorMock->expects($this->once()) ->method('process') diff --git a/app/code/Magento/Catalog/Test/Unit/Model/CategoryTest.php b/app/code/Magento/Catalog/Test/Unit/Model/CategoryTest.php index 64eedbce2d9..b4042d6b02c 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/CategoryTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/CategoryTest.php @@ -383,13 +383,16 @@ public function testReindexFlatEnabled( public function reindexFlatDisabledTestDataProvider() { return [ - [false, null, null, null, 0], - [true, null, null, null, 0], - [false, [], null, null, 0], - [false, ["1", "2"], null, null, 1], - [false, null, 1, null, 1], - [false, ["1", "2"], 0, 1, 1], - [false, null, 1, 1, 0], + [false, null, null, null, null, null, 0], + [true, null, null, null, null, null, 0], + [false, [], null, null, null, null, 0], + [false, ["1", "2"], null, null, null, null, 1], + [false, null, 1, null, null, null, 1], + [false, ["1", "2"], 0, 1, null, null, 1], + [false, null, 1, 1, null, null, 0], + [false, ["1", "2"], null, null, 0, 1, 1], + [false, ["1", "2"], null, null, 1, 0, 1], + ]; } @@ -407,11 +410,16 @@ public function testReindexFlatDisabled( $affectedIds, $isAnchorOrig, $isAnchor, + $isActiveOrig, + $isActive, $expectedProductReindexCall ) { $this->category->setAffectedProductIds($affectedIds); $this->category->setData('is_anchor', $isAnchor); $this->category->setOrigData('is_anchor', $isAnchorOrig); + $this->category->setData('is_active', $isActive); + $this->category->setOrigData('is_active', $isActiveOrig); + $this->category->setAffectedProductIds($affectedIds); $pathIds = ['path/1/2', 'path/2/3']; @@ -422,7 +430,7 @@ public function testReindexFlatDisabled( ->method('isFlatEnabled') ->will($this->returnValue(false)); - $this->productIndexer->expects($this->exactly(1)) + $this->productIndexer ->method('isScheduled') ->willReturn($productScheduled); $this->productIndexer->expects($this->exactly($expectedProductReindexCall)) diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/DefaultValidatorTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/DefaultValidatorTest.php index da6b790fedf..7c2ec8abb76 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/DefaultValidatorTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/DefaultValidatorTest.php @@ -18,6 +18,11 @@ class DefaultValidatorTest extends \PHPUnit\Framework\TestCase */ protected $valueMock; + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $localeFormatMock; + /** * @inheritdoc */ @@ -26,6 +31,8 @@ protected function setUp() $configMock = $this->createMock(\Magento\Catalog\Model\ProductOptions\ConfigInterface::class); $storeManagerMock = $this->createMock(\Magento\Store\Model\StoreManagerInterface::class); $priceConfigMock = new \Magento\Catalog\Model\Config\Source\Product\Options\Price($storeManagerMock); + $this->localeFormatMock = $this->createMock(\Magento\Framework\Locale\FormatInterface::class); + $config = [ [ 'label' => 'group label 1', @@ -51,7 +58,8 @@ protected function setUp() $configMock->expects($this->once())->method('getAll')->will($this->returnValue($config)); $this->validator = new \Magento\Catalog\Model\Product\Option\Validator\DefaultValidator( $configMock, - $priceConfigMock + $priceConfigMock, + $this->localeFormatMock ); } @@ -63,10 +71,10 @@ public function isValidTitleDataProvider() { $mess = ['option required fields' => 'Missed values for option required fields']; return [ - ['option_title', 'name 1.1', 'fixed', new \Magento\Framework\DataObject(['store_id' => 1]), [], true], - ['option_title', 'name 1.1', 'fixed', new \Magento\Framework\DataObject(['store_id' => 0]), [], true], - [null, 'name 1.1', 'fixed', new \Magento\Framework\DataObject(['store_id' => 1]), [], true], - [null, 'name 1.1', 'fixed', new \Magento\Framework\DataObject(['store_id' => 0]), $mess, false], + ['option_title', 'name 1.1', 'fixed', 10, new \Magento\Framework\DataObject(['store_id' => 1]), [], true], + ['option_title', 'name 1.1', 'fixed', 10, new \Magento\Framework\DataObject(['store_id' => 0]), [], true], + [null, 'name 1.1', 'fixed', 10, new \Magento\Framework\DataObject(['store_id' => 1]), [], true], + [null, 'name 1.1', 'fixed', 10, new \Magento\Framework\DataObject(['store_id' => 0]), $mess, false], ]; } @@ -79,15 +87,18 @@ public function isValidTitleDataProvider() * @param bool $result * @dataProvider isValidTitleDataProvider */ - public function testIsValidTitle($title, $type, $priceType, $product, $messages, $result) + public function testIsValidTitle($title, $type, $priceType, $price, $product, $messages, $result) { - $methods = ['getTitle', 'getType', 'getPriceType', '__wakeup', 'getProduct']; + $methods = ['getTitle', 'getType', 'getPriceType', 'getPrice', '__wakeup', 'getProduct']; $valueMock = $this->createPartialMock(\Magento\Catalog\Model\Product\Option::class, $methods); $valueMock->expects($this->once())->method('getTitle')->will($this->returnValue($title)); $valueMock->expects($this->any())->method('getType')->will($this->returnValue($type)); $valueMock->expects($this->once())->method('getPriceType')->will($this->returnValue($priceType)); - // $valueMock->expects($this->once())->method('getPrice')->will($this->returnValue($price)); + $valueMock->expects($this->once())->method('getPrice')->will($this->returnValue($price)); $valueMock->expects($this->once())->method('getProduct')->will($this->returnValue($product)); + + $this->localeFormatMock->expects($this->once())->method('getNumber')->will($this->returnValue($price)); + $this->assertEquals($result, $this->validator->isValid($valueMock)); $this->assertEquals($messages, $this->validator->getMessages()); } @@ -126,4 +137,43 @@ public function testIsValidFail($product) $this->assertFalse($this->validator->isValid($valueMock)); $this->assertEquals($messages, $this->validator->getMessages()); } + + /** + * Data provider for testValidationNegativePrice + * @return array + */ + public function validationPriceDataProvider() + { + return [ + ['option_title', 'name 1.1', 'fixed', -12, new \Magento\Framework\DataObject(['store_id' => 1])], + ['option_title', 'name 1.1', 'fixed', -12, new \Magento\Framework\DataObject(['store_id' => 0])], + ['option_title', 'name 1.1', 'fixed', 12, new \Magento\Framework\DataObject(['store_id' => 1])], + ['option_title', 'name 1.1', 'fixed', 12, new \Magento\Framework\DataObject(['store_id' => 0])] + ]; + } + + /** + * @param $title + * @param $type + * @param $priceType + * @param $price + * @param $product + * @dataProvider validationPriceDataProvider + */ + public function testValidationPrice($title, $type, $priceType, $price, $product) + { + $methods = ['getTitle', 'getType', 'getPriceType', 'getPrice', '__wakeup', 'getProduct']; + $valueMock = $this->createPartialMock(\Magento\Catalog\Model\Product\Option::class, $methods); + $valueMock->expects($this->once())->method('getTitle')->will($this->returnValue($title)); + $valueMock->expects($this->exactly(2))->method('getType')->will($this->returnValue($type)); + $valueMock->expects($this->once())->method('getPriceType')->will($this->returnValue($priceType)); + $valueMock->expects($this->once())->method('getPrice')->will($this->returnValue($price)); + $valueMock->expects($this->once())->method('getProduct')->will($this->returnValue($product)); + + $this->localeFormatMock->expects($this->once())->method('getNumber')->will($this->returnValue($price)); + + $messages = []; + $this->assertTrue($this->validator->isValid($valueMock)); + $this->assertEquals($messages, $this->validator->getMessages()); + } } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/FileTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/FileTest.php index 2de993c0755..e688da1c6aa 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/FileTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/FileTest.php @@ -18,6 +18,11 @@ class FileTest extends \PHPUnit\Framework\TestCase */ protected $valueMock; + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $localeFormatMock; + /** * @inheritdoc */ @@ -26,6 +31,8 @@ protected function setUp() $configMock = $this->createMock(\Magento\Catalog\Model\ProductOptions\ConfigInterface::class); $storeManagerMock = $this->createMock(\Magento\Store\Model\StoreManagerInterface::class); $priceConfigMock = new \Magento\Catalog\Model\Config\Source\Product\Options\Price($storeManagerMock); + $this->localeFormatMock = $this->createMock(\Magento\Framework\Locale\FormatInterface::class); + $config = [ [ 'label' => 'group label 1', @@ -53,7 +60,8 @@ protected function setUp() $this->valueMock = $this->createPartialMock(\Magento\Catalog\Model\Product\Option::class, $methods); $this->validator = new \Magento\Catalog\Model\Product\Option\Validator\File( $configMock, - $priceConfigMock + $priceConfigMock, + $this->localeFormatMock ); } @@ -70,6 +78,15 @@ public function testIsValidSuccess() ->willReturn(10); $this->valueMock->expects($this->once())->method('getImageSizeX')->will($this->returnValue(10)); $this->valueMock->expects($this->once())->method('getImageSizeY')->will($this->returnValue(15)); + $this->localeFormatMock->expects($this->at(0)) + ->method('getNumber') + ->with($this->equalTo(10)) + ->will($this->returnValue(10)); + $this->localeFormatMock + ->expects($this->at(2)) + ->method('getNumber') + ->with($this->equalTo(15)) + ->will($this->returnValue(15)); $this->assertEmpty($this->validator->getMessages()); $this->assertTrue($this->validator->isValid($this->valueMock)); } @@ -87,6 +104,16 @@ public function testIsValidWithNegativeImageSize() ->willReturn(10); $this->valueMock->expects($this->once())->method('getImageSizeX')->will($this->returnValue(-10)); $this->valueMock->expects($this->never())->method('getImageSizeY'); + $this->localeFormatMock->expects($this->at(0)) + ->method('getNumber') + ->with($this->equalTo(10)) + ->will($this->returnValue(10)); + $this->localeFormatMock + ->expects($this->at(1)) + ->method('getNumber') + ->with($this->equalTo(-10)) + ->will($this->returnValue(-10)); + $messages = [ 'option values' => 'Invalid option value', ]; @@ -107,6 +134,15 @@ public function testIsValidWithNegativeImageSizeY() ->willReturn(10); $this->valueMock->expects($this->once())->method('getImageSizeX')->will($this->returnValue(10)); $this->valueMock->expects($this->once())->method('getImageSizeY')->will($this->returnValue(-10)); + $this->localeFormatMock->expects($this->at(0)) + ->method('getNumber') + ->with($this->equalTo(10)) + ->will($this->returnValue(10)); + $this->localeFormatMock + ->expects($this->at(2)) + ->method('getNumber') + ->with($this->equalTo(-10)) + ->will($this->returnValue(-10)); $messages = [ 'option values' => 'Invalid option value', ]; diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/SelectTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/SelectTest.php index b97783edf85..7fad5592a2d 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/SelectTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/SelectTest.php @@ -18,6 +18,11 @@ class SelectTest extends \PHPUnit\Framework\TestCase */ protected $valueMock; + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $localeFormatMock; + /** * @inheritdoc */ @@ -26,6 +31,7 @@ protected function setUp() $configMock = $this->createMock(\Magento\Catalog\Model\ProductOptions\ConfigInterface::class); $storeManagerMock = $this->createMock(\Magento\Store\Model\StoreManagerInterface::class); $priceConfigMock = new \Magento\Catalog\Model\Config\Source\Product\Options\Price($storeManagerMock); + $this->localeFormatMock = $this->createMock(\Magento\Framework\Locale\FormatInterface::class); $config = [ [ 'label' => 'group label 1', @@ -53,7 +59,8 @@ protected function setUp() $this->valueMock = $this->createPartialMock(\Magento\Catalog\Model\Product\Option::class, $methods, []); $this->validator = new \Magento\Catalog\Model\Product\Option\Validator\Select( $configMock, - $priceConfigMock + $priceConfigMock, + $this->localeFormatMock ); } @@ -69,6 +76,12 @@ public function testIsValidSuccess($expectedResult, array $value) $this->valueMock->expects($this->never())->method('getPriceType'); $this->valueMock->expects($this->never())->method('getPrice'); $this->valueMock->expects($this->any())->method('getData')->with('values')->will($this->returnValue([$value])); + if (isset($value['price'])) { + $this->localeFormatMock + ->expects($this->once()) + ->method('getNumber') + ->will($this->returnValue($value['price'])); + } $this->assertEquals($expectedResult, $this->validator->isValid($this->valueMock)); } @@ -117,6 +130,7 @@ public function testIsValidateWithInvalidOptionValues() ->method('getData') ->with('values') ->will($this->returnValue('invalid_data')); + $messages = [ 'option values' => 'Invalid option value', ]; @@ -159,6 +173,7 @@ public function testIsValidateWithInvalidData($priceType, $price, $title) $this->valueMock->expects($this->never())->method('getPriceType'); $this->valueMock->expects($this->never())->method('getPrice'); $this->valueMock->expects($this->any())->method('getData')->with('values')->will($this->returnValue([$value])); + $this->localeFormatMock->expects($this->any())->method('getNumber')->will($this->returnValue($price)); $messages = [ 'option values' => 'Invalid option value', ]; diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/TextTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/TextTest.php index 4881154728d..a3e6189f749 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/TextTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/TextTest.php @@ -18,6 +18,11 @@ class TextTest extends \PHPUnit\Framework\TestCase */ protected $valueMock; + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $localeFormatMock; + /** * @inheritdoc */ @@ -26,6 +31,7 @@ protected function setUp() $configMock = $this->createMock(\Magento\Catalog\Model\ProductOptions\ConfigInterface::class); $storeManagerMock = $this->createMock(\Magento\Store\Model\StoreManagerInterface::class); $priceConfigMock = new \Magento\Catalog\Model\Config\Source\Product\Options\Price($storeManagerMock); + $this->localeFormatMock = $this->createMock(\Magento\Framework\Locale\FormatInterface::class); $config = [ [ 'label' => 'group label 1', @@ -53,7 +59,8 @@ protected function setUp() $this->valueMock = $this->createPartialMock(\Magento\Catalog\Model\Product\Option::class, $methods); $this->validator = new \Magento\Catalog\Model\Product\Option\Validator\Text( $configMock, - $priceConfigMock + $priceConfigMock, + $this->localeFormatMock ); } @@ -69,6 +76,10 @@ public function testIsValidSuccess() $this->valueMock->method('getPrice') ->willReturn(10); $this->valueMock->expects($this->once())->method('getMaxCharacters')->will($this->returnValue(10)); + $this->localeFormatMock->expects($this->exactly(2)) + ->method('getNumber') + ->with($this->equalTo(10)) + ->will($this->returnValue(10)); $this->assertTrue($this->validator->isValid($this->valueMock)); $this->assertEmpty($this->validator->getMessages()); } @@ -85,6 +96,15 @@ public function testIsValidWithNegativeMaxCharacters() $this->valueMock->method('getPrice') ->willReturn(10); $this->valueMock->expects($this->once())->method('getMaxCharacters')->will($this->returnValue(-10)); + $this->localeFormatMock->expects($this->at(0)) + ->method('getNumber') + ->with($this->equalTo(10)) + ->will($this->returnValue(10)); + $this->localeFormatMock + ->expects($this->at(1)) + ->method('getNumber') + ->with($this->equalTo(-10)) + ->will($this->returnValue(-10)); $messages = [ 'option values' => 'Invalid option value', ]; diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php index 9867fdf9102..22ba6bfa9f7 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php @@ -8,11 +8,11 @@ use Magento\Catalog\Api\Data\ProductExtensionInterface; use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\Product\Attribute\Source\Status; use Magento\Framework\Api\Data\ImageContentInterface; use Magento\Framework\Api\ExtensibleDataInterface; use Magento\Framework\Api\ExtensionAttributesFactory; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; -use Magento\Catalog\Model\Product\Attribute\Source\Status; /** * Product Test @@ -180,7 +180,7 @@ class ProductTest extends \PHPUnit\Framework\TestCase /** * @var \PHPUnit_Framework_MockObject_MockObject */ - private $extensionAttrbutes; + private $extensionAttributes; /** * @var \PHPUnit_Framework_MockObject_MockObject @@ -200,7 +200,7 @@ class ProductTest extends \PHPUnit\Framework\TestCase /** * @var ProductExtensionInterface|\PHPUnit_Framework_MockObject_MockObject */ - private $extensionAttributes; + private $productExtAttributes; /** * @var \Magento\Eav\Model\Config|\PHPUnit_Framework_MockObject_MockObject @@ -218,7 +218,7 @@ protected function setUp() \Magento\Framework\Module\Manager::class, ['isEnabled'] ); - $this->extensionAttrbutes = $this->getMockBuilder(\Magento\Framework\Api\ExtensionAttributesInterface::class) + $this->extensionAttributes = $this->getMockBuilder(\Magento\Framework\Api\ExtensionAttributesInterface::class) ->setMethods(['getWebsiteIds', 'setWebsiteIds']) ->disableOriginalConstructor() ->getMock(); @@ -372,13 +372,13 @@ protected function setUp() $this->mediaConfig = $this->createMock(\Magento\Catalog\Model\Product\Media\Config::class); $this->eavConfig = $this->createMock(\Magento\Eav\Model\Config::class); - $this->extensionAttributes = $this->getMockBuilder(ProductExtensionInterface::class) + $this->productExtAttributes = $this->getMockBuilder(ProductExtensionInterface::class) ->setMethods(['getStockItem']) ->getMockForAbstractClass(); $this->extensionAttributesFactory ->expects($this->any()) ->method('create') - ->willReturn($this->extensionAttributes); + ->willReturn($this->productExtAttributes); $this->filterCustomAttribute = $this->createTestProxy( \Magento\Catalog\Model\FilterProductCustomAttribute::class @@ -567,14 +567,6 @@ public function testGetCategoryId() $this->assertEquals(10, $this->model->getCategoryId()); } - public function testGetCategoryIdWhenProductNotInCurrentCategory() - { - $this->model->setData('category_ids', [12]); - $this->category->expects($this->once())->method('getId')->will($this->returnValue(10)); - $this->registry->expects($this->any())->method('registry')->will($this->returnValue($this->category)); - $this->assertFalse($this->model->getCategoryId()); - } - public function testGetIdBySku() { $this->resource->expects($this->once())->method('getIdBySku')->will($this->returnValue(5)); diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CollectionTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CollectionTest.php index bb39aa7f9db..3eb219ee293 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CollectionTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CollectionTest.php @@ -323,7 +323,7 @@ public function testAddTierPriceDataByGroupId() [ '(customer_group_id=? AND all_groups=0) OR all_groups=1', $customerGroupId] ) ->willReturnSelf(); - $select->expects($this->once())->method('order')->with('entity_id')->willReturnSelf(); + $select->expects($this->once())->method('order')->with('qty')->willReturnSelf(); $this->connectionMock->expects($this->once()) ->method('fetchAll') ->with($select) @@ -375,7 +375,7 @@ public function testAddTierPriceData() $select->expects($this->exactly(1))->method('where') ->with('entity_id IN(?)', [1]) ->willReturnSelf(); - $select->expects($this->once())->method('order')->with('entity_id')->willReturnSelf(); + $select->expects($this->once())->method('order')->with('qty')->willReturnSelf(); $this->connectionMock->expects($this->once()) ->method('fetchAll') ->with($select) diff --git a/app/code/Magento/Catalog/Test/Unit/Model/_files/converted_view.php b/app/code/Magento/Catalog/Test/Unit/Model/_files/converted_view.php index 37b0e15cac6..e225ec0daef 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/_files/converted_view.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/_files/converted_view.php @@ -11,7 +11,41 @@ "type" => "swatch_thumb", "width" => 75, "height" => 75, - "background" => [255, 25, 2] + "constrain" => false, + "aspect_ratio" => false, + "frame" => false, + "transparency" => false, + "background" => [255, 25, 2], + ], + "swatch_thumb_medium" => [ + "type" => "swatch_medium", + "width" => 750, + "height" => 750, + "constrain" => true, + "aspect_ratio" => true, + "frame" => true, + "transparency" => true, + "background" => [255, 25, 2], + ], + "swatch_thumb_large" => [ + "type" => "swatch_large", + "width" => 1080, + "height" => 720, + "constrain" => false, + "aspect_ratio" => false, + "frame" => false, + "transparency" => false, + "background" => [255, 25, 2], + ], + "swatch_thumb_small" => [ + "type" => "swatch_small", + "width" => 100, + "height" => 100, + "constrain" => true, + "aspect_ratio" => true, + "frame" => true, + "transparency" => true, + "background" => [255, 25, 2], ] ] ] diff --git a/app/code/Magento/Catalog/Test/Unit/Model/_files/valid_view.xml b/app/code/Magento/Catalog/Test/Unit/Model/_files/valid_view.xml index 253abc5e2e4..ee4ddaad534 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/_files/valid_view.xml +++ b/app/code/Magento/Catalog/Test/Unit/Model/_files/valid_view.xml @@ -11,6 +11,37 @@ <image id="swatch_thumb_base" type="swatch_thumb"> <width>75</width> <height>75</height> + <constrain>false</constrain> + <aspect_ratio>false</aspect_ratio> + <frame>false</frame> + <transparency>false</transparency> + <background>[255, 25, 2]</background> + </image> + <image id="swatch_thumb_medium" type="swatch_medium"> + <width>750</width> + <height>750</height> + <constrain>true</constrain> + <aspect_ratio>true</aspect_ratio> + <frame>true</frame> + <transparency>true</transparency> + <background>[255, 25, 2]</background> + </image> + <image id="swatch_thumb_large" type="swatch_large"> + <width>1080</width> + <height>720</height> + <constrain>0</constrain> + <aspect_ratio>0</aspect_ratio> + <frame>0</frame> + <transparency>0</transparency> + <background>[255, 25, 2]</background> + </image> + <image id="swatch_thumb_small" type="swatch_small"> + <width>100</width> + <height>100</height> + <constrain>1</constrain> + <aspect_ratio>1</aspect_ratio> + <frame>1</frame> + <transparency>1</transparency> <background>[255, 25, 2]</background> </image> </images> diff --git a/app/code/Magento/Catalog/Test/Unit/ViewModel/Product/BreadcrumbsTest.php b/app/code/Magento/Catalog/Test/Unit/ViewModel/Product/BreadcrumbsTest.php index dbf1292e573..a4ccaffc8fb 100644 --- a/app/code/Magento/Catalog/Test/Unit/ViewModel/Product/BreadcrumbsTest.php +++ b/app/code/Magento/Catalog/Test/Unit/ViewModel/Product/BreadcrumbsTest.php @@ -152,21 +152,21 @@ public function productJsonEncodeDataProvider() : array return [ [ $this->getObjectManager()->getObject(Product::class, ['data' => ['name' => 'Test ™']]), - '{"breadcrumbs":{"categoryUrlSuffix":"."html","userCategoryPathInUrl":0,"product":"Test \u2122"}}', + '{"breadcrumbs":{"categoryUrlSuffix":"."html","useCategoryPathInUrl":0,"product":"Test \u2122"}}', ], [ $this->getObjectManager()->getObject(Product::class, ['data' => ['name' => 'Test "']]), - '{"breadcrumbs":{"categoryUrlSuffix":"."html","userCategoryPathInUrl":0,"product":"Test ""}}', + '{"breadcrumbs":{"categoryUrlSuffix":"."html","useCategoryPathInUrl":0,"product":"Test ""}}', ], [ $this->getObjectManager()->getObject(Product::class, ['data' => ['name' => 'Test <b>x</b>']]), - '{"breadcrumbs":{"categoryUrlSuffix":"."html","userCategoryPathInUrl":0,"product":' + '{"breadcrumbs":{"categoryUrlSuffix":"."html","useCategoryPathInUrl":0,"product":' . '"Test <b>x<\/b>"}}', ], [ $this->getObjectManager()->getObject(Product::class, ['data' => ['name' => 'Test \'abc\'']]), '{"breadcrumbs":' - . '{"categoryUrlSuffix":"."html","userCategoryPathInUrl":0,"product":"Test 'abc'"}}' + . '{"categoryUrlSuffix":"."html","useCategoryPathInUrl":0,"product":"Test 'abc'"}}' ], ]; } diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AdvancedPricing.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AdvancedPricing.php index 336aeffa105..2a4d2ff52d4 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AdvancedPricing.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AdvancedPricing.php @@ -139,7 +139,8 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc + * * @since 101.0.0 */ public function modifyMeta(array $meta) @@ -158,7 +159,8 @@ public function modifyMeta(array $meta) } /** - * {@inheritdoc} + * @inheritdoc + * * @since 101.0.0 */ public function modifyData(array $data) @@ -381,6 +383,7 @@ private function addAdvancedPriceLink() ); $advancedPricingButton['arguments']['data']['config'] = [ + 'dataScope' => 'advanced_pricing_button', 'displayAsLink' => true, 'formElement' => Container::NAME, 'componentType' => Container::NAME, 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 7379600011b..99f7122efa0 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 @@ -560,7 +560,7 @@ private function getAttributes() * Loads attributes for specified groups at once * * @param AttributeGroupInterface[] $groups - * @return @return ProductAttributeInterface[] + * @return ProductAttributeInterface[] */ private function loadAttributesForGroups(array $groups) { diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav/CompositeConfigProcessor.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav/CompositeConfigProcessor.php index 5513af9d98e..fed94193225 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav/CompositeConfigProcessor.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav/CompositeConfigProcessor.php @@ -10,6 +10,9 @@ use Psr\Log\LoggerInterface as Logger; +/** + * Process config for Wysiwyg. + */ class CompositeConfigProcessor implements WysiwygConfigDataProcessorInterface { /** @@ -24,6 +27,7 @@ class CompositeConfigProcessor implements WysiwygConfigDataProcessorInterface /** * CompositeConfigProcessor constructor. + * @param Logger $logger * @param array $eavWysiwygDataProcessors */ public function __construct(Logger $logger, array $eavWysiwygDataProcessors) @@ -33,7 +37,7 @@ public function __construct(Logger $logger, array $eavWysiwygDataProcessors) } /** - * {@inheritdoc} + * @inheritdoc */ public function process(\Magento\Catalog\Api\Data\ProductAttributeInterface $attribute) { diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Listing/DataProvider.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Listing/DataProvider.php index 3090734df01..4de0b94d068 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Listing/DataProvider.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Listing/DataProvider.php @@ -24,8 +24,6 @@ class DataProvider extends \Magento\Framework\View\Element\UiComponent\DataProvi /** * @param string $name - * @param string $primaryFieldName - * @param string $requestFieldName * @param Reporting $reporting * @param SearchCriteriaBuilder $searchCriteriaBuilder * @param RequestInterface $request @@ -61,7 +59,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function getData() { diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductRenderCollectorInterface.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductRenderCollectorInterface.php index 5d14cd21f7b..3f16e0a6617 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductRenderCollectorInterface.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductRenderCollectorInterface.php @@ -21,7 +21,6 @@ interface ProductRenderCollectorInterface * * @param ProductInterface $product * @param ProductRenderInterface $productRender - * @param array $data * @return void * @since 101.1.0 */ diff --git a/app/code/Magento/Catalog/ViewModel/Product/Breadcrumbs.php b/app/code/Magento/Catalog/ViewModel/Product/Breadcrumbs.php index 95f2531e5fd..d1424d63793 100644 --- a/app/code/Magento/Catalog/ViewModel/Product/Breadcrumbs.php +++ b/app/code/Magento/Catalog/ViewModel/Product/Breadcrumbs.php @@ -105,7 +105,7 @@ public function getJsonConfigurationHtmlEscaped() : string [ 'breadcrumbs' => [ 'categoryUrlSuffix' => $this->escaper->escapeHtml($this->getCategoryUrlSuffix()), - 'userCategoryPathInUrl' => (int)$this->isCategoryUsedInProductUrl(), + 'useCategoryPathInUrl' => (int)$this->isCategoryUsedInProductUrl(), 'product' => $this->escaper->escapeHtml($this->getProductName()) ] ], diff --git a/app/code/Magento/Catalog/etc/adminhtml/di.xml b/app/code/Magento/Catalog/etc/adminhtml/di.xml index fddc01ac4c1..c04cfb2dce0 100644 --- a/app/code/Magento/Catalog/etc/adminhtml/di.xml +++ b/app/code/Magento/Catalog/etc/adminhtml/di.xml @@ -78,7 +78,7 @@ <type name="Magento\Catalog\Model\ResourceModel\Attribute"> <plugin name="invalidate_pagecache_after_attribute_save" type="Magento\Catalog\Plugin\Model\ResourceModel\Attribute\Save" /> </type> - <virtualType name="\Magento\Catalog\Ui\DataProvider\Product\ProductCollectionFactory" type="\Magento\Catalog\Model\ResourceModel\Product\CollectionFactory"> + <virtualType name="Magento\Catalog\Ui\DataProvider\Product\ProductCollectionFactory" type="Magento\Catalog\Model\ResourceModel\Product\CollectionFactory"> <arguments> <argument name="instanceName" xsi:type="string">\Magento\Catalog\Ui\DataProvider\Product\ProductCollection</argument> </arguments> diff --git a/app/code/Magento/Catalog/etc/frontend/di.xml b/app/code/Magento/Catalog/etc/frontend/di.xml index ee9c5b29da8..793a2291f59 100644 --- a/app/code/Magento/Catalog/etc/frontend/di.xml +++ b/app/code/Magento/Catalog/etc/frontend/di.xml @@ -120,4 +120,5 @@ <plugin name="catalog_app_action_dispatch_controller_context_plugin" type="Magento\Catalog\Plugin\Framework\App\Action\ContextPlugin" /> </type> + <preference for="Magento\Catalog\Model\Product\Type\Price" type="Magento\Catalog\Model\Product\Type\FrontSpecialPrice" /> </config> diff --git a/app/code/Magento/Catalog/etc/webapi_rest/di.xml b/app/code/Magento/Catalog/etc/webapi_rest/di.xml index 2a5d60222e9..44cdd473bf7 100644 --- a/app/code/Magento/Catalog/etc/webapi_rest/di.xml +++ b/app/code/Magento/Catalog/etc/webapi_rest/di.xml @@ -19,4 +19,7 @@ <plugin name="get_catalog_category_product_index_table_name" type="Magento\Catalog\Model\Indexer\Category\Product\Plugin\TableResolver"/> <plugin name="get_catalog_product_price_index_table_name" type="Magento\Catalog\Model\Indexer\Product\Price\Plugin\TableResolver"/> </type> + <type name="Magento\Catalog\Api\ProductCustomOptionRepositoryInterface"> + <plugin name="updateProductCustomOptionsAttributes" type="Magento\Catalog\Plugin\Model\Product\Option\UpdateProductCustomOptionsAttributes"/> + </type> </config> diff --git a/app/code/Magento/Catalog/etc/webapi_soap/di.xml b/app/code/Magento/Catalog/etc/webapi_soap/di.xml index 2a5d60222e9..44cdd473bf7 100644 --- a/app/code/Magento/Catalog/etc/webapi_soap/di.xml +++ b/app/code/Magento/Catalog/etc/webapi_soap/di.xml @@ -19,4 +19,7 @@ <plugin name="get_catalog_category_product_index_table_name" type="Magento\Catalog\Model\Indexer\Category\Product\Plugin\TableResolver"/> <plugin name="get_catalog_product_price_index_table_name" type="Magento\Catalog\Model\Indexer\Product\Price\Plugin\TableResolver"/> </type> + <type name="Magento\Catalog\Api\ProductCustomOptionRepositoryInterface"> + <plugin name="updateProductCustomOptionsAttributes" type="Magento\Catalog\Plugin\Model\Product\Option\UpdateProductCustomOptionsAttributes"/> + </type> </config> 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 00a1580923a..ee67acd0ebd 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 @@ -20,7 +20,7 @@ "categoryCheckboxTree": { "dataUrl": "<?= $block->escapeUrl($block->getLoadTreeUrl()) ?>", "divId": "<?= /* @noEscape */ $divId ?>", - "rootVisible": <?= /* @noEscape */ $block->getRoot()->getIsVisible() ? 'true' : 'false' ?>, + "rootVisible": false, "useAjax": <?= $block->escapeHtml($block->getUseAjax()) ?>, "currentNodeId": <?= (int)$block->getCategoryId() ?>, "jsFormObject": "<?= /* @noEscape */ $block->getJsFormObject() ?>", @@ -28,7 +28,7 @@ "checked": "<?= $block->escapeHtml($block->getRoot()->getChecked()) ?>", "allowdDrop": <?= /* @noEscape */ $block->getRoot()->getIsVisible() ? 'true' : 'false' ?>, "rootId": <?= (int)$block->getRoot()->getId() ?>, - "expanded": <?= (int)$block->getIsWasExpanded() ?>, + "expanded": true, "categoryId": <?= (int)$block->getCategoryId() ?>, "treeJson": <?= /* @noEscape */ $block->getTreeJson() ?> } 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 93666470b1b..f448edc692c 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 @@ -302,6 +302,7 @@ } <?php endif;?> //updateContent(url); //commented since ajax requests replaced with http ones to load a category + jQuery('#tree-div').find('.x-tree-node-el').first().remove(); } jQuery(function () { 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 dbe66ef1aec..69737b8a37c 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 @@ -160,7 +160,7 @@ jQuery(function() loader: categoryLoader, enableDD: false, containerScroll: true, - rootVisible: '<?= /* @escapeNotVerified */ $block->getRoot()->getIsVisible() ?>', + rootVisible: false, useAjax: true, currentNodeId: <?= (int) $block->getCategoryId() ?>, addNodeTo: false @@ -177,7 +177,7 @@ jQuery(function() text: 'Psw', draggable: false, id: <?= (int) $block->getRoot()->getId() ?>, - expanded: <?= (int) $block->getIsWasExpanded() ?>, + expanded: true, category_id: <?= (int) $block->getCategoryId() ?> }; 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 efc06d675c3..64c8ba7dcf4 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 @@ -30,6 +30,13 @@ }); </script> +<?php +$defaultMinSaleQty = $block->getDefaultConfigValue('min_sale_qty'); +if (!is_numeric($defaultMinSaleQty)) { + $defaultMinSaleQty = json_decode($defaultMinSaleQty, true); + $defaultMinSaleQty = (float) $defaultMinSaleQty[\Magento\Customer\Api\Data\GroupInterface::CUST_GROUP_ALL] ?? 1; +} +?> <div class="fieldset-wrapper form-inline advanced-inventory-edit"> <div class="fieldset-wrapper-title"> <strong class="title"> @@ -132,7 +139,7 @@ <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 */ $block->getDefaultConfigValue('min_sale_qty') * 1 ?>" + value="<?= /* @escapeNotVerified */ $defaultMinSaleQty ?>" disabled="disabled"/> </div> <div class="field choice"> diff --git a/app/code/Magento/Catalog/view/adminhtml/ui_component/category_form.xml b/app/code/Magento/Catalog/view/adminhtml/ui_component/category_form.xml index 1a54db0d59f..90d6e0b4840 100644 --- a/app/code/Magento/Catalog/view/adminhtml/ui_component/category_form.xml +++ b/app/code/Magento/Catalog/view/adminhtml/ui_component/category_form.xml @@ -77,6 +77,13 @@ <dataType>text</dataType> </settings> </field> + <field name="level" formElement="hidden"> + <argument name="data" xsi:type="array"> + <item name="config" xsi:type="array"> + <item name="source" xsi:type="string">category</item> + </item> + </argument> + </field> <field name="store_id" formElement="hidden"> <argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> diff --git a/app/code/Magento/Catalog/view/adminhtml/web/catalog/category/form.js b/app/code/Magento/Catalog/view/adminhtml/web/catalog/category/form.js index 0a04358e411..76aaddf55ac 100644 --- a/app/code/Magento/Catalog/view/adminhtml/web/catalog/category/form.js +++ b/app/code/Magento/Catalog/view/adminhtml/web/catalog/category/form.js @@ -15,6 +15,7 @@ define([ categoryIdSelector: 'input[name="id"]', categoryPathSelector: 'input[name="path"]', categoryParentSelector: 'input[name="parent"]', + categoryLevelSelector: 'input[name="level"]', refreshUrl: config.refreshUrl }, @@ -47,6 +48,7 @@ define([ $(this.options.categoryIdSelector).val(data.id).change(); $(this.options.categoryPathSelector).val(data.path).change(); $(this.options.categoryParentSelector).val(data.parentId).change(); + $(this.options.categoryLevelSelector).val(data.level).change(); } } }; diff --git a/app/code/Magento/Catalog/view/adminhtml/web/catalog/product/composite/configure.js b/app/code/Magento/Catalog/view/adminhtml/web/catalog/product/composite/configure.js index a2804a8723c..1ac2a4ffada 100644 --- a/app/code/Magento/Catalog/view/adminhtml/web/catalog/product/composite/configure.js +++ b/app/code/Magento/Catalog/view/adminhtml/web/catalog/product/composite/configure.js @@ -91,8 +91,8 @@ define([ /** * Add product list types as scope and their urls - * expamle: addListType('product_to_add', {urlFetch: 'http://magento...'}) - * expamle: addListType('wishlist', {urlSubmit: 'http://magento...'}) + * example: addListType('product_to_add', {urlFetch: 'http://magento...'}) + * example: addListType('wishlist', {urlSubmit: 'http://magento...'}) * * @param type types as scope * @param urls obj can be @@ -112,7 +112,7 @@ define([ /** * Adds complex list type - that is used to submit several list types at once * Only urlSubmit is possible for this list type - * expamle: addComplexListType(['wishlist', 'product_list'], 'http://magento...') + * example: addComplexListType(['wishlist', 'product_list'], 'http://magento...') * * @param type types as scope * @param urls obj can be diff --git a/app/code/Magento/Catalog/view/adminhtml/web/js/form/element/action-delete.js b/app/code/Magento/Catalog/view/adminhtml/web/js/form/element/action-delete.js index 97f978de47b..f829c66c401 100644 --- a/app/code/Magento/Catalog/view/adminhtml/web/js/form/element/action-delete.js +++ b/app/code/Magento/Catalog/view/adminhtml/web/js/form/element/action-delete.js @@ -5,10 +5,10 @@ define([ 'underscore', 'Magento_Ui/js/form/element/abstract' -], function (_, Acstract) { +], function (_, Abstract) { 'use strict'; - return Acstract.extend({ + return Abstract.extend({ defaults: { prefixName: '', prefixElementName: '', diff --git a/app/code/Magento/Catalog/view/adminhtml/web/js/form/element/input.js b/app/code/Magento/Catalog/view/adminhtml/web/js/form/element/input.js index 2f6703cc92e..4bbdea066b7 100644 --- a/app/code/Magento/Catalog/view/adminhtml/web/js/form/element/input.js +++ b/app/code/Magento/Catalog/view/adminhtml/web/js/form/element/input.js @@ -5,10 +5,10 @@ define([ 'underscore', 'Magento_Ui/js/form/element/abstract' -], function (_, Acstract) { +], function (_, Abstract) { 'use strict'; - return Acstract.extend({ + return Abstract.extend({ defaults: { prefixName: '', prefixElementName: '', diff --git a/app/code/Magento/Catalog/view/base/web/js/price-box.js b/app/code/Magento/Catalog/view/base/web/js/price-box.js index de68d769885..783d39cddbc 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 @@ -78,11 +78,7 @@ define([ pricesCode = [], priceValue, origin, finalPrice; - if (typeof newPrices !== 'undefined' && newPrices.hasOwnProperty('prices')) { - this.cache.additionalPriceObject = {}; - } else { - this.cache.additionalPriceObject = this.cache.additionalPriceObject || {}; - } + this.cache.additionalPriceObject = this.cache.additionalPriceObject || {}; if (newPrices) { $.extend(this.cache.additionalPriceObject, newPrices); diff --git a/app/code/Magento/Catalog/view/base/web/js/price-utils.js b/app/code/Magento/Catalog/view/base/web/js/price-utils.js index e2ea42f7d5f..7b83d12cc98 100644 --- a/app/code/Magento/Catalog/view/base/web/js/price-utils.js +++ b/app/code/Magento/Catalog/view/base/web/js/price-utils.js @@ -60,7 +60,7 @@ define([ pattern = pattern.indexOf('{sign}') < 0 ? s + pattern : pattern.replace('{sign}', s); // we're avoiding the usage of to fixed, and using round instead with the e representation to address - // numbers like 1.005 = 1.01. Using ToFixed to only provide trailig zeroes in case we have a whole number + // numbers like 1.005 = 1.01. Using ToFixed to only provide trailing zeroes in case we have a whole number i = parseInt( amount = Number(Math.round(Math.abs(+amount || 0) + 'e+' + precision) + ('e-' + precision)), 10 diff --git a/app/code/Magento/Catalog/view/frontend/layout/catalog_product_view.xml b/app/code/Magento/Catalog/view/frontend/layout/catalog_product_view.xml index 3630fddb326..8d3248896b4 100644 --- a/app/code/Magento/Catalog/view/frontend/layout/catalog_product_view.xml +++ b/app/code/Magento/Catalog/view/frontend/layout/catalog_product_view.xml @@ -136,7 +136,7 @@ </arguments> </block> </container> - <block class="Magento\Catalog\Block\Product\View\Description" name="product.info.details" template="Magento_Catalog::product/view/details.phtml" after="product.info.media"> + <block class="Magento\Catalog\Block\Product\View\Details" name="product.info.details" template="Magento_Catalog::product/view/details.phtml" after="product.info.media"> <block class="Magento\Catalog\Block\Product\View\Description" name="product.info.description" as="description" template="Magento_Catalog::product/view/attribute.phtml" group="detailed_info"> <arguments> <argument name="at_call" xsi:type="string">getDescription</argument> @@ -144,11 +144,13 @@ <argument name="css_class" xsi:type="string">description</argument> <argument name="at_label" xsi:type="string">none</argument> <argument name="title" translate="true" xsi:type="string">Details</argument> + <argument name="sort_order" xsi:type="string">10</argument> </arguments> </block> <block class="Magento\Catalog\Block\Product\View\Attributes" name="product.attributes" as="additional" template="Magento_Catalog::product/view/attributes.phtml" group="detailed_info"> <arguments> <argument translate="true" name="title" xsi:type="string">More Information</argument> + <argument name="sort_order" xsi:type="string">20</argument> </arguments> </block> </block> 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 949d365e789..7daf0499803 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 @@ -116,7 +116,9 @@ <?php $block->getImage($item, 'product_small_image')->toHtml(); ?> <?php break; default: ?> - <?= /* @escapeNotVerified */ $helper->productAttribute($item, $block->getProductAttributeValue($item, $attribute), $attribute->getAttributeCode()) ?> + <?php if (is_string($block->getProductAttributeValue($item, $attribute))): ?> + <?= /* @escapeNotVerified */ $helper->productAttribute($item, $block->getProductAttributeValue($item, $attribute), $attribute->getAttributeCode()) ?> + <?php endif; ?> <?php break; } ?> </div> 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 038bea86e7d..af664051b14 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 @@ -6,8 +6,9 @@ // @codingStandardsIgnoreFile +/** @var \Magento\Catalog\Block\Product\View\Details $block */ ?> -<?php if ($detailedInfoGroup = $block->getGroupChildNames('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"}}'> 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 7434678d169..bcb7c668657 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 @@ -165,6 +165,16 @@ define([ self.enableAddToCartButton(form); }, + /** @inheritdoc */ + error: function (res) { + $(document).trigger('ajax:addToCart:error', { + 'sku': form.data().productSku, + 'productIds': productIds, + 'form': form, + 'response': res + }); + }, + /** @inheritdoc */ complete: function (res) { if (res.state() === 'rejected') { diff --git a/app/code/Magento/CatalogAnalytics/README.md b/app/code/Magento/CatalogAnalytics/README.md index df125446117..f93b223c342 100644 --- a/app/code/Magento/CatalogAnalytics/README.md +++ b/app/code/Magento/CatalogAnalytics/README.md @@ -1,3 +1,3 @@ # Magento_CatalogAnalytics module -The Magento_CatalogAnalytics module configures data definitions for a data collection related to the Catalog module entities to be used in [Advanced Reporting](http://devdocs.magento.com/guides/v2.2/advanced-reporting/modules.html). +The Magento_CatalogAnalytics module configures data definitions for a data collection related to the Catalog module entities to be used in [Advanced Reporting](https://devdocs.magento.com/guides/v2.2/advanced-reporting/modules.html). diff --git a/app/code/Magento/CatalogGraphQl/Model/Category/LevelCalculator.php b/app/code/Magento/CatalogGraphQl/Model/Category/LevelCalculator.php index 0401e1c4233..f587be245c9 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Category/LevelCalculator.php +++ b/app/code/Magento/CatalogGraphQl/Model/Category/LevelCalculator.php @@ -15,6 +15,16 @@ */ class LevelCalculator { + /** + * @var ResourceConnection + */ + private $resourceConnection; + + /** + * @var Category + */ + private $resourceCategory; + /** * @param ResourceConnection $resourceConnection * @param Category $resourceCategory @@ -39,6 +49,7 @@ public function calculate(int $rootCategoryId) : int $select = $connection->select() ->from($this->resourceConnection->getTableName('catalog_category_entity'), 'level') ->where($this->resourceCategory->getLinkField() . " = ?", $rootCategoryId); + return (int) $connection->fetchOne($select); } } diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php index ff53299b00e..e910a5c8be4 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php @@ -7,7 +7,7 @@ namespace Magento\CatalogGraphQl\Model\Resolver; -use Magento\CatalogGraphQl\Model\Resolver\Layer\DataProvider\Filters; +use Magento\Framework\Exception\InputException; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; use Magento\CatalogGraphQl\Model\Resolver\Products\Query\Filter; use Magento\CatalogGraphQl\Model\Resolver\Products\Query\Search; @@ -17,6 +17,7 @@ use Magento\Framework\GraphQl\Query\Resolver\Argument\SearchCriteria\SearchFilter; use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Catalog\Model\Layer\Resolver; +use Magento\Framework\Api\Search\SearchCriteriaInterface; /** * Products field resolver, used for GraphQL request processing. @@ -81,10 +82,10 @@ public function resolve( } elseif (isset($args['search'])) { $layerType = Resolver::CATALOG_LAYER_SEARCH; $this->searchFilter->add($args['search'], $searchCriteria); - $searchResult = $this->searchQuery->getResult($searchCriteria, $info); + $searchResult = $this->getSearchResult($this->searchQuery, $searchCriteria, $info); } else { $layerType = Resolver::CATALOG_LAYER_CATEGORY; - $searchResult = $this->filterQuery->getResult($searchCriteria, $info); + $searchResult = $this->getSearchResult($this->filterQuery, $searchCriteria, $info); } //possible division by 0 if ($searchCriteria->getPageSize()) { @@ -116,4 +117,25 @@ public function resolve( return $data; } + + /** + * Get search result. + * + * @param Filter|Search $query + * @param SearchCriteriaInterface $searchCriteria + * @param ResolveInfo $info + * + * @return \Magento\CatalogGraphQl\Model\Resolver\Products\SearchResult + * @throws GraphQlInputException + */ + private function getSearchResult($query, SearchCriteriaInterface $searchCriteria, ResolveInfo $info) + { + try { + $searchResult = $query->getResult($searchCriteria, $info); + } catch (InputException $e) { + throw new GraphQlInputException(__($e->getMessage())); + } + + return $searchResult; + } } diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php index f2634574a2d..fc5a563c82b 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/CategoryTree.php @@ -101,11 +101,21 @@ public function getTree(ResolveInfo $resolveInfo, int $rootCategoryId): \Iterato $collection->addFieldToFilter('level', ['gt' => $level]); $collection->addFieldToFilter('level', ['lteq' => $level + $depth - self::DEPTH_OFFSET]); + $collection->addAttributeToFilter('is_active', 1, "left"); $collection->setOrder('level'); + $collection->setOrder( + 'position', + $collection::SORT_ORDER_DESC + ); $collection->getSelect()->orWhere( - $this->metadata->getMetadata(CategoryInterface::class)->getIdentifierField() . ' = ?', + $collection->getSelect() + ->getConnection() + ->quoteIdentifier( + 'e.' . $this->metadata->getMetadata(CategoryInterface::class)->getIdentifierField() + ) . ' = ?', $rootCategoryId ); + return $collection->getIterator(); } diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/ExtractDataFromCategoryTree.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/ExtractDataFromCategoryTree.php index ac8d5709c85..3525ccbb6a2 100644 --- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/ExtractDataFromCategoryTree.php +++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/ExtractDataFromCategoryTree.php @@ -20,6 +20,16 @@ class ExtractDataFromCategoryTree */ private $categoryHydrator; + /** + * @var CategoryInterface + */ + private $iteratingCategory; + + /** + * @var int + */ + private $startCategoryFetchLevel = 1; + /** * @param Hydrator $categoryHydrator */ @@ -42,14 +52,60 @@ public function execute(\Iterator $iterator): array /** @var CategoryInterface $category */ $category = $iterator->current(); $iterator->next(); - $nextCategory = $iterator->current(); - $tree[$category->getId()] = $this->categoryHydrator->hydrateCategory($category); - $tree[$category->getId()]['model'] = $category; - if ($nextCategory && (int) $nextCategory->getLevel() !== (int) $category->getLevel()) { - $tree[$category->getId()]['children'] = $this->execute($iterator); + $pathElements = explode("/", $category->getPath()); + if (empty($tree)) { + $this->startCategoryFetchLevel = count($pathElements) - 1; + } + $this->iteratingCategory = $category; + $currentLevelTree = $this->explodePathToArray($pathElements, $this->startCategoryFetchLevel); + if (empty($tree)) { + $tree = $currentLevelTree; + } + $tree = $this->mergeCategoriesTrees($currentLevelTree, $tree); + } + return $tree; + } + + /** + * Merge together complex categories trees + * + * @param array $tree1 + * @param array $tree2 + * @return array + */ + private function mergeCategoriesTrees(array &$tree1, array &$tree2): array + { + $mergedTree = $tree1; + foreach ($tree2 as $currentKey => &$value) { + if (is_array($value) && isset($mergedTree[$currentKey]) && is_array($mergedTree[$currentKey])) { + $mergedTree[$currentKey] = $this->mergeCategoriesTrees($mergedTree[$currentKey], $value); + } else { + $mergedTree[$currentKey] = $value; } } + return $mergedTree; + } + /** + * Recursive method to generate tree for one category path + * + * @param array $pathElements + * @param int $index + * @return array + */ + private function explodePathToArray(array $pathElements, int $index): array + { + $tree = []; + $tree[$pathElements[$index]]['id'] = $pathElements[$index]; + if ($index === count($pathElements) - 1) { + $tree[$pathElements[$index]] = $this->categoryHydrator->hydrateCategory($this->iteratingCategory); + $tree[$pathElements[$index]]['model'] = $this->iteratingCategory; + } + $currentIndex = $index; + $index++; + if (isset($pathElements[$index])) { + $tree[$pathElements[$currentIndex]]['children'] = $this->explodePathToArray($pathElements, $index); + } return $tree; } } diff --git a/app/code/Magento/CatalogImportExport/Model/Export/Product.php b/app/code/Magento/CatalogImportExport/Model/Export/Product.php index 345d323e6e1..75249e49078 100644 --- a/app/code/Magento/CatalogImportExport/Model/Export/Product.php +++ b/app/code/Magento/CatalogImportExport/Model/Export/Product.php @@ -351,6 +351,7 @@ class Product extends \Magento\ImportExport\Model\Export\Entity\AbstractEntity /** * Product constructor. + * * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate * @param \Magento\Eav\Model\Config $config * @param \Magento\Framework\App\ResourceConnection $resource @@ -941,15 +942,17 @@ protected function getExportData() protected function loadCollection(): array { $data = []; - $collection = $this->_getEntityCollection(); foreach (array_keys($this->_storeIdToCode) as $storeId) { + $collection->setOrder('entity_id', 'asc'); + $this->_prepareEntityCollection($collection); $collection->setStoreId($storeId); + $collection->load(); foreach ($collection as $itemId => $item) { $data[$itemId][$storeId] = $item; } + $collection->clear(); } - $collection->clear(); return $data; } @@ -1158,7 +1161,7 @@ protected function collectMultiselectValues($item, $attrCode, $storeId) } /** - * Check attribute is valid + * Check attribute is valid. * * @param string $code * @param mixed $value @@ -1175,6 +1178,10 @@ protected function isValidAttributeValue($code, $value) $isValid = false; } + if (is_array($value)) { + $isValid = false; + } + return $isValid; } @@ -1290,11 +1297,23 @@ private function appendMultirowData(&$dataRow, $multiRawData) } if (!empty($multiRawData['customOptionsData'][$productLinkId][$storeId])) { + $shouldBeMerged = true; $customOptionsRows = $multiRawData['customOptionsData'][$productLinkId][$storeId]; - $multiRawData['customOptionsData'][$productLinkId][$storeId] = []; - $customOptions = implode(ImportProduct::PSEUDO_MULTI_LINE_SEPARATOR, $customOptionsRows); - $dataRow = array_merge($dataRow, ['custom_options' => $customOptions]); + if ($storeId != Store::DEFAULT_STORE_ID + && !empty($multiRawData['customOptionsData'][$productLinkId][Store::DEFAULT_STORE_ID]) + ) { + $defaultCustomOptions = $multiRawData['customOptionsData'][$productLinkId][Store::DEFAULT_STORE_ID]; + if (!array_diff($defaultCustomOptions, $customOptionsRows)) { + $shouldBeMerged = false; + } + } + + if ($shouldBeMerged) { + $multiRawData['customOptionsData'][$productLinkId][$storeId] = []; + $customOptions = implode(ImportProduct::PSEUDO_MULTI_LINE_SEPARATOR, $customOptionsRows); + $dataRow = array_merge($dataRow, ['custom_options' => $customOptions]); + } } if (empty($dataRow)) { @@ -1390,6 +1409,7 @@ protected function optionRowToCellString($option) protected function getCustomOptionsData($productIds) { $customOptionsData = []; + $defaultOptionsData = []; foreach (array_keys($this->_storeIdToCode) as $storeId) { $options = $this->_optionColFactory->create(); @@ -1402,38 +1422,42 @@ protected function getCustomOptionsData($productIds) ->addValuesToResult($storeId); foreach ($options as $option) { + $optionData = $option->toArray(); $row = []; $productId = $option['product_id']; $row['name'] = $option['title']; $row['type'] = $option['type']; - if (Store::DEFAULT_STORE_ID === $storeId) { - $row['required'] = $option['is_require']; - $row['price'] = $option['price']; - $row['price_type'] = ($option['price_type'] === 'percent') ? 'percent' : 'fixed'; - $row['sku'] = $option['sku']; - if ($option['max_characters']) { - $row['max_characters'] = $option['max_characters']; - } - - foreach (['file_extension', 'image_size_x', 'image_size_y'] as $fileOptionKey) { - if (!isset($option[$fileOptionKey])) { - continue; - } - $row[$fileOptionKey] = $option[$fileOptionKey]; + $row['required'] = $this->getOptionValue('is_require', $defaultOptionsData, $optionData); + $row['price'] = $this->getOptionValue('price', $defaultOptionsData, $optionData); + $row['sku'] = $this->getOptionValue('sku', $defaultOptionsData, $optionData); + if (array_key_exists('max_characters', $optionData) + || array_key_exists('max_characters', $defaultOptionsData) + ) { + $row['max_characters'] = $this->getOptionValue('max_characters', $defaultOptionsData, $optionData); + } + foreach (['file_extension', 'image_size_x', 'image_size_y'] as $fileOptionKey) { + if (isset($option[$fileOptionKey]) || isset($defaultOptionsData[$fileOptionKey])) { + $row[$fileOptionKey] = $this->getOptionValue($fileOptionKey, $defaultOptionsData, $optionData); } } + $percentType = $this->getOptionValue('price_type', $defaultOptionsData, $optionData); + $row['price_type'] = ($percentType === 'percent') ? 'percent' : 'fixed'; + + if (Store::DEFAULT_STORE_ID === $storeId) { + $optionId = $option['option_id']; + $defaultOptionsData[$optionId] = $option->toArray(); + } + $values = $option->getValues(); if ($values) { foreach ($values as $value) { $row['option_title'] = $value['title']; - if (Store::DEFAULT_STORE_ID === $storeId) { - $row['option_title'] = $value['title']; - $row['price'] = $value['price']; - $row['price_type'] = ($value['price_type'] === 'percent') ? 'percent' : 'fixed'; - $row['sku'] = $value['sku']; - } + $row['option_title'] = $value['title']; + $row['price'] = $value['price']; + $row['price_type'] = ($value['price_type'] === 'percent') ? 'percent' : 'fixed'; + $row['sku'] = $value['sku']; $customOptionsData[$productId][$storeId][] = $this->optionRowToCellString($row); } } else { @@ -1447,6 +1471,31 @@ protected function getCustomOptionsData($productIds) return $customOptionsData; } + /** + * Get value for custom option according to store or default value + * + * @param string $optionName + * @param array $defaultOptionsData + * @param array $optionData + * @return mixed + */ + private function getOptionValue($optionName, $defaultOptionsData, $optionData) + { + $optionId = $optionData['option_id']; + + if (array_key_exists($optionName, $optionData) && $optionData[$optionName] !== null) { + return $optionData[$optionName]; + } + + if (array_key_exists($optionId, $defaultOptionsData) + && array_key_exists($optionName, $defaultOptionsData[$optionId]) + ) { + return $defaultOptionsData[$optionId][$optionName]; + } + + return null; + } + /** * Clean up already loaded attribute collection. * diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product.php b/app/code/Magento/CatalogImportExport/Model/Import/Product.php index 9298939791e..908cf66e3f6 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product.php @@ -3,16 +3,17 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\CatalogImportExport\Model\Import; use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\Catalog\Model\Config as CatalogConfig; use Magento\Catalog\Model\Product\Visibility; -use Magento\CatalogImportExport\Model\Import\Product\MediaGalleryProcessor; use Magento\CatalogImportExport\Model\Import\Product\ImageTypeProcessor; +use Magento\CatalogImportExport\Model\Import\Product\MediaGalleryProcessor; use Magento\CatalogImportExport\Model\Import\Product\RowValidatorInterface as ValidatorInterface; -use Magento\CatalogInventory\Api\Data\StockItemInterface; use Magento\CatalogImportExport\Model\StockItemImporterInterface; +use Magento\CatalogInventory\Api\Data\StockItemInterface; use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\App\ObjectManager; use Magento\Framework\Exception\LocalizedException; @@ -132,16 +133,6 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity */ const COL_NAME = 'name'; - /** - * Column new_from_date. - */ - const COL_NEW_FROM_DATE = 'new_from_date'; - - /** - * Column new_to_date. - */ - const COL_NEW_TO_DATE = 'new_to_date'; - /** * Column product website. */ @@ -307,8 +298,8 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity ValidatorInterface::ERROR_MEDIA_URL_NOT_ACCESSIBLE => 'Imported resource (image) could not be downloaded from external resource due to timeout or access permissions', ValidatorInterface::ERROR_INVALID_WEIGHT => 'Product weight is invalid', 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", - ValidatorInterface::ERROR_NEW_TO_DATE => 'Make sure new_to_date is later than or the same as new_from_date', + 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', ]; //@codingStandardsIgnoreEnd @@ -330,8 +321,8 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity Product::COL_TYPE => 'product_type', Product::COL_PRODUCT_WEBSITES => 'product_websites', 'status' => 'product_online', - 'news_from_date' => self::COL_NEW_FROM_DATE, - 'news_to_date' => self::COL_NEW_TO_DATE, + 'news_from_date' => 'new_from_date', + 'news_to_date' => 'new_to_date', 'options_container' => 'display_product_options_in', 'minimal_price' => 'map_price', 'msrp' => 'msrp_price', @@ -795,6 +786,8 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity * @param StockItemImporterInterface|null $stockItemImporter * @param DateTimeFactory $dateTimeFactory * @param ProductRepositoryInterface|null $productRepository + * @throws LocalizedException + * @throws \Magento\Framework\Exception\FileSystemException * @SuppressWarnings(PHPMD.ExcessiveParameterList) * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ @@ -913,7 +906,7 @@ public function isAttributeValid($attrCode, array $attrParams, array $rowData, $ { if (!$this->validator->isAttributeValid($attrCode, $attrParams, $rowData)) { foreach ($this->validator->getMessages() as $message) { - $this->addRowError($message, $rowNum, $attrCode); + $this->skipRow($rowNum, $message, ProcessingError::ERROR_LEVEL_NOT_CRITICAL, $attrCode); } return false; } @@ -1646,11 +1639,8 @@ protected function _saveProducts() continue; } if ($this->getErrorAggregator()->hasToBeTerminated()) { - $validationStrategy = $this->_parameters[Import::FIELD_NAME_VALIDATION_STRATEGY]; - if (ProcessingErrorAggregatorInterface::VALIDATION_STRATEGY_SKIP_ERRORS !== $validationStrategy) { - $this->getErrorAggregator()->addRowToSkip($rowNum); - continue; - } + $this->getErrorAggregator()->addRowToSkip($rowNum); + continue; } $rowScope = $this->getRowScope($rowData); @@ -1658,7 +1648,7 @@ protected function _saveProducts() if (!empty($rowData[self::URL_KEY])) { // If url_key column and its value were in the CSV file $rowData[self::URL_KEY] = $urlKey; - } else if ($this->isNeedToChangeUrlKey($rowData)) { + } elseif ($this->isNeedToChangeUrlKey($rowData)) { // If url_key column was empty or even not declared in the CSV file but by the rules it is need to // be setteed. In case when url_key is generating from name column we have to ensure that the bunch // of products will pass for the event with url_key column. @@ -1670,7 +1660,9 @@ protected function _saveProducts() if (null === $rowSku) { $this->getErrorAggregator()->addRowToSkip($rowNum); continue; - } elseif (self::SCOPE_STORE == $rowScope) { + } + + if (self::SCOPE_STORE == $rowScope) { // set necessary data from SCOPE_DEFAULT row $rowData[self::COL_TYPE] = $this->skuProcessor->getNewSku($rowSku)['type_id']; $rowData['attribute_set_id'] = $this->skuProcessor->getNewSku($rowSku)['attr_set_id']; @@ -1806,13 +1798,7 @@ protected function _saveProducts() $uploadedImages[$columnImage] = $uploadedFile; } else { unset($rowData[$column]); - $this->addRowError( - ValidatorInterface::ERROR_MEDIA_URL_NOT_ACCESSIBLE, - $rowNum, - null, - null, - ProcessingError::ERROR_LEVEL_NOT_CRITICAL - ); + $this->skipRow($rowNum, ValidatorInterface::ERROR_MEDIA_URL_NOT_ACCESSIBLE); } } else { $uploadedFile = $uploadedImages[$columnImage]; @@ -2436,6 +2422,7 @@ public function getRowScope(array $rowData) * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @throws \Zend_Validate_Exception */ public function validateRow(array $rowData, $rowNum) { @@ -2451,32 +2438,35 @@ public function validateRow(array $rowData, $rowNum) // BEHAVIOR_DELETE and BEHAVIOR_REPLACE use specific validation logic if (Import::BEHAVIOR_REPLACE == $this->getBehavior()) { if (self::SCOPE_DEFAULT == $rowScope && !$this->isSkuExist($sku)) { - $this->addRowError(ValidatorInterface::ERROR_SKU_NOT_FOUND_FOR_DELETE, $rowNum); + $this->skipRow($rowNum, ValidatorInterface::ERROR_SKU_NOT_FOUND_FOR_DELETE); return false; } } if (Import::BEHAVIOR_DELETE == $this->getBehavior()) { if (self::SCOPE_DEFAULT == $rowScope && !$this->isSkuExist($sku)) { - $this->addRowError(ValidatorInterface::ERROR_SKU_NOT_FOUND_FOR_DELETE, $rowNum); + $this->skipRow($rowNum, ValidatorInterface::ERROR_SKU_NOT_FOUND_FOR_DELETE); return false; } return true; } + // if product doesn't exist, need to throw critical error else all errors should be not critical. + $errorLevel = $this->getValidationErrorLevel($sku); + if (!$this->validator->isValid($rowData)) { foreach ($this->validator->getMessages() as $message) { - $this->addRowError($message, $rowNum, $this->validator->getInvalidAttribute()); + $this->skipRow($rowNum, $message, $errorLevel, $this->validator->getInvalidAttribute()); } } if (null === $sku) { - $this->addRowError(ValidatorInterface::ERROR_SKU_IS_EMPTY, $rowNum); + $this->skipRow($rowNum, ValidatorInterface::ERROR_SKU_IS_EMPTY, $errorLevel); } elseif (false === $sku) { - $this->addRowError(ValidatorInterface::ERROR_ROW_IS_ORPHAN, $rowNum); + $this->skipRow($rowNum, ValidatorInterface::ERROR_ROW_IS_ORPHAN, $errorLevel); } elseif (self::SCOPE_STORE == $rowScope && !$this->storeResolver->getStoreCodeToId($rowData[self::COL_STORE]) ) { - $this->addRowError(ValidatorInterface::ERROR_INVALID_STORE, $rowNum); + $this->skipRow($rowNum, ValidatorInterface::ERROR_INVALID_STORE, $errorLevel); } // SKU is specified, row is SCOPE_DEFAULT, new product block begins @@ -2491,16 +2481,15 @@ public function validateRow(array $rowData, $rowNum) $this->prepareNewSkuData($sku) ); } else { - $this->addRowError(ValidatorInterface::ERROR_TYPE_UNSUPPORTED, $rowNum); + $this->skipRow($rowNum, ValidatorInterface::ERROR_TYPE_UNSUPPORTED, $errorLevel); } } else { // validate new product type and attribute set - if (!isset($rowData[self::COL_TYPE]) || !isset($this->_productTypeModels[$rowData[self::COL_TYPE]])) { - $this->addRowError(ValidatorInterface::ERROR_INVALID_TYPE, $rowNum); - } elseif (!isset($rowData[self::COL_ATTR_SET]) - || !isset($this->_attrSetNameToId[$rowData[self::COL_ATTR_SET]]) + if (!isset($rowData[self::COL_TYPE], $this->_productTypeModels[$rowData[self::COL_TYPE]])) { + $this->skipRow($rowNum, ValidatorInterface::ERROR_INVALID_TYPE, $errorLevel); + } elseif (!isset($rowData[self::COL_ATTR_SET], $this->_attrSetNameToId[$rowData[self::COL_ATTR_SET]]) ) { - $this->addRowError(ValidatorInterface::ERROR_INVALID_ATTR_SET, $rowNum); + $this->skipRow($rowNum, ValidatorInterface::ERROR_INVALID_ATTR_SET, $errorLevel); } elseif ($this->skuProcessor->getNewSku($sku) === null) { $this->skuProcessor->addNewSku( $sku, @@ -2556,21 +2545,25 @@ public function validateRow(array $rowData, $rowNum) ValidatorInterface::ERROR_DUPLICATE_URL_KEY, $rowNum, $rowData[self::COL_NAME], - $message - ); + $message, + ProcessingError::ERROR_LEVEL_NOT_CRITICAL + ) + ->getErrorAggregator() + ->addRowToSkip($rowNum); } } } - if (!empty($rowData[self::COL_NEW_FROM_DATE]) && !empty($rowData[self::COL_NEW_TO_DATE]) + if (!empty($rowData['new_from_date']) && !empty($rowData['new_to_date']) ) { - $newFromTimestamp = strtotime($this->dateTime->formatDate($rowData[self::COL_NEW_FROM_DATE], false)); - $newToTimestamp = strtotime($this->dateTime->formatDate($rowData[self::COL_NEW_TO_DATE], false)); + $newFromTimestamp = strtotime($this->dateTime->formatDate($rowData['new_from_date'], false)); + $newToTimestamp = strtotime($this->dateTime->formatDate($rowData['new_to_date'], false)); if ($newFromTimestamp > $newToTimestamp) { - $this->addRowError( - ValidatorInterface::ERROR_NEW_TO_DATE, + $this->skipRow( $rowNum, - $rowData[self::COL_NEW_TO_DATE] + 'invalidNewToDateValue', + $errorLevel, + $rowData['new_to_date'] ); } } @@ -2588,8 +2581,8 @@ private function isNeedToValidateUrlKey($rowData) { return (!empty($rowData[self::URL_KEY]) || !empty($rowData[self::COL_NAME])) && (empty($rowData[self::COL_VISIBILITY]) - || $rowData[self::COL_VISIBILITY] - !== (string)Visibility::getOptionArray()[Visibility::VISIBILITY_NOT_VISIBLE]); + || $rowData[self::COL_VISIBILITY] + !== (string)Visibility::getOptionArray()[Visibility::VISIBILITY_NOT_VISIBLE]); } /** @@ -3067,9 +3060,7 @@ private function formatStockDataForRow(array $rowData): array if ($this->stockConfiguration->isQty($this->skuProcessor->getNewSku($sku)['type_id'])) { $stockItemDo->setData($row); - $row['is_in_stock'] = isset($row['is_in_stock']) && $stockItemDo->getBackorders() - ? $row['is_in_stock'] - : $this->stockStateProvider->verifyStock($stockItemDo); + $row['is_in_stock'] = $row['is_in_stock'] ?? $this->stockStateProvider->verifyStock($stockItemDo); if ($this->stockStateProvider->verifyNotification($stockItemDo)) { $date = $this->dateTimeFactory->create('now', new \DateTimeZone('UTC')); $row['low_stock_date'] = $date->format(DateTime::DATETIME_PHP_FORMAT); @@ -3097,4 +3088,38 @@ private function retrieveProductBySku($sku) } return $product; } + + /** + * Add row as skipped + * + * @param int $rowNum + * @param string $errorCode Error code or simply column name + * @param string $errorLevel error level + * @param string|null $colName optional column name + * @return $this + */ + private function skipRow( + $rowNum, + string $errorCode, + string $errorLevel = ProcessingError::ERROR_LEVEL_NOT_CRITICAL, + $colName = null + ): self { + $this->addRowError($errorCode, $rowNum, $colName, null, $errorLevel); + $this->getErrorAggregator() + ->addRowToSkip($rowNum); + return $this; + } + + /** + * Returns errorLevel for validation + * + * @param string $sku + * @return string + */ + private function getValidationErrorLevel($sku): string + { + return (!$this->isSkuExist($sku) && Import::BEHAVIOR_REPLACE !== $this->getBehavior()) + ? ProcessingError::ERROR_LEVEL_CRITICAL + : ProcessingError::ERROR_LEVEL_NOT_CRITICAL; + } } diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/Option.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/Option.php index fa2853c7386..7435c0bebfc 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product/Option.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/Option.php @@ -431,7 +431,10 @@ protected function _initMessageTemplates() ); $this->_productEntity->addMessageTemplate( self::ERROR_INVALID_TYPE, - __('Value for \'type\' sub attribute in \'custom_options\' attribute contains incorrect value, acceptable values are: \'dropdown\', \'checkbox\'') + __( + 'Value for \'type\' sub attribute in \'custom_options\' attribute contains incorrect value, acceptable values are: %1', + '\''.implode('\', \'', array_keys($this->_specificTypes)).'\'' + ) ); $this->_productEntity->addMessageTemplate(self::ERROR_EMPTY_TITLE, __('Please enter a value for title.')); $this->_productEntity->addMessageTemplate( @@ -629,7 +632,7 @@ public function validateAmbiguousData() $this->_addRowsErrors(self::ERROR_AMBIGUOUS_NEW_NAMES, $errorRows); return false; } - if ($this->getBehavior() == \Magento\ImportExport\Model\Import::BEHAVIOR_APPEND) { + if ($this->getBehavior() == Import::BEHAVIOR_APPEND) { $errorRows = $this->_findOldOptionsWithTheSameTitles(); if ($errorRows) { $this->_addRowsErrors(self::ERROR_AMBIGUOUS_OLD_NAMES, $errorRows); @@ -967,11 +970,10 @@ public function validateRow(array $rowData, $rowNumber) return false; } } - return true; } } - return false; + return true; } /** @@ -1381,7 +1383,7 @@ private function setLastOptionTitle(array &$titles) : void */ private function removeExistingOptions(array $products, array $optionsToRemove): void { - if ($this->getBehavior() != \Magento\ImportExport\Model\Import::BEHAVIOR_APPEND) { + if ($this->getBehavior() != Import::BEHAVIOR_APPEND) { $this->_deleteEntities(array_keys($products)); } elseif (!empty($optionsToRemove)) { // Remove options for products with empty "custom_options" row @@ -2108,7 +2110,7 @@ private function savePreparedCustomOptions( array $types ): void { if ($this->_isReadyForSaving($options, $titles, $types['values'])) { - if ($this->getBehavior() == \Magento\ImportExport\Model\Import::BEHAVIOR_APPEND) { + if ($this->getBehavior() == Import::BEHAVIOR_APPEND) { $this->_compareOptionsWithExisting($options, $titles, $prices, $types['values']); $this->restoreOriginalOptionTypeIds($types['values'], $types['prices'], $types['titles']); } diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/RowValidatorInterface.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/RowValidatorInterface.php index cbdc5f5beaa..f41596ad185 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product/RowValidatorInterface.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/RowValidatorInterface.php @@ -87,8 +87,6 @@ interface RowValidatorInterface extends \Magento\Framework\Validator\ValidatorIn const ERROR_DUPLICATE_MULTISELECT_VALUES = 'duplicatedMultiselectValues'; - const ERROR_NEW_TO_DATE = 'invalidNewToDateValue'; - /** * Value that means all entities (e.g. websites, groups etc.) */ diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Uploader.php b/app/code/Magento/CatalogImportExport/Model/Import/Uploader.php index 7e6ada724a8..3ac7f98818d 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Uploader.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Uploader.php @@ -180,9 +180,9 @@ public function move($fileName, $renameFileOff = false) } $fileName = preg_replace('/[^a-z0-9\._-]+/i', '', $fileName); - $filePath = $this->_directory->getRelativePath($filePath . $fileName); + $relativePath = $this->_directory->getRelativePath($filePath . $fileName); $this->_directory->writeFile( - $filePath, + $relativePath, $read->readAll() ); } diff --git a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/Type/OptionTest.php b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/Type/OptionTest.php index 98e434f2174..f0a52a67e00 100644 --- a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/Type/OptionTest.php +++ b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/Type/OptionTest.php @@ -702,7 +702,7 @@ public function testValidateRowNoCustomOption() { $rowData = include __DIR__ . '/_files/row_data_no_custom_option.php'; $this->_bypassModelMethodGetMultiRowFormat($rowData); - $this->assertFalse($this->modelMock->validateRow($rowData, 0)); + $this->assertTrue($this->modelMock->validateRow($rowData, 0)); } /** diff --git a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/ProductTest.php b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/ProductTest.php index a562916b3b4..f85d33edb5d 100644 --- a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/ProductTest.php +++ b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/ProductTest.php @@ -3,11 +3,14 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\CatalogImportExport\Test\Unit\Model\Import; +use Magento\CatalogImportExport\Model\Import\Product; use Magento\CatalogImportExport\Model\Import\Product\ImageTypeProcessor; use Magento\Framework\App\Filesystem\DirectoryList; use Magento\ImportExport\Model\Import; +use PHPUnit\Framework\MockObject\MockObject; /** * Class ProductTest @@ -26,126 +29,126 @@ class ProductTest extends \Magento\ImportExport\Test\Unit\Model\Import\AbstractI const ENTITY_ID = 13; - /** @var \Magento\Framework\DB\Adapter\AdapterInterface|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\Framework\DB\Adapter\AdapterInterface| MockObject */ protected $_connection; - /** @var \Magento\Framework\Json\Helper\Data|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\Framework\Json\Helper\Data| MockObject */ protected $jsonHelper; - /** @var \Magento\ImportExport\Model\ResourceModel\Import\Data|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\ImportExport\Model\ResourceModel\Import\Data| MockObject */ protected $_dataSourceModel; - /** @var \Magento\Framework\App\ResourceConnection|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\Framework\App\ResourceConnection| MockObject */ protected $resource; - /** @var \Magento\ImportExport\Model\ResourceModel\Helper|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\ImportExport\Model\ResourceModel\Helper| MockObject */ protected $_resourceHelper; - /** @var \Magento\Framework\Stdlib\StringUtils|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\Framework\Stdlib\StringUtils|MockObject */ protected $string; - /** @var \Magento\Framework\Event\ManagerInterface|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\Framework\Event\ManagerInterface|MockObject */ protected $_eventManager; - /** @var \Magento\CatalogInventory\Api\StockRegistryInterface|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\CatalogInventory\Api\StockRegistryInterface|MockObject */ protected $stockRegistry; - /** @var \Magento\CatalogImportExport\Model\Import\Product\OptionFactory|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\CatalogImportExport\Model\Import\Product\OptionFactory|MockObject */ protected $optionFactory; - /** @var \Magento\CatalogInventory\Api\StockConfigurationInterface|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\CatalogInventory\Api\StockConfigurationInterface|MockObject */ protected $stockConfiguration; - /** @var \Magento\CatalogInventory\Model\Spi\StockStateProviderInterface|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\CatalogInventory\Model\Spi\StockStateProviderInterface|MockObject */ protected $stockStateProvider; - /** @var \Magento\CatalogImportExport\Model\Import\Product\Option|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\CatalogImportExport\Model\Import\Product\Option|MockObject */ protected $optionEntity; - /** @var \Magento\Framework\Stdlib\DateTime|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\Framework\Stdlib\DateTime|MockObject */ protected $dateTime; /** @var array */ protected $data; - /** @var \Magento\ImportExport\Helper\Data|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\ImportExport\Helper\Data|MockObject */ protected $importExportData; - /** @var \Magento\ImportExport\Model\ResourceModel\Import\Data|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\ImportExport\Model\ResourceModel\Import\Data|MockObject */ protected $importData; - /** @var \Magento\Eav\Model\Config|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\Eav\Model\Config|MockObject */ protected $config; - /** @var \Magento\ImportExport\Model\ResourceModel\Helper|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\ImportExport\Model\ResourceModel\Helper|MockObject */ protected $resourceHelper; - /** @var \Magento\Catalog\Helper\Data|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\Catalog\Helper\Data|MockObject */ protected $_catalogData; - /** @var \Magento\ImportExport\Model\Import\Config|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\ImportExport\Model\Import\Config|MockObject */ protected $_importConfig; - /** @var \PHPUnit_Framework_MockObject_MockObject */ + /** @var MockObject */ protected $_resourceFactory; // @codingStandardsIgnoreStart - /** @var \Magento\Eav\Model\ResourceModel\Entity\Attribute\Set\CollectionFactory|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\Eav\Model\ResourceModel\Entity\Attribute\Set\CollectionFactory|MockObject */ protected $_setColFactory; - /** @var \Magento\CatalogImportExport\Model\Import\Product\Type\Factory|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\CatalogImportExport\Model\Import\Product\Type\Factory|MockObject */ protected $_productTypeFactory; - /** @var \Magento\Catalog\Model\ResourceModel\Product\LinkFactory|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\Catalog\Model\ResourceModel\Product\LinkFactory|MockObject */ protected $_linkFactory; - /** @var \Magento\CatalogImportExport\Model\Import\Proxy\ProductFactory|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\CatalogImportExport\Model\Import\Proxy\ProductFactory|MockObject */ protected $_proxyProdFactory; - /** @var \Magento\CatalogImportExport\Model\Import\UploaderFactory|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\CatalogImportExport\Model\Import\UploaderFactory|MockObject */ protected $_uploaderFactory; - /** @var \Magento\Framework\Filesystem|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\Framework\Filesystem|MockObject */ protected $_filesystem; - /** @var \Magento\Framework\Filesystem\Directory\WriteInterface|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\Framework\Filesystem\Directory\WriteInterface|MockObject */ protected $_mediaDirectory; - /** @var \Magento\CatalogInventory\Model\ResourceModel\Stock\ItemFactory|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\CatalogInventory\Model\ResourceModel\Stock\ItemFactory|MockObject */ protected $_stockResItemFac; - /** @var \Magento\Framework\Stdlib\DateTime\TimezoneInterface|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\Framework\Stdlib\DateTime\TimezoneInterface|MockObject */ protected $_localeDate; - /** @var \Magento\Framework\Indexer\IndexerRegistry|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\Framework\Indexer\IndexerRegistry|MockObject */ protected $indexerRegistry; - /** @var \Psr\Log\LoggerInterface|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Psr\Log\LoggerInterface|MockObject */ protected $_logger; - /** @var \Magento\CatalogImportExport\Model\Import\Product\StoreResolver|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\CatalogImportExport\Model\Import\Product\StoreResolver|MockObject */ protected $storeResolver; - /** @var \Magento\CatalogImportExport\Model\Import\Product\SkuProcessor|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\CatalogImportExport\Model\Import\Product\SkuProcessor|MockObject */ protected $skuProcessor; - /** @var \Magento\CatalogImportExport\Model\Import\Product\CategoryProcessor|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\CatalogImportExport\Model\Import\Product\CategoryProcessor|MockObject */ protected $categoryProcessor; - /** @var \Magento\CatalogImportExport\Model\Import\Product\Validator|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\CatalogImportExport\Model\Import\Product\Validator|MockObject */ protected $validator; - /** @var \Magento\Framework\Model\ResourceModel\Db\ObjectRelationProcessor|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\Framework\Model\ResourceModel\Db\ObjectRelationProcessor|MockObject */ protected $objectRelationProcessor; - /** @var \Magento\Framework\Model\ResourceModel\Db\TransactionManagerInterface|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\Framework\Model\ResourceModel\Db\TransactionManagerInterface|MockObject */ protected $transactionManager; - /** @var \Magento\CatalogImportExport\Model\Import\Product\TaxClassProcessor|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\CatalogImportExport\Model\Import\Product\TaxClassProcessor|MockObject */ // @codingStandardsIgnoreEnd protected $taxClassProcessor; - /** @var \Magento\CatalogImportExport\Model\Import\Product */ + /** @var Product */ protected $importProduct; /** @@ -153,13 +156,13 @@ class ProductTest extends \Magento\ImportExport\Test\Unit\Model\Import\AbstractI */ protected $errorAggregator; - /** @var \Magento\Framework\App\Config\ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject*/ + /** @var \Magento\Framework\App\Config\ScopeConfigInterface|MockObject */ protected $scopeConfig; - /** @var \Magento\Catalog\Model\Product\Url|\PHPUnit_Framework_MockObject_MockObject*/ + /** @var \Magento\Catalog\Model\Product\Url|MockObject */ protected $productUrl; - /** @var ImageTypeProcessor|\PHPUnit_Framework_MockObject_MockObject */ + /** @var ImageTypeProcessor|MockObject */ protected $imageTypeProcessor; /** @@ -343,7 +346,7 @@ protected function setUp() $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); $this->importProduct = $objectManager->getObject( - \Magento\CatalogImportExport\Model\Import\Product::class, + Product::class, [ 'jsonHelper' => $this->jsonHelper, 'importExportData' => $this->importExportData, @@ -385,7 +388,7 @@ protected function setUp() 'imageTypeProcessor' => $this->imageTypeProcessor ] ); - $reflection = new \ReflectionClass(\Magento\CatalogImportExport\Model\Import\Product::class); + $reflection = new \ReflectionClass(Product::class); $reflectionProperty = $reflection->getProperty('metadataPool'); $reflectionProperty->setAccessible(true); $reflectionProperty->setValue($this->importProduct, $metadataPoolMock); @@ -627,7 +630,7 @@ public function testGetEmptyAttributeValueConstantFromParameters() public function testDeleteProductsForReplacement() { - $importProduct = $this->getMockBuilder(\Magento\CatalogImportExport\Model\Import\Product::class) + $importProduct = $this->getMockBuilder(Product::class) ->disableOriginalConstructor() ->setMethods([ 'setParameters', '_deleteProducts' @@ -693,7 +696,7 @@ public function testValidateRowIsAlreadyValidated() */ public function testValidateRow($rowScope, $oldSku, $expectedResult, $behaviour = Import::BEHAVIOR_DELETE) { - $importProduct = $this->getMockBuilder(\Magento\CatalogImportExport\Model\Import\Product::class) + $importProduct = $this->getMockBuilder(Product::class) ->disableOriginalConstructor() ->setMethods(['getBehavior', 'getRowScope', 'getErrorAggregator']) ->getMock(); @@ -705,7 +708,7 @@ public function testValidateRow($rowScope, $oldSku, $expectedResult, $behaviour ->method('getErrorAggregator') ->willReturn($this->getErrorAggregatorObject()); $importProduct->expects($this->once())->method('getRowScope')->willReturn($rowScope); - $skuKey = \Magento\CatalogImportExport\Model\Import\Product::COL_SKU; + $skuKey = Product::COL_SKU; $rowData = [ $skuKey => 'sku', ]; @@ -717,18 +720,22 @@ public function testValidateRow($rowScope, $oldSku, $expectedResult, $behaviour public function testValidateRowDeleteBehaviourAddRowErrorCall() { - $importProduct = $this->getMockBuilder(\Magento\CatalogImportExport\Model\Import\Product::class) + $importProduct = $this->getMockBuilder(Product::class) ->disableOriginalConstructor() - ->setMethods(['getBehavior', 'getRowScope', 'addRowError']) + ->setMethods(['getBehavior', 'getRowScope', 'addRowError', 'getErrorAggregator']) ->getMock(); $importProduct->expects($this->exactly(2))->method('getBehavior') ->willReturn(\Magento\ImportExport\Model\Import::BEHAVIOR_DELETE); $importProduct->expects($this->once())->method('getRowScope') - ->willReturn(\Magento\CatalogImportExport\Model\Import\Product::SCOPE_DEFAULT); + ->willReturn(Product::SCOPE_DEFAULT); $importProduct->expects($this->once())->method('addRowError'); + $importProduct->method('getErrorAggregator') + ->willReturn( + $this->getErrorAggregatorObject(['addRowToSkip']) + ); $rowData = [ - \Magento\CatalogImportExport\Model\Import\Product::COL_SKU => 'sku', + Product::COL_SKU => 'sku', ]; $importProduct->validateRow($rowData, 0); @@ -739,7 +746,7 @@ public function testValidateRowValidatorCheck() $messages = ['validator message']; $this->validator->expects($this->once())->method('getMessages')->willReturn($messages); $rowData = [ - \Magento\CatalogImportExport\Model\Import\Product::COL_SKU => 'sku', + Product::COL_SKU => 'sku', ]; $rowNum = 0; $this->importProduct->validateRow($rowData, $rowNum); @@ -841,7 +848,7 @@ public function getStoreIdByCodeDataProvider() return [ [ '$storeCode' => null, - '$expectedResult' => \Magento\CatalogImportExport\Model\Import\Product::SCOPE_DEFAULT, + '$expectedResult' => Product::SCOPE_DEFAULT, ], [ '$storeCode' => 'value', @@ -856,17 +863,17 @@ public function getStoreIdByCodeDataProvider() public function testValidateRowCheckSpecifiedSku($sku, $expectedError) { $importProduct = $this->createModelMockWithErrorAggregator( - [ 'addRowError', 'getOptionEntity', 'getRowScope'], + ['addRowError', 'getOptionEntity', 'getRowScope'], ['isRowInvalid' => true] ); $rowNum = 0; $rowData = [ - \Magento\CatalogImportExport\Model\Import\Product::COL_SKU => $sku, - \Magento\CatalogImportExport\Model\Import\Product::COL_STORE => '', + Product::COL_SKU => $sku, + Product::COL_STORE => '', ]; - $this->storeResolver->expects($this->any())->method('getStoreCodeToId')->willReturn(null); + $this->storeResolver->method('getStoreCodeToId')->willReturn(null); $this->setPropertyValue($importProduct, 'storeResolver', $this->storeResolver); $this->setPropertyValue($importProduct, 'skuProcessor', $this->skuProcessor); @@ -875,7 +882,7 @@ public function testValidateRowCheckSpecifiedSku($sku, $expectedError) $importProduct ->expects($this->once()) ->method('getRowScope') - ->willReturn(\Magento\CatalogImportExport\Model\Import\Product::SCOPE_STORE); + ->willReturn(Product::SCOPE_STORE); $importProduct->expects($this->at(1))->method('addRowError')->with($expectedError, $rowNum)->willReturn(null); $importProduct->validateRow($rowData, $rowNum); @@ -889,7 +896,7 @@ public function testValidateRowProcessEntityIncrement() $errorAggregator->method('isRowInvalid')->willReturn(true); $this->setPropertyValue($this->importProduct, '_processedEntitiesCount', $count); $this->setPropertyValue($this->importProduct, 'errorAggregator', $errorAggregator); - $rowData = [\Magento\CatalogImportExport\Model\Import\Product::COL_SKU => false]; + $rowData = [Product::COL_SKU => false]; //suppress validator $this->_setValidatorMockInImportProduct($this->importProduct); $this->importProduct->validateRow($rowData, $rowNum); @@ -899,14 +906,14 @@ public function testValidateRowProcessEntityIncrement() public function testValidateRowValidateExistingProductTypeAddNewSku() { $importProduct = $this->createModelMockWithErrorAggregator( - [ 'addRowError', 'getOptionEntity'], + ['addRowError', 'getOptionEntity'], ['isRowInvalid' => true] ); $sku = 'sku'; $rowNum = 0; $rowData = [ - \Magento\CatalogImportExport\Model\Import\Product::COL_SKU => $sku, + Product::COL_SKU => $sku, ]; $oldSku = [ $sku => [ @@ -929,7 +936,7 @@ public function testValidateRowValidateExistingProductTypeAddNewSku() $this->setPropertyValue($importProduct, '_oldSku', $oldSku); $expectedData = [ - 'entity_id' => $oldSku[$sku]['entity_id'], //entity_id_val + 'entity_id' => $oldSku[$sku]['entity_id'], //entity_id_val 'type_id' => $oldSku[$sku]['type_id'],// type_id_val 'attr_set_id' => $oldSku[$sku]['attr_set_id'], //attr_set_id_val 'attr_set_code' => $_attrSetIdToName[$oldSku[$sku]['attr_set_id']],//attr_set_id_val @@ -947,7 +954,7 @@ public function testValidateRowValidateExistingProductTypeAddErrorRowCall() $sku = 'sku'; $rowNum = 0; $rowData = [ - \Magento\CatalogImportExport\Model\Import\Product::COL_SKU => $sku, + Product::COL_SKU => $sku, ]; $oldSku = [ $sku => [ @@ -972,6 +979,11 @@ public function testValidateRowValidateExistingProductTypeAddErrorRowCall() /** * @dataProvider validateRowValidateNewProductTypeAddRowErrorCallDataProvider + * @param string $colType + * @param string $productTypeModelsColType + * @param string $colAttrSet + * @param string $attrSetNameToIdColAttrSet + * @param string $error */ public function testValidateRowValidateNewProductTypeAddRowErrorCall( $colType, @@ -983,15 +995,15 @@ public function testValidateRowValidateNewProductTypeAddRowErrorCall( $sku = 'sku'; $rowNum = 0; $rowData = [ - \Magento\CatalogImportExport\Model\Import\Product::COL_SKU => $sku, - \Magento\CatalogImportExport\Model\Import\Product::COL_TYPE => $colType, - \Magento\CatalogImportExport\Model\Import\Product::COL_ATTR_SET => $colAttrSet, + Product::COL_SKU => $sku, + Product::COL_TYPE => $colType, + Product::COL_ATTR_SET => $colAttrSet, ]; $_attrSetNameToId = [ - $rowData[\Magento\CatalogImportExport\Model\Import\Product::COL_ATTR_SET] => $attrSetNameToIdColAttrSet, + $rowData[Product::COL_ATTR_SET] => $attrSetNameToIdColAttrSet, ]; $_productTypeModels = [ - $rowData[\Magento\CatalogImportExport\Model\Import\Product::COL_TYPE] => $productTypeModelsColType, + $rowData[Product::COL_TYPE] => $productTypeModelsColType, ]; $oldSku = [ $sku => null, @@ -1019,29 +1031,25 @@ public function testValidateRowValidateNewProductTypeGetNewSkuCall() $sku = 'sku'; $rowNum = 0; $rowData = [ - \Magento\CatalogImportExport\Model\Import\Product::COL_SKU => $sku, - \Magento\CatalogImportExport\Model\Import\Product::COL_TYPE => 'value', - \Magento\CatalogImportExport\Model\Import\Product::COL_ATTR_SET => 'value', + Product::COL_SKU => $sku, + Product::COL_TYPE => 'value', + Product::COL_ATTR_SET => 'value', ]; $_productTypeModels = [ - $rowData[\Magento\CatalogImportExport\Model\Import\Product::COL_TYPE] => 'value', + $rowData[Product::COL_TYPE] => 'value', ]; $oldSku = [ $sku => null, ]; $_attrSetNameToId = [ - $rowData[\Magento\CatalogImportExport\Model\Import\Product::COL_ATTR_SET] => 'attr_set_code_val' + $rowData[Product::COL_ATTR_SET] => 'attr_set_code_val' ]; $expectedData = [ 'entity_id' => null, - 'type_id' => $rowData[\Magento\CatalogImportExport\Model\Import\Product::COL_TYPE],//value + 'type_id' => $rowData[Product::COL_TYPE],//value //attr_set_id_val - 'attr_set_id' => $_attrSetNameToId[ - $rowData[ - \Magento\CatalogImportExport\Model\Import\Product::COL_ATTR_SET - ] - ], - 'attr_set_code' => $rowData[\Magento\CatalogImportExport\Model\Import\Product::COL_ATTR_SET],//value + 'attr_set_id' => $_attrSetNameToId[$rowData[Product::COL_ATTR_SET]], + 'attr_set_code' => $rowData[Product::COL_ATTR_SET],//value 'row_id' => null ]; $importProduct = $this->createModelMockWithErrorAggregator( @@ -1077,8 +1085,8 @@ public function testValidateRowSetAttributeSetCodeIntoRowData() $sku = 'sku'; $rowNum = 0; $rowData = [ - \Magento\CatalogImportExport\Model\Import\Product::COL_SKU => $sku, - \Magento\CatalogImportExport\Model\Import\Product::COL_ATTR_SET => 'col_attr_set_val', + Product::COL_SKU => $sku, + Product::COL_ATTR_SET => 'col_attr_set_val', ]; $expectedAttrSetCode = 'new_attr_set_code'; $newSku = [ @@ -1086,8 +1094,8 @@ public function testValidateRowSetAttributeSetCodeIntoRowData() 'type_id' => 'new_type_id_val', ]; $expectedRowData = [ - \Magento\CatalogImportExport\Model\Import\Product::COL_SKU => $sku, - \Magento\CatalogImportExport\Model\Import\Product::COL_ATTR_SET => $newSku['attr_set_code'], + Product::COL_SKU => $sku, + Product::COL_ATTR_SET => $newSku['attr_set_code'], ]; $oldSku = [ $sku => [ @@ -1121,8 +1129,8 @@ public function testValidateValidateOptionEntity() $sku = 'sku'; $rowNum = 0; $rowData = [ - \Magento\CatalogImportExport\Model\Import\Product::COL_SKU => $sku, - \Magento\CatalogImportExport\Model\Import\Product::COL_ATTR_SET => 'col_attr_set_val', + Product::COL_SKU => $sku, + Product::COL_ATTR_SET => 'col_attr_set_val', ]; $oldSku = [ $sku => [ @@ -1374,7 +1382,7 @@ public function validateRowDataProvider() { return [ [ - '$rowScope' => \Magento\CatalogImportExport\Model\Import\Product::SCOPE_DEFAULT, + '$rowScope' => Product::SCOPE_DEFAULT, '$oldSku' => null, '$expectedResult' => false, ], @@ -1389,12 +1397,12 @@ public function validateRowDataProvider() '$expectedResult' => true, ], [ - '$rowScope' => \Magento\CatalogImportExport\Model\Import\Product::SCOPE_DEFAULT, + '$rowScope' => Product::SCOPE_DEFAULT, '$oldSku' => true, '$expectedResult' => true, ], [ - '$rowScope' => \Magento\CatalogImportExport\Model\Import\Product::SCOPE_DEFAULT, + '$rowScope' => Product::SCOPE_DEFAULT, '$oldSku' => null, '$expectedResult' => false, '$behaviour' => Import::BEHAVIOR_REPLACE @@ -1415,7 +1423,7 @@ public function isAttributeValidAssertAttrValidDataProvider() '$rowData' => [ 'code' => str_repeat( 'a', - \Magento\CatalogImportExport\Model\Import\Product::DB_MAX_VARCHAR_LENGTH - 1 + Product::DB_MAX_VARCHAR_LENGTH - 1 ), ], ], @@ -1468,7 +1476,7 @@ public function isAttributeValidAssertAttrValidDataProvider() '$rowData' => [ 'code' => str_repeat( 'a', - \Magento\CatalogImportExport\Model\Import\Product::DB_MAX_TEXT_LENGTH - 1 + Product::DB_MAX_TEXT_LENGTH - 1 ), ], ], @@ -1488,7 +1496,7 @@ public function isAttributeValidAssertAttrInvalidDataProvider() '$rowData' => [ 'code' => str_repeat( 'a', - \Magento\CatalogImportExport\Model\Import\Product::DB_MAX_VARCHAR_LENGTH + 1 + Product::DB_MAX_VARCHAR_LENGTH + 1 ), ], ], @@ -1541,7 +1549,7 @@ public function isAttributeValidAssertAttrInvalidDataProvider() '$rowData' => [ 'code' => str_repeat( 'a', - \Magento\CatalogImportExport\Model\Import\Product::DB_MAX_TEXT_LENGTH + 1 + Product::DB_MAX_TEXT_LENGTH + 1 ), ], ], @@ -1553,8 +1561,8 @@ public function isAttributeValidAssertAttrInvalidDataProvider() */ public function getRowScopeDataProvider() { - $colSku = \Magento\CatalogImportExport\Model\Import\Product::COL_SKU; - $colStore = \Magento\CatalogImportExport\Model\Import\Product::COL_STORE; + $colSku = Product::COL_SKU; + $colStore = Product::COL_STORE; return [ [ @@ -1562,21 +1570,21 @@ public function getRowScopeDataProvider() $colSku => null, $colStore => 'store', ], - '$expectedResult' => \Magento\CatalogImportExport\Model\Import\Product::SCOPE_STORE + '$expectedResult' => Product::SCOPE_STORE ], [ '$rowData' => [ $colSku => 'sku', $colStore => null, ], - '$expectedResult' => \Magento\CatalogImportExport\Model\Import\Product::SCOPE_DEFAULT + '$expectedResult' => Product::SCOPE_DEFAULT ], [ '$rowData' => [ $colSku => 'sku', $colStore => 'store', ], - '$expectedResult' => \Magento\CatalogImportExport\Model\Import\Product::SCOPE_STORE + '$expectedResult' => Product::SCOPE_STORE ], ]; } @@ -1653,9 +1661,9 @@ protected function overrideMethod(&$object, $methodName, array $parameters = []) * * @see _rewriteGetOptionEntityInImportProduct() * @see _setValidatorMockInImportProduct() - * @param \Magento\CatalogImportExport\Model\Import\Product + * @param Product * Param should go with rewritten getOptionEntity method. - * @return \Magento\CatalogImportExport\Model\Import\Product\Option|\PHPUnit_Framework_MockObject_MockObject + * @return \Magento\CatalogImportExport\Model\Import\Product\Option|MockObject */ private function _suppressValidateRowOptionValidatorInvalidRows($importProduct) { @@ -1671,8 +1679,8 @@ private function _suppressValidateRowOptionValidatorInvalidRows($importProduct) * Used in group of validateRow method's tests. * Set validator mock in importProduct, return true for isValid method. * - * @param \Magento\CatalogImportExport\Model\Import\Product - * @return \Magento\CatalogImportExport\Model\Import\Product\Validator|\PHPUnit_Framework_MockObject_MockObject + * @param Product + * @return \Magento\CatalogImportExport\Model\Import\Product\Validator|MockObject */ private function _setValidatorMockInImportProduct($importProduct) { @@ -1686,9 +1694,9 @@ private function _setValidatorMockInImportProduct($importProduct) * Used in group of validateRow method's tests. * Make getOptionEntity return option mock. * - * @param \Magento\CatalogImportExport\Model\Import\Product + * @param Product * Param should go with rewritten getOptionEntity method. - * @return \Magento\CatalogImportExport\Model\Import\Product\Option|\PHPUnit_Framework_MockObject_MockObject + * @return \Magento\CatalogImportExport\Model\Import\Product\Option|MockObject */ private function _rewriteGetOptionEntityInImportProduct($importProduct) { @@ -1703,12 +1711,12 @@ private function _rewriteGetOptionEntityInImportProduct($importProduct) /** * @param array $methods * @param array $errorAggregatorMethods - * @return \PHPUnit_Framework_MockObject_MockObject + * @return MockObject */ protected function createModelMockWithErrorAggregator(array $methods = [], array $errorAggregatorMethods = []) { $methods[] = 'getErrorAggregator'; - $importProduct = $this->getMockBuilder(\Magento\CatalogImportExport\Model\Import\Product::class) + $importProduct = $this->getMockBuilder(Product::class) ->disableOriginalConstructor() ->setMethods($methods) ->getMock(); diff --git a/app/code/Magento/CatalogInventory/Model/ResourceModel/Stock/Item.php b/app/code/Magento/CatalogInventory/Model/ResourceModel/Stock/Item.php index ce8930ad4f7..edccad60231 100644 --- a/app/code/Magento/CatalogInventory/Model/ResourceModel/Stock/Item.php +++ b/app/code/Magento/CatalogInventory/Model/ResourceModel/Stock/Item.php @@ -263,6 +263,12 @@ public function updateLowStockDate(int $websiteId) $connection->update($this->getMainTable(), $value, $where); } + /** + * Get Manage Stock Expression + * + * @param string $tableAlias + * @return \Zend_Db_Expr + */ public function getManageStockExpr(string $tableAlias = ''): \Zend_Db_Expr { if ($tableAlias) { @@ -277,6 +283,12 @@ public function getManageStockExpr(string $tableAlias = ''): \Zend_Db_Expr return $manageStock; } + /** + * Get Backorders Expression + * + * @param string $tableAlias + * @return \Zend_Db_Expr + */ public function getBackordersExpr(string $tableAlias = ''): \Zend_Db_Expr { if ($tableAlias) { @@ -291,6 +303,12 @@ public function getBackordersExpr(string $tableAlias = ''): \Zend_Db_Expr return $itemBackorders; } + /** + * Get Minimum Sale Quantity Expression + * + * @param string $tableAlias + * @return \Zend_Db_Expr + */ public function getMinSaleQtyExpr(string $tableAlias = ''): \Zend_Db_Expr { if ($tableAlias) { diff --git a/app/code/Magento/CatalogInventory/Model/StockManagement.php b/app/code/Magento/CatalogInventory/Model/StockManagement.php index b3939f2e514..5d7d099dc01 100644 --- a/app/code/Magento/CatalogInventory/Model/StockManagement.php +++ b/app/code/Magento/CatalogInventory/Model/StockManagement.php @@ -85,6 +85,7 @@ public function __construct( /** * Subtract product qtys from stock. + * * Return array of items that require full save. * * @param string[] $items @@ -141,17 +142,25 @@ public function registerProductsSale($items, $websiteId = null) } /** - * @param string[] $items - * @param int $websiteId - * @return bool + * @inheritdoc */ public function revertProductsSale($items, $websiteId = null) { //if (!$websiteId) { $websiteId = $this->stockConfiguration->getDefaultScopeId(); //} - $this->qtyCounter->correctItemsQty($items, $websiteId, '+'); - return true; + $revertItems = []; + foreach ($items as $productId => $qty) { + $stockItem = $this->stockRegistryProvider->getStockItem($productId, $websiteId); + $canSubtractQty = $stockItem->getItemId() && $this->canSubtractQty($stockItem); + if (!$canSubtractQty || !$this->stockConfiguration->isQty($stockItem->getTypeId())) { + continue; + } + $revertItems[$productId] = $qty; + } + $this->qtyCounter->correctItemsQty($revertItems, $websiteId, '+'); + + return $revertItems; } /** @@ -195,6 +204,8 @@ protected function getProductType($productId) } /** + * Get stock resource. + * * @return ResourceStock */ protected function getResource() diff --git a/app/code/Magento/CatalogInventory/Observer/CancelOrderItemObserver.php b/app/code/Magento/CatalogInventory/Observer/CancelOrderItemObserver.php index 1e99794d68a..098e254d785 100644 --- a/app/code/Magento/CatalogInventory/Observer/CancelOrderItemObserver.php +++ b/app/code/Magento/CatalogInventory/Observer/CancelOrderItemObserver.php @@ -6,15 +6,21 @@ namespace Magento\CatalogInventory\Observer; -use Magento\Framework\Event\ObserverInterface; use Magento\CatalogInventory\Api\StockManagementInterface; +use Magento\CatalogInventory\Model\Configuration; use Magento\Framework\Event\Observer as EventObserver; +use Magento\Framework\Event\ObserverInterface; /** * Catalog inventory module observer */ class CancelOrderItemObserver implements ObserverInterface { + /** + * @var \Magento\CatalogInventory\Model\Configuration + */ + protected $configuration; + /** * @var StockManagementInterface */ @@ -26,13 +32,16 @@ class CancelOrderItemObserver implements ObserverInterface protected $priceIndexer; /** + * @param Configuration $configuration * @param StockManagementInterface $stockManagement * @param \Magento\Catalog\Model\Indexer\Product\Price\Processor $priceIndexer */ public function __construct( + Configuration $configuration, StockManagementInterface $stockManagement, \Magento\Catalog\Model\Indexer\Product\Price\Processor $priceIndexer ) { + $this->configuration = $configuration; $this->stockManagement = $stockManagement; $this->priceIndexer = $priceIndexer; } @@ -49,7 +58,8 @@ public function execute(EventObserver $observer) $item = $observer->getEvent()->getItem(); $children = $item->getChildrenItems(); $qty = $item->getQtyOrdered() - max($item->getQtyShipped(), $item->getQtyInvoiced()) - $item->getQtyCanceled(); - if ($item->getId() && $item->getProductId() && empty($children) && $qty) { + if ($item->getId() && $item->getProductId() && empty($children) && $qty && $this->configuration + ->getCanBackInStock()) { $this->stockManagement->backItemQty($item->getProductId(), $qty, $item->getStore()->getWebsiteId()); } $this->priceIndexer->reindexRow($item->getProductId()); diff --git a/app/code/Magento/CatalogInventory/Observer/RevertQuoteInventoryObserver.php b/app/code/Magento/CatalogInventory/Observer/RevertQuoteInventoryObserver.php index 93a50cc9a7a..ab21f32b3f6 100644 --- a/app/code/Magento/CatalogInventory/Observer/RevertQuoteInventoryObserver.php +++ b/app/code/Magento/CatalogInventory/Observer/RevertQuoteInventoryObserver.php @@ -64,8 +64,8 @@ public function execute(EventObserver $observer) { $quote = $observer->getEvent()->getQuote(); $items = $this->productQty->getProductQty($quote->getAllItems()); - $this->stockManagement->revertProductsSale($items, $quote->getStore()->getWebsiteId()); - $productIds = array_keys($items); + $revertedItems = $this->stockManagement->revertProductsSale($items, $quote->getStore()->getWebsiteId()); + $productIds = array_keys($revertedItems); if (!empty($productIds)) { $this->stockIndexerProcessor->reindexList($productIds); $this->priceIndexer->reindexList($productIds); diff --git a/app/code/Magento/CatalogInventory/etc/di.xml b/app/code/Magento/CatalogInventory/etc/di.xml index ace72bb11c3..8d57fab843f 100644 --- a/app/code/Magento/CatalogInventory/etc/di.xml +++ b/app/code/Magento/CatalogInventory/etc/di.xml @@ -111,7 +111,7 @@ <argument name="batchSizeManagement" xsi:type="object">Magento\CatalogInventory\Model\Indexer\Stock\BatchSizeManagement</argument> </arguments> </type> - <type name="\Magento\Framework\Data\CollectionModifier"> + <type name="Magento\Framework\Data\CollectionModifier"> <arguments> <argument name="conditions" xsi:type="array"> <item name="stockStatusCondition" xsi:type="object">Magento\CatalogInventory\Model\ProductCollectionStockCondition</item> diff --git a/app/code/Magento/CatalogRule/Model/Indexer/AbstractIndexer.php b/app/code/Magento/CatalogRule/Model/Indexer/AbstractIndexer.php index 5d93e6f2168..6b7c12dfdf4 100644 --- a/app/code/Magento/CatalogRule/Model/Indexer/AbstractIndexer.php +++ b/app/code/Magento/CatalogRule/Model/Indexer/AbstractIndexer.php @@ -10,6 +10,9 @@ use Magento\Framework\DataObject\IdentityInterface; use Magento\Framework\Indexer\CacheContext; +/** + * Abstract class for CatalogRule indexers. + */ abstract class AbstractIndexer implements IndexerActionInterface, MviewActionInterface, IdentityInterface { /** @@ -66,7 +69,6 @@ public function executeFull() { $this->indexBuilder->reindexFull(); $this->_eventManager->dispatch('clean_cache_by_tags', ['object' => $this]); - //TODO: remove after fix fpc. MAGETWO-50668 $this->getCacheManager()->clean($this->getIdentities()); } @@ -137,8 +139,9 @@ public function executeRow($id) abstract protected function doExecuteRow($id); /** - * @return \Magento\Framework\App\CacheInterface|mixed + * Get cache manager * + * @return \Magento\Framework\App\CacheInterface|mixed * @deprecated 100.0.7 */ private function getCacheManager() diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/CatalogPriceRuleAndCustomerGroupMembershipArePersistedUnderLongTermCookieTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/CatalogPriceRuleAndCustomerGroupMembershipArePersistedUnderLongTermCookieTest.xml index cac7c94de44..e3eac52a8d4 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Test/CatalogPriceRuleAndCustomerGroupMembershipArePersistedUnderLongTermCookieTest.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/CatalogPriceRuleAndCustomerGroupMembershipArePersistedUnderLongTermCookieTest.xml @@ -64,7 +64,7 @@ <amOnPage url="{{StorefrontCategoryPage.url($$createCategory.name$$)}}" stepKey="onStorefrontCategoryPage"/> <see selector="{{StorefrontCategoryProductSection.ProductPriceByNumber('1')}}" userInput="$$createProduct.price$$" stepKey="checkPriceSimpleProduct"/> - <!--Login to storfront from customer and check price--> + <!--Login to storefront from customer and check price--> <actionGroup ref="LoginToStorefrontActionGroup" stepKey="logInFromCustomer"> <argument name="Customer" value="$$createCustomer$$"/> </actionGroup> diff --git a/app/code/Magento/CatalogRule/etc/db_schema.xml b/app/code/Magento/CatalogRule/etc/db_schema.xml index 6f7d713e49f..894f057ba73 100644 --- a/app/code/Magento/CatalogRule/etc/db_schema.xml +++ b/app/code/Magento/CatalogRule/etc/db_schema.xml @@ -23,7 +23,7 @@ <column xsi:type="int" name="sort_order" padding="10" unsigned="true" nullable="false" identity="false" default="0" comment="Sort Order"/> <column xsi:type="varchar" name="simple_action" nullable="true" length="32" comment="Simple Action"/> - <column xsi:type="decimal" name="discount_amount" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="discount_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Discount Amount"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="rule_id"/> @@ -49,7 +49,7 @@ default="0" comment="Product Id"/> <column xsi:type="varchar" name="action_operator" nullable="true" length="10" default="to_fixed" comment="Action Operator"/> - <column xsi:type="decimal" name="action_amount" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="action_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Action Amount"/> <column xsi:type="smallint" name="action_stop" padding="6" unsigned="false" nullable="false" identity="false" default="0" comment="Action Stop"/> diff --git a/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Aggregation/DataProvider.php b/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Aggregation/DataProvider.php index 15856bbee74..66f5ad7a719 100644 --- a/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Aggregation/DataProvider.php +++ b/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Aggregation/DataProvider.php @@ -16,8 +16,11 @@ use Magento\Framework\DB\Select; use Magento\Framework\Search\Adapter\Mysql\Aggregation\DataProviderInterface; use Magento\Framework\Search\Request\BucketInterface; +use Magento\Framework\Event\Manager; /** + * Data Provider for catalog search. + * * @deprecated * @see \Magento\ElasticSearch */ @@ -43,12 +46,18 @@ class DataProvider implements DataProviderInterface */ private $selectBuilderForAttribute; + /** + * @var Manager + */ + private $eventManager; + /** * @param Config $eavConfig * @param ResourceConnection $resource * @param ScopeResolverInterface $scopeResolver * @param null $customerSession @deprecated * @param SelectBuilderForAttribute|null $selectBuilderForAttribute + * @param Manager|null $eventManager * * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ @@ -57,17 +66,19 @@ public function __construct( ResourceConnection $resource, ScopeResolverInterface $scopeResolver, $customerSession, - SelectBuilderForAttribute $selectBuilderForAttribute = null + SelectBuilderForAttribute $selectBuilderForAttribute = null, + Manager $eventManager = null ) { $this->eavConfig = $eavConfig; $this->connection = $resource->getConnection(); $this->scopeResolver = $scopeResolver; $this->selectBuilderForAttribute = $selectBuilderForAttribute ?: ObjectManager::getInstance()->get(SelectBuilderForAttribute::class); + $this->eventManager = $eventManager ?: ObjectManager::getInstance()->get(Manager::class); } /** - * {@inheritdoc} + * @inheritdoc */ public function getDataSet( BucketInterface $bucket, @@ -83,13 +94,17 @@ public function getDataSet( 'main_table.entity_id = entities.entity_id', [] ); + $this->eventManager->dispatch( + 'catalogsearch_query_add_filter_after', + ['bucket' => $bucket, 'select' => $select] + ); $select = $this->selectBuilderForAttribute->build($select, $attribute, $currentScope); return $select; } /** - * {@inheritdoc} + * @inheritdoc */ public function execute(Select $select) { @@ -97,6 +112,8 @@ public function execute(Select $select) } /** + * Get select. + * * @return Select */ private function getSelect() diff --git a/app/code/Magento/CatalogSearch/Model/Layer/Filter/Attribute.php b/app/code/Magento/CatalogSearch/Model/Layer/Filter/Attribute.php index 7aac6e98fc0..794d0ac9715 100644 --- a/app/code/Magento/CatalogSearch/Model/Layer/Filter/Attribute.php +++ b/app/code/Magento/CatalogSearch/Model/Layer/Filter/Attribute.php @@ -91,12 +91,10 @@ protected function _getItemsData() return $this->itemDataBuilder->build(); } - $productSize = $productCollection->getSize(); - $options = $attribute->getFrontend() ->getSelectOptions(); foreach ($options as $option) { - $this->buildOptionData($option, $isAttributeFilterable, $optionsFacetedData, $productSize); + $this->buildOptionData($option, $isAttributeFilterable, $optionsFacetedData); } return $this->itemDataBuilder->build(); @@ -108,17 +106,16 @@ protected function _getItemsData() * @param array $option * @param boolean $isAttributeFilterable * @param array $optionsFacetedData - * @param int $productSize * @return void */ - private function buildOptionData($option, $isAttributeFilterable, $optionsFacetedData, $productSize) + private function buildOptionData($option, $isAttributeFilterable, $optionsFacetedData) { $value = $this->getOptionValue($option); if ($value === false) { return; } $count = $this->getOptionCount($value, $optionsFacetedData); - if ($isAttributeFilterable && (!$this->isOptionReducesResults($count, $productSize) || $count === 0)) { + if ($isAttributeFilterable && $count === 0) { return; } @@ -156,4 +153,12 @@ private function getOptionCount($value, $optionsFacetedData) ? (int)$optionsFacetedData[$value]['count'] : 0; } + + /** + * @inheritdoc + */ + protected function isOptionReducesResults($optionCount, $totalSize) + { + return $optionCount <= $totalSize; + } } diff --git a/app/code/Magento/CatalogSearch/Model/ResourceModel/Advanced/Collection.php b/app/code/Magento/CatalogSearch/Model/ResourceModel/Advanced/Collection.php index b4b15554f60..98c0f91668f 100644 --- a/app/code/Magento/CatalogSearch/Model/ResourceModel/Advanced/Collection.php +++ b/app/code/Magento/CatalogSearch/Model/ResourceModel/Advanced/Collection.php @@ -21,6 +21,7 @@ * * This collection should be refactored to not have dependencies on MySQL-specific implementation. * + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @api * @since 100.0.2 @@ -296,7 +297,7 @@ private function getSearchCriteriaBuilder() } /** - * Get fielter builder. + * Get filter builder. * * @return FilterBuilder */ diff --git a/app/code/Magento/CatalogSearch/Model/ResourceModel/Engine.php b/app/code/Magento/CatalogSearch/Model/ResourceModel/Engine.php index 49caede8c4a..93ae2c94e21 100644 --- a/app/code/Magento/CatalogSearch/Model/ResourceModel/Engine.php +++ b/app/code/Magento/CatalogSearch/Model/ResourceModel/Engine.php @@ -110,7 +110,9 @@ public function processAttributeValue($attribute, $value) && in_array($attribute->getFrontendInput(), ['text', 'textarea']) ) { $result = $value; - } elseif ($this->isTermFilterableAttribute($attribute)) { + } elseif ($this->isTermFilterableAttribute($attribute) + || ($attribute->getIsSearchable() && in_array($attribute->getFrontendInput(), ['select', 'multiselect'])) + ) { $result = ''; } @@ -119,6 +121,7 @@ public function processAttributeValue($attribute, $value) /** * Prepare index array as a string glued by separator + * * Support 2 level array gluing * * @param array $index diff --git a/app/code/Magento/CatalogSearch/Setup/Patch/Data/MySQLSearchDeprecationNotification.php b/app/code/Magento/CatalogSearch/Setup/Patch/Data/MySQLSearchDeprecationNotification.php index d3c5aa5aaa6..8fa9f56d784 100644 --- a/app/code/Magento/CatalogSearch/Setup/Patch/Data/MySQLSearchDeprecationNotification.php +++ b/app/code/Magento/CatalogSearch/Setup/Patch/Data/MySQLSearchDeprecationNotification.php @@ -25,6 +25,10 @@ class MySQLSearchDeprecationNotification implements \Magento\Framework\Setup\Pat */ private $notifier; + /** + * @param \Magento\Framework\Search\EngineResolverInterface $searchEngineResolver + * @param \Magento\Framework\Notification\NotifierInterface $notifier + */ public function __construct( \Magento\Framework\Search\EngineResolverInterface $searchEngineResolver, \Magento\Framework\Notification\NotifierInterface $notifier diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Section/StorefrontCatalogSearchMainSection.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Section/StorefrontCatalogSearchMainSection.xml index 8b35ef33361..667f08fea65 100644 --- a/app/code/Magento/CatalogSearch/Test/Mftf/Section/StorefrontCatalogSearchMainSection.xml +++ b/app/code/Magento/CatalogSearch/Test/Mftf/Section/StorefrontCatalogSearchMainSection.xml @@ -15,5 +15,6 @@ <element name="SuccessMsg" type="button" selector="div.message-success"/> <element name="productCount" type="text" selector="#toolbar-amount"/> <element name="message" type="text" selector="div.message div"/> + <element name="searchResults" type="block" selector="#maincontent .column.main"/> </section> </sections> diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/Adapter/Mysql/Aggregation/DataProviderTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/Adapter/Mysql/Aggregation/DataProviderTest.php index 85b1b136e78..e3cc3e1d183 100644 --- a/app/code/Magento/CatalogSearch/Test/Unit/Model/Adapter/Mysql/Aggregation/DataProviderTest.php +++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/Adapter/Mysql/Aggregation/DataProviderTest.php @@ -20,6 +20,7 @@ use Magento\Eav\Model\Entity\Attribute; use Magento\Catalog\Model\Product; use Magento\Framework\DB\Ddl\Table; +use Magento\Framework\Event\Manager; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -64,6 +65,11 @@ class DataProviderTest extends \PHPUnit\Framework\TestCase */ private $selectBuilderForAttribute; + /** + * @var Manager|\PHPUnit_Framework_MockObject_MockObject + */ + private $eventManager; + protected function setUp() { $this->eavConfigMock = $this->createMock(Config::class); @@ -73,12 +79,14 @@ protected function setUp() $this->adapterMock = $this->createMock(AdapterInterface::class); $this->resourceConnectionMock->expects($this->once())->method('getConnection')->willReturn($this->adapterMock); $this->selectBuilderForAttribute = $this->createMock(SelectBuilderForAttribute::class); + $this->eventManager = $this->createMock(Manager::class); $this->model = new DataProvider( $this->eavConfigMock, $this->resourceConnectionMock, $this->scopeResolverMock, $this->sessionMock, - $this->selectBuilderForAttribute + $this->selectBuilderForAttribute, + $this->eventManager ); } @@ -102,6 +110,7 @@ public function testGetDataSetUsesFrontendPriceIndexerTableIfAttributeIsPrice() $selectMock = $this->createMock(Select::class); $this->adapterMock->expects($this->atLeastOnce())->method('select')->willReturn($selectMock); + $this->eventManager->expects($this->once())->method('dispatch')->willReturn($selectMock); $tableMock = $this->createMock(Table::class); $this->model->getDataSet($bucketMock, ['scope' => $dimensionMock], $tableMock); @@ -129,6 +138,7 @@ public function testGetDataSetUsesFrontendPriceIndexerTableForDecimalAttributes( $selectMock = $this->createMock(Select::class); $this->selectBuilderForAttribute->expects($this->once())->method('build')->willReturn($selectMock); $this->adapterMock->expects($this->atLeastOnce())->method('select')->willReturn($selectMock); + $this->eventManager->expects($this->once())->method('dispatch')->willReturn($selectMock); $tableMock = $this->createMock(Table::class); $this->model->getDataSet($bucketMock, ['scope' => $dimensionMock], $tableMock); } diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/Layer/Filter/AttributeTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/Layer/Filter/AttributeTest.php index abc0fdd1069..69e2c33d02d 100644 --- a/app/code/Magento/CatalogSearch/Test/Unit/Model/Layer/Filter/AttributeTest.php +++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/Layer/Filter/AttributeTest.php @@ -321,10 +321,6 @@ public function testGetItemsWithoutApply() ->method('build') ->will($this->returnValue($builtData)); - $this->fulltextCollection->expects($this->once()) - ->method('getSize') - ->will($this->returnValue(50)); - $expectedFilterItems = [ $this->createFilterItem(0, $builtData[0]['label'], $builtData[0]['value'], $builtData[0]['count']), $this->createFilterItem(1, $builtData[1]['label'], $builtData[1]['value'], $builtData[1]['count']), @@ -383,9 +379,6 @@ public function testGetItemsOnlyWithResults() $this->fulltextCollection->expects($this->once()) ->method('getFacetedData') ->willReturn($facetedData); - $this->fulltextCollection->expects($this->once()) - ->method('getSize') - ->will($this->returnValue(50)); $this->itemDataBuilder->expects($this->once()) ->method('addItemData') diff --git a/app/code/Magento/CatalogUrlRewrite/Model/ResourceModel/Category/Product.php b/app/code/Magento/CatalogUrlRewrite/Model/ResourceModel/Category/Product.php index 685a9d28287..311cc6de761 100644 --- a/app/code/Magento/CatalogUrlRewrite/Model/ResourceModel/Category/Product.php +++ b/app/code/Magento/CatalogUrlRewrite/Model/ResourceModel/Category/Product.php @@ -8,6 +8,9 @@ use Magento\Framework\Model\ResourceModel\Db\AbstractDb; use Magento\UrlRewrite\Model\Storage\DbStorage; +/** + * Product Resource Class + */ class Product extends AbstractDb { /** @@ -38,6 +41,8 @@ protected function _construct() } /** + * Save multiple data + * * @param array $insertData * @return int */ @@ -70,7 +75,10 @@ public function removeMultiple(array $removeData) } /** - * Removes multiple entities from url_rewrite table using entities from catalog_url_rewrite_product_category + * Removes multiple data by filter + * + * Removes multiple entities from url_rewrite table + * using entities from catalog_url_rewrite_product_category * Example: $filter = ['category_id' => [1, 2, 3], 'product_id' => [1, 2, 3]] * * @param array $filter @@ -78,10 +86,7 @@ public function removeMultiple(array $removeData) */ public function removeMultipleByProductCategory(array $filter) { - return $this->getConnection()->delete( - $this->getTable(self::TABLE_NAME), - ['url_rewrite_id in (?)' => $this->prepareSelect($filter)] - ); + return $this->getConnection()->deleteFromSelect($this->prepareSelect($filter), self::TABLE_NAME); } /** diff --git a/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php b/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php index 022a78be001..9aaa3847768 100644 --- a/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php +++ b/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php @@ -135,6 +135,7 @@ class AfterImportDataObserver implements ObserverInterface 'url_path', 'name', 'visibility', + 'save_rewrites_history' ]; /** @@ -199,6 +200,7 @@ public function __construct( /** * Action after data import. + * * Save new url rewrites and remove old if exist. * * @param Observer $observer @@ -267,6 +269,8 @@ protected function _populateForUrlGeneration($rowData) } /** + * Add store id to product data. + * * @param \Magento\Catalog\Model\Product $product * @param array $rowData * @return void @@ -436,6 +440,8 @@ protected function currentUrlRewritesRegenerate() } /** + * Generate url-rewrite for outogenerated url-rewirte. + * * @param UrlRewrite $url * @param Category $category * @return array @@ -470,6 +476,8 @@ protected function generateForAutogenerated($url, $category) } /** + * Generate url-rewrite for custom url-rewirte. + * * @param UrlRewrite $url * @param Category $category * @return array @@ -503,6 +511,8 @@ protected function generateForCustom($url, $category) } /** + * Retrieve category from url metadata. + * * @param UrlRewrite $url * @return Category|null|bool */ @@ -517,6 +527,8 @@ protected function retrieveCategoryFromMetadata($url) } /** + * Check, category suited for url-rewrite generation. + * * @param \Magento\Catalog\Model\Category $category * @param int $storeId * @return bool diff --git a/app/code/Magento/CatalogUrlRewrite/Observer/CategoryProcessUrlRewriteSavingObserver.php b/app/code/Magento/CatalogUrlRewrite/Observer/CategoryProcessUrlRewriteSavingObserver.php index 5d7e323e8b2..3cfd49b1d21 100644 --- a/app/code/Magento/CatalogUrlRewrite/Observer/CategoryProcessUrlRewriteSavingObserver.php +++ b/app/code/Magento/CatalogUrlRewrite/Observer/CategoryProcessUrlRewriteSavingObserver.php @@ -100,16 +100,19 @@ public function execute(\Magento\Framework\Event\Observer $observer) } $mapsGenerated = false; - if ($category->dataHasChangedFor('url_key') - || $category->dataHasChangedFor('is_anchor') - || !empty($category->getChangedProductIds()) - ) { + if ($this->isCategoryHasChanged($category)) { if ($category->dataHasChangedFor('url_key')) { $categoryUrlRewriteResult = $this->categoryUrlRewriteGenerator->generate($category); $this->urlRewriteBunchReplacer->doBunchReplace($categoryUrlRewriteResult); } - $productUrlRewriteResult = $this->urlRewriteHandler->generateProductUrlRewrites($category); - $this->urlRewriteBunchReplacer->doBunchReplace($productUrlRewriteResult); + if ($this->isChangedOnlyProduct($category)) { + $productUrlRewriteResult = + $this->urlRewriteHandler->updateProductUrlRewritesForChangedProduct($category); + $this->urlRewriteBunchReplacer->doBunchReplace($productUrlRewriteResult); + } else { + $productUrlRewriteResult = $this->urlRewriteHandler->generateProductUrlRewrites($category); + $this->urlRewriteBunchReplacer->doBunchReplace($productUrlRewriteResult); + } $mapsGenerated = true; } @@ -119,6 +122,38 @@ public function execute(\Magento\Framework\Event\Observer $observer) } } + /** + * Check is category changed changed. + * + * @param Category $category + * @return bool + */ + private function isCategoryHasChanged(Category $category): bool + { + if ($category->dataHasChangedFor('url_key') + || $category->dataHasChangedFor('is_anchor') + || !empty($category->getChangedProductIds())) { + return true; + } + return false; + } + + /** + * Check is only product changed. + * + * @param Category $category + * @return bool + */ + private function isChangedOnlyProduct(Category $category): bool + { + if (!empty($category->getChangedProductIds()) + && !$category->dataHasChangedFor('is_anchor') + && !$category->dataHasChangedFor('url_key')) { + return true; + } + return false; + } + /** * In case store_id is not set for category then we can assume that it was passed through product import. * Store group must have only one root category, so receiving category's path and checking if one of it parts diff --git a/app/code/Magento/CatalogUrlRewrite/Observer/ProductToWebsiteChangeObserver.php b/app/code/Magento/CatalogUrlRewrite/Observer/ProductToWebsiteChangeObserver.php index fc2056e83ec..cacc761dbee 100644 --- a/app/code/Magento/CatalogUrlRewrite/Observer/ProductToWebsiteChangeObserver.php +++ b/app/code/Magento/CatalogUrlRewrite/Observer/ProductToWebsiteChangeObserver.php @@ -14,6 +14,9 @@ use Magento\UrlRewrite\Model\UrlPersistInterface; use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; +/** + * Observer to assign the products to website + */ class ProductToWebsiteChangeObserver implements ObserverInterface { /** @@ -69,12 +72,14 @@ public function execute(\Magento\Framework\Event\Observer $observer) $this->request->getParam('store_id', Store::DEFAULT_STORE_ID) ); - $this->urlPersist->deleteByData([ - UrlRewrite::ENTITY_ID => $product->getId(), - UrlRewrite::ENTITY_TYPE => ProductUrlRewriteGenerator::ENTITY_TYPE, - ]); - if ($product->getVisibility() != Visibility::VISIBILITY_NOT_VISIBLE) { - $this->urlPersist->replace($this->productUrlRewriteGenerator->generate($product)); + if (!empty($this->productUrlRewriteGenerator->generate($product))) { + $this->urlPersist->deleteByData([ + UrlRewrite::ENTITY_ID => $product->getId(), + UrlRewrite::ENTITY_TYPE => ProductUrlRewriteGenerator::ENTITY_TYPE, + ]); + if ($product->getVisibility() != Visibility::VISIBILITY_NOT_VISIBLE) { + $this->urlPersist->replace($this->productUrlRewriteGenerator->generate($product)); + } } } } diff --git a/app/code/Magento/CatalogUrlRewrite/Observer/UrlRewriteHandler.php b/app/code/Magento/CatalogUrlRewrite/Observer/UrlRewriteHandler.php index c4ec0bb3a74..b4a35f323e1 100644 --- a/app/code/Magento/CatalogUrlRewrite/Observer/UrlRewriteHandler.php +++ b/app/code/Magento/CatalogUrlRewrite/Observer/UrlRewriteHandler.php @@ -24,6 +24,8 @@ use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; /** + * Class for management url rewrites. + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class UrlRewriteHandler @@ -156,6 +158,30 @@ public function generateProductUrlRewrites(Category $category): array } /** + * Update product url rewrites for changed product. + * + * @param Category $category + * @return array + */ + public function updateProductUrlRewritesForChangedProduct(Category $category): array + { + $mergeDataProvider = clone $this->mergeDataProviderPrototype; + $this->isSkippedProduct[$category->getEntityId()] = []; + $saveRewriteHistory = (bool)$category->getData('save_rewrites_history'); + $storeIds = $this->getCategoryStoreIds($category); + + if ($category->getChangedProductIds()) { + foreach ($storeIds as $storeId) { + $this->generateChangedProductUrls($mergeDataProvider, $category, (int)$storeId, $saveRewriteHistory); + } + } + + return $mergeDataProvider->getData(); + } + + /** + * Delete category rewrites for children. + * * @param Category $category * @return void */ @@ -184,6 +210,8 @@ public function deleteCategoryRewritesForChildren(Category $category) } /** + * Get category products url rewrites. + * * @param Category $category * @param int $storeId * @param bool $saveRewriteHistory diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/AdminUrlForProductRewrittenCorrectlyTest.xml b/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/AdminUrlForProductRewrittenCorrectlyTest.xml new file mode 100644 index 00000000000..593df1c5bc6 --- /dev/null +++ b/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/AdminUrlForProductRewrittenCorrectlyTest.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="AdminUrlForProductRewrittenCorrectlyTest"> + <annotations> + <features value="CatalogUrlRewrite"/> + <title value="Check that URL for product rewritten correctly"/> + <description value="Check that URL for product rewritten correctly"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-97224"/> + <useCaseId value="MAGETWO-64191"/> + <group value="CatalogUrlRewrite"/> + </annotations> + + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + + <!--Create product--> + <createData entity="_defaultCategory" stepKey="category"/> + <createData entity="ApiSimpleProduct" stepKey="createProduct"> + <requiredEntity createDataKey="category"/> + </createData> + </before> + <after> + <!--Delete created data--> + <deleteData createDataKey="createProduct" stepKey="deleteSimpleProduct"/> + <deleteData createDataKey="category" stepKey="deleteCategory"/> + + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Open Created product--> + <amOnPage url="{{AdminProductEditPage.url($$createProduct.id$$)}}" stepKey="amOnEditPage"/> + <waitForPageLoad stepKey="waitForEditPage"/> + + <!--Switch to Default Store view--> + <actionGroup ref="SwitchToTheNewStoreView" stepKey="selectSecondStoreView"> + <argument name="storeViewName" value="Default Store View"/> + </actionGroup> + <waitForPageLoad stepKey="waitForStoreViewLoad"/> + + <!--Set use default url--> + <click selector="{{AdminProductSEOSection.sectionHeader}}" stepKey="clickOnSearchEngineOptimization"/> + <waitForElementVisible selector="{{AdminProductSEOSection.useDefaultUrl}}" stepKey="waitForUseDefaultUrlCheckbox"/> + <click selector="{{AdminProductSEOSection.useDefaultUrl}}" stepKey="clickUseDefaultUrlCheckbox"/> + <fillField selector="{{AdminProductSEOSection.urlKeyInput}}" userInput="$$createProduct.sku$$-new" stepKey="changeUrlKey"/> + <actionGroup ref="saveProductForm" stepKey="saveProduct"/> + + <!--Select product and go toUpdate Attribute page--> + <amOnPage url="{{ProductCatalogPage.url}}" stepKey="GoToCatalogPageChangingView"/> + <waitForPageLoad stepKey="WaitForPageToLoadFullyChangingView"/> + <actionGroup ref="filterProductGridByName" stepKey="filterBundleProductOptionsDownToName"> + <argument name="product" value="ApiSimpleProduct"/> + </actionGroup> + <click selector="{{AdminProductFiltersSection.allCheckbox}}" stepKey="ClickOnSelectAllCheckBoxChangingView"/> + <click selector="{{AdminProductGridSection.bulkActionDropdown}}" stepKey="clickActionDropdown"/> + <click selector="{{AdminProductGridSection.bulkActionOption('Update attributes')}}" stepKey="clickBulkUpdate"/> + <waitForPageLoad stepKey="waitForUpdateAttributesPageLoad"/> + <seeInCurrentUrl url="{{ProductAttributesEditPage.url}}" stepKey="seeInUrlAttributeUpdatePage"/> + <click selector="{{AdminUpdateAttributesWebsiteSection.website}}" stepKey="clickWebsiteTab"/> + <waitForAjaxLoad stepKey="waitForLoadWebSiteTab"/> + <click selector="{{AdminUpdateAttributesWebsiteSection.addProductToWebsite}}" stepKey="checkAddProductToWebsiteCheckbox"/> + <click selector="{{AdminUpdateAttributesSection.saveButton}}" stepKey="clickSave"/> + <see selector="{{AdminProductMessagesSection.successMessage}}" userInput="A total of 1 record(s) were updated." stepKey="seeSaveSuccess"/> + + <!--Got to Store front product page and check url--> + <amOnPage url="{{StorefrontProductPage.url($$createProduct.sku$$-new)}}" stepKey="navigateToSimpleProductPage"/> + <seeInCurrentUrl url="{{StorefrontProductPage.url($$createProduct.sku$$-new)}}" stepKey="seeProductNewUrl"/> + </test> +</tests> diff --git a/app/code/Magento/CatalogWidget/Block/Product/ProductsList.php b/app/code/Magento/CatalogWidget/Block/Product/ProductsList.php index 2b95de24d02..711d5a2da9f 100644 --- a/app/code/Magento/CatalogWidget/Block/Product/ProductsList.php +++ b/app/code/Magento/CatalogWidget/Block/Product/ProductsList.php @@ -6,10 +6,13 @@ namespace Magento\CatalogWidget\Block\Product; +use Magento\Catalog\Model\Product; use Magento\Framework\App\ObjectManager; +use Magento\Framework\App\ActionInterface; use Magento\Framework\DataObject\IdentityInterface; use Magento\Framework\Pricing\PriceCurrencyInterface; use Magento\Framework\Serialize\Serializer\Json; +use Magento\Framework\View\LayoutFactory; use Magento\Widget\Block\BlockInterface; use Magento\Framework\Url\EncoderInterface; @@ -96,11 +99,21 @@ class ProductsList extends \Magento\Catalog\Block\Product\AbstractProduct implem */ private $json; + /** + * @var LayoutFactory + */ + private $layoutFactory; + /** * @var \Magento\Framework\Url\EncoderInterface|null */ private $urlEncoder; + /** + * @var \Magento\Framework\View\Element\RendererList + */ + private $rendererListBlock; + /** * @param \Magento\Catalog\Block\Product\Context $context * @param \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $productCollectionFactory @@ -111,7 +124,10 @@ class ProductsList extends \Magento\Catalog\Block\Product\AbstractProduct implem * @param \Magento\Widget\Helper\Conditions $conditionsHelper * @param array $data * @param Json|null $json + * @param LayoutFactory|null $layoutFactory * @param \Magento\Framework\Url\EncoderInterface|null $urlEncoder + * + * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( \Magento\Catalog\Block\Product\Context $context, @@ -123,6 +139,7 @@ public function __construct( \Magento\Widget\Helper\Conditions $conditionsHelper, array $data = [], Json $json = null, + LayoutFactory $layoutFactory = null, EncoderInterface $urlEncoder = null ) { $this->productCollectionFactory = $productCollectionFactory; @@ -132,6 +149,7 @@ public function __construct( $this->rule = $rule; $this->conditionsHelper = $conditionsHelper; $this->json = $json ?: ObjectManager::getInstance()->get(Json::class); + $this->layoutFactory = $layoutFactory ?: ObjectManager::getInstance()->get(LayoutFactory::class); $this->urlEncoder = $urlEncoder ?: ObjectManager::getInstance()->get(EncoderInterface::class); parent::__construct( $context, @@ -228,6 +246,41 @@ public function getProductPriceHtml( return $price; } + /** + * @inheritdoc + */ + protected function getDetailsRendererList() + { + if (empty($this->rendererListBlock)) { + /** @var $layout \Magento\Framework\View\LayoutInterface */ + $layout = $this->layoutFactory->create(['cacheable' => false]); + $layout->getUpdate()->addHandle('catalog_widget_product_list')->load(); + $layout->generateXml(); + $layout->generateElements(); + + $this->rendererListBlock = $layout->getBlock('category.product.type.widget.details.renderers'); + } + return $this->rendererListBlock; + } + + /** + * Get post parameters. + * + * @param Product $product + * @return array + */ + public function getAddToCartPostParams(Product $product) + { + $url = $this->getAddToCartUrl($product); + return [ + 'action' => $url, + 'data' => [ + 'product' => $product->getEntityId(), + ActionInterface::PARAM_NAME_URL_ENCODED => $this->urlEncoder->encode($url), + ] + ]; + } + /** * @inheritdoc */ @@ -247,10 +300,16 @@ public function createCollection() { /** @var $collection \Magento\Catalog\Model\ResourceModel\Product\Collection */ $collection = $this->productCollectionFactory->create(); + + if ($this->getData('store_id') !== null) { + $collection->setStoreId($this->getData('store_id')); + } + $collection->setVisibility($this->catalogProductVisibility->getVisibleInCatalogIds()); $collection = $this->_addProductAttributesAndPrices($collection) ->addStoreFilter() + ->addAttributeToSort('created_at', 'desc') ->setPageSize($this->getPageSize()) ->setCurPage($this->getRequest()->getParam($this->getData('page_var_name'), 1)); diff --git a/app/code/Magento/CatalogWidget/Test/Mftf/Section/CatalogWidgetSection.xml b/app/code/Magento/CatalogWidget/Test/Mftf/Section/CatalogWidgetSection.xml index 2957cfdfe6f..855d325c985 100644 --- a/app/code/Magento/CatalogWidget/Test/Mftf/Section/CatalogWidgetSection.xml +++ b/app/code/Magento/CatalogWidget/Test/Mftf/Section/CatalogWidgetSection.xml @@ -21,5 +21,6 @@ <element name="conditionIs" type="button" selector="//*[@id='conditions__1--1__attribute']/following-sibling::span[1]"/> <element name="conditionOperator" type="button" selector="#conditions__1--{{arg3}}__operator" parameterized="true"/> <element name="checkElementStorefrontByPrice" type="button" selector="//*[@class='product-items widget-product-grid']//*[contains(text(),'${{arg4}}.00')]" parameterized="true"/> + <element name="checkElementStorefrontByName" type="button" selector="//*[@class='product-items widget-product-grid']//*[@class='product-item'][{{productPosition}}]//a[contains(text(), '{{productName}}')]" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/CatalogWidget/Test/Mftf/Test/CatalogProductListWidgetOrderTest.xml b/app/code/Magento/CatalogWidget/Test/Mftf/Test/CatalogProductListWidgetOrderTest.xml new file mode 100644 index 00000000000..11586207c4d --- /dev/null +++ b/app/code/Magento/CatalogWidget/Test/Mftf/Test/CatalogProductListWidgetOrderTest.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="CatalogProductListWidgetOrderTest"> + <annotations> + <features value="CatalogWidget"/> + <stories value="MC-5905: Wrong sorting on Products component"/> + <title value="Checking order of products in the 'catalog Products List' widget"/> + <description value="Check that products are ordered with recently added products first"/> + <severity value="MAJOR"/> + <testCaseId value="MC-13794"/> + <group value="CatalogWidget"/> + <group value="WYSIWYGDisabled"/> + <skip> + <issueId value="MC-13923"/> + </skip> + </annotations> + <before> + <createData entity="SimpleSubCategory" stepKey="simplecategory"/> + <createData entity="SimpleProduct" stepKey="createFirstProduct"> + <requiredEntity createDataKey="simplecategory"/> + <field key="price">10</field> + </createData> + <createData entity="SimpleProduct" stepKey="createSecondProduct"> + <requiredEntity createDataKey="simplecategory"/> + <field key="price">20</field> + </createData> + <createData entity="SimpleProduct" stepKey="createThirdProduct"> + <requiredEntity createDataKey="simplecategory"/> + <field key="price">30</field> + </createData> + <createData entity="_defaultCmsPage" stepKey="createPreReqPage"/> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <actionGroup ref="EnabledWYSIWYG" stepKey="enableWYSIWYG"/> + </before> + <!--Open created cms page--> + <comment userInput="Open created cms page" stepKey="commentOpenCreatedCmsPage"/> + <actionGroup ref="navigateToCreatedCMSPage" stepKey="navigateToCreatedCMSPage1"> + <argument name="CMSPage" value="$$createPreReqPage$$"/> + </actionGroup> + <!--Add widget to cms page--> + <comment userInput="Add widget to cms page" stepKey="commentAddWidgetToCmsPage"/> + <click selector="{{TinyMCESection.InsertWidgetIcon}}" stepKey="clickInsertWidgetIcon" /> + <waitForPageLoad stepKey="waitForPageLoad1" /> + <selectOption selector="{{WidgetSection.WidgetType}}" userInput="Catalog Products List" stepKey="selectCatalogProductsList" /> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskDisappear1" /> + <click selector="{{WidgetSection.AddParam}}" stepKey="clickAddParamBtn" /> + <waitForElementVisible selector="{{WidgetSection.ConditionsDropdown}}" stepKey="waitForDropdownVisible"/> + <selectOption selector="{{WidgetSection.ConditionsDropdown}}" userInput="Category" stepKey="selectCategoryCondition" /> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskDisappear2" /> + <click selector="{{WidgetSection.RuleParam}}" stepKey="clickRuleParam" /> + <waitForElementVisible selector="{{WidgetSection.Chooser}}" stepKey="waitForElement" /> + <click selector="{{WidgetSection.Chooser}}" stepKey="clickChooser" /> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskDisappear3" /> + <click selector="{{WidgetSection.PreCreateCategory('$$simplecategory.name$$')}}" stepKey="selectCategory" /> + <click selector="{{WidgetSection.InsertWidget}}" stepKey="clickInsertWidget" /> + <waitForPageLoad stepKey="waitForPageLoad2" /> + <!--Save cms page and go to Storefront--> + <comment userInput="Save cms page and go to Storefront" stepKey="commentSaveCmsPageAndGoToStorefront"/> + <click selector="{{CmsNewPagePageActionsSection.expandSplitButton}}" stepKey="expandButtonMenu"/> + <waitForElementVisible selector="{{CmsNewPagePageActionsSection.splitButtonMenu}}" stepKey="waitForSplitButtonMenuVisible"/> + <click selector="{{CmsNewPagePageActionsSection.savePage}}" stepKey="clickSavePage"/> + <see userInput="You saved the page." stepKey="seeSuccessMessage"/> + <amOnPage url="$$createPreReqPage.identifier$$" stepKey="amOnPageTestPage"/> + <waitForPageLoad stepKey="waitForPageLoad3" /> + <!--Check order of products: recently added first--> + <comment userInput="Check order of products: recently added first" stepKey="commentCheckOrderOfProductsRecentlyAddedFirst"/> + <seeElement selector="{{InsertWidgetSection.checkElementStorefrontByName('1','$$createThirdProduct.name$$')}}" stepKey="seeElementByName1"/> + <seeElement selector="{{InsertWidgetSection.checkElementStorefrontByName('2','$$createSecondProduct.name$$')}}" stepKey="seeElementByName2"/> + <seeElement selector="{{InsertWidgetSection.checkElementStorefrontByName('3','$$createFirstProduct.name$$')}}" stepKey="seeElementByName3"/> + <after> + <actionGroup ref="DisabledWYSIWYG" stepKey="disableWYSIWYG"/> + <deleteData createDataKey="createPreReqPage" stepKey="deletePreReqPage" /> + <deleteData createDataKey="simplecategory" stepKey="deleteSimpleCategory"/> + <deleteData createDataKey="createFirstProduct" stepKey="deleteFirstProduct"/> + <deleteData createDataKey="createSecondProduct" stepKey="deleteSecondProduct"/> + <deleteData createDataKey="createThirdProduct" stepKey="deleteThirdProduct"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/CatalogWidget/Test/Unit/Block/Product/ProductsListTest.php b/app/code/Magento/CatalogWidget/Test/Unit/Block/Product/ProductsListTest.php index 5de8b9d9632..dc6e100ab1a 100644 --- a/app/code/Magento/CatalogWidget/Test/Unit/Block/Product/ProductsListTest.php +++ b/app/code/Magento/CatalogWidget/Test/Unit/Block/Product/ProductsListTest.php @@ -274,6 +274,7 @@ public function testCreateCollection($pagerEnable, $productsCount, $productsPerP 'addAttributeToSelect', 'addUrlRewrite', 'addStoreFilter', + 'addAttributeToSort', 'setPageSize', 'setCurPage', 'distinct' @@ -288,6 +289,7 @@ public function testCreateCollection($pagerEnable, $productsCount, $productsPerP $collection->expects($this->once())->method('addAttributeToSelect')->willReturnSelf(); $collection->expects($this->once())->method('addUrlRewrite')->willReturnSelf(); $collection->expects($this->once())->method('addStoreFilter')->willReturnSelf(); + $collection->expects($this->once())->method('addAttributeToSort')->with('created_at', 'desc')->willReturnSelf(); $collection->expects($this->once())->method('setPageSize')->with($expectedPageSize)->willReturnSelf(); $collection->expects($this->once())->method('setCurPage')->willReturnSelf(); $collection->expects($this->once())->method('distinct')->willReturnSelf(); diff --git a/app/code/Magento/CatalogWidget/view/frontend/layout/catalog_widget_product_list.xml b/app/code/Magento/CatalogWidget/view/frontend/layout/catalog_widget_product_list.xml new file mode 100644 index 00000000000..db44d8b62dc --- /dev/null +++ b/app/code/Magento/CatalogWidget/view/frontend/layout/catalog_widget_product_list.xml @@ -0,0 +1,17 @@ +<!-- + ~ Copyright © Magento, Inc. All rights reserved. + ~ See COPYING.txt for license details. + --> + +<!-- + ~ 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> + <block class="Magento\Framework\View\Element\RendererList" name="category.product.type.widget.details.renderers"> + <block class="Magento\Framework\View\Element\Template" name="category.product.type.details.renderers.default" as="default"/> + </block> + </body> +</page> \ No newline at end of file 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 6789ace243b..29efe8a8c1c 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 @@ -3,13 +3,14 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +use Magento\Framework\App\Action\Action; // @codingStandardsIgnoreFile /** @var \Magento\CatalogWidget\Block\Product\ProductsList $block */ ?> <?php if ($exist = ($block->getProductCollection() && $block->getProductCollection()->getSize())): ?> -<?php + <?php $type = 'widget-product-grid'; $mode = 'grid'; @@ -20,14 +21,14 @@ $showWishlist = true; $showCompare = true; $showCart = true; - $templateType = \Magento\Catalog\Block\Product\ReviewRendererInterface::DEFAULT_VIEW; + $templateType = \Magento\Catalog\Block\Product\ReviewRendererInterface::SHORT_VIEW; $description = false; -?> + ?> <div class="block widget block-products-list <?= /* @noEscape */ $mode ?>"> <?php if ($block->getTitle()): ?> - <div class="block-title"> - <strong><?= $block->escapeHtml(__($block->getTitle())) ?></strong> - </div> + <div class="block-title"> + <strong><?= $block->escapeHtml(__($block->getTitle())) ?></strong> + </div> <?php endif ?> <div class="block-content"> <?= /* @noEscape */ '<!-- ' . $image . '-->' ?> @@ -48,57 +49,57 @@ <?= $block->escapeHtml($_item->getName()) ?> </a> </strong> - <?php - echo $block->getProductPriceHtml($_item, $type); - ?> - <?php if ($templateType): ?> <?= $block->getReviewsSummaryHtml($_item, $templateType) ?> <?php endif; ?> + <?= $block->getProductPriceHtml($_item, $type) ?> + + <?= $block->getProductDetailsHtml($_item) ?> + <?php if ($showWishlist || $showCompare || $showCart): ?> - <div class="product-item-actions"> - <?php if ($showCart): ?> - <div class="actions-primary"> - <?php if ($_item->isSaleable()): ?> - <?php if (!$_item->getTypeInstance()->isPossibleBuyFromList($_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> + <div class="product-item-inner"> + <div class="product-item-actions"> + <?php if ($showCart): ?> + <div class="actions-primary"> + <?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] ?>"> + <?= $block->getBlockHtml('formkey') ?> + <button type="submit" + title="<?= $block->escapeHtml(__('Add to Cart')) ?>" + class="action tocart primary"> + <span><?= /* @escapeNotVerified */ __('Add to Cart') ?></span> + </button> + </form> <?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='<?= /* @noEscape */ $postData ?>' type="button" title="<?= $block->escapeHtmlAttr(__('Add to Cart')) ?>"> - <span><?= $block->escapeHtml(__('Add to Cart')) ?></span> - </button> + <?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; ?> - <?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> + </div> + <?php endif; ?> + <?php if ($showWishlist || $showCompare): ?> + <div class="actions-secondary" data-role="add-to-links"> + <?php if ($this->helper('Magento\Wishlist\Helper\Data')->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');?> + <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> <?php endif; ?> - <?php endif; ?> - </div> - <?php endif; ?> - <?php if ($showWishlist || $showCompare): ?> - <div class="actions-secondary" data-role="add-to-links"> - <?php if ($this->helper('Magento\Wishlist\Helper\Data')->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');?> - <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> - <?php endif; ?> - </div> - <?php endif; ?> + </div> + <?php endif; ?> + </div> </div> <?php endif; ?> </div> diff --git a/app/code/Magento/Checkout/Block/Cart/Sidebar.php b/app/code/Magento/Checkout/Block/Cart/Sidebar.php index 92ba6bf2bbb..c5e309df3ca 100644 --- a/app/code/Magento/Checkout/Block/Cart/Sidebar.php +++ b/app/code/Magento/Checkout/Block/Cart/Sidebar.php @@ -82,11 +82,14 @@ public function getConfig() 'baseUrl' => $this->getBaseUrl(), 'minicartMaxItemsVisible' => $this->getMiniCartMaxItemsCount(), 'websiteId' => $this->_storeManager->getStore()->getWebsiteId(), - 'maxItemsToDisplay' => $this->getMaxItemsToDisplay() + 'maxItemsToDisplay' => $this->getMaxItemsToDisplay(), + 'storeId' => $this->_storeManager->getStore()->getId() ]; } /** + * Get serialized config + * * @return string * @since 100.2.0 */ @@ -96,6 +99,8 @@ public function getSerializedConfig() } /** + * Get image html template + * * @return string */ public function getImageHtmlTemplate() @@ -130,6 +135,7 @@ public function getShoppingCartUrl() * * @return string * @codeCoverageIgnore + * @SuppressWarnings(PHPMD.RequestAwareBlockMethod) */ public function getUpdateItemQtyUrl() { @@ -141,6 +147,7 @@ public function getUpdateItemQtyUrl() * * @return string * @codeCoverageIgnore + * @SuppressWarnings(PHPMD.RequestAwareBlockMethod) */ public function getRemoveItemUrl() { @@ -210,6 +217,7 @@ private function getMiniCartMaxItemsCount() /** * Returns maximum cart items to display + * * This setting regulates how many items will be displayed in minicart * * @return int diff --git a/app/code/Magento/Checkout/Block/Checkout/AttributeMerger.php b/app/code/Magento/Checkout/Block/Checkout/AttributeMerger.php index de996bed024..a20c146d68d 100644 --- a/app/code/Magento/Checkout/Block/Checkout/AttributeMerger.php +++ b/app/code/Magento/Checkout/Block/Checkout/AttributeMerger.php @@ -6,10 +6,18 @@ namespace Magento\Checkout\Block\Checkout; use Magento\Customer\Api\CustomerRepositoryInterface as CustomerRepository; +use Magento\Customer\Api\Data\CustomerInterface; use Magento\Customer\Helper\Address as AddressHelper; use Magento\Customer\Model\Session; use Magento\Directory\Helper\Data as DirectoryHelper; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Exception\NoSuchEntityException; +/** + * Fields attribute merger. + * + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) + */ class AttributeMerger { /** @@ -46,6 +54,7 @@ class AttributeMerger 'alpha' => 'validate-alpha', 'numeric' => 'validate-number', 'alphanumeric' => 'validate-alphanum', + 'alphanum-with-spaces' => 'validate-alphanum-with-spaces', 'url' => 'validate-url', 'email' => 'email2', 'length' => 'validate-length', @@ -67,7 +76,7 @@ class AttributeMerger private $customerRepository; /** - * @var \Magento\Customer\Api\Data\CustomerInterface + * @var CustomerInterface */ private $customer; @@ -309,10 +318,14 @@ protected function getMultilineFieldConfig($attributeCode, array $attributeConfi } /** + * Returns default attribute value. + * * @param string $attributeCode + * @throws NoSuchEntityException + * @throws LocalizedException * @return null|string */ - protected function getDefaultValue($attributeCode) + protected function getDefaultValue($attributeCode): ?string { if ($attributeCode === 'country_id') { return $this->directoryHelper->getDefaultCountry(); @@ -346,9 +359,13 @@ protected function getDefaultValue($attributeCode) } /** - * @return \Magento\Customer\Api\Data\CustomerInterface|null + * Returns logged customer. + * + * @throws NoSuchEntityException + * @throws LocalizedException + * @return CustomerInterface|null */ - protected function getCustomer() + protected function getCustomer(): ?CustomerInterface { if (!$this->customer) { if ($this->customerSession->isLoggedIn()) { diff --git a/app/code/Magento/Checkout/Block/Onepage.php b/app/code/Magento/Checkout/Block/Onepage.php index ca6b045ddbb..e01d5835b4c 100644 --- a/app/code/Magento/Checkout/Block/Onepage.php +++ b/app/code/Magento/Checkout/Block/Onepage.php @@ -38,7 +38,7 @@ class Onepage extends \Magento\Framework\View\Element\Template protected $layoutProcessors; /** - * @var \Magento\Framework\Serialize\Serializer\Json + * @var \Magento\Framework\Serialize\SerializerInterface */ private $serializer; @@ -48,8 +48,9 @@ class Onepage extends \Magento\Framework\View\Element\Template * @param \Magento\Checkout\Model\CompositeConfigProvider $configProvider * @param array $layoutProcessors * @param array $data - * @param \Magento\Framework\Serialize\Serializer\Json|null $serializer - * @throws \RuntimeException + * @param \Magento\Framework\Serialize\Serializer\Json $serializer + * @param \Magento\Framework\Serialize\SerializerInterface $serializerInterface + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function __construct( \Magento\Framework\View\Element\Template\Context $context, @@ -57,7 +58,8 @@ public function __construct( \Magento\Checkout\Model\CompositeConfigProvider $configProvider, array $layoutProcessors = [], array $data = [], - \Magento\Framework\Serialize\Serializer\Json $serializer = null + \Magento\Framework\Serialize\Serializer\Json $serializer = null, + \Magento\Framework\Serialize\SerializerInterface $serializerInterface = null ) { parent::__construct($context, $data); $this->formKey = $formKey; @@ -65,12 +67,12 @@ public function __construct( $this->jsLayout = isset($data['jsLayout']) && is_array($data['jsLayout']) ? $data['jsLayout'] : []; $this->configProvider = $configProvider; $this->layoutProcessors = $layoutProcessors; - $this->serializer = $serializer ?: \Magento\Framework\App\ObjectManager::getInstance() - ->get(\Magento\Framework\Serialize\Serializer\Json::class); + $this->serializer = $serializerInterface ?: \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Magento\Framework\Serialize\Serializer\JsonHexTag::class); } /** - * @return string + * @inheritdoc */ public function getJsLayout() { @@ -78,7 +80,7 @@ public function getJsLayout() $this->jsLayout = $processor->process($this->jsLayout); } - return json_encode($this->jsLayout, JSON_HEX_TAG); + return $this->serializer->serialize($this->jsLayout); } /** @@ -115,11 +117,13 @@ public function getBaseUrl() } /** + * Retrieve serialized checkout config. + * * @return bool|string * @since 100.2.0 */ public function getSerializedCheckoutConfig() { - return json_encode($this->getCheckoutConfig(), JSON_HEX_TAG); + return $this->serializer->serialize($this->getCheckoutConfig()); } } diff --git a/app/code/Magento/Checkout/Block/QuoteShortcutButtons.php b/app/code/Magento/Checkout/Block/QuoteShortcutButtons.php index 6b3774f7e38..3b2f1604fae 100644 --- a/app/code/Magento/Checkout/Block/QuoteShortcutButtons.php +++ b/app/code/Magento/Checkout/Block/QuoteShortcutButtons.php @@ -8,6 +8,8 @@ use Magento\Framework\View\Element\Template; /** + * Displays buttons on shopping cart page + * * @api */ class QuoteShortcutButtons extends \Magento\Catalog\Block\ShortcutButtons @@ -45,7 +47,8 @@ protected function _beforeToHtml() 'container' => $this, 'is_catalog_product' => $this->_isCatalogProduct, 'or_position' => $this->_orPosition, - 'checkout_session' => $this->_checkoutSession + 'checkout_session' => $this->_checkoutSession, + 'is_shopping_cart' => true ] ); return $this; diff --git a/app/code/Magento/Checkout/CustomerData/Cart.php b/app/code/Magento/Checkout/CustomerData/Cart.php index 01e91d75c02..169be4cc62f 100644 --- a/app/code/Magento/Checkout/CustomerData/Cart.php +++ b/app/code/Magento/Checkout/CustomerData/Cart.php @@ -10,6 +10,8 @@ /** * Cart source + * + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) */ class Cart extends \Magento\Framework\DataObject implements SectionSourceInterface { @@ -98,7 +100,8 @@ public function getSectionData() 'items' => $this->getRecentItems(), 'extra_actions' => $this->layout->createBlock(\Magento\Catalog\Block\ShortcutButtons::class)->toHtml(), 'isGuestCheckoutAllowed' => $this->isGuestCheckoutAllowed(), - 'website_id' => $this->getQuote()->getStore()->getWebsiteId() + 'website_id' => $this->getQuote()->getStore()->getWebsiteId(), + 'storeId' => $this->getQuote()->getStore()->getStoreId() ]; } diff --git a/app/code/Magento/Checkout/Model/PaymentInformationManagement.php b/app/code/Magento/Checkout/Model/PaymentInformationManagement.php index d2bd680aa38..e0de45a3f0d 100644 --- a/app/code/Magento/Checkout/Model/PaymentInformationManagement.php +++ b/app/code/Magento/Checkout/Model/PaymentInformationManagement.php @@ -118,7 +118,9 @@ public function savePaymentInformation( $shippingAddress = $quote->getShippingAddress(); if ($shippingAddress && $shippingAddress->getShippingMethod()) { $shippingRate = $shippingAddress->getShippingRateByCode($shippingAddress->getShippingMethod()); - $shippingAddress->setLimitCarrier($shippingRate->getCarrier()); + $shippingAddress->setLimitCarrier( + $shippingRate ? $shippingRate->getCarrier() : $shippingAddress->getShippingMethod() + ); } } $this->paymentMethodManagement->set($cartId, $paymentMethod); diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/CheckoutActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/CheckoutActionGroup.xml index 54a4c6fd1b6..b67b7451d59 100644 --- a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/CheckoutActionGroup.xml +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/CheckoutActionGroup.xml @@ -17,6 +17,7 @@ <!-- Go to checkout from minicart --> <actionGroup name="GoToCheckoutFromMinicartActionGroup"> <waitForElementNotVisible selector="{{StorefrontMinicartSection.emptyCart}}" stepKey="waitUpdateQuantity" /> + <wait time="5" stepKey="waitMinicartRendering"/> <click selector="{{StorefrontMinicartSection.showCart}}" stepKey="clickCart"/> <click selector="{{StorefrontMinicartSection.goToCheckout}}" stepKey="goToCheckout"/> </actionGroup> @@ -114,8 +115,8 @@ <!-- Logged in user checkout filling shipping section --> <actionGroup name="LoggedInUserCheckoutFillingShippingSectionActionGroup"> <arguments> - <argument name="customerVar"/> - <argument name="customerAddressVar"/> + <argument name="customerVar" defaultValue="CustomerEntityOne"/> + <argument name="customerAddressVar" defaultValue="CustomerAddressSimple"/> </arguments> <fillField selector="{{CheckoutShippingSection.firstName}}" userInput="{{customerVar.firstname}}" stepKey="enterFirstName"/> <fillField selector="{{CheckoutShippingSection.lastName}}" userInput="{{customerVar.lastname}}" stepKey="enterLastName"/> @@ -187,16 +188,16 @@ <!-- Check order summary in checkout --> <actionGroup name="CheckOrderSummaryInCheckoutActionGroup"> <arguments> - <argument name="subtotal"/> - <argument name="shippingTotal"/> - <argument name="shippingMethod"/> - <argument name="total"/> + <argument name="subtotal" type="string"/> + <argument name="shippingTotal" type="string"/> + <argument name="shippingMethod" type="string"/> + <argument name="total" type="string"/> </arguments> <waitForElement selector="{{CheckoutPaymentSection.paymentSectionTitle}}" time="30" stepKey="waitForPaymentSectionLoaded"/> - <see userInput="${{subtotal}}" selector="{{CheckoutPaymentSection.orderSummarySubtotal}}" stepKey="assertSubtotal"/> - <see userInput="${{shippingTotal}}" selector="{{CheckoutPaymentSection.orderSummaryShippingTotal}}" stepKey="assertShipping"/> + <see userInput="{{subtotal}}" selector="{{CheckoutPaymentSection.orderSummarySubtotal}}" stepKey="assertSubtotal"/> + <see userInput="{{shippingTotal}}" selector="{{CheckoutPaymentSection.orderSummaryShippingTotal}}" stepKey="assertShipping"/> <see userInput="{{shippingMethod}}" selector="{{CheckoutPaymentSection.orderSummaryShippingMethod}}" stepKey="assertShippingMethod"/> - <see userInput="${{total}}" selector="{{CheckoutPaymentSection.orderSummaryTotal}}" stepKey="assertTotal"/> + <see userInput="{{total}}" selector="{{CheckoutPaymentSection.orderSummaryTotal}}" stepKey="assertTotal"/> </actionGroup> <actionGroup name="CheckTotalsSortOrderInSummarySection"> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/FillShippingZipFormActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/FillShippingZipFormActionGroup.xml index 83f6b635bfe..f12bf4344ab 100644 --- a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/FillShippingZipFormActionGroup.xml +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/FillShippingZipFormActionGroup.xml @@ -13,6 +13,8 @@ <argument name="address"/> </arguments> <conditionalClick stepKey="openShippingDetails" selector="{{CheckoutCartSummarySection.shippingHeading}}" dependentSelector="{{CheckoutCartSummarySection.country}}" visible="false"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMask"/> + <waitForElementVisible selector="{{CheckoutCartSummarySection.country}}" time="30" stepKey="waitForCountryFieldAppears"/> <selectOption stepKey="selectCountry" selector="{{CheckoutCartSummarySection.country}}" userInput="{{address.country}}"/> <selectOption stepKey="selectStateProvince" selector="{{CheckoutCartSummarySection.stateProvince}}" userInput="{{address.state}}"/> <fillField stepKey="fillPostCode" selector="{{CheckoutCartSummarySection.postcode}}" userInput="{{address.postcode}}"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/IdentityOfDefaultBillingAndShippingAddressActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/IdentityOfDefaultBillingAndShippingAddressActionGroup.xml index 6e5f127eefc..15c157a9826 100644 --- a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/IdentityOfDefaultBillingAndShippingAddressActionGroup.xml +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/IdentityOfDefaultBillingAndShippingAddressActionGroup.xml @@ -5,9 +5,9 @@ * See COPYING.txt for license details. */ --> -<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <!--Assert That Shipping And Billing Address are the same--> <actionGroup name="AssertThatShippingAndBillingAddressTheSame"> <!--Get shipping and billing addresses--> @@ -18,5 +18,4 @@ <see userInput="Billing Address" stepKey="seeBillingAddress"/> <assertEquals stepKey="assert" actual="$billingAddr" expected="$shippingAddr"/> </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 72c5648991e..24ed05583b6 100644 --- a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontProductCartActionGroup.xml +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontProductCartActionGroup.xml @@ -78,20 +78,20 @@ <!-- Check the Cart --> <actionGroup name="StorefrontCheckCartActionGroup"> <arguments> - <argument name="subtotal"/> - <argument name="shipping"/> - <argument name="shippingMethod"/> - <argument name="total"/> + <argument name="subtotal" type="string"/> + <argument name="shipping" type="string"/> + <argument name="shippingMethod" type="string"/> + <argument name="total" type="string"/> </arguments> <seeInCurrentUrl url="{{CheckoutCartPage.url}}" stepKey="assertUrl"/> <waitForPageLoad stepKey="waitForCartPage"/> <conditionalClick selector="{{CheckoutCartSummarySection.shippingHeading}}" dependentSelector="{{CheckoutCartSummarySection.shippingMethodForm}}" visible="false" stepKey="openEstimateShippingSection"/> <waitForElementVisible selector="{{CheckoutCartSummarySection.flatRateShippingMethod}}" stepKey="waitForShippingSection"/> <checkOption selector="{{CheckoutCartSummarySection.flatRateShippingMethod}}" stepKey="selectShippingMethod"/> - <see userInput="${{subtotal}}" selector="{{CheckoutCartSummarySection.subtotal}}" stepKey="assertSubtotal"/> + <see userInput="{{subtotal}}" selector="{{CheckoutCartSummarySection.subtotal}}" stepKey="assertSubtotal"/> <see userInput="({{shippingMethod}})" selector="{{CheckoutCartSummarySection.shippingMethod}}" stepKey="assertShippingMethod"/> - <waitForText userInput="${{shipping}}" selector="{{CheckoutCartSummarySection.shipping}}" time="45" stepKey="assertShipping"/> - <see userInput="${{total}}" selector="{{CheckoutCartSummarySection.total}}" stepKey="assertTotal"/> + <waitForText userInput="{{shipping}}" selector="{{CheckoutCartSummarySection.shipping}}" time="45" stepKey="assertShipping"/> + <see userInput="{{total}}" selector="{{CheckoutCartSummarySection.total}}" stepKey="assertTotal"/> </actionGroup> <!-- Open the Cart from Minicart--> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontShippmentFromActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontShippmentFromActionGroup.xml index 354ad6d2b44..d3d96cb9c74 100644 --- a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontShippmentFromActionGroup.xml +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontShippmentFromActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <!--Fill shipment form for free shipping--> <actionGroup name="ShipmentFormFreeShippingActionGroup"> <fillField selector="{{CheckoutShippingSection.email}}" userInput="{{CustomerEntityOne.email}}" stepKey="setCustomerEmail"/> @@ -26,4 +26,4 @@ <waitForPageLoad time="5" stepKey="waitForReviewAndPaymentsPageIsLoaded"/> <seeInCurrentUrl url="payment" stepKey="reviewAndPaymentIsShown"/> </actionGroup> -</actionGroups> \ No newline at end of file +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/Data/CountryData.xml b/app/code/Magento/Checkout/Test/Mftf/Data/CountryData.xml index dc82932ec5c..7fc349bf9f0 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Data/CountryData.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Data/CountryData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="Countries" type="countryArray"> <array key="country"> <item>Bahamas</item> @@ -35,4 +35,4 @@ <item>United Kingdom</item> </array> </entity> -</entities> \ No newline at end of file +</entities> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartProductSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartProductSection.xml index ab82d9fdd93..dcfb12fd4e9 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartProductSection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartProductSection.xml @@ -26,10 +26,12 @@ 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="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"/> <element name="nthBundleOptionName" type="text" selector=".product-item-details .item-options:nth-of-type({{numOption}}) dt" parameterized="true"/> <element name="productSubtotalByName" type="input" selector="//main//table[@id='shopping-cart-table']//tbody//tr[..//strong[contains(@class, 'product-item-name')]//a/text()='{{var1}}'][1]//td[contains(@class, 'subtotal')]//span[@class='price']" parameterized="true"/> <element name="updateShoppingCartButton" type="button" selector="#form-validate button[type='submit'].update" timeout="30"/> + <element name="qty" type="input" selector="//input[@data-cart-item-id='{{var}}'][@title='Qty']" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartSummarySection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartSummarySection.xml index d84df3401ba..8d14a9a5619 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartSummarySection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartSummarySection.xml @@ -23,6 +23,6 @@ <element name="country" type="select" selector="select[name='country_id']" timeout="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="radio" selector="#s_method_flatrate_flatrate" timeout="30"/> + <element name="flatRateShippingMethod" type="input" selector="#s_method_flatrate_flatrate" timeout="30"/> </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 ad2a43eb90c..6838824400b 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingGuestInfoSection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingGuestInfoSection.xml @@ -19,5 +19,9 @@ <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"/> + + <!--Order Summary--> + <element name="itemInCart" type="button" selector="//div[@class='title']"/> + <element name="productName" type="text" selector="//strong[@class='product-item-name']"/> </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 34819f641cb..bc65f8a2c08 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutSuccessMainSection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutSuccessMainSection.xml @@ -16,6 +16,7 @@ <element name="orderLink" type="text" selector="a[href*=order_id].order-number" timeout="30"/> <element name="orderNumberText" type="text" selector=".checkout-success > p:nth-child(1)"/> <element name="continueShoppingButton" type="button" selector=".action.primary.continue" timeout="30"/> + <element name="createAnAccount" type="button" selector="input[value='Create an Account']" timeout="30"/> <element name="printLink" type="button" selector=".print" timeout="30"/> </section> </sections> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutSuccessRegisterSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutSuccessRegisterSection.xml index d0ef8d347ef..0d692e4ab14 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutSuccessRegisterSection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutSuccessRegisterSection.xml @@ -12,5 +12,6 @@ <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="orderNumber" type="text" selector="//p[text()='Your order # is: ']//span"/> </section> </sections> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/IdentityOfDefaultBillingAndShippingAddressSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/IdentityOfDefaultBillingAndShippingAddressSection.xml index 89b3a25b45e..2039128ac2d 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/IdentityOfDefaultBillingAndShippingAddressSection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/IdentityOfDefaultBillingAndShippingAddressSection.xml @@ -7,8 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> - + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="ShipmentFormSection"> <element name="shippingAddress" type="textarea" selector="//*[@class='box box-billing-address']//address"/> <element name="billingAddress" type="textarea" selector="//*[@class='box box-shipping-address']//address"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMiniCartSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMiniCartSection.xml index 3f717943fe8..bdb02835c62 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMiniCartSection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMiniCartSection.xml @@ -13,6 +13,7 @@ <element name="productLinkByName" type="button" selector="//header//ol[@id='mini-cart']//div[@class='product-item-details']//a[contains(text(), '{{var1}}')]" parameterized="true"/> <element name="productPriceByName" type="text" selector="//header//ol[@id='mini-cart']//div[@class='product-item-details'][.//a[contains(text(), '{{var1}}')]]//span[@class='price']" parameterized="true"/> <element name="productImageByName" type="text" selector="//header//ol[@id='mini-cart']//span[@class='product-image-container']//img[@alt='{{var1}}']" parameterized="true"/> + <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"/> @@ -29,5 +30,6 @@ <element name="itemDiscount" type="text" selector="//tr[@class='totals']//td[@class='amount']/span"/> <element name="subtotal" type="text" selector="//tr[@class='totals sub']//td[@class='amount']/span"/> <element name="emptyCart" type="text" selector=".counter.qty.empty"/> + <element name="minicartContent" type="block" selector="#minicart-content-wrapper"/> </section> </sections> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/CheckoutSpecificDestinationsTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/CheckoutSpecificDestinationsTest.xml index 269ca94b3f7..f3807388399 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/CheckoutSpecificDestinationsTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/CheckoutSpecificDestinationsTest.xml @@ -1,86 +1,86 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> -<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> - <test name="CheckoutSpecificDestinationsTest"> - <annotations> - <title value="Check that top destinations can be removed after a selection was previously saved"/> - <stories value="MAGETWO-91511: Top destinations cannot be removed after a selection was previously saved"/> - <description value="Check that top destinations can be removed after a selection was previously saved"/> - <features value="Checkout"/> - <severity value="AVERAGE"/> - <testCaseId value="MAGETWO-94195"/> - <group value="Checkout"/> - </annotations> - - <before> - <createData entity="_defaultCategory" stepKey="defaultCategory"/> - <createData entity="_defaultProduct" stepKey="simpleProduct"> - <requiredEntity createDataKey="defaultCategory"/> - </createData> - - <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> - </before> - - <!--Go to configuration general page--> - <actionGroup ref="NavigateToConfigurationGeneralPage" stepKey="navigateToConfigurationGeneralPage"/> - - <!--Open country options section--> - <conditionalClick selector="{{CountryOptionsSection.countryOptions}}" dependentSelector="{{CountryOptionsSection.countryOptionsOpen}}" visible="false" stepKey="clickOnStoreInformation"/> - - <!--Select top destinations country--> - <actionGroup ref="SelectTopDestinationsCountry" stepKey="selectTopDestinationsCountry"> - <argument name="countries" value="Countries"/> - </actionGroup> - - <!--Go to product page--> - <amOnPage url="{{StorefrontProductPage.url($$simpleProduct.name$$)}}" stepKey="amOnStorefrontProductPage"/> - <waitForPageLoad stepKey="waitForProductPageLoad"/> - - <!--Add product to cart--> - <actionGroup ref="StorefrontAddToCartCustomOptionsProductPageActionGroup" stepKey="addToCart"> - <argument name="productName" value="$$simpleProduct.name$$"/> - </actionGroup> - - <!--Go to shopping cart--> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="amOnPageShoppingCart"/> - - <!--Verify country options in checkout top destination section--> - <actionGroup ref="VerifyTopDestinationsCountry" stepKey="verifyTopDestinationsCountry"> - <argument name="country" value="Bahamas"/> - <argument name="placeNumber" value="2"/> - </actionGroup> - - <!--Go to configuration general page--> - <actionGroup ref="NavigateToConfigurationGeneralPage" stepKey="navigateToConfigurationGeneralPage2"/> - - <!--Open country options section--> - <conditionalClick selector="{{CountryOptionsSection.countryOptions}}" dependentSelector="{{CountryOptionsSection.countryOptionsOpen}}" visible="false" stepKey="clickOnStoreInformation2"/> - - <!--Deselect top destinations country--> - <actionGroup ref="UnSelectTopDestinationsCountry" stepKey="unSelectTopDestinationsCountry"> - <argument name="countries" value="Countries"/> - </actionGroup> - - <!--Go to shopping cart--> - <amOnPage url="{{CheckoutCartPage.url}}" stepKey="amOnPageShoppingCart2"/> - - <!--Verify country options is shown by default--> - <actionGroup ref="VerifyTopDestinationsCountry" stepKey="verifyTopDestinationsCountry2"> - <argument name="country" value="Afghanistan"/> - <argument name="placeNumber" value="2"/> - </actionGroup> - - <after> - <actionGroup ref="logout" stepKey="logout"/> - - <deleteData createDataKey="simpleProduct" stepKey="deleteProduct"/> - <deleteData createDataKey="defaultCategory" stepKey="deleteCategory"/> - </after> - </test> -</tests> \ No newline at end of file +<?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="CheckoutSpecificDestinationsTest"> + <annotations> + <title value="Check that top destinations can be removed after a selection was previously saved"/> + <stories value="MAGETWO-91511: Top destinations cannot be removed after a selection was previously saved"/> + <description value="Check that top destinations can be removed after a selection was previously saved"/> + <features value="Checkout"/> + <severity value="AVERAGE"/> + <testCaseId value="MAGETWO-94195"/> + <group value="Checkout"/> + </annotations> + + <before> + <createData entity="_defaultCategory" stepKey="defaultCategory"/> + <createData entity="_defaultProduct" stepKey="simpleProduct"> + <requiredEntity createDataKey="defaultCategory"/> + </createData> + + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + + <!--Go to configuration general page--> + <actionGroup ref="NavigateToConfigurationGeneralPage" stepKey="navigateToConfigurationGeneralPage"/> + + <!--Open country options section--> + <conditionalClick selector="{{CountryOptionsSection.countryOptions}}" dependentSelector="{{CountryOptionsSection.countryOptionsOpen}}" visible="false" stepKey="clickOnStoreInformation"/> + + <!--Select top destinations country--> + <actionGroup ref="SelectTopDestinationsCountry" stepKey="selectTopDestinationsCountry"> + <argument name="countries" value="Countries"/> + </actionGroup> + + <!--Go to product page--> + <amOnPage url="{{StorefrontProductPage.url($$simpleProduct.name$$)}}" stepKey="amOnStorefrontProductPage"/> + <waitForPageLoad stepKey="waitForProductPageLoad"/> + + <!--Add product to cart--> + <actionGroup ref="StorefrontAddToCartCustomOptionsProductPageActionGroup" stepKey="addToCart"> + <argument name="productName" value="$$simpleProduct.name$$"/> + </actionGroup> + + <!--Go to shopping cart--> + <amOnPage url="{{CheckoutCartPage.url}}" stepKey="amOnPageShoppingCart"/> + + <!--Verify country options in checkout top destination section--> + <actionGroup ref="VerifyTopDestinationsCountry" stepKey="verifyTopDestinationsCountry"> + <argument name="country" value="Bahamas"/> + <argument name="placeNumber" value="2"/> + </actionGroup> + + <!--Go to configuration general page--> + <actionGroup ref="NavigateToConfigurationGeneralPage" stepKey="navigateToConfigurationGeneralPage2"/> + + <!--Open country options section--> + <conditionalClick selector="{{CountryOptionsSection.countryOptions}}" dependentSelector="{{CountryOptionsSection.countryOptionsOpen}}" visible="false" stepKey="clickOnStoreInformation2"/> + + <!--Deselect top destinations country--> + <actionGroup ref="UnSelectTopDestinationsCountry" stepKey="unSelectTopDestinationsCountry"> + <argument name="countries" value="Countries"/> + </actionGroup> + + <!--Go to shopping cart--> + <amOnPage url="{{CheckoutCartPage.url}}" stepKey="amOnPageShoppingCart2"/> + + <!--Verify country options is shown by default--> + <actionGroup ref="VerifyTopDestinationsCountry" stepKey="verifyTopDestinationsCountry2"> + <argument name="country" value="Afghanistan"/> + <argument name="placeNumber" value="2"/> + </actionGroup> + + <after> + <actionGroup ref="logout" stepKey="logout"/> + + <deleteData createDataKey="simpleProduct" stepKey="deleteProduct"/> + <deleteData createDataKey="defaultCategory" stepKey="deleteCategory"/> + </after> + </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 35e0058440f..5335ec2ad77 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/EndToEndB2CGuestUserTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/EndToEndB2CGuestUserTest.xml @@ -96,14 +96,10 @@ <comment userInput="Check cart information" stepKey="commentCheckCartInformation" after="cartMinicartAssertSimpleProduct2PageImageNotDefault" /> <actionGroup ref="StorefrontOpenCartFromMinicartActionGroup" stepKey="cartOpenCart" after="commentCheckCartInformation"/> <actionGroup ref="StorefrontCheckCartActionGroup" stepKey="cartAssertCart" after="cartOpenCart"> - <!-- @TODO: Change to scalar value after MQE-498 is implemented --> - <argument name="subtotal" value="E2EB2CQuote.subtotal"/> - <!-- @TODO: Change to scalar value after MQE-498 is implemented --> - <argument name="shipping" value="E2EB2CQuote.shipping"/> - <!-- @TODO: Change to scalar value after MQE-498 is implemented --> - <argument name="shippingMethod" value="E2EB2CQuote.shippingMethod"/> - <!-- @TODO: Change to scalar value after MQE-498 is implemented --> - <argument name="total" value="E2EB2CQuote.total"/> + <argument name="subtotal" value="480.00"/> + <argument name="shipping" value="15.00"/> + <argument name="shippingMethod" value="Flat Rate - Fixed"/> + <argument name="total" value="495.00"/> </actionGroup> <!-- Check simple product 1 in cart --> @@ -157,14 +153,10 @@ <!-- Check order summary in checkout --> <comment userInput="Check order summary in checkout" stepKey="commentCheckOrderSummaryInCheckout" after="guestCheckoutFillingShippingSection" /> <actionGroup ref="CheckOrderSummaryInCheckoutActionGroup" stepKey="guestCheckoutCheckOrderSummary" after="commentCheckOrderSummaryInCheckout"> - <!-- @TODO: Change to scalar value after MQE-498 is implemented --> - <argument name="subtotal" value="{{E2EB2CQuote.subtotal}}"/> - <!-- @TODO: Change to scalar value after MQE-498 is implemented --> - <argument name="shippingTotal" value="{{E2EB2CQuote.shipping}}"/> - <!-- @TODO: Change to scalar value after MQE-498 is implemented --> - <argument name="shippingMethod" value="{{E2EB2CQuote.shippingMethod}}"/> - <!-- @TODO: Change to scalar value after MQE-498 is implemented --> - <argument name="total" value="{{E2EB2CQuote.total}}"/> + <argument name="subtotal" value="480.00"/> + <argument name="shippingTotal" value="15.00"/> + <argument name="shippingMethod" value="Flat Rate - Fixed"/> + <argument name="total" value="495.00"/> </actionGroup> <!-- Check ship to information in checkout --> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml index 371826c9e78..65627787e2a 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml @@ -96,14 +96,10 @@ <comment userInput="Check cart information" stepKey="commentCheckCartInformation" after="cartMinicartAssertSimpleProduct2PageImageNotDefault" /> <actionGroup ref="StorefrontOpenCartFromMinicartActionGroup" stepKey="cartOpenCart" after="commentCheckCartInformation"/> <actionGroup ref="StorefrontCheckCartActionGroup" stepKey="cartAssertCart" after="cartOpenCart"> - <!-- @TODO: Change to scalar value after MQE-498 is implemented --> - <argument name="subtotal" value="E2EB2CQuote.subtotal"/> - <!-- @TODO: Change to scalar value after MQE-498 is implemented --> - <argument name="shipping" value="E2EB2CQuote.shipping"/> - <!-- @TODO: Change to scalar value after MQE-498 is implemented --> - <argument name="shippingMethod" value="E2EB2CQuote.shippingMethod"/> - <!-- @TODO: Change to scalar value after MQE-498 is implemented --> - <argument name="total" value="E2EB2CQuote.total"/> + <argument name="subtotal" value="480.00"/> + <argument name="shipping" value="15.00"/> + <argument name="shippingMethod" value="Flat Rate - Fixed"/> + <argument name="total" value="495.00"/> </actionGroup> <!-- Check simple product 1 in cart --> @@ -157,14 +153,10 @@ <!-- Check order summary in checkout --> <comment userInput="Check order summary in checkout" stepKey="commentCheckOrderSummaryInCheckout" after="checkoutFillingShippingSection" /> <actionGroup ref="CheckOrderSummaryInCheckoutActionGroup" stepKey="checkoutCheckOrderSummary" after="commentCheckOrderSummaryInCheckout"> - <!-- @TODO: Change to scalar value after MQE-498 is implemented --> - <argument name="subtotal" value="{{E2EB2CQuote.subtotal}}"/> - <!-- @TODO: Change to scalar value after MQE-498 is implemented --> - <argument name="shippingTotal" value="{{E2EB2CQuote.shipping}}"/> - <!-- @TODO: Change to scalar value after MQE-498 is implemented --> - <argument name="shippingMethod" value="{{E2EB2CQuote.shippingMethod}}"/> - <!-- @TODO: Change to scalar value after MQE-498 is implemented --> - <argument name="total" value="{{E2EB2CQuote.total}}"/> + <argument name="subtotal" value="480.00"/> + <argument name="shippingTotal" value="15.00"/> + <argument name="shippingMethod" value="Flat Rate - Fixed"/> + <argument name="total" value="495.00"/> </actionGroup> <!-- Check ship to information in checkout --> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/IdentityOfDefaultBillingAndShippingAddressTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/IdentityOfDefaultBillingAndShippingAddressTest.xml index 9664ec47420..89028e146c3 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/IdentityOfDefaultBillingAndShippingAddressTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/IdentityOfDefaultBillingAndShippingAddressTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="IdentityOfDefaultBillingAndShippingAddressTest"> <annotations> <features value="Customer"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StoreFrontCheckCustomerInfoCreatedByGuestTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StoreFrontCheckCustomerInfoCreatedByGuestTest.xml new file mode 100644 index 00000000000..693c05684f2 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StoreFrontCheckCustomerInfoCreatedByGuestTest.xml @@ -0,0 +1,62 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StoreFrontCheckCustomerInfoCreatedByGuestTest"> + <annotations> + <features value="Checkout"/> + <stories value="Check customer information created by guest"/> + <title value="Check Customer Information Created By Guest"/> + <description value="Check customer information after placing the order as the guest who created an account"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-95932"/> + <useCaseId value="MAGETWO-95820"/> + <group value="checkout"/> + </annotations> + + <before> + <createData entity="_defaultCategory" stepKey="category"/> + <createData entity="_defaultProduct" stepKey="product"> + <requiredEntity createDataKey="category"/> + </createData> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </before> + + <after> + <deleteData createDataKey="product" stepKey="deleteProduct" /> + <deleteData createDataKey="category" stepKey="deleteCategory" /> + <actionGroup ref="logout" stepKey="logoutFromAdmin"/> + </after> + + <amOnPage url="$$product.name$$.html" stepKey="navigateToProductPage"/> + <waitForPageLoad stepKey="waitForProductPage"/> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addToCartFromStorefrontProductPage"> + <argument name="productName" value="$$product.name$$"/> + </actionGroup> + <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="goToCheckoutFromMinicart"/> + <actionGroup ref="GuestCheckoutFillingShippingSectionActionGroup" stepKey="guestCheckoutFillingShippingSection"> + <argument name="customerVar" value="CustomerEntityOne"/> + <argument name="customerAddressVar" value="CustomerAddressSimple"/> + </actionGroup> + <actionGroup ref="CheckoutPlaceOrderActionGroup" stepKey="placeOrder"> + <argument name="orderNumberMessage" value="CONST.successGuestCheckoutOrderNumberMessage"/> + <argument name="emailYouMessage" value="CONST.successCheckoutEmailYouMessage" /> + </actionGroup> + <grabTextFrom selector="{{CheckoutSuccessRegisterSection.orderNumber}}" stepKey="grabOrderNumber"/> + <click selector="{{CheckoutSuccessRegisterSection.createAccountButton}}" stepKey="clickCreateAccountButton"/> + <fillField selector="{{StorefrontCustomerCreateFormSection.passwordField}}" userInput="{{CustomerEntityOne.password}}" stepKey="TypePassword"/> + <fillField selector="{{StorefrontCustomerCreateFormSection.confirmPasswordField}}" userInput="{{CustomerEntityOne.password}}" stepKey="TypeConfirmationPassword"/> + <click selector="{{StorefrontCustomerCreateFormSection.createAccountButton}}" stepKey="clickOnCreateAccount"/> + <see userInput="Thank you for registering" stepKey="verifyAccountCreated"/> + <actionGroup ref="LoginAsAdmin" stepKey="loginToAdmin"/> + <amOnPage url="{{AdminOrderPage.url({$grabOrderNumber})}}" stepKey="navigateToOrderPage"/> + <waitForPageLoad stepKey="waitForCreatedOrderPage"/> + <see stepKey="seeCustomerName" userInput="{{CustomerEntityOne.firstname}}" selector="{{AdminShipmentOrderInformationSection.customerName}}"/> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StoreFrontFreeShippingRecalculationAfterCouponCodeAddedTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StoreFrontFreeShippingRecalculationAfterCouponCodeAddedTest.xml new file mode 100644 index 00000000000..330a026bb94 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StoreFrontFreeShippingRecalculationAfterCouponCodeAddedTest.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="StoreFrontFreeShippingRecalculationAfterCouponCodeAddedTest"> + <annotations> + <title value="Checkout Free Shipping Recalculation after Coupon Code Added"/> + <stories value="Checkout Free Shipping Recalculation after Coupon Code Added"/> + <description value="User should be able to do checkout free shipping recalculation after adding coupon code"/> + <features value="Checkout"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-96537"/> + <useCaseId value="MAGETWO-96431"/> + <group value="Checkout"/> + </annotations> + + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <createData entity="Simple_US_Customer" stepKey="createSimpleUsCustomer"> + <field key="group_id">1</field> + </createData> + <createData entity="_defaultCategory" stepKey="defaultCategory"/> + <createData entity="_defaultProduct" stepKey="simpleProduct"> + <field key="price">90</field> + <requiredEntity createDataKey="defaultCategory"/> + </createData> + <!--It is default for FlatRate--> + <createData entity="FlatRateShippingMethodConfig" stepKey="enableFlatRate"/> + <createData entity="FreeShippingMethodsSettingConfig" stepKey="freeShippingMethodsSettingConfig"/> + <createData entity="MinimumOrderAmount90" stepKey="minimumOrderAmount90"/> + <magentoCLI command="cache:flush" stepKey="flushCache1"/> + <actionGroup ref="AdminCreateCartPriceRuleWithCouponCode" stepKey="createCartPriceRule"> + <argument name="ruleName" value="CatPriceRule"/> + <argument name="couponCode" value="CatPriceRule.coupon_code"/> + </actionGroup> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginToStoreFront"> + <argument name="Customer" value="$$createSimpleUsCustomer$$"/> + </actionGroup> + <amOnPage url="$$simpleProduct.name$$.html" stepKey="navigateToProductPage"/> + <waitForPageLoad stepKey="waitForProductPage"/> + </before> + <after> + <deleteData createDataKey="simpleProduct" stepKey="deleteProduct"/> + <deleteData createDataKey="defaultCategory" stepKey="deleteCategory"/> + <createData entity="DefaultShippingMethodsConfig" stepKey="defaultShippingMethodsConfig"/> + <createData entity="DefaultMinimumOrderAmount" stepKey="defaultMinimumOrderAmount"/> + <deleteData createDataKey="createSimpleUsCustomer" stepKey="deleteCustomer"/> + <magentoCLI command="cache:flush" stepKey="flushCache2"/> + <actionGroup ref="DeleteCartPriceRuleByName" stepKey="deleteCartPriceRule"> + <argument name="ruleName" value="{{CatPriceRule.name}}"/> + </actionGroup> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <actionGroup ref="ApplyCartRuleOnStorefrontActionGroup" stepKey="applyCartRule"> + <argument name="product" value="$$simpleProduct$$"/> + <argument name="couponCode" value="{{CatPriceRule.coupon_code}}"/> + </actionGroup> + <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="goToCheckoutFromMinicart1"/> + <waitForPageLoad stepKey="waitForpageLoad1"/> + <dontSee selector="{{CheckoutShippingMethodsSection.shippingMethodRowByName('Free')}}" stepKey="dontSeeFreeShipping"/> + <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToShoppingCartPage"/> + <waitForPageLoad stepKey="waitForShoppingCartPage"/> + <conditionalClick selector="{{DiscountSection.DiscountTab}}" dependentSelector="{{DiscountSection.CouponInput}}" visible="false" stepKey="clickIfDiscountTabClosed1"/> + <waitForPageLoad stepKey="waitForCouponTabOpen1"/> + <click selector="{{DiscountSection.CancelCoupon}}" stepKey="cancelCoupon"/> + <waitForPageLoad stepKey="waitForCancel"/> + <see userInput='You canceled the coupon code.' stepKey="seeCancellationMessage"/> + <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="goToCheckoutFromMinicart2"/> + <waitForPageLoad stepKey="waitForShippingMethods"/> + <click stepKey="chooseFreeShipping" selector="{{CheckoutShippingMethodsSection.shippingMethodRowByName('Free')}}"/> + <click selector="{{CheckoutShippingMethodsSection.next}}" stepKey="clickNext1"/> + <waitForPageLoad stepKey="waitForReviewAndPayments1"/> + <conditionalClick selector="{{DiscountSection.DiscountTab}}" dependentSelector="{{DiscountSection.CouponInput}}" visible="false" stepKey="clickIfDiscountTabClosed2"/> + <waitForPageLoad stepKey="waitForCouponTabOpen2"/> + <fillField selector="{{DiscountSection.DiscountInput}}" userInput="{{CatPriceRule.coupon_code}}" stepKey="fillCouponCode"/> + <click selector="{{DiscountSection.ApplyCodeBtn}}" stepKey="applyCode"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <see userInput="Your coupon was successfully applied." stepKey="seeSuccessMessage"/> + <click selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="clickPlaceOrder1"/> + <waitForPageLoad stepKey="waitForError"/> + <see stepKey="seeShippingMethodError" userInput="The shipping method is missing. Select the shipping method and try again."/> + <amOnPage stepKey="navigateToShippingPage" url="{{CheckoutShippingPage.url}}"/> + <waitForPageLoad stepKey="waitForShippingPageLoad"/> + <click stepKey="chooseFlatRateShipping" selector="{{CheckoutShippingMethodsSection.shippingMethodRowByName('Flat Rate')}}"/> + <click selector="{{CheckoutShippingMethodsSection.next}}" stepKey="clickNext2"/> + <waitForPageLoad stepKey="waitForReviewAndPayments2"/> + <click selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="clickPlaceOrder2"/> + <waitForPageLoad stepKey="waitForSuccessfullyPlacedOrder"/> + <see stepKey="seeSuccessMessageForPlacedOrder" userInput="Thank you for your purchase!"/> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StoreFrontUpdateShoppingCartWhileUpdateMinicartTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StoreFrontUpdateShoppingCartWhileUpdateMinicartTest.xml new file mode 100644 index 00000000000..fb80b4880a6 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StoreFrontUpdateShoppingCartWhileUpdateMinicartTest.xml @@ -0,0 +1,56 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StoreFrontUpdateShoppingCartWhileUpdateMinicartTest"> + <annotations> + <stories value="Shopping Cart"/> + <title value="Check updating shopping cart while updating items from minicart"/> + <description value="Check updating shopping cart while updating items from minicart"/> + <severity value="AVERAGE"/> + <testCaseId value="MAGETWO-97280"/> + <useCaseId value="MAGETWO-71344"/> + <group value="checkout"/> + </annotations> + + <before> + <!--Create product--> + <createData entity="SimpleProduct2" stepKey="createProduct"/> + </before> + + <after> + <!--Delete created data--> + <deleteData createDataKey="createProduct" stepKey="deleteProduct" /> + </after> + + <!--Add product to cart--> + <amOnPage url="$$createProduct.name$$.html" stepKey="navigateToProductPage"/> + <waitForPageLoad stepKey="waitForProductPage"/> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addToCartFromStorefrontProductPage"> + <argument name="productName" value="$$createProduct.name$$"/> + </actionGroup> + + <!--Go to Shopping cart and check Qty--> + <actionGroup ref="clickViewAndEditCartFromMiniCart" stepKey="goToShoppingCart"/> + <grabValueFrom selector="{{CheckoutCartProductSection.ProductQuantityByName($$createProduct.name$$)}}" stepKey="grabQtyShoppingCart"/> + <assertEquals expected="1" actual="$grabQtyShoppingCart" stepKey="assertQtyShoppingCart"/> + + <!--Open minicart and change Qty--> + <click selector="{{StorefrontMinicartSection.showCart}}" stepKey="openMiniCart"/> + <waitForElementVisible selector="{{StorefrontMinicartSection.quantity}}" stepKey="waitForElementQty"/> + <pressKey selector="{{StorefrontMinicartSection.itemQuantity($$createProduct.name$$)}}" parameterArray="[\Facebook\WebDriver\WebDriverKeys::BACKSPACE]" stepKey="deleteFiled"/> + <fillField selector="{{StorefrontMinicartSection.itemQuantity($$createProduct.name$$)}}" userInput="5" stepKey="changeQty"/> + <click selector="{{StorefrontMinicartSection.itemQuantityUpdate($$createProduct.name$$)}}" stepKey="updateQty"/> + <waitForAjaxLoad stepKey="waitForAjaxLoad"/> + + <!--Check Qty in shopping cart after updating--> + <grabValueFrom selector="{{CheckoutCartProductSection.ProductQuantityByName($$createProduct.name$$)}}" stepKey="grabQtyShoppingCart1"/> + <assertEquals expected="5" actual="$grabQtyShoppingCart1" stepKey="assertQtyShoppingCart1"/> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerPlaceOrderWithNewAddressesThatWasEditedTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerPlaceOrderWithNewAddressesThatWasEditedTest.xml index cc5e723c72e..8537e10ce5a 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerPlaceOrderWithNewAddressesThatWasEditedTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerPlaceOrderWithNewAddressesThatWasEditedTest.xml @@ -79,8 +79,12 @@ <!--Verify New addresses in Customer's Address Book--> <amOnPage url="{{StorefrontCustomerAddressesPage.url}}" stepKey="goToCustomerAddressBook"/> - <see userInput="{{UK_Not_Default_Address.street[0]}} {{UK_Not_Default_Address.city}}, {{UK_Not_Default_Address.postcode}}" - selector="{{StorefrontCustomerAddressesSection.addressesList}}" stepKey="checkNewAddresses"/> + <see userInput="{{UK_Not_Default_Address.street[0]}}" + selector="{{StorefrontCustomerAddressesSection.addressesList}}" stepKey="checkNewAddressesStreet"/> + <see userInput="{{UK_Not_Default_Address.city}}" + selector="{{StorefrontCustomerAddressesSection.addressesList}}" stepKey="checkNewAddressesCity"/> + <see userInput="{{UK_Not_Default_Address.postcode}}" + selector="{{StorefrontCustomerAddressesSection.addressesList}}" stepKey="checkNewAddressesPostcode"/> <!--Order review page has address that was created during checkout--> <amOnPage url="{{StorefrontCustomerOrderViewPage.url({$grabOrderNumber})}}" stepKey="goToOrderReviewPage"/> <see userInput="{{UK_Not_Default_Address.street[0]}} {{UK_Not_Default_Address.city}}, {{UK_Not_Default_Address.postcode}}" diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutDataPersistTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutDataPersistTest.xml new file mode 100644 index 00000000000..626f095604f --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutDataPersistTest.xml @@ -0,0 +1,57 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontGuestCheckoutDataPersistTest"> + <annotations> + <features value="Checkout"/> + <stories value="MAGETWO-95068: Checkout data (shipping address etc) not persistant after cart update"/> + <title value="Check that checkout data persist after cart update"/> + <description value="Checkout data should be persist after updating cart"/> + <severity value="CRITICAL"/> + <testCaseId value="MAGETWO-96979"/> + <group value="checkout"/> + </annotations> + <before> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="_defaultProduct" stepKey="createProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + </before> + <after> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> + </after> + + <!-- Add simple product to cart --> + <actionGroup ref="AddSimpleProductToCart" stepKey="addProductToCart"> + <argument name="product" value="$$createProduct$$"/> + </actionGroup> + <!-- Navigate to checkout --> + <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="goToCheckoutFromMinicart"/> + <!-- Fill shipping address --> + <actionGroup ref="GuestCheckoutFillingShippingSectionActionGroup" stepKey="guestCheckoutFillingShipping"> + <argument name="shippingMethod" value="Flat Rate"/> + </actionGroup> + <!-- Add simple product to cart --> + <actionGroup ref="AddSimpleProductToCart" stepKey="addProductToCart1"> + <argument name="product" value="$$createProduct$$"/> + </actionGroup> + <!-- Navigate to checkout --> + <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="goToCheckoutFromMinicart1"/> + <seeInField selector="{{CheckoutShippingGuestInfoSection.email}}" userInput="{{CustomerEntityOne.email}}" stepKey="assertGuestEmail"/> + <seeInField selector="{{CheckoutShippingGuestInfoSection.firstName}}" userInput="{{CustomerEntityOne.firstname}}" stepKey="assertGuestFirstName"/> + <seeInField selector="{{CheckoutShippingGuestInfoSection.lastName}}" userInput="{{CustomerEntityOne.lastname}}" stepKey="assertGuestLastName"/> + <seeInField selector="{{CheckoutShippingGuestInfoSection.street}}" userInput="{{CustomerAddressSimple.street[0]}}" stepKey="assertGuestStreet"/> + <seeInField selector="{{CheckoutShippingGuestInfoSection.city}}" userInput="{{CustomerAddressSimple.city}}" stepKey="assertGuestCity"/> + <seeInField selector="{{CheckoutShippingGuestInfoSection.region}}" userInput="{{CustomerAddressSimple.state}}" stepKey="assertGuestRegion"/> + <seeInField selector="{{CheckoutShippingGuestInfoSection.postcode}}" userInput="{{CustomerAddressSimple.postcode}}" stepKey="assertGuestPostcode"/> + <seeInField selector="{{CheckoutShippingGuestInfoSection.telephone}}" userInput="{{CustomerAddressSimple.telephone}}" stepKey="assertGuestTelephone"/> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontOnePageCheckoutDataWhenChangeQtyTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontOnePageCheckoutDataWhenChangeQtyTest.xml new file mode 100644 index 00000000000..913eb34b34d --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontOnePageCheckoutDataWhenChangeQtyTest.xml @@ -0,0 +1,95 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontOnePageCheckoutDataWhenChangeQtyTest"> + <annotations> + <stories value="Checkout"/> + <title value="One page Checkout Customer data when changing Product Qty"/> + <description value="One page Checkout Customer data when changing Product Qty"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-96960"/> + <useCaseId value="MAGETWO-96850"/> + <group value="checkout"/> + </annotations> + <before> + <!--Create a product--> + <createData entity="SimpleProduct2" stepKey="createProduct"/> + </before> + <after> + <!--Delete created data--> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> + </after> + + <!--Add product to cart and checkout--> + <amOnPage url="{{StorefrontProductPage.url($$createProduct.name$$)}}" stepKey="amOnSimpleProductPage"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addToCartFromStorefrontProductPage"> + <argument name="productName" value="$createProduct.name$$"/> + </actionGroup> + <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="goToCheckoutFromMinicart"/> + <fillField selector="{{CheckoutShippingSection.email}}" userInput="{{CustomerEntityOne.email}}" stepKey="enterEmail"/> + <fillField selector="{{CheckoutShippingSection.firstName}}" userInput="{{CustomerEntityOne.firstname}}" stepKey="enterFirstName"/> + <fillField selector="{{CheckoutShippingSection.lastName}}" userInput="{{CustomerEntityOne.lastname}}" stepKey="enterLastName"/> + <fillField selector="{{CheckoutShippingSection.street}}" userInput="{{CustomerAddressSimple.street[0]}}" stepKey="enterStreet"/> + <fillField selector="{{CheckoutShippingSection.city}}" userInput="{{CustomerAddressSimple.city}}" stepKey="enterCity"/> + <selectOption selector="{{CheckoutShippingSection.region}}" userInput="{{CustomerAddressSimple.state}}" stepKey="selectRegion"/> + <fillField selector="{{CheckoutShippingSection.postcode}}" userInput="{{CustomerAddressSimple.postcode}}" stepKey="enterPostcode"/> + <fillField selector="{{CheckoutShippingSection.telephone}}" userInput="{{CustomerAddressSimple.telephone}}" stepKey="enterTelephone"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMask"/> + + <!--Grab customer data to check it--> + <grabValueFrom selector="{{CheckoutShippingSection.email}}" stepKey="grabEmail"/> + <grabValueFrom selector="{{CheckoutShippingSection.firstName}}" stepKey="grabFirstName"/> + <grabValueFrom selector="{{CheckoutShippingSection.lastName}}" stepKey="grabLastName"/> + <grabValueFrom selector="{{CheckoutShippingSection.street}}" stepKey="grabStreet"/> + <grabValueFrom selector="{{CheckoutShippingSection.city}}" stepKey="grabCity"/> + <grabTextFrom selector="{{CheckoutShippingSection.region}}" stepKey="grabRegion"/> + <grabValueFrom selector="{{CheckoutShippingSection.postcode}}" stepKey="grabPostcode"/> + <grabValueFrom selector="{{CheckoutShippingSection.telephone}}" stepKey="grabTelephone"/> + + <!--Select shipping method and finalize checkout--> + <click selector="{{CheckoutShippingSection.firstShippingMethod}}" stepKey="selectFirstShippingMethod"/> + <waitForElement selector="{{CheckoutShippingSection.next}}" time="30" stepKey="waitForNextButton"/> + <waitForPageLoad stepKey="waitForShippingMethodLoad"/> + <click selector="{{CheckoutShippingSection.next}}" stepKey="clickNext"/> + <waitForElement selector="{{CheckoutPaymentSection.paymentSectionTitle}}" time="30" stepKey="waitForPaymentSectionLoaded"/> + <seeInCurrentUrl url="{{CheckoutPage.url}}/#payment" stepKey="assertCheckoutPaymentUrl"/> + + <!--Go to cart page, update qty and proceed to checkout--> + <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCartPage"/> + <waitForPageLoad stepKey="waitForCartPageLoad"/> + <see userInput="Shopping Cart" stepKey="seeCartPageIsOpened"/> + <fillField selector="{{CheckoutCartProductSection.qty($$createProduct.name$$)}}" userInput="2" stepKey="updateProductQty"/> + <click selector="{{CheckoutCartProductSection.updateShoppingCartButton}}" stepKey="clickUpdateShoppingCart"/> + <waitForAjaxLoad stepKey="waitForAjaxLoad"/> + <grabValueFrom selector="{{CheckoutCartProductSection.qty($$createProduct.name$$)}}" stepKey="grabQty"/> + <assertEquals expected="2" actual="$grabQty" stepKey="assertQty"/> + <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="clickProceedToCheckout"/> + + <!--Check that form is filled with customer data--> + <grabValueFrom selector="{{CheckoutShippingSection.email}}" stepKey="grabEmail1"/> + <grabValueFrom selector="{{CheckoutShippingSection.firstName}}" stepKey="grabFirstName1"/> + <grabValueFrom selector="{{CheckoutShippingSection.lastName}}" stepKey="grabLastName1"/> + <grabValueFrom selector="{{CheckoutShippingSection.street}}" stepKey="grabStreet1"/> + <grabValueFrom selector="{{CheckoutShippingSection.city}}" stepKey="grabCity1"/> + <grabTextFrom selector="{{CheckoutShippingSection.region}}" stepKey="grabRegion1"/> + <grabValueFrom selector="{{CheckoutShippingSection.postcode}}" stepKey="grabPostcode1"/> + <grabValueFrom selector="{{CheckoutShippingSection.telephone}}" stepKey="grabTelephone1"/> + + <assertEquals expected="$grabEmail" actual="$grabEmail1" stepKey="assertEmail"/> + <assertEquals expected="$grabFirstName" actual="$grabFirstName1" stepKey="assertFirstName"/> + <assertEquals expected="$grabLastName" actual="$grabLastName1" stepKey="assertLastName"/> + <assertEquals expected="$grabStreet" actual="$grabStreet1" stepKey="assertStreet"/> + <assertEquals expected="$grabCity" actual="$grabCity1" stepKey="assertCity"/> + <assertEquals expected="$grabRegion" actual="$grabRegion1" stepKey="assertRegion"/> + <assertEquals expected="$grabPostcode" actual="$grabPostcode1" stepKey="assertPostcode"/> + <assertEquals expected="$grabTelephone" actual="$grabTelephone1" stepKey="assertTelephone"/> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontProductNameMinicartOnCheckoutPageDifferentStoreViewsTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontProductNameMinicartOnCheckoutPageDifferentStoreViewsTest.xml new file mode 100644 index 00000000000..3401369a8c7 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontProductNameMinicartOnCheckoutPageDifferentStoreViewsTest.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="StorefrontProductNameMinicartOnCheckoutPageDifferentStoreViewsTest"> + <annotations> + <features value="Checkout"/> + <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"/> + <testCaseId value="MAGETWO-96466"/> + <useCaseId value="MAGETWO-96421"/> + <group value="checkout"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + + <!--Create a product--> + <createData entity="SimpleProduct2" stepKey="createProduct"/> + </before> + <after> + <!--Delete created data--> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> + <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteStoreView"> + <argument name="customStore" value="customStore"/> + </actionGroup> + + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Create store view --> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createStoreView"> + <argument name="customStore" value="customStore"/> + </actionGroup> + + <!--Go to created product page--> + <amOnPage url="{{AdminProductEditPage.url($$createProduct.id$$)}}" stepKey="goToEditPage"/> + <waitForPageLoad stepKey="waitForProductPage"/> + + <!--Switch to second store view and change the product name--> + <actionGroup ref="SwitchToTheNewStoreView" stepKey="switchToCustomStoreView"> + <argument name="storeViewName" value="{{customStore.name}}"/> + </actionGroup> + <waitForPageLoad stepKey="waitForPageLoad"/> + <click selector="{{AdminProductFormSection.productNameUseDefault}}" stepKey="uncheckUseDefault"/> + <fillField selector="{{AdminProductFormSection.productName}}" userInput="$$createProduct.name$$-new" stepKey="fillProductName"/> + <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="saveProduct"/> + + <!--Add product to cart--> + <amOnPage url="{{StorefrontProductPage.url($$createProduct.name$$)}}" stepKey="amOnSimpleProductPage"/> + <waitForPageLoad stepKey="waitForProductPageLoad"/> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addToCartFromStorefrontProductPage"> + <argument name="productName" value="$createProduct.name$$"/> + </actionGroup> + + <!--Switch to second store view--> + <actionGroup ref="StorefrontSwitchStoreViewActionGroup" stepKey="switchStoreView"> + <argument name="storeView" value="customStore"/> + </actionGroup> + <waitForPageLoad stepKey="waitForStoreView"/> + + <!--Check product name in Minicart--> + <click selector="{{StorefrontMinicartSection.showCart}}" stepKey="clickCart"/> + <grabTextFrom selector="{{StorefrontMinicartSection.productName}}" stepKey="grabProductNameMinicart"/> + <assertContains expected="$$createProduct.name$$" actual="$grabProductNameMinicart" stepKey="assertProductNameMinicart"/> + <assertContains expectedType="string" expected="-new" actual="$grabProductNameMinicart" stepKey="assertProductNameMinicart1"/> + + <!--Check product name in Shopping Cart page--> + <click selector="{{StorefrontMinicartSection.viewAndEditCart}}" stepKey="clickViewAndEdit"/> + <waitForPageLoad stepKey="waitForShoppingCartPageLoad"/> + <grabTextFrom selector="{{CheckoutCartProductSection.productName}}" stepKey="grabProductNameCart"/> + <assertContains expected="$$createProduct.name$$" actual="$grabProductNameCart" stepKey="assertProductNameCart"/> + <assertContains expectedType="string" expected="-new" actual="$grabProductNameCart" stepKey="assertProductNameCart1"/> + + <!--Proceed to checkout and check product name in Order Summary area--> + <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="proceedToCheckout"/> + <waitForPageLoad stepKey="waitForShippingPageLoad"/> + <click selector="{{CheckoutShippingGuestInfoSection.itemInCart}}" stepKey="clickItemInCart"/> + <grabTextFrom selector="{{CheckoutShippingGuestInfoSection.productName}}" stepKey="grabProductNameShipping"/> + <assertContains expected="$$createProduct.name$$" actual="$grabProductNameShipping" stepKey="assertProductNameShipping"/> + <assertContains expectedType="string" expected="-new" actual="$grabProductNameShipping" stepKey="assertProductNameShipping1"/> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontShoppingCartCheckCustomerDefaultShippingAddressForVirtualQuoteTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontShoppingCartCheckCustomerDefaultShippingAddressForVirtualQuoteTest.xml index 70f950f6f6c..b0e1dead1ff 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontShoppingCartCheckCustomerDefaultShippingAddressForVirtualQuoteTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontShoppingCartCheckCustomerDefaultShippingAddressForVirtualQuoteTest.xml @@ -19,18 +19,12 @@ <group value="checkout"/> </annotations> <before> - <createData entity="_defaultCategory" stepKey="createCategory"/> - <createData entity="defaultVirtualProduct" stepKey="createVirtualProduct"> - <requiredEntity createDataKey="createCategory"/> - </createData> - <createData entity="Simple_US_Customer_CA" stepKey="createCustomer"> - <field key="group_id">1</field> - </createData> + <createData entity="VirtualProduct" stepKey="createVirtualProduct"/> + <createData entity="Customer_With_Different_Default_Billing_Shipping_Addresses" stepKey="createCustomer"/> </before> <after> <actionGroup ref="StorefrontCustomerLogoutActionGroup" stepKey="customerLogout"/> <deleteData createDataKey="createVirtualProduct" stepKey="deleteVirtualProduct"/> - <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> </after> <!-- Steps --> @@ -48,8 +42,8 @@ <actionGroup ref="clickViewAndEditCartFromMiniCart" stepKey="goToShoppingcart"/> <!-- Step 4: Open Estimate Tax section --> <click selector="{{CheckoutCartSummarySection.estimateShippingAndTax}}" stepKey="openEstimateTaxSection"/> - <see selector="{{CheckoutCartSummarySection.country}}" userInput="{{US_Address_CA.country_id}}" stepKey="checkCountry"/> - <see selector="{{CheckoutCartSummarySection.stateProvince}}" userInput="{{US_Address_CA.state}}" stepKey="checkState"/> + <seeOptionIsSelected selector="{{CheckoutCartSummarySection.country}}" userInput="{{US_Address_CA.country}}" stepKey="checkCountry"/> + <seeOptionIsSelected selector="{{CheckoutCartSummarySection.stateProvince}}" userInput="{{US_Address_CA.state}}" stepKey="checkState"/> <scrollTo selector="{{CheckoutCartSummarySection.postcode}}" stepKey="scrollToPostCodeField"/> <grabValueFrom selector="{{CheckoutCartSummarySection.postcode}}" stepKey="grabTextPostCode"/> <assertEquals message="Customer postcode is invalid" stepKey="checkCustomerPostcode"> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/ZeroSubtotalOrdersWithProcessingStatusTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/ZeroSubtotalOrdersWithProcessingStatusTest.xml index 2cc21df85ab..4b3e18fb318 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/ZeroSubtotalOrdersWithProcessingStatusTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/ZeroSubtotalOrdersWithProcessingStatusTest.xml @@ -36,7 +36,6 @@ <createData entity="DefaultShippingMethodsConfig" stepKey="defaultShippingMethodsConfig"/> <createData entity="DisableFreeShippingConfig" stepKey="disableFreeShippingConfig"/> <createData entity="DisablePaymentMethodsSettingConfig" stepKey="disablePaymentMethodsSettingConfig"/> - <actionGroup ref="AdminOrdersGridClearFiltersActionGroup" stepKey="clearOrderFilters"/> <actionGroup ref="logout" stepKey="logout"/> <deleteData createDataKey="simpleproduct" stepKey="deleteProduct"/> <deleteData createDataKey="simplecategory" stepKey="deleteCategory"/> @@ -99,5 +98,6 @@ <!--Verify that Created order is in Processing status--> <see selector="{{AdminShipmentOrderInformationSection.orderStatus}}" userInput="Processing" stepKey="seeShipmentOrderStatus"/> + <actionGroup ref="AdminOrdersGridClearFiltersActionGroup" stepKey="clearOrderFilters"/> </test> </tests> diff --git a/app/code/Magento/Checkout/Test/Unit/Block/Cart/SidebarTest.php b/app/code/Magento/Checkout/Test/Unit/Block/Cart/SidebarTest.php index 1c5224d007e..f69ced3b094 100644 --- a/app/code/Magento/Checkout/Test/Unit/Block/Cart/SidebarTest.php +++ b/app/code/Magento/Checkout/Test/Unit/Block/Cart/SidebarTest.php @@ -144,7 +144,8 @@ public function testGetConfig() 'baseUrl' => $baseUrl, 'minicartMaxItemsVisible' => 3, 'websiteId' => 100, - 'maxItemsToDisplay' => 8 + 'maxItemsToDisplay' => 8, + 'storeId' => null ]; $valueMap = [ @@ -161,7 +162,7 @@ public function testGetConfig() $this->urlBuilderMock->expects($this->exactly(4)) ->method('getUrl') ->willReturnMap($valueMap); - $this->storeManagerMock->expects($this->exactly(2))->method('getStore')->willReturn($storeMock); + $this->storeManagerMock->expects($this->any())->method('getStore')->willReturn($storeMock); $storeMock->expects($this->once())->method('getBaseUrl')->willReturn($baseUrl); $this->scopeConfigMock->expects($this->at(0)) diff --git a/app/code/Magento/Checkout/Test/Unit/Block/Checkout/AttributeMergerTest.php b/app/code/Magento/Checkout/Test/Unit/Block/Checkout/AttributeMergerTest.php new file mode 100644 index 00000000000..23840da97bd --- /dev/null +++ b/app/code/Magento/Checkout/Test/Unit/Block/Checkout/AttributeMergerTest.php @@ -0,0 +1,121 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Checkout\Test\Unit\Block\Checkout; + +use Magento\Customer\Api\CustomerRepositoryInterface as CustomerRepository; +use Magento\Customer\Helper\Address as AddressHelper; +use Magento\Customer\Model\Session as CustomerSession; +use Magento\Directory\Helper\Data as DirectoryHelper; +use Magento\Checkout\Block\Checkout\AttributeMerger; +use PHPUnit\Framework\TestCase; + +class AttributeMergerTest extends TestCase +{ + /** + * @var CustomerRepository + */ + private $customerRepository; + + /** + * @var CustomerSession + */ + private $customerSession; + + /** + * @var AddressHelper + */ + private $addressHelper; + + /** + * @var DirectoryHelper + */ + private $directoryHelper; + + /** + * @var AttributeMerger + */ + private $attributeMerger; + + /** + * @inheritdoc + */ + protected function setUp() + { + + $this->customerRepository = $this->createMock(CustomerRepository::class); + $this->customerSession = $this->createMock(CustomerSession::class); + $this->addressHelper = $this->createMock(AddressHelper::class); + $this->directoryHelper = $this->createMock(DirectoryHelper::class); + + $this->attributeMerger = new AttributeMerger( + $this->addressHelper, + $this->customerSession, + $this->customerRepository, + $this->directoryHelper + ); + } + + /** + * Tests of element attributes merging. + * + * @param String $validationRule - validation rule. + * @param String $expectedValidation - expected mapped validation. + * @dataProvider validationRulesDataProvider + */ + public function testMerge(String $validationRule, String $expectedValidation): void + { + $elements = [ + 'field' => [ + 'visible' => true, + 'formElement' => 'input', + 'label' => __('City'), + 'value' => null, + 'sortOrder' => 1, + 'validation' => [ + 'input_validation' => $validationRule + ], + ] + ]; + + $actualResult = $this->attributeMerger->merge( + $elements, + 'provider', + 'dataScope', + ['field' => + [ + 'validation' => ['length' => true] + ] + ] + ); + + $expectedResult = [ + $expectedValidation => true, + 'length' => true + ]; + + self::assertEquals($expectedResult, $actualResult['field']['validation']); + } + + /** + * Provides possible validation types. + * + * @return array + */ + public function validationRulesDataProvider(): array + { + return [ + ['alpha', 'validate-alpha'], + ['numeric', 'validate-number'], + ['alphanumeric', 'validate-alphanum'], + ['alphanum-with-spaces', 'validate-alphanum-with-spaces'], + ['url', 'validate-url'], + ['email', 'email2'], + ['length', 'validate-length'] + ]; + } +} diff --git a/app/code/Magento/Checkout/Test/Unit/Block/OnepageTest.php b/app/code/Magento/Checkout/Test/Unit/Block/OnepageTest.php index 54f77c95148..b54339aa2c1 100644 --- a/app/code/Magento/Checkout/Test/Unit/Block/OnepageTest.php +++ b/app/code/Magento/Checkout/Test/Unit/Block/OnepageTest.php @@ -35,7 +35,7 @@ class OnepageTest extends \PHPUnit\Framework\TestCase /** * @var \PHPUnit_Framework_MockObject_MockObject */ - private $serializer; + private $serializerMock; protected function setUp() { @@ -49,7 +49,7 @@ protected function setUp() \Magento\Checkout\Block\Checkout\LayoutProcessorInterface::class ); - $this->serializer = $this->createMock(\Magento\Framework\Serialize\Serializer\Json::class); + $this->serializerMock = $this->createMock(\Magento\Framework\Serialize\Serializer\JsonHexTag::class); $this->model = new \Magento\Checkout\Block\Onepage( $contextMock, @@ -57,7 +57,8 @@ protected function setUp() $this->configProviderMock, [$this->layoutProcessorMock], [], - $this->serializer + $this->serializerMock, + $this->serializerMock ); } @@ -93,6 +94,7 @@ public function testGetJsLayout() $processedLayout = ['layout' => ['processed' => true]]; $jsonLayout = '{"layout":{"processed":true}}'; $this->layoutProcessorMock->expects($this->once())->method('process')->with([])->willReturn($processedLayout); + $this->serializerMock->expects($this->once())->method('serialize')->willReturn($jsonLayout); $this->assertEquals($jsonLayout, $this->model->getJsLayout()); } @@ -101,6 +103,7 @@ public function testGetSerializedCheckoutConfig() { $checkoutConfig = ['checkout', 'config']; $this->configProviderMock->expects($this->once())->method('getConfig')->willReturn($checkoutConfig); + $this->serializerMock->expects($this->once())->method('serialize')->willReturn(json_encode($checkoutConfig)); $this->assertEquals(json_encode($checkoutConfig), $this->model->getSerializedCheckoutConfig()); } diff --git a/app/code/Magento/Checkout/Test/Unit/CustomerData/CartTest.php b/app/code/Magento/Checkout/Test/Unit/CustomerData/CartTest.php index 75e181cbabd..e3e13cc5b1e 100644 --- a/app/code/Magento/Checkout/Test/Unit/CustomerData/CartTest.php +++ b/app/code/Magento/Checkout/Test/Unit/CustomerData/CartTest.php @@ -113,7 +113,7 @@ public function testGetSectionData() $storeMock = $this->createPartialMock(\Magento\Store\Model\System\Store::class, ['getWebsiteId']); $storeMock->expects($this->once())->method('getWebsiteId')->willReturn($websiteId); - $quoteMock->expects($this->once())->method('getStore')->willReturn($storeMock); + $quoteMock->expects($this->any())->method('getStore')->willReturn($storeMock); $productMock = $this->createPartialMock( \Magento\Catalog\Model\Product::class, @@ -162,6 +162,7 @@ public function testGetSectionData() 'isGuestCheckoutAllowed' => 1, 'website_id' => $websiteId, 'subtotalAmount' => 200, + 'storeId' => null ]; $this->assertEquals($expectedResult, $this->model->getSectionData()); } @@ -199,7 +200,7 @@ public function testGetSectionDataWithCompositeProduct() $storeMock = $this->createPartialMock(\Magento\Store\Model\System\Store::class, ['getWebsiteId']); $storeMock->expects($this->once())->method('getWebsiteId')->willReturn($websiteId); - $quoteMock->expects($this->once())->method('getStore')->willReturn($storeMock); + $quoteMock->expects($this->any())->method('getStore')->willReturn($storeMock); $this->checkoutCartMock->expects($this->once())->method('getSummaryQty')->willReturn($summaryQty); $this->checkoutHelperMock->expects($this->once()) @@ -265,6 +266,7 @@ public function testGetSectionDataWithCompositeProduct() 'isGuestCheckoutAllowed' => 1, 'website_id' => $websiteId, 'subtotalAmount' => 200, + 'storeId' => null ]; $this->assertEquals($expectedResult, $this->model->getSectionData()); } diff --git a/app/code/Magento/Checkout/etc/frontend/di.xml b/app/code/Magento/Checkout/etc/frontend/di.xml index d80f88786c8..00bcd2a2700 100644 --- a/app/code/Magento/Checkout/etc/frontend/di.xml +++ b/app/code/Magento/Checkout/etc/frontend/di.xml @@ -59,6 +59,7 @@ <item name="totalsSortOrder" xsi:type="object">Magento\Checkout\Block\Checkout\TotalsProcessor</item> <item name="directoryData" xsi:type="object">Magento\Checkout\Block\Checkout\DirectoryDataProcessor</item> </argument> + <argument name="serializer" xsi:type="object">Magento\Framework\Serialize\Serializer\JsonHexTag</argument> </arguments> </type> <type name="Magento\Checkout\Block\Cart\Totals"> diff --git a/app/code/Magento/Checkout/etc/frontend/sections.xml b/app/code/Magento/Checkout/etc/frontend/sections.xml index 35733a6119a..90c2878f501 100644 --- a/app/code/Magento/Checkout/etc/frontend/sections.xml +++ b/app/code/Magento/Checkout/etc/frontend/sections.xml @@ -46,7 +46,6 @@ </action> <action name="rest/*/V1/guest-carts/*/payment-information"> <section name="cart"/> - <section name="checkout-data"/> </action> <action name="rest/*/V1/guest-carts/*/selected-payment-method"> <section name="cart"/> diff --git a/app/code/Magento/Checkout/view/frontend/web/js/checkout-data.js b/app/code/Magento/Checkout/view/frontend/web/js/checkout-data.js index 22b37b2da0b..1858ce946fb 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/checkout-data.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/checkout-data.js @@ -10,7 +10,8 @@ */ define([ 'jquery', - 'Magento_Customer/js/customer-data' + 'Magento_Customer/js/customer-data', + 'jquery/jquery-storageapi' ], function ($, storage) { 'use strict'; @@ -23,6 +24,22 @@ define([ storage.set(cacheKey, data); }, + /** + * @return {*} + */ + initData = function () { + return { + 'selectedShippingAddress': null, //Selected shipping address pulled from persistence storage + 'shippingAddressFromData': null, //Shipping address pulled from persistence storage + 'newCustomerShippingAddress': null, //Shipping address pulled from persistence storage for customer + 'selectedShippingRate': null, //Shipping rate pulled from persistence storage + 'selectedPaymentMethod': null, //Payment method pulled from persistence storage + 'selectedBillingAddress': null, //Selected billing address pulled from persistence storage + 'billingAddressFromData': null, //Billing address pulled from persistence storage + 'newCustomerBillingAddress': null //Billing address pulled from persistence storage for new customer + }; + }, + /** * @return {*} */ @@ -30,17 +47,12 @@ define([ var data = storage.get(cacheKey)(); if ($.isEmptyObject(data)) { - data = { - 'selectedShippingAddress': null, //Selected shipping address pulled from persistence storage - 'shippingAddressFromData': null, //Shipping address pulled from persistence storage - 'newCustomerShippingAddress': null, //Shipping address pulled from persistence storage for customer - 'selectedShippingRate': null, //Shipping rate pulled from persistence storage - 'selectedPaymentMethod': null, //Payment method pulled from persistence storage - 'selectedBillingAddress': null, //Selected billing address pulled from persistence storage - 'billingAddressFromData': null, //Billing address pulled from persistence storage - 'newCustomerBillingAddress': null //Billing address pulled from persistence storage for new customer - }; - saveData(data); + data = $.initNamespaceStorage('mage-cache-storage').localStorage.get(cacheKey); + + if ($.isEmptyObject(data)) { + data = initData(); + saveData(data); + } } return data; diff --git a/app/code/Magento/Checkout/view/frontend/web/js/model/payment/additional-validators.js b/app/code/Magento/Checkout/view/frontend/web/js/model/payment/additional-validators.js index 1cb35a4cee2..1337e1affd3 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/model/payment/additional-validators.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/model/payment/additional-validators.js @@ -35,15 +35,17 @@ define([], function () { * * @returns {Boolean} */ - validate: function () { + validate: function (hideError) { var validationResult = true; + hideError = hideError || false; + if (validators.length <= 0) { return validationResult; } validators.forEach(function (item) { - if (item.validate() == false) { //eslint-disable-line eqeqeq + if (item.validate(hideError) == false) { //eslint-disable-line eqeqeq validationResult = false; return false; diff --git a/app/code/Magento/Checkout/view/frontend/web/js/model/place-order.js b/app/code/Magento/Checkout/view/frontend/web/js/model/place-order.js index c3c5b9d68ce..c07878fcaea 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/model/place-order.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/model/place-order.js @@ -9,9 +9,10 @@ define( [ 'mage/storage', 'Magento_Checkout/js/model/error-processor', - 'Magento_Checkout/js/model/full-screen-loader' + 'Magento_Checkout/js/model/full-screen-loader', + 'Magento_Customer/js/customer-data' ], - function (storage, errorProcessor, fullScreenLoader) { + function (storage, errorProcessor, fullScreenLoader, customerData) { 'use strict'; return function (serviceUrl, payload, messageContainer) { @@ -23,6 +24,23 @@ define( function (response) { errorProcessor.process(response, messageContainer); } + ).success( + function (response) { + var clearData = { + 'selectedShippingAddress': null, + 'shippingAddressFromData': null, + 'newCustomerShippingAddress': null, + 'selectedShippingRate': null, + 'selectedPaymentMethod': null, + 'selectedBillingAddress': null, + 'billingAddressFromData': null, + 'newCustomerBillingAddress': null + }; + + if (response.responseType !== 'error') { + customerData.set('checkout-data', clearData); + } + } ).always( function () { fullScreenLoader.stopLoader(); diff --git a/app/code/Magento/Checkout/view/frontend/web/js/model/postcode-validator.js b/app/code/Magento/Checkout/view/frontend/web/js/model/postcode-validator.js index a95471d90da..0a5334a42c7 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/model/postcode-validator.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/model/postcode-validator.js @@ -14,11 +14,13 @@ define([ /** * @param {*} postCode * @param {*} countryId + * @param {Array} postCodesPatterns * @return {Boolean} */ - validate: function (postCode, countryId) { - var patterns = window.checkoutConfig.postCodes[countryId], - pattern, regex; + validate: function (postCode, countryId, postCodesPatterns) { + var pattern, regex, + patterns = postCodesPatterns ? postCodesPatterns[countryId] : + window.checkoutConfig.postCodes[countryId]; this.validatedPostCodeExample = []; diff --git a/app/code/Magento/Checkout/view/frontend/web/js/model/shipping-rates-validator.js b/app/code/Magento/Checkout/view/frontend/web/js/model/shipping-rates-validator.js index fde88ebadb3..8b07c02e4d3 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/model/shipping-rates-validator.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/model/shipping-rates-validator.js @@ -42,6 +42,7 @@ define([ return { validateAddressTimeout: 0, + validateZipCodeTimeout: 0, validateDelay: 2000, /** @@ -133,16 +134,20 @@ define([ }); } else { element.on('value', function () { + clearTimeout(self.validateZipCodeTimeout); + self.validateZipCodeTimeout = setTimeout(function () { + if (element.index === postcodeElementName) { + self.postcodeValidation(element); + } else { + $.each(postcodeElements, function (index, elem) { + self.postcodeValidation(elem); + }); + } + }, delay); + if (!formPopUpState.isVisible()) { clearTimeout(self.validateAddressTimeout); self.validateAddressTimeout = setTimeout(function () { - if (element.index === postcodeElementName) { - self.postcodeValidation(element); - } else { - $.each(postcodeElements, function (index, elem) { - self.postcodeValidation(elem); - }); - } self.validateFields(); }, delay); } 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 dde1ad72ba1..e66c6600624 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/sidebar.js @@ -25,6 +25,7 @@ define([ } }, scrollHeight: 0, + shoppingCartUrl: window.checkout.shoppingCartUrl, /** * Create sidebar. @@ -227,6 +228,10 @@ define([ if (!_.isUndefined(productData)) { $(document).trigger('ajax:updateCartItemQty'); + + if (window.location.href === this.shoppingCartUrl) { + window.location.reload(false); + } } this._hideItemButton(elem); }, diff --git a/app/code/Magento/Checkout/view/frontend/web/js/view/minicart.js b/app/code/Magento/Checkout/view/frontend/web/js/view/minicart.js index a2f8c8c56ff..5e29fa209a6 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 @@ -81,6 +81,7 @@ define([ maxItemsToDisplay: window.checkout.maxItemsToDisplay, cart: {}, + // jscs:disable requireCamelCaseOrUpperCaseIdentifiers /** * @override */ @@ -101,12 +102,16 @@ define([ self.isLoading(true); }); - if (cartData()['website_id'] !== window.checkout.websiteId) { + if (cartData().website_id !== window.checkout.websiteId || + cartData().store_id !== window.checkout.storeId + ) { customerData.reload(['cart'], false); } return this._super(); }, + //jscs:enable requireCamelCaseOrUpperCaseIdentifiers + isLoading: ko.observable(false), initSidebar: initSidebar, 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 2daca51a2f5..fb128a891ae 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 @@ -97,7 +97,7 @@ </div> </div> - <div id="minicart-widgets" class="minicart-widgets"> + <div id="minicart-widgets" class="minicart-widgets" if="getRegion('promotion').length"> <each args="getRegion('promotion')" render=""/> </div> </div> diff --git a/app/code/Magento/CheckoutAgreements/etc/di.xml b/app/code/Magento/CheckoutAgreements/etc/di.xml index 081e3daa781..a8ff8f5941f 100644 --- a/app/code/Magento/CheckoutAgreements/etc/di.xml +++ b/app/code/Magento/CheckoutAgreements/etc/di.xml @@ -23,7 +23,7 @@ <type name="Magento\Checkout\Api\GuestPaymentInformationManagementInterface"> <plugin name="validate-guest-agreements" type="Magento\CheckoutAgreements\Model\Checkout\Plugin\GuestValidation"/> </type> - <type name="\Magento\CheckoutAgreements\Model\CheckoutAgreementsList"> + <type name="Magento\CheckoutAgreements\Model\CheckoutAgreementsList"> <arguments> <argument name="collectionProcessor" xsi:type="object">Magento\CheckoutAgreements\Model\Api\SearchCriteria\CollectionProcessor</argument> </arguments> diff --git a/app/code/Magento/CheckoutAgreements/view/frontend/web/js/model/agreement-validator.js b/app/code/Magento/CheckoutAgreements/view/frontend/web/js/model/agreement-validator.js index 157923323fd..cbd06b51fe1 100644 --- a/app/code/Magento/CheckoutAgreements/view/frontend/web/js/model/agreement-validator.js +++ b/app/code/Magento/CheckoutAgreements/view/frontend/web/js/model/agreement-validator.js @@ -19,7 +19,7 @@ define([ * * @returns {Boolean} */ - validate: function () { + validate: function (hideError) { var isValid = true; if (!agreementsConfig.isEnabled || $(agreementsInputPath).length === 0) { @@ -28,7 +28,8 @@ define([ $(agreementsInputPath).each(function (index, element) { if (!$.validator.validateSingleElement(element, { - errorElement: 'div' + errorElement: 'div', + hideError: hideError || false })) { isValid = false; } diff --git a/app/code/Magento/Cms/Block/Widget/Block.php b/app/code/Magento/Cms/Block/Widget/Block.php index aa6aeaff4ec..c665f2afc5d 100644 --- a/app/code/Magento/Cms/Block/Widget/Block.php +++ b/app/code/Magento/Cms/Block/Widget/Block.php @@ -83,7 +83,7 @@ protected function _beforeToHtml() if ($block && $block->isActive()) { try { - $storeId = $this->_storeManager->getStore()->getId(); + $storeId = $this->getData('store_id') ?? $this->_storeManager->getStore()->getId(); $this->setText( $this->_filterProvider->getBlockFilter()->setStoreId($storeId)->filter($block->getContent()) ); diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Block/Edit.php b/app/code/Magento/Cms/Controller/Adminhtml/Block/Edit.php index b5b035e0c67..655b3eb5b91 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Block/Edit.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Block/Edit.php @@ -5,8 +5,11 @@ */ namespace Magento\Cms\Controller\Adminhtml\Block; -use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; +use Magento\Framework\App\Action\HttpGetActionInterface; +/** + * Edit CMS block action. + */ class Edit extends \Magento\Cms\Controller\Adminhtml\Block implements HttpGetActionInterface { /** diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Block/Index.php b/app/code/Magento/Cms/Controller/Adminhtml/Block/Index.php index f92d38c0d85..b7504a5c2b2 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Block/Index.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Block/Index.php @@ -1,13 +1,15 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Cms\Controller\Adminhtml\Block; -use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; +use Magento\Framework\App\Action\HttpGetActionInterface; +/** + * Index action. + */ class Index extends \Magento\Cms\Controller\Adminhtml\Block implements HttpGetActionInterface { /** diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Block/MassDelete.php b/app/code/Magento/Cms/Controller/Adminhtml/Block/MassDelete.php index 92bc7ad71f5..ccdddc7c2b5 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Block/MassDelete.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Block/MassDelete.php @@ -6,6 +6,7 @@ */ namespace Magento\Cms\Controller\Adminhtml\Block; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Framework\Controller\ResultFactory; use Magento\Backend\App\Action\Context; use Magento\Ui\Component\MassAction\Filter; @@ -14,7 +15,7 @@ /** * Class MassDelete */ -class MassDelete extends \Magento\Backend\App\Action +class MassDelete extends \Magento\Backend\App\Action implements HttpPostActionInterface { /** * Authorization level of a basic admin session diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Block/NewAction.php b/app/code/Magento/Cms/Controller/Adminhtml/Block/NewAction.php index 28db422fb51..5983594f876 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Block/NewAction.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Block/NewAction.php @@ -1,13 +1,15 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Cms\Controller\Adminhtml\Block; -use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; +use Magento\Framework\App\Action\HttpGetActionInterface; +/** + * Create CMS block action. + */ class NewAction extends \Magento\Cms\Controller\Adminhtml\Block implements HttpGetActionInterface { /** diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Block/Save.php b/app/code/Magento/Cms/Controller/Adminhtml/Block/Save.php index 7526f59ce36..0c6c6470398 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Block/Save.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Block/Save.php @@ -1,12 +1,11 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Cms\Controller\Adminhtml\Block; -use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Backend\App\Action\Context; use Magento\Cms\Api\BlockRepositoryInterface; use Magento\Cms\Model\Block; @@ -15,6 +14,9 @@ use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Registry; +/** + * Save CMS block action. + */ class Save extends \Magento\Cms\Controller\Adminhtml\Block implements HttpPostActionInterface { /** diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Page/Delete.php b/app/code/Magento/Cms/Controller/Adminhtml/Page/Delete.php index 16c99e9857c..15bb4093053 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Page/Delete.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Page/Delete.php @@ -1,12 +1,16 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Cms\Controller\Adminhtml\Page; -class Delete extends \Magento\Backend\App\Action +use Magento\Framework\App\Action\HttpPostActionInterface; + +/** + * Delete CMS page action. + */ +class Delete extends \Magento\Backend\App\Action implements HttpPostActionInterface { /** * Authorization level of a basic admin session diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Page/Edit.php b/app/code/Magento/Cms/Controller/Adminhtml/Page/Edit.php index e7bdfe9ed0e..f50fb2b19c0 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Page/Edit.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Page/Edit.php @@ -1,14 +1,16 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Cms\Controller\Adminhtml\Page; -use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; +use Magento\Framework\App\Action\HttpGetActionInterface; use Magento\Backend\App\Action; +/** + * Edit CMS page action. + */ class Edit extends \Magento\Backend\App\Action implements HttpGetActionInterface { /** diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Page/Index.php b/app/code/Magento/Cms/Controller/Adminhtml/Page/Index.php index 42fa4d62673..04557ddaeec 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Page/Index.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Page/Index.php @@ -1,15 +1,17 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Cms\Controller\Adminhtml\Page; -use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; +use Magento\Framework\App\Action\HttpGetActionInterface; use Magento\Backend\App\Action\Context; use Magento\Framework\View\Result\PageFactory; +/** + * Index action. + */ class Index extends \Magento\Backend\App\Action implements HttpGetActionInterface { /** diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Page/MassDelete.php b/app/code/Magento/Cms/Controller/Adminhtml/Page/MassDelete.php index a1d32aa97a3..222849f9760 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Page/MassDelete.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Page/MassDelete.php @@ -5,6 +5,7 @@ */ namespace Magento\Cms\Controller\Adminhtml\Page; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Framework\Controller\ResultFactory; use Magento\Backend\App\Action\Context; use Magento\Ui\Component\MassAction\Filter; @@ -13,7 +14,7 @@ /** * Class MassDelete */ -class MassDelete extends \Magento\Backend\App\Action +class MassDelete extends \Magento\Backend\App\Action implements HttpPostActionInterface { /** * Authorization level of a basic admin session diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Page/MassDisable.php b/app/code/Magento/Cms/Controller/Adminhtml/Page/MassDisable.php index b07894e6a9d..0a8c667d4d7 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Page/MassDisable.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Page/MassDisable.php @@ -5,7 +5,7 @@ */ namespace Magento\Cms\Controller\Adminhtml\Page; -use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Framework\Controller\ResultFactory; use Magento\Backend\App\Action\Context; use Magento\Ui\Component\MassAction\Filter; diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Page/MassEnable.php b/app/code/Magento/Cms/Controller/Adminhtml/Page/MassEnable.php index 3f26769e4c9..e2cb8d984e0 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Page/MassEnable.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Page/MassEnable.php @@ -5,6 +5,7 @@ */ namespace Magento\Cms\Controller\Adminhtml\Page; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Framework\Controller\ResultFactory; use Magento\Backend\App\Action\Context; use Magento\Ui\Component\MassAction\Filter; @@ -13,7 +14,7 @@ /** * Class MassEnable */ -class MassEnable extends \Magento\Backend\App\Action +class MassEnable extends \Magento\Backend\App\Action implements HttpPostActionInterface { /** * Authorization level of a basic admin session diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Page/NewAction.php b/app/code/Magento/Cms/Controller/Adminhtml/Page/NewAction.php index 407657b05c1..6a4f4951cef 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Page/NewAction.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Page/NewAction.php @@ -6,7 +6,12 @@ */ namespace Magento\Cms\Controller\Adminhtml\Page; -class NewAction extends \Magento\Backend\App\Action +use Magento\Framework\App\Action\HttpGetActionInterface; + +/** + * Create CMS page action. + */ +class NewAction extends \Magento\Backend\App\Action implements HttpGetActionInterface { /** * Authorization level of a basic admin session diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Page/Save.php b/app/code/Magento/Cms/Controller/Adminhtml/Page/Save.php index 5a7b900bf2d..37cb4575317 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Page/Save.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Page/Save.php @@ -1,17 +1,19 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Cms\Controller\Adminhtml\Page; -use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Backend\App\Action; use Magento\Cms\Model\Page; use Magento\Framework\App\Request\DataPersistorInterface; use Magento\Framework\Exception\LocalizedException; +/** + * Save CMS page action. + */ class Save extends \Magento\Backend\App\Action implements HttpPostActionInterface { /** diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Page/Widget/Chooser.php b/app/code/Magento/Cms/Controller/Adminhtml/Page/Widget/Chooser.php index b268bee4c60..340f4b24dd1 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Page/Widget/Chooser.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Page/Widget/Chooser.php @@ -1,15 +1,17 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Cms\Controller\Adminhtml\Page\Widget; use Magento\Framework\App\Action\HttpGetActionInterface; -use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Backend\App\Action; +/** + * Chooser Source action. + */ class Chooser extends Action implements HttpPostActionInterface, HttpGetActionInterface { /** diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/DeleteFolder.php b/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/DeleteFolder.php index a1de11c3c46..5344472a79a 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/DeleteFolder.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/DeleteFolder.php @@ -6,12 +6,13 @@ */ namespace Magento\Cms\Controller\Adminhtml\Wysiwyg\Images; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Framework\App\Filesystem\DirectoryList; /** * Delete image folder. */ -class DeleteFolder extends \Magento\Cms\Controller\Adminhtml\Wysiwyg\Images +class DeleteFolder extends \Magento\Cms\Controller\Adminhtml\Wysiwyg\Images implements HttpPostActionInterface { /** * @var \Magento\Framework\Controller\Result\JsonFactory diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/Upload.php b/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/Upload.php index 5c9aa2243bc..31b01ce115c 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/Upload.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/Upload.php @@ -6,12 +6,13 @@ */ namespace Magento\Cms\Controller\Adminhtml\Wysiwyg\Images; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Framework\App\Filesystem\DirectoryList; /** * Upload image. */ -class Upload extends \Magento\Cms\Controller\Adminhtml\Wysiwyg\Images +class Upload extends \Magento\Cms\Controller\Adminhtml\Wysiwyg\Images implements HttpPostActionInterface { /** * @var \Magento\Framework\Controller\Result\JsonFactory diff --git a/app/code/Magento/Cms/Model/Page/Source/PageLayout.php b/app/code/Magento/Cms/Model/Page/Source/PageLayout.php index fb759348759..23a452c0fe5 100644 --- a/app/code/Magento/Cms/Model/Page/Source/PageLayout.php +++ b/app/code/Magento/Cms/Model/Page/Source/PageLayout.php @@ -20,6 +20,7 @@ class PageLayout implements OptionSourceInterface /** * @var array + * @deprecated since the cache is now handled by \Magento\Theme\Model\PageLayout\Config\Builder::$configFiles */ protected $options; @@ -34,16 +35,10 @@ public function __construct(BuilderInterface $pageLayoutBuilder) } /** - * Get options - * - * @return array + * @inheritdoc */ public function toOptionArray() { - if ($this->options !== null) { - return $this->options; - } - $configOptions = $this->pageLayoutBuilder->getPageLayoutsConfig()->getOptions(); $options = []; foreach ($configOptions as $key => $value) { @@ -54,6 +49,6 @@ public function toOptionArray() } $this->options = $options; - return $this->options; + return $options; } } diff --git a/app/code/Magento/Cms/Model/ResourceModel/Block.php b/app/code/Magento/Cms/Model/ResourceModel/Block.php index 9aab54b02bc..30e81771375 100644 --- a/app/code/Magento/Cms/Model/ResourceModel/Block.php +++ b/app/code/Magento/Cms/Model/ResourceModel/Block.php @@ -95,9 +95,11 @@ protected function _beforeSave(AbstractModel $object) } /** + * Get block id. + * * @param AbstractModel $object * @param mixed $value - * @param null $field + * @param string $field * @return bool|int|string * @throws LocalizedException * @throws \Exception @@ -183,10 +185,12 @@ public function getIsUniqueBlockToStores(AbstractModel $object) $entityMetadata = $this->metadataPool->getMetadata(BlockInterface::class); $linkField = $entityMetadata->getLinkField(); - if ($this->_storeManager->isSingleStoreMode()) { - $stores = [Store::DEFAULT_STORE_ID]; - } else { - $stores = (array)$object->getData('store_id'); + $stores = (array)$object->getData('store_id'); + $isDefaultStore = $this->_storeManager->isSingleStoreMode() + || array_search(Store::DEFAULT_STORE_ID, $stores) !== false; + + if (!$isDefaultStore) { + $stores[] = Store::DEFAULT_STORE_ID; } $select = $this->getConnection()->select() @@ -196,8 +200,11 @@ public function getIsUniqueBlockToStores(AbstractModel $object) 'cb.' . $linkField . ' = cbs.' . $linkField, [] ) - ->where('cb.identifier = ?', $object->getData('identifier')) - ->where('cbs.store_id IN (?)', $stores); + ->where('cb.identifier = ? ', $object->getData('identifier')); + + if (!$isDefaultStore) { + $select->where('cbs.store_id IN (?)', $stores); + } if ($object->getId()) { $select->where('cb.' . $entityMetadata->getIdentifierField() . ' <> ?', $object->getId()); @@ -236,6 +243,8 @@ public function lookupStoreIds($id) } /** + * Save an object. + * * @param AbstractModel $object * @return $this * @throws \Exception diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/CMSActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/CMSActionGroup.xml index 75e059f620c..07e43347d9d 100644 --- a/app/code/Magento/Cms/Test/Mftf/ActionGroup/CMSActionGroup.xml +++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/CMSActionGroup.xml @@ -44,4 +44,75 @@ <waitForPageLoad stepKey="waitForPageLoad3"/> <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskOfStagingSection" /> </actionGroup> + <actionGroup name="DeleteCMSBlockActionGroup"> + <amOnPage url="{{CmsBlocksPage.url}}" stepKey="navigateToCMSPagesGrid"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <click selector="{{CmsPagesPageActionsSection.select(_defaultBlock.title)}}" stepKey="ClickOnSelect"/> + <click selector="{{CmsPagesPageActionsSection.delete(_defaultBlock.title)}}" stepKey="ClickOnEdit"/> + <waitForPageLoad stepKey="waitForPageLoad3"/> + <click selector="{{CmsPagesPageActionsSection.deleteConfirm}}" stepKey="ClickToConfirm"/> + <waitForPageLoad stepKey="waitForPageLoad4"/> + <see userInput="You deleted the block." stepKey="VerifyBlockIsDeleted"/> + </actionGroup> + <actionGroup name="AddStoreViewToCmsPage" extends="navigateToCreatedCMSPage"> + <arguments> + <argument name="storeViewName" type="string"/> + </arguments> + <remove keyForRemoval="clickExpandContentTabForPage"/> + <remove keyForRemoval="waitForLoadingMaskOfStagingSection"/> + <click selector="{{CmsNewPagePiwSection.header}}" stepKey="clickPageInWebsites" after="waitForPageLoad3"/> + <waitForElementVisible selector="{{CmsNewPagePiwSection.selectStoreView(storeViewName)}}" stepKey="waitForStoreGridReload"/> + <clickWithLeftButton selector="{{CmsNewPagePiwSection.selectStoreView(storeViewName)}}" stepKey="clickStoreView"/> + <click selector="{{CmsNewPagePageActionsSection.expandSplitButton}}" stepKey="expandButtonMenu"/> + <waitForElementVisible selector="{{CmsNewPagePageActionsSection.splitButtonMenu}}" stepKey="waitForSplitButtonMenuVisible"/> + <click selector="{{CmsNewPagePageActionsSection.savePage}}" stepKey="clickSavePage"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <see userInput="You saved the page." stepKey="seeMessage"/> + </actionGroup> + <actionGroup name="saveAndCloseCMSBlockWithSplitButton"> + <waitForElementVisible selector="{{BlockNewPagePageActionsSection.expandSplitButton}}" stepKey="waitForExpandSplitButtonToBeVisible" /> + <click selector="{{BlockNewPagePageActionsSection.expandSplitButton}}" stepKey="expandSplitButton"/> + <click selector="{{BlockNewPagePageActionsSection.saveAndClose}}" stepKey="clickSaveBlock"/> + <waitForPageLoad stepKey="waitForPageLoadAfterClickingSave" /> + <see userInput="You saved the block." stepKey="assertSaveBlockSuccessMessage"/> + </actionGroup> + <actionGroup name="navigateToStorefrontForCreatedPage"> + <arguments> + <argument name="page" type="string"/> + </arguments> + <amOnPage url="{{page}}" stepKey="goToStorefront"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + </actionGroup> + <actionGroup name="saveCMSBlock"> + <waitForElementVisible selector="{{CmsNewBlockBlockActionsSection.savePage}}" stepKey="waitForSaveButton"/> + <click selector="{{CmsNewBlockBlockActionsSection.savePage}}" stepKey="clickSaveButton"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <see userInput="You saved the block." stepKey="seeSuccessfulSaveMessage"/> + </actionGroup> + <actionGroup name="saveAndContinueEditCmsPage"> + <waitForElementVisible time="10" selector="{{CmsNewPagePageActionsSection.saveAndContinueEdit}}" stepKey="waitForSaveAndContinueVisibility"/> + <click selector="{{CmsNewPagePageActionsSection.saveAndContinueEdit}}" stepKey="clickSaveAndContinueEditCmsPage"/> + <waitForPageLoad stepKey="waitForCmsPageLoad"/> + <waitForElementVisible time="1" selector="{{CmsNewPagePageActionsSection.cmsPageTitle}}" stepKey="waitForCmsPageSaveButton"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMask"/> + </actionGroup> + <actionGroup name="saveCmsPage"> + <waitForElementVisible selector="{{CmsNewPagePageActionsSection.expandSplitButton}}" stepKey="waitForSplitButton"/> + <click selector="{{CmsNewPagePageActionsSection.expandSplitButton}}" stepKey="expandSplitButton"/> + <waitForElementVisible selector="{{CmsNewPagePageActionsSection.savePage}}" stepKey="waitForSaveCmsPage"/> + <click selector="{{CmsNewPagePageActionsSection.savePage}}" stepKey="clickSaveCmsPage"/> + <waitForElementVisible time="1" selector="{{CmsPagesPageActionsSection.addNewPageButton}}" stepKey="waitForCmsPageSaveButton"/> + <see userInput="You saved the page." selector="{{CmsPagesPageActionsSection.savePageSuccessMessage}}" stepKey="assertSavePageSuccessMessage"/> + </actionGroup> + <actionGroup name="setLayout"> + <arguments> + <argument name="designSection"/> + <argument name="layoutOption"/> + </arguments> + <waitForElementVisible selector="{{designSection.DesignTab}}" stepKey="waitForDesignTabVisible"/> + <conditionalClick selector="{{designSection.DesignTab}}" dependentSelector="{{designSection.LayoutDropdown}}" visible="false" stepKey="clickOnDesignTab"/> + <waitForPageLoad stepKey="waitForPageLoadDesignTab"/> + <waitForElementVisible selector="{{designSection.LayoutDropdown}}" stepKey="waitForLayoutDropDown" /> + <selectOption selector="{{designSection.LayoutDropdown}}" userInput="{{layoutOption}}" stepKey="selectLayout"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/CreateNewPageWithWidgetActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/CreateNewPageWithWidgetActionGroup.xml new file mode 100644 index 00000000000..a4b88c544de --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/CreateNewPageWithWidgetActionGroup.xml @@ -0,0 +1,40 @@ +<?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="CreateNewPageWithWidget"> + <arguments> + <argument name="pageTitle" type="string" defaultValue="{{defaultCmsPage.title}}"/> + <argument name="category" type="string"/> + <argument name="condition" type="string"/> + <argument name="widgetType" type="string"/> + </arguments> + <amOnPage url="{{CmsNewPagePage.url}}" stepKey="amOnCMSNewPage"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <fillField selector="{{CmsNewPagePageBasicFieldsSection.pageTitle}}" userInput="{{pageTitle}}" stepKey="fillFieldTitle"/> + <click selector="{{CmsNewPagePageContentSection.header}}" stepKey="clickExpandContent"/> + <click selector="{{CmsNewPagePageActionsSection.insertWidget}}" stepKey="clickToInsertWidget"/> + <waitForPageLoad stepKey="waitForPageLoad1"/> + <waitForElementVisible stepKey="waitForInsertWidgetTitle" selector="{{WidgetSection.InsertWidgetTitle}}"/> + <selectOption selector="{{WidgetSection.WidgetType}}" userInput="{{widgetType}}" stepKey="selectCatalogProductsList"/> + <waitForElementVisible selector="{{WidgetSection.AddParam}}" stepKey="waitForAddParam"/> + <scrollTo selector="{{WidgetSection.AddParam}}" stepKey="scrollToAddParamElement"/> + <click selector="{{WidgetSection.AddParam}}" stepKey="addParam"/> + <selectOption selector="{{WidgetSection.ConditionsDropdown}}" userInput="{{condition}}" stepKey="selectCategory"/> + <waitForElementVisible selector="{{WidgetSection.RuleParam}}" stepKey="waitForRuleParam"/> + <click selector="{{WidgetSection.RuleParam}}" stepKey="clickToAddRuleParam"/> + <click selector="{{WidgetSection.Chooser}}" stepKey="clickToSelectFromList"/> + <waitForPageLoad stepKey="waitForPageLoad2"/> + <click selector="{{WidgetSection.PreCreateCategory(category)}}" stepKey="selectPreCategory" /> + <click selector="{{WidgetSection.InsertWidget}}" stepKey="clickToSaveInsertedWidget"/> + <waitForPageLoad stepKey="waitForPageLoad3"/> + <click selector="{{CmsNewBlockBlockActionsSection.savePage}}" stepKey="saveCMSPage"/> + <waitForElementVisible selector="{{CmsPagesPageActionsSection.savePageSuccessMessage}}" stepKey="waitForSuccessMessageLoggedOut" time="5"/> + <see userInput="You saved the page." stepKey="seeSuccessMessage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Cms/Test/Mftf/Data/BlockData.xml b/app/code/Magento/Cms/Test/Mftf/Data/BlockData.xml new file mode 100644 index 00000000000..dea047ec435 --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/Data/BlockData.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="Sales25offBlock" type="block"> + <data key="title" unique="suffix">Sales25off</data> + <data key="identifier" unique="suffix">Sales25off</data> + <data key="store_id">All Store Views</data> + <data key="content">sales25off everything!</data> + <data key="is_active">0</data> + </entity> +</entities> diff --git a/app/code/Magento/Cms/Test/Mftf/Data/CmsPageData.xml b/app/code/Magento/Cms/Test/Mftf/Data/CmsPageData.xml index d8a8401c31f..2ec2eccba23 100644 --- a/app/code/Magento/Cms/Test/Mftf/Data/CmsPageData.xml +++ b/app/code/Magento/Cms/Test/Mftf/Data/CmsPageData.xml @@ -86,4 +86,13 @@ <data key="content">1<br/>2<br/>3<br/>4<br/>5<br/>6<br/>7<br/>8<br/>9<br/>10<br/>11<br/>12<br/>13<br/>14<br/>15<br/>16<br/>17<br/>18<br/>19<br/>20<br/>line21<br/>22<br/>23<br/>24<br/>25<br/>26<br/>line27<br/>2<br/>3<br/>4<br/>5</data> <data key="identifier" unique="suffix">test-page-</data> </entity> + <entity name="_emptyCmsPage" type="cms_page"> + <data key="title" unique="suffix">Test CMS Page</data> + <data key="identifier" unique="suffix">test-page-</data> + </entity> + <entity name="_emptyCmsBlock" type="block"> + <data key="title" unique="suffix">Test CMS Block</data> + <data key="identifier" unique="suffix" >block</data> + <data key="active">true</data> + </entity> </entities> diff --git a/app/code/Magento/Cms/Test/Mftf/Data/NewCMSPageData.xml b/app/code/Magento/Cms/Test/Mftf/Data/NewCMSPageData.xml new file mode 100644 index 00000000000..61dfb051d10 --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/Data/NewCMSPageData.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="defaultCmsPage" type="block"> + <data key="title" unique="suffix">CMSpage</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 new file mode 100644 index 00000000000..3fd100ee02a --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/Page/AdminCmsEditBlockPage.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="AdminEditBlockPage" url="cms/block/edit/block_id" area="admin" module="Magento_Cms"> + <section name="AdminUpdateBlockSection"/> + </page> +</pages> diff --git a/app/code/Magento/Cms/Test/Mftf/Section/AdminBlockGridSection.xml b/app/code/Magento/Cms/Test/Mftf/Section/AdminBlockGridSection.xml new file mode 100644 index 00000000000..ab15570a01f --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/Section/AdminBlockGridSection.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="AdminBlockGridSection"> + <element name="search" type="input" selector="//input[@placeholder='Search by keyword']"/> + <element name="searchButton" type="button" selector="//div[@class='data-grid-search-control-wrap']//label[@class='data-grid-search-label']/following-sibling::button[@class='action-submit']"/> + <element name="checkbox" type="checkbox" selector="//label[@class='data-grid-checkbox-cell-inner']//input[@class='admin__control-checkbox']"/> + <element name="select" type="select" selector="//tr[@class='data-row']//button[@class='action-select']"/> + <element name="editInSelect" type="text" selector="//a[contains(text(), 'Edit')]"/> + </section> +</sections> diff --git a/app/code/Magento/Cms/Test/Mftf/Section/CmsNewBlockBlockActionsSection.xml b/app/code/Magento/Cms/Test/Mftf/Section/CmsNewBlockBlockActionsSection.xml index 4c6014af512..2efa7f62fc4 100644 --- a/app/code/Magento/Cms/Test/Mftf/Section/CmsNewBlockBlockActionsSection.xml +++ b/app/code/Magento/Cms/Test/Mftf/Section/CmsNewBlockBlockActionsSection.xml @@ -17,6 +17,7 @@ <element name="saveAndDuplicate" type="button" selector="#save_and_duplicate" timeout="10"/> <element name="saveAndClose" type="button" selector="#save_and_close" timeout="10"/> <element name="expandSplitButton" type="button" selector="//button[@data-ui-id='save-button-dropdown']" timeout="10"/> + <element name="back" type="button" selector="#back"/> </section> <section name="BlockWYSIWYGSection"> <element name="ShowHideBtn" type="button" selector="#togglecms_block_form_content"/> diff --git a/app/code/Magento/Cms/Test/Mftf/Section/CmsNewPagePageActionsSection.xml b/app/code/Magento/Cms/Test/Mftf/Section/CmsNewPagePageActionsSection.xml index 42f8f4d00ee..a340d0af1e7 100644 --- a/app/code/Magento/Cms/Test/Mftf/Section/CmsNewPagePageActionsSection.xml +++ b/app/code/Magento/Cms/Test/Mftf/Section/CmsNewPagePageActionsSection.xml @@ -22,5 +22,6 @@ <element name="content" type="input" selector="//textarea[@name='content']"/> <element name="spinner" type="input" selector='//div[@data-component="cms_page_form.cms_page_form"]' /> <element name="saveAndClose" type="button" selector="#save_and_close" timeout="10"/> + <element name="insertWidget" type="button" selector="//span[contains(text(),'Insert Widget...')]"/> </section> </sections> diff --git a/app/code/Magento/Cms/Test/Mftf/Section/StorefrontCMSPageSection.xml b/app/code/Magento/Cms/Test/Mftf/Section/StorefrontCMSPageSection.xml index 7a358c61263..280c7dfd826 100644 --- a/app/code/Magento/Cms/Test/Mftf/Section/StorefrontCMSPageSection.xml +++ b/app/code/Magento/Cms/Test/Mftf/Section/StorefrontCMSPageSection.xml @@ -10,7 +10,7 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontCMSPageSection"> <element name="mediaDescription" type="text" selector=".column.main>p>img"/> - <element name="imageSource" type="text" selector="//img[contains(@src,'{{var1}}')]" parameterized="true"/> + <element name="imageSource" type="text" selector="//img[contains(@src,'{{imageName}}')]" parameterized="true"/> <element name="mainTitle" type="text" selector="#maincontent .page-title"/> <element name="mainContent" type="text" selector="#maincontent"/> <element name="footerTop" type="text" selector="footer.page-footer"/> diff --git a/app/code/Magento/Cms/Test/Mftf/Section/TinyMCESection.xml b/app/code/Magento/Cms/Test/Mftf/Section/TinyMCESection.xml index c7ea85e441b..8559334238d 100644 --- a/app/code/Magento/Cms/Test/Mftf/Section/TinyMCESection.xml +++ b/app/code/Magento/Cms/Test/Mftf/Section/TinyMCESection.xml @@ -57,6 +57,7 @@ <element name="WysiwygArrow" type="button" selector="#d3lzaXd5Zw-- > .jstree-icon" /> <element name="checkIfWysiwygArrowExpand" type="button" selector="//li[@id='d3lzaXd5Zw--' and contains(@class,'jstree-closed')]" /> <element name="confirmDelete" type="button" selector=".action-primary.action-accept" /> + <element name="imageBlockByName" type="block" selector="//div[@data-row='file'][contains(., '{{imageName}}')]" parameterized="true"/> </section> <section name="VariableSection"> <element name="InsertWidget" type="button" selector="#insert_variable"/> @@ -110,5 +111,7 @@ <element name="CompareBtn" type="button" selector=".action.tocompare"/> <element name="ClearCompare" type="button" selector="#compare-clear-all"/> <element name="AcceptClear" type="button" selector=".action-primary.action-accept" /> + <element name="SelectPageButton" type="button" selector="//button[@title='Select Page...']"/> + <element name="SelectPageFilterInput" type="input" selector="input.admin__control-text[name='{{filterName}}']" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToWYSIWYGBlockTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToWYSIWYGBlockTest.xml index 03edc69e6d6..05b7dfeeb39 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToWYSIWYGBlockTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToWYSIWYGBlockTest.xml @@ -16,6 +16,7 @@ <description value="Admin should be able to add image to WYSIWYG content of Block"/> <severity value="CRITICAL"/> <testCaseId value="MAGETWO-84376"/> + <group value="WYSIWYGDisabled" /> </annotations> <before> <createData entity="_defaultCmsPage" stepKey="createCMSPage" /> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCMSPageLinkTypeTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCMSPageLinkTypeTest.xml index ded94eab920..1adb781a675 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCMSPageLinkTypeTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCMSPageLinkTypeTest.xml @@ -36,7 +36,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="CMS Page Link" stepKey="selectCMSPageLink" /> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogProductListTypeTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogProductListTypeTest.xml index 2586ffc11d0..394d79bda1a 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogProductListTypeTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogProductListTypeTest.xml @@ -42,7 +42,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 Products List" stepKey="selectCatalogProductsList" /> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithRecentlyComparedProductsTypeTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithRecentlyComparedProductsTypeTest.xml index 691a99a73b9..862f51ea72f 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithRecentlyComparedProductsTypeTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithRecentlyComparedProductsTypeTest.xml @@ -41,7 +41,7 @@ <waitForPageLoad stepKey="wait2"/> <!--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="Recently Compared Products" stepKey="selectRecentlyComparedProducts" /> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithRecentlyViewedProductsTypeTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithRecentlyViewedProductsTypeTest.xml index 9cdbccd1f8c..298aed917fc 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithRecentlyViewedProductsTypeTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithRecentlyViewedProductsTypeTest.xml @@ -40,7 +40,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="Recently Viewed Products" stepKey="selectRecentlyViewedProducts" /> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/CheckStaticBlocksTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/CheckStaticBlocksTest.xml new file mode 100644 index 00000000000..b4bcdaadf9a --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/Test/CheckStaticBlocksTest.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="CheckStaticBlocksTest"> + <annotations> + <features value="Cms"/> + <stories value="MAGETWO-91559 - Static blocks with same ID appear in place of correct block"/> + <title value="Check static blocks: ID should be unique per Store View"/> + <description value="Check static blocks: ID should be unique per Store View"/> + <severity value="CRITICAL"/> + <testCaseId value="MAGETWO-94229"/> + <group value="Cms"/> + <group value="WYSIWYGDisabled"/> + </annotations> + <before> + <magentoCLI command="config:set cms/wysiwyg/enabled disabled" stepKey="disableWYSIWYG"/> + <actionGroup ref="LoginAsAdmin" stepKey="LoginAsAdmin"/> + <actionGroup ref="AdminCreateWebsiteActionGroup" stepKey="AdminCreateWebsite"> + <argument name="newWebsiteName" value="secondWebsite"/> + <argument name="websiteCode" value="second_website"/> + </actionGroup> + <actionGroup ref="AdminCreateNewStoreGroupActionGroup" stepKey="AdminCreateStore"> + <argument name="website" value="secondWebsite"/> + <argument name="storeGroupName" value="{{customStoreGroup.name}}"/> + <argument name="storeGroupCode" value="{{customStoreGroup.code}}"/> + </actionGroup> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="AdminCreateStoreView"> + <argument name="StoreGroup" value="customStoreGroup"/> + <argument name="customStore" value="customStore"/> + </actionGroup> + </before> + + <!--Go to Cms blocks page--> + <amOnPage url="{{CmsBlocksPage.url}}" stepKey="navigateToCMSPagesGrid"/> + <waitForPageLoad stepKey="waitForPageLoad1"/> + <seeInCurrentUrl url="cms/block/" stepKey="VerifyPageIsOpened"/> + <!--Click to create new block--> + <click selector="{{BlockPageActionsSection.addNewBlock}}" stepKey="ClickToAddNewBlock"/> + <waitForPageLoad stepKey="waitForPageLoad2"/> + <seeInCurrentUrl url="cms/block/new" stepKey="VerifyNewBlockPageIsOpened"/> + <actionGroup ref="FillOutBlockContent" stepKey="FillOutBlockContent"/> + <click selector="{{BlockNewPagePageActionsSection.saveBlock}}" stepKey="ClickToSaveBlock"/> + <waitForPageLoad stepKey="waitForPageLoad3"/> + <see userInput="You saved the block." stepKey="VerifyBlockIsSaved"/> + <!--Click to go back and add new block--> + <click selector="{{BlockNewPagePageActionsSection.back}}" stepKey="ClickToGoBack"/> + <waitForPageLoad stepKey="waitForPageLoad4"/> + <click selector="{{BlockPageActionsSection.addNewBlock}}" stepKey="ClickToAddNewBlock1"/> + <waitForPageLoad stepKey="waitForPageLoad5"/> + <seeInCurrentUrl url="cms/block/new" stepKey="VerifyNewBlockPageIsOpened1"/> + <!--Add new BLock with the same data--> + <actionGroup ref="FillOutBlockContent" stepKey="FillOutBlockContent1"/> + <selectOption selector="{{BlockNewPageBasicFieldsSection.storeView}}" userInput="Default Store View" stepKey="selectDefaultStoreView" /> + <selectOption selector="{{BlockNewPageBasicFieldsSection.storeView}}" userInput="{{customStore.name}}" stepKey="selectSecondStoreView1" /> + <click selector="{{BlockNewPagePageActionsSection.saveBlock}}" stepKey="ClickToSaveBlock1"/> + <waitForPageLoad stepKey="waitForPageLoad6"/> + <!--Verify that corresponding message is displayed--> + <see userInput="A block identifier with the same properties already exists in the selected store." stepKey="VerifyBlockIsSaved1"/> + + <after> + <actionGroup ref="AdminDeleteWebsiteActionGroup" stepKey="DeleteWebsite"> + <argument name="websiteName" value="secondWebsite"/> + </actionGroup> + <actionGroup ref="DeleteCMSBlockActionGroup" stepKey="DeleteCMSBlockActionGroup"/> + <magentoCLI command="config:set cms/wysiwyg/enabled enabled" stepKey="enableWYSIWYG"/> + </after> + </test> +</tests> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/StoreViewLanguageCorrectSwitchTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/StoreViewLanguageCorrectSwitchTest.xml new file mode 100644 index 00000000000..65fabfe25e8 --- /dev/null +++ b/app/code/Magento/Cms/Test/Mftf/Test/StoreViewLanguageCorrectSwitchTest.xml @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StoreViewLanguageCorrectSwitchTest"> + <annotations> + <features value="Cms"/> + <stories value="Store View (language) switch leads to 404"/> + <group value="Cms"/> + <title value="Check that Store View(language) switches correct"/> + <description value="Check that Store View(language) switches correct"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-96388"/> + <useCaseId value="MAGETWO-57337"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + + <!-- Create Cms Pages --> + <createData entity="_newDefaultCmsPage" stepKey="createFirstCmsPage"/> + <createData entity="_newDefaultCmsPage" stepKey="createSecondCmsPage"/> + </before> + <after> + <deleteData createDataKey="createFirstCmsPage" stepKey="deleteFirstCmsPage"/> + <deleteData createDataKey="createSecondCmsPage" stepKey="deleteSecondCmsPage"/> + <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteStoreView"> + <argument name="customStore" value="NewStoreViewData"/> + </actionGroup> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Create StoreView --> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createStoreView"> + <argument name="customStore" value="NewStoreViewData"/> + </actionGroup> + + <!-- Add StoreView To Cms Page--> + <actionGroup ref="AddStoreViewToCmsPage" stepKey="gotToCmsPage"> + <argument name="CMSPage" value="$$createSecondCmsPage$$"/> + <argument name="storeViewName" value="{{NewStoreViewData.name}}"/> + </actionGroup> + + <!-- Check that Cms Page is open --> + <amOnPage url="{{StorefrontHomePage.url}}/$$createFirstCmsPage.identifier$$" stepKey="gotToFirstCmsPage"/> + <see userInput="$$createFirstCmsPage.title$$" stepKey="seePageTitle"/> + + <!-- Switch StoreView and check that Cms Page is open --> + <click selector="{{StorefrontHeaderSection.storeViewSwitcher}}" stepKey="clickStoreViewSwitcher"/> + <waitForElementVisible selector="{{StorefrontHeaderSection.storeViewDropdown}}" stepKey="waitForStoreViewDropDown"/> + <click selector="{{StorefrontHeaderSection.storeViewOption(NewStoreViewData.code)}}" stepKey="selectStoreView"/> + <amOnPage url="{{StorefrontHomePage.url}}/$$createSecondCmsPage.identifier$$" stepKey="gotToSecondCmsPage"/> + <see userInput="$$createSecondCmsPage.title$$" stepKey="seePageTitle1"/> + </test> +</tests> diff --git a/app/code/Magento/Cms/Test/Unit/Ui/Component/Listing/DataProviderTest.php b/app/code/Magento/Cms/Test/Unit/Ui/Component/Listing/DataProviderTest.php index 54e0e17ab7a..a624823d02c 100644 --- a/app/code/Magento/Cms/Test/Unit/Ui/Component/Listing/DataProviderTest.php +++ b/app/code/Magento/Cms/Test/Unit/Ui/Component/Listing/DataProviderTest.php @@ -118,7 +118,8 @@ public function testPrepareMetadata() 'config' => [ 'editorConfig' => [ 'enabled' => false - ] + ], + 'componentType' => \Magento\Ui\Component\Container::NAME ] ] ] diff --git a/app/code/Magento/Cms/Ui/Component/DataProvider.php b/app/code/Magento/Cms/Ui/Component/DataProvider.php index 5fc9c5a8960..b02dd6ba98e 100644 --- a/app/code/Magento/Cms/Ui/Component/DataProvider.php +++ b/app/code/Magento/Cms/Ui/Component/DataProvider.php @@ -13,6 +13,9 @@ use Magento\Framework\AuthorizationInterface; use Magento\Framework\View\Element\UiComponent\DataProvider\Reporting; +/** + * DataProvider for cms ui. + */ class DataProvider extends \Magento\Framework\View\Element\UiComponent\DataProvider\DataProvider { /** @@ -67,6 +70,8 @@ public function __construct( } /** + * Get authorization info. + * * @deprecated 101.0.7 * @return AuthorizationInterface|mixed */ @@ -95,7 +100,8 @@ public function prepareMetadata() 'config' => [ 'editorConfig' => [ 'enabled' => false - ] + ], + 'componentType' => \Magento\Ui\Component\Container::NAME ] ] ] 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 da899918699..44bd7d3ba3d 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 @@ -21,7 +21,7 @@ $_height = $block->getImagesHeight(); data-size="<?= $block->escapeHtmlAttr($file->getSize()) ?>" data-mime-type="<?= $block->escapeHtmlAttr($file->getMimeType()) ?>" > - <p class="nm" style="height:<?= $block->escapeHtmlAttr($_height) ?>px;width:<?= $block->escapeHtmlAttr($_width) ?>px;"> + <p class="nm" style="height:<?= $block->escapeHtmlAttr($_height) ?>px;"> <?php if ($block->getFileThumbUrl($file)):?> <img src="<?= $block->escapeHtmlAttr($block->getFileThumbUrl($file)) ?>" alt="<?= $block->escapeHtmlAttr($block->getFileName($file)) ?>"/> <?php endif; ?> diff --git a/app/code/Magento/Config/App/Config/Type/System.php b/app/code/Magento/Config/App/Config/Type/System.php index c237d0ea996..2c4b8a8dc48 100644 --- a/app/code/Magento/Config/App/Config/Type/System.php +++ b/app/code/Magento/Config/App/Config/Type/System.php @@ -13,6 +13,7 @@ use Magento\Config\App\Config\Type\System\Reader; use Magento\Framework\App\ScopeInterface; use Magento\Framework\Cache\FrontendInterface; +use Magento\Framework\Lock\LockManagerInterface; use Magento\Framework\Serialize\SerializerInterface; use Magento\Store\Model\Config\Processor\Fallback; use Magento\Store\Model\ScopeInterface as StoreScope; @@ -27,9 +28,37 @@ */ class System implements ConfigTypeInterface { + /** + * Config cache tag. + */ const CACHE_TAG = 'config_scopes'; + + /** + * System config type. + */ const CONFIG_TYPE = 'system'; + /** + * @var string + */ + private static $lockName = 'SYSTEM_CONFIG'; + /** + * Timeout between retrieves to load the configuration from the cache. + * + * Value of the variable in microseconds. + * + * @var int + */ + private static $delayTimeout = 100000; + /** + * Lifetime of the lock for write in cache. + * + * Value of the variable in seconds. + * + * @var int + */ + private static $lockTimeout = 42; + /** * @var array */ @@ -76,6 +105,11 @@ class System implements ConfigTypeInterface */ private $encryptor; + /** + * @var LockManagerInterface + */ + private $locker; + /** * @param ConfigSourceInterface $source * @param PostProcessorInterface $postProcessor @@ -87,7 +121,7 @@ class System implements ConfigTypeInterface * @param string $configType * @param Reader|null $reader * @param Encryptor|null $encryptor - * + * @param LockManagerInterface|null $locker * @SuppressWarnings(PHPMD.UnusedFormalParameter) * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ @@ -101,14 +135,18 @@ public function __construct( $cachingNestedLevel = 1, $configType = self::CONFIG_TYPE, Reader $reader = null, - Encryptor $encryptor = null + Encryptor $encryptor = null, + LockManagerInterface $locker = null ) { $this->postProcessor = $postProcessor; $this->cache = $cache; $this->serializer = $serializer; $this->configType = $configType; $this->reader = $reader ?: ObjectManager::getInstance()->get(Reader::class); - $this->encryptor = $encryptor ?: ObjectManager::getInstance()->get(\Magento\Framework\Encryption\Encryptor::class); + $this->encryptor = $encryptor + ?: ObjectManager::getInstance()->get(Encryptor::class); + $this->locker = $locker + ?: ObjectManager::getInstance()->get(LockManagerInterface::class); } /** @@ -187,21 +225,61 @@ private function getWithParts($path) } /** - * Load configuration data for all scopes + * Make lock on data load. * + * @param callable $dataLoader + * @param bool $flush * @return array */ - private function loadAllData() + private function lockedLoadData(callable $dataLoader, bool $flush = false): array { - $cachedData = $this->cache->load($this->configType); + $cachedData = $dataLoader(); //optimistic read - if ($cachedData === false) { - $data = $this->readData(); - } else { - $data = $this->serializer->unserialize($this->encryptor->decrypt($cachedData)); + while ($cachedData === false && $this->locker->isLocked(self::$lockName)) { + usleep(self::$delayTimeout); + $cachedData = $dataLoader(); } - return $data; + while ($cachedData === false) { + try { + if ($this->locker->lock(self::$lockName, self::$lockTimeout)) { + if (!$flush) { + $data = $this->readData(); + $this->cacheData($data); + $cachedData = $data; + } else { + $this->cache->clean(\Zend_Cache::CLEANING_MODE_MATCHING_TAG, [self::CACHE_TAG]); + $cachedData = []; + } + } + } finally { + $this->locker->unlock(self::$lockName); + } + + if ($cachedData === false) { + usleep(self::$delayTimeout); + $cachedData = $dataLoader(); + } + } + + return $cachedData; + } + + /** + * Load configuration data for all scopes + * + * @return array + */ + private function loadAllData() + { + return $this->lockedLoadData(function () { + $cachedData = $this->cache->load($this->configType); + $data = false; + if ($cachedData !== false) { + $data = $this->serializer->unserialize($this->encryptor->decrypt($cachedData)); + } + return $data; + }); } /** @@ -212,16 +290,14 @@ private function loadAllData() */ private function loadDefaultScopeData($scopeType) { - $cachedData = $this->cache->load($this->configType . '_' . $scopeType); - - if ($cachedData === false) { - $data = $this->readData(); - $this->cacheData($data); - } else { - $data = [$scopeType => $this->serializer->unserialize($this->encryptor->decrypt($cachedData))]; - } - - return $data; + return $this->lockedLoadData(function () use ($scopeType) { + $cachedData = $this->cache->load($this->configType . '_' . $scopeType); + $scopeData = false; + if ($cachedData !== false) { + $scopeData = [$scopeType => $this->serializer->unserialize($this->encryptor->decrypt($cachedData))]; + } + return $scopeData; + }); } /** @@ -233,31 +309,31 @@ private function loadDefaultScopeData($scopeType) */ private function loadScopeData($scopeType, $scopeId) { - $cachedData = $this->cache->load($this->configType . '_' . $scopeType . '_' . $scopeId); - - if ($cachedData === false) { - if ($this->availableDataScopes === null) { - $cachedScopeData = $this->cache->load($this->configType . '_scopes'); - if ($cachedScopeData !== false) { - $serializedCachedData = $this->encryptor->decrypt($cachedScopeData); - $this->availableDataScopes = $this->serializer->unserialize($serializedCachedData); + return $this->lockedLoadData(function () use ($scopeType, $scopeId) { + $cachedData = $this->cache->load($this->configType . '_' . $scopeType . '_' . $scopeId); + $scopeData = false; + if ($cachedData === false) { + if ($this->availableDataScopes === null) { + $cachedScopeData = $this->cache->load($this->configType . '_scopes'); + if ($cachedScopeData !== false) { + $serializedCachedData = $this->encryptor->decrypt($cachedScopeData); + $this->availableDataScopes = $this->serializer->unserialize($serializedCachedData); + } } + if (is_array($this->availableDataScopes) && !isset($this->availableDataScopes[$scopeType][$scopeId])) { + $scopeData = [$scopeType => [$scopeId => []]]; + } + } else { + $serializedCachedData = $this->encryptor->decrypt($cachedData); + $scopeData = [$scopeType => [$scopeId => $this->serializer->unserialize($serializedCachedData)]]; } - if (is_array($this->availableDataScopes) && !isset($this->availableDataScopes[$scopeType][$scopeId])) { - return [$scopeType => [$scopeId => []]]; - } - $data = $this->readData(); - $this->cacheData($data); - } else { - $serializedCachedData = $this->encryptor->decrypt($cachedData); - $data = [$scopeType => [$scopeId => $this->serializer->unserialize($serializedCachedData)]]; - } - return $data; + return $scopeData; + }); } /** - * Cache configuration data + * Cache configuration data. * * Caches data per scope to avoid reading data for all scopes on every request * @@ -344,6 +420,11 @@ private function readData(): array public function clean() { $this->data = []; - $this->cache->clean(\Zend_Cache::CLEANING_MODE_MATCHING_TAG, [self::CACHE_TAG]); + $this->lockedLoadData( + function () { + return false; + }, + true + ); } } diff --git a/app/code/Magento/Config/Block/System/Config/Form.php b/app/code/Magento/Config/Block/System/Config/Form.php index 81e39a83296..2a29fa33feb 100644 --- a/app/code/Magento/Config/Block/System/Config/Form.php +++ b/app/code/Magento/Config/Block/System/Config/Form.php @@ -134,6 +134,7 @@ class Form extends \Magento\Backend\Block\Widget\Form\Generic * @param \Magento\Config\Block\System\Config\Form\Fieldset\Factory $fieldsetFactory * @param \Magento\Config\Block\System\Config\Form\Field\Factory $fieldFactory * @param array $data + * @param SettingChecker|null $settingChecker */ public function __construct( \Magento\Backend\Block\Template\Context $context, @@ -143,13 +144,15 @@ public function __construct( \Magento\Config\Model\Config\Structure $configStructure, \Magento\Config\Block\System\Config\Form\Fieldset\Factory $fieldsetFactory, \Magento\Config\Block\System\Config\Form\Field\Factory $fieldFactory, - array $data = [] + array $data = [], + SettingChecker $settingChecker = null ) { parent::__construct($context, $registry, $formFactory, $data); $this->_configFactory = $configFactory; $this->_configStructure = $configStructure; $this->_fieldsetFactory = $fieldsetFactory; $this->_fieldFactory = $fieldFactory; + $this->settingChecker = $settingChecker ?: ObjectManager::getInstance()->get(SettingChecker::class); $this->_scopeLabels = [ self::SCOPE_DEFAULT => __('[GLOBAL]'), @@ -158,18 +161,6 @@ public function __construct( ]; } - /** - * @deprecated 100.1.2 - * @return SettingChecker - */ - private function getSettingChecker() - { - if ($this->settingChecker === null) { - $this->settingChecker = ObjectManager::getInstance()->get(SettingChecker::class); - } - return $this->settingChecker; - } - /** * Initialize objects required to render config form * @@ -366,9 +357,8 @@ protected function _initElement( $sharedClass = $this->_getSharedCssClass($field); $requiresClass = $this->_getRequiresCssClass($field, $fieldPrefix); + $isReadOnly = $this->isReadOnly($field, $path); - $isReadOnly = $this->getElementVisibility()->isDisabled($field->getPath()) - ?: $this->getSettingChecker()->isReadOnly($path, $this->getScope(), $this->getStringScopeCode()); $formField = $fieldset->addField( $elementId, $field->getType(), @@ -417,7 +407,7 @@ private function getFieldData(\Magento\Config\Model\Config\Structure\Element\Fie { $data = $this->getAppConfigDataValue($path); - $placeholderValue = $this->getSettingChecker()->getPlaceholderValue( + $placeholderValue = $this->settingChecker->getPlaceholderValue( $path, $this->getScope(), $this->getStringScopeCode() @@ -541,7 +531,7 @@ public function getConfigValue($path) } /** - * @return \Magento\Backend\Block\Widget\Form|\Magento\Framework\View\Element\AbstractBlock + * @inheritdoc */ protected function _beforeToHtml() { @@ -718,6 +708,7 @@ protected function _getAdditionalElementTypes() * * @TODO delete this methods when {^see above^} is done * @return string + * @SuppressWarnings(PHPMD.RequestAwareBlockMethod) */ public function getSectionCode() { @@ -729,6 +720,7 @@ public function getSectionCode() * * @TODO delete this methods when {^see above^} is done * @return string + * @SuppressWarnings(PHPMD.RequestAwareBlockMethod) */ public function getWebsiteCode() { @@ -740,6 +732,7 @@ public function getWebsiteCode() * * @TODO delete this methods when {^see above^} is done * @return string + * @SuppressWarnings(PHPMD.RequestAwareBlockMethod) */ public function getStoreCode() { @@ -797,6 +790,26 @@ private function getAppConfig() return $this->appConfig; } + /** + * Check Path is Readonly + * + * @param \Magento\Config\Model\Config\Structure\Element\Field $field + * @param string $path + * @return boolean + */ + private function isReadOnly(\Magento\Config\Model\Config\Structure\Element\Field $field, $path) + { + $isReadOnly = $this->settingChecker->isReadOnly( + $path, + ScopeConfigInterface::SCOPE_TYPE_DEFAULT + ); + if (!$isReadOnly) { + $isReadOnly = $this->getElementVisibility()->isDisabled($field->getPath()) + ?: $this->settingChecker->isReadOnly($path, $this->getScope(), $this->getStringScopeCode()); + } + return $isReadOnly; + } + /** * Retrieve deployment config data value by path * diff --git a/app/code/Magento/Config/Console/Command/ConfigSet/DefaultProcessor.php b/app/code/Magento/Config/Console/Command/ConfigSet/DefaultProcessor.php index d7d513bfad4..86ae1f96749 100644 --- a/app/code/Magento/Config/Console/Command/ConfigSet/DefaultProcessor.php +++ b/app/code/Magento/Config/Console/Command/ConfigSet/DefaultProcessor.php @@ -7,17 +7,19 @@ use Magento\Config\App\Config\Type\System; use Magento\Config\Console\Command\ConfigSetCommand; +use Magento\Config\Model\Config\Factory as ConfigFactory; use Magento\Framework\App\Config\ConfigPathResolver; use Magento\Framework\App\DeploymentConfig; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Exception\CouldNotSaveException; use Magento\Config\Model\PreparedValueFactory; -use Magento\Framework\App\Config\Value; /** * Processes default flow of config:set command. + * * This processor saves the value of configuration into database. * - * {@inheritdoc} + * @inheritdoc * @api * @since 100.2.0 */ @@ -44,26 +46,36 @@ class DefaultProcessor implements ConfigSetProcessorInterface */ private $preparedValueFactory; + /** + * @var ConfigFactory + */ + private $configFactory; + /** * @param PreparedValueFactory $preparedValueFactory The factory for prepared value * @param DeploymentConfig $deploymentConfig The deployment configuration reader * @param ConfigPathResolver $configPathResolver The resolver for configuration paths according to source type + * @param ConfigFactory|null $configFactory */ public function __construct( PreparedValueFactory $preparedValueFactory, DeploymentConfig $deploymentConfig, - ConfigPathResolver $configPathResolver + ConfigPathResolver $configPathResolver, + ConfigFactory $configFactory = null ) { $this->preparedValueFactory = $preparedValueFactory; $this->deploymentConfig = $deploymentConfig; $this->configPathResolver = $configPathResolver; + + $this->configFactory = $configFactory ?? ObjectManager::getInstance()->get(ConfigFactory::class); } /** * Processes database flow of config:set command. + * * Requires installed application. * - * {@inheritdoc} + * @inheritdoc * @since 100.2.0 */ public function process($path, $value, $scope, $scopeCode) @@ -78,12 +90,12 @@ public function process($path, $value, $scope, $scopeCode) } try { - /** @var Value $backendModel */ - $backendModel = $this->preparedValueFactory->create($path, $value, $scope, $scopeCode); - if ($backendModel instanceof Value) { - $resourceModel = $backendModel->getResource(); - $resourceModel->save($backendModel); - } + $config = $this->configFactory->create([ + 'scope' => $scope, + 'scope_code' => $scopeCode, + ]); + $config->setDataByPath($path, $value); + $config->save(); } catch (\Exception $exception) { throw new CouldNotSaveException(__('%1', $exception->getMessage()), $exception); } diff --git a/app/code/Magento/Config/Model/Config.php b/app/code/Magento/Config/Model/Config.php index a9c400c4664..b1074e92cc9 100644 --- a/app/code/Magento/Config/Model/Config.php +++ b/app/code/Magento/Config/Model/Config.php @@ -9,15 +9,32 @@ use Magento\Config\Model\Config\Structure\Element\Group; use Magento\Config\Model\Config\Structure\Element\Field; use Magento\Framework\App\ObjectManager; +use Magento\Framework\App\ScopeInterface; +use Magento\Framework\App\ScopeResolverPool; +use Magento\Store\Model\ScopeInterface as StoreScopeInterface; +use Magento\Store\Model\ScopeTypeNormalizer; /** * Backend config model * * Used to save configuration + * * @author Magento Core Team <core@magentocommerce.com> * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @api * @since 100.0.2 + * @method string getSection() + * @method void setSection(string $section) + * @method string getWebsite() + * @method void setWebsite(string $website) + * @method string getStore() + * @method void setStore(string $store) + * @method string getScope() + * @method void setScope(string $scope) + * @method int getScopeId() + * @method void setScopeId(int $scopeId) + * @method string getScopeCode() + * @method void setScopeCode(string $scopeCode) */ class Config extends \Magento\Framework\DataObject { @@ -87,6 +104,16 @@ class Config extends \Magento\Framework\DataObject */ private $settingChecker; + /** + * @var ScopeResolverPool + */ + private $scopeResolverPool; + + /** + * @var ScopeTypeNormalizer + */ + private $scopeTypeNormalizer; + /** * @param \Magento\Framework\App\Config\ReinitableConfigInterface $config * @param \Magento\Framework\Event\ManagerInterface $eventManager @@ -97,6 +124,9 @@ class Config extends \Magento\Framework\DataObject * @param \Magento\Store\Model\StoreManagerInterface $storeManager * @param Config\Reader\Source\Deployed\SettingChecker|null $settingChecker * @param array $data + * @param ScopeResolverPool|null $scopeResolverPool + * @param ScopeTypeNormalizer|null $scopeTypeNormalizer + * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( \Magento\Framework\App\Config\ReinitableConfigInterface $config, @@ -107,7 +137,9 @@ public function __construct( \Magento\Framework\App\Config\ValueFactory $configValueFactory, \Magento\Store\Model\StoreManagerInterface $storeManager, SettingChecker $settingChecker = null, - array $data = [] + array $data = [], + ScopeResolverPool $scopeResolverPool = null, + ScopeTypeNormalizer $scopeTypeNormalizer = null ) { parent::__construct($data); $this->_eventManager = $eventManager; @@ -117,7 +149,12 @@ public function __construct( $this->_configLoader = $configLoader; $this->_configValueFactory = $configValueFactory; $this->_storeManager = $storeManager; - $this->settingChecker = $settingChecker ?: ObjectManager::getInstance()->get(SettingChecker::class); + $this->settingChecker = $settingChecker + ?? ObjectManager::getInstance()->get(SettingChecker::class); + $this->scopeResolverPool = $scopeResolverPool + ?? ObjectManager::getInstance()->get(ScopeResolverPool::class); + $this->scopeTypeNormalizer = $scopeTypeNormalizer + ?? ObjectManager::getInstance()->get(ScopeTypeNormalizer::class); } /** @@ -505,9 +542,8 @@ public function setDataByPath($path, $value) } /** - * Get scope name and scopeId + * Set scope data * - * @todo refactor to scope resolver * @return void */ private function initScope() @@ -515,31 +551,66 @@ private function initScope() if ($this->getSection() === null) { $this->setSection(''); } + + $scope = $this->retrieveScope(); + $this->setScope($this->scopeTypeNormalizer->normalize($scope->getScopeType())); + $this->setScopeCode($scope->getCode()); + $this->setScopeId($scope->getId()); + if ($this->getWebsite() === null) { - $this->setWebsite(''); + $this->setWebsite(StoreScopeInterface::SCOPE_WEBSITES === $this->getScope() ? $scope->getId() : ''); } if ($this->getStore() === null) { - $this->setStore(''); + $this->setStore(StoreScopeInterface::SCOPE_STORES === $this->getScope() ? $scope->getId() : ''); } + } - if ($this->getStore()) { - $scope = 'stores'; - $store = $this->_storeManager->getStore($this->getStore()); - $scopeId = (int)$store->getId(); - $scopeCode = $store->getCode(); - } elseif ($this->getWebsite()) { - $scope = 'websites'; - $website = $this->_storeManager->getWebsite($this->getWebsite()); - $scopeId = (int)$website->getId(); - $scopeCode = $website->getCode(); + /** + * Retrieve scope from initial data + * + * @return ScopeInterface + */ + private function retrieveScope(): ScopeInterface + { + $scopeType = $this->getScope(); + if (!$scopeType) { + switch (true) { + case $this->getStore(): + $scopeType = StoreScopeInterface::SCOPE_STORES; + $scopeIdentifier = $this->getStore(); + break; + case $this->getWebsite(): + $scopeType = StoreScopeInterface::SCOPE_WEBSITES; + $scopeIdentifier = $this->getWebsite(); + break; + default: + $scopeType = ScopeInterface::SCOPE_DEFAULT; + $scopeIdentifier = null; + break; + } } else { - $scope = 'default'; - $scopeId = 0; - $scopeCode = ''; + switch (true) { + case $this->getScopeId() !== null: + $scopeIdentifier = $this->getScopeId(); + break; + case $this->getScopeCode() !== null: + $scopeIdentifier = $this->getScopeCode(); + break; + case $this->getStore() !== null: + $scopeIdentifier = $this->getStore(); + break; + case $this->getWebsite() !== null: + $scopeIdentifier = $this->getWebsite(); + break; + default: + $scopeIdentifier = null; + break; + } } - $this->setScope($scope); - $this->setScopeId($scopeId); - $this->setScopeCode($scopeCode); + $scope = $this->scopeResolverPool->get($scopeType) + ->getScope($scopeIdentifier); + + return $scope; } /** diff --git a/app/code/Magento/Config/Model/Config/Backend/Currency/AbstractCurrency.php b/app/code/Magento/Config/Model/Config/Backend/Currency/AbstractCurrency.php index 4ae66bfd969..25303093ace 100644 --- a/app/code/Magento/Config/Model/Config/Backend/Currency/AbstractCurrency.php +++ b/app/code/Magento/Config/Model/Config/Backend/Currency/AbstractCurrency.php @@ -14,6 +14,8 @@ namespace Magento\Config\Model\Config\Backend\Currency; /** + * Base currency class + * * @api * @since 100.0.2 */ @@ -26,18 +28,19 @@ abstract class AbstractCurrency extends \Magento\Framework\App\Config\Value */ protected function _getAllowedCurrencies() { - if (!$this->isFormData() || $this->getData('groups/options/fields/allow/inherit')) { - return explode( + $allowValue = $this->getData('groups/options/fields/allow/value'); + $allowedCurrencies = $allowValue === null || $this->getData('groups/options/fields/allow/inherit') + ? explode( ',', (string)$this->_config->getValue( \Magento\Directory\Model\Currency::XML_PATH_CURRENCY_ALLOW, $this->getScope(), $this->getScopeId() ) - ); - } + ) + : (array) $allowValue; - return (array)$this->getData('groups/options/fields/allow/value'); + return $allowedCurrencies; } /** diff --git a/app/code/Magento/Config/Setup/ConfigOptionsList.php b/app/code/Magento/Config/Setup/ConfigOptionsList.php new file mode 100644 index 00000000000..c410eeae615 --- /dev/null +++ b/app/code/Magento/Config/Setup/ConfigOptionsList.php @@ -0,0 +1,135 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Config\Setup; + +use Magento\Framework\App\DeploymentConfig; +use Magento\Framework\Config\Data\ConfigData; +use Magento\Framework\Config\Data\ConfigDataFactory; +use Magento\Framework\Config\File\ConfigFilePool; +use Magento\Framework\Setup\ConfigOptionsListInterface; +use Magento\Framework\Setup\Option\SelectConfigOption; + +/** + * Deployment configuration options required for the Config module. + */ +class ConfigOptionsList implements ConfigOptionsListInterface +{ + /** + * Input key for the debug_logging option. + */ + const INPUT_KEY_DEBUG_LOGGING = 'enable-debug-logging'; + + /** + * Path to the debug_logging value in the deployment config. + */ + const CONFIG_PATH_DEBUG_LOGGING = 'dev/debug/debug_logging'; + + /** + * Input key for the syslog_logging option. + */ + const INPUT_KEY_SYSLOG_LOGGING = 'enable-syslog-logging'; + + /** + * Path to the syslog_logging value in the deployment config. + */ + const CONFIG_PATH_SYSLOG_LOGGING = 'dev/syslog/syslog_logging'; + + /** + * @var ConfigDataFactory + */ + private $configDataFactory; + + /** + * @param ConfigDataFactory $configDataFactory + */ + public function __construct(ConfigDataFactory $configDataFactory) + { + $this->configDataFactory = $configDataFactory; + } + + /** + * @inheritdoc + */ + public function getOptions() + { + return [ + new SelectConfigOption( + self::INPUT_KEY_DEBUG_LOGGING, + SelectConfigOption::FRONTEND_WIZARD_RADIO, + [true, false, 1, 0], + self::CONFIG_PATH_DEBUG_LOGGING, + 'Enable debug logging' + ), + new SelectConfigOption( + self::INPUT_KEY_SYSLOG_LOGGING, + SelectConfigOption::FRONTEND_WIZARD_RADIO, + [true, false, 1, 0], + self::CONFIG_PATH_SYSLOG_LOGGING, + 'Enable syslog logging' + ), + ]; + } + + /** + * @inheritdoc + */ + public function createConfig(array $options, DeploymentConfig $deploymentConfig) + { + $deploymentOption = [ + self::INPUT_KEY_DEBUG_LOGGING => self::CONFIG_PATH_DEBUG_LOGGING, + self::INPUT_KEY_SYSLOG_LOGGING => self::CONFIG_PATH_SYSLOG_LOGGING, + ]; + + $config = []; + foreach ($deploymentOption as $inputKey => $configPath) { + $configValue = $this->processBooleanConfigValue( + $inputKey, + $configPath, + $options + ); + if ($configValue) { + $config[] = $configValue; + } + } + + return $config; + } + + /** + * Provide config value from input. + * + * @param string $inputKey + * @param string $configPath + * @param array $options + * @return ConfigData|null + */ + private function processBooleanConfigValue(string $inputKey, string $configPath, array &$options): ?ConfigData + { + $configData = null; + if (isset($options[$inputKey])) { + $configData = $this->configDataFactory->create(ConfigFilePool::APP_ENV); + if ($options[$inputKey] === 'true' + || $options[$inputKey] === '1') { + $value = 1; + } else { + $value = 0; + } + $configData->set($configPath, $value); + } + + return $configData; + } + + /** + * @inheritdoc + */ + public function validate(array $options, DeploymentConfig $deploymentConfig) + { + return []; + } +} diff --git a/app/code/Magento/Config/Test/Mftf/ActionGroup/ConfigSalesTaxClassActionGroup.xml b/app/code/Magento/Config/Test/Mftf/ActionGroup/ConfigSalesTaxClassActionGroup.xml index 06c041fabeb..1a7b641070a 100644 --- a/app/code/Magento/Config/Test/Mftf/ActionGroup/ConfigSalesTaxClassActionGroup.xml +++ b/app/code/Magento/Config/Test/Mftf/ActionGroup/ConfigSalesTaxClassActionGroup.xml @@ -28,4 +28,32 @@ <click selector="{{SalesConfigSection.TaxClassesTab}}" stepKey="collapseTaxClassesTab"/> <click selector="{{ContentManagementSection.Save}}" stepKey="saveConfiguration"/> </actionGroup> + <actionGroup name="SetTaxApplyOnSetting"> + <arguments> + <argument name="userInput" type="string"/> + </arguments> + <conditionalClick selector="{{AdminConfigureTaxSection.taxCalculationSettings}}" dependentSelector="{{AdminConfigureTaxSection.taxCalculationAlgorithm}}" visible="false" stepKey="openTaxCalcSettingsSection"/> + <scrollTo selector="{{AdminConfigureTaxSection.taxCalculationApplyTaxOnInherit}}" stepKey="goToCheckbox"/> + <uncheckOption selector="{{AdminConfigureTaxSection.taxCalculationApplyTaxOnInherit}}" stepKey="enableApplyTaxOnSetting"/> + <selectOption selector="{{AdminConfigureTaxSection.taxCalculationApplyTaxOn}}" userInput="{{userInput}}" stepKey="setApplyTaxOn"/> + <scrollTo selector="{{SalesConfigSection.TaxClassesTab}}" stepKey="scrollToTop"/> + <click selector="{{AdminConfigureTaxSection.taxCalculationSettings}}" stepKey="collapseCalcSettingsTab"/> + <click selector="{{AdminConfigureTaxSection.save}}" stepKey="saveConfig"/> + <waitForPageLoad stepKey="waitForConfigSaved"/> + <see userInput="You saved the configuration." stepKey="seeSuccessMessage"/> + </actionGroup> + <actionGroup name="DisableTaxApplyOnOriginalPrice"> + <arguments> + <argument name="userInput" type="string"/> + </arguments> + <amOnPage url="{{AdminSalesTaxClassPage.url}}" stepKey="navigateToSalesTaxPage"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <conditionalClick selector="{{AdminConfigureTaxSection.taxCalculationSettings}}" dependentSelector="{{AdminConfigureTaxSection.taxCalculationAlgorithm}}" visible="false" stepKey="openTaxCalcSettingsSection"/> + <scrollTo selector="{{AdminConfigureTaxSection.taxCalculationApplyTaxOnInherit}}" stepKey="goToCheckbox"/> + <selectOption selector="{{AdminConfigureTaxSection.taxCalculationApplyTaxOn}}" userInput="{{userInput}}" stepKey="setApplyTaxOff"/> + <checkOption selector="{{AdminConfigureTaxSection.taxCalculationApplyTaxOnInherit}}" stepKey="disableApplyTaxOnSetting"/> + <click selector="{{AdminConfigureTaxSection.save}}" stepKey="saveConfig"/> + <waitForPageLoad stepKey="waitForConfigSaved"/> + <see userInput="You saved the configuration." stepKey="seeSuccessMessage"/> + </actionGroup> </actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Config/Test/Mftf/ActionGroup/ConfigWYSIWYGActionGroup.xml b/app/code/Magento/Config/Test/Mftf/ActionGroup/ConfigWYSIWYGActionGroup.xml index 771f0035b82..82411faddfe 100644 --- a/app/code/Magento/Config/Test/Mftf/ActionGroup/ConfigWYSIWYGActionGroup.xml +++ b/app/code/Magento/Config/Test/Mftf/ActionGroup/ConfigWYSIWYGActionGroup.xml @@ -12,15 +12,15 @@ <magentoCLI stepKey="enableWYSIWYG" command="config:set cms/wysiwyg/enabled enabled"/> </actionGroup> <actionGroup name="SwitchToTinyMCE3"> - <comment userInput="Choose TinyMCE3 as the default editor" stepKey="chooseTinyMCE3AsEditor"/> - <conditionalClick stepKey="expandWYSIWYGOptions1" selector="{{ContentManagementSection.WYSIWYGOptions}}" dependentSelector="{{ContentManagementSection.CheckIfTabExpand}}" visible="true" /> - <waitForElementVisible selector="{{ContentManagementSection.SwitcherSystemValue}}" stepKey="waitForCheckbox2" /> - <uncheckOption selector="{{ContentManagementSection.SwitcherSystemValue}}" stepKey="uncheckUseSystemValue2"/> - <waitForElementVisible selector="{{ContentManagementSection.Switcher}}" stepKey="waitForSwitcherDropdown2" /> - <selectOption selector="{{ContentManagementSection.Switcher}}" userInput="TinyMCE 3" stepKey="switchToVersion3" /> - <click selector="{{ContentManagementSection.WYSIWYGOptions}}" stepKey="collapseWYSIWYGOptions" /> - <click selector="{{ContentManagementSection.Save}}" stepKey="saveConfig" /> - <see selector="{{AdminMessagesSection.success}}" userInput="You saved the configuration." stepKey="seeConfigurationSuccessMessage"/> + <comment userInput="Choose TinyMCE3 as the default editor" stepKey="chooseTinyMCE3AsEditor"/> + <conditionalClick stepKey="expandWYSIWYGOptions1" selector="{{ContentManagementSection.WYSIWYGOptions}}" dependentSelector="{{ContentManagementSection.CheckIfTabExpand}}" visible="true" /> + <waitForElementVisible selector="{{ContentManagementSection.SwitcherSystemValue}}" stepKey="waitForCheckbox2" /> + <uncheckOption selector="{{ContentManagementSection.SwitcherSystemValue}}" stepKey="uncheckUseSystemValue2"/> + <waitForElementVisible selector="{{ContentManagementSection.Switcher}}" stepKey="waitForSwitcherDropdown2" /> + <selectOption selector="{{ContentManagementSection.Switcher}}" userInput="TinyMCE 3" stepKey="switchToVersion3" /> + <click selector="{{ContentManagementSection.WYSIWYGOptions}}" stepKey="collapseWYSIWYGOptions" /> + <click selector="{{ContentManagementSection.Save}}" stepKey="saveConfig" /> + <see selector="{{AdminMessagesSection.success}}" userInput="You saved the configuration." stepKey="seeConfigurationSuccessMessage"/> </actionGroup> <actionGroup name="DisabledWYSIWYG"> <magentoCLI stepKey="disableWYSIWYG" command="config:set cms/wysiwyg/enabled disabled"/> diff --git a/app/code/Magento/Config/Test/Mftf/Data/AllowGuestCheckoutData.xml b/app/code/Magento/Config/Test/Mftf/Data/AllowGuestCheckoutData.xml new file mode 100644 index 00000000000..f89cdf1a87b --- /dev/null +++ b/app/code/Magento/Config/Test/Mftf/Data/AllowGuestCheckoutData.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="EnableAllowGuestCheckout" type="allow_guest_checkout_config"> + <requiredEntity type="guest_checkout">AllowGuestCheckoutYes</requiredEntity> + </entity> + <entity name="AllowGuestCheckoutYes" type="guest_checkout"> + <data key="value">1</data> + </entity> + + <entity name="DisableAllowGuestCheckout" type="allow_guest_checkout_config"> + <requiredEntity type="guest_checkout">AllowGuestCheckoutNo</requiredEntity> + </entity> + <entity name="AllowGuestCheckoutNo" type="guest_checkout"> + <data key="value">0</data> + </entity> +</entities> diff --git a/app/code/Magento/Config/Test/Mftf/Data/CountryOptionConfigData.xml b/app/code/Magento/Config/Test/Mftf/Data/CountryOptionConfigData.xml new file mode 100644 index 00000000000..53ca46e7462 --- /dev/null +++ b/app/code/Magento/Config/Test/Mftf/Data/CountryOptionConfigData.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="EnableAdminAccountAllowCountry" type="admin_account_country_options_config"> + <requiredEntity type="admin_account_country_options_value">AdminAccountAllowCountryUS</requiredEntity> + </entity> + <entity name="AdminAccountAllowCountryUS" type="admin_account_country_options_value"> + <data key="value">US</data> + </entity> + + <entity name="DisableAdminAccountAllowCountry" type="default_admin_account_country_options_config"> + <requiredEntity type="checkoutTotalFlagZero">DefaultAdminAccountAllowCountry</requiredEntity> + </entity> + <entity name="DefaultAdminAccountAllowCountry" type="checkoutTotalFlagZero"> + <data key="value">0</data> + </entity> +</entities> diff --git a/app/code/Magento/Config/Test/Mftf/Data/LocaleOptionsData.xml b/app/code/Magento/Config/Test/Mftf/Data/LocaleOptionsData.xml new file mode 100644 index 00000000000..5647283fae1 --- /dev/null +++ b/app/code/Magento/Config/Test/Mftf/Data/LocaleOptionsData.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="SetLocaleOptions" type="locale_options_config"> + <requiredEntity type="code">setLocaleOptionsFrance</requiredEntity> + </entity> + <entity name="setLocaleOptionsFrance" type="code"> + <data key="value">fr_FR</data> + </entity> + + <entity name="DefaultLocaleOptions" type="locale_options_config"> + <requiredEntity type="code">setLocaleOptionsUSA</requiredEntity> + </entity> + <entity name="setLocaleOptionsUSA" type="code"> + <data key="value">en_US</data> + </entity> +</entities> diff --git a/app/code/Magento/Config/Test/Mftf/Data/SystemConfigData.xml b/app/code/Magento/Config/Test/Mftf/Data/SystemConfigData.xml index 75dc19dc99c..85188eb6e04 100644 --- a/app/code/Magento/Config/Test/Mftf/Data/SystemConfigData.xml +++ b/app/code/Magento/Config/Test/Mftf/Data/SystemConfigData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="AdminAccountSharingYes" type="admin_account_sharing_value"> <data key="value">Yes</data> </entity> diff --git a/app/code/Magento/Config/Test/Mftf/Metadata/allow_guest_checkout-meta.xml b/app/code/Magento/Config/Test/Mftf/Metadata/allow_guest_checkout-meta.xml new file mode 100644 index 00000000000..052d9b65747 --- /dev/null +++ b/app/code/Magento/Config/Test/Mftf/Metadata/allow_guest_checkout-meta.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> + <operation name="AllowGuestCheckoutConfig" dataType="allow_guest_checkout_config" type="create" auth="adminFormKey" url="/admin/system_config/save/section/checkout/" method="POST"> + <object key="groups" dataType="allow_guest_checkout_config"> + <object key="options" dataType="allow_guest_checkout_config"> + <object key="fields" dataType="allow_guest_checkout_config"> + <object key="guest_checkout" dataType="guest_checkout"> + <field key="value">string</field> + </object> + </object> + </object> + </object> + </operation> +</operations> diff --git a/app/code/Magento/Config/Test/Mftf/Metadata/locale_options_config-meta.xml b/app/code/Magento/Config/Test/Mftf/Metadata/locale_options_config-meta.xml new file mode 100644 index 00000000000..055a9896cd2 --- /dev/null +++ b/app/code/Magento/Config/Test/Mftf/Metadata/locale_options_config-meta.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> + <operation name="GeneralLocaleOptionsConfig" dataType="locale_options_config" type="create" auth="adminFormKey" url="/admin/system_config/save/section/general/" method="POST"> + <object key="groups" dataType="locale_options_config"> + <object key="locale" dataType="locale_options_config"> + <object key="fields" dataType="locale_options_config"> + <object key="code" dataType="code"> + <field key="value">string</field> + </object> + </object> + </object> + </object> + </operation> +</operations> diff --git a/app/code/Magento/Config/Test/Mftf/Metadata/system_config-countries-meta.xml b/app/code/Magento/Config/Test/Mftf/Metadata/system_config-countries-meta.xml new file mode 100644 index 00000000000..bd16c225af5 --- /dev/null +++ b/app/code/Magento/Config/Test/Mftf/Metadata/system_config-countries-meta.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> + <operation name="AdminAccountCountryOptionConfig" dataType="admin_account_country_options_config" type="create" auth="adminFormKey" url="/admin/system_config/save/section/general/" method="POST"> + <object key="groups" dataType="admin_account_country_options_config"> + <object key="country" dataType="admin_account_country_options_config"> + <object key="fields" dataType="admin_account_country_options_config"> + <object key="allow" dataType="admin_account_country_options_value"> + <field key="value">string</field> + </object> + </object> + </object> + </object> + </operation> + + <operation name="DefaultAdminAccountCountryOptionConfig" dataType="default_admin_account_country_options_config" type="create" auth="adminFormKey" url="/admin/system_config/save/section/general/" method="POST"> + <object key="groups" dataType="default_admin_account_country_options_config"> + <object key="country" dataType="default_admin_account_country_options_config"> + <object key="fields" dataType="default_admin_account_country_options_config"> + <object key="allow" dataType="default_admin_account_country_options_config"> + <object key="inherit" dataType="checkoutTotalFlagZero"> + <field key="value">string</field> + </object> + </object> + </object> + </object> + </object> + </operation> +</operations> diff --git a/app/code/Magento/Config/Test/Mftf/Metadata/system_config-meta.xml b/app/code/Magento/Config/Test/Mftf/Metadata/system_config-meta.xml index 37b8414d1f3..e7544c4e8ae 100644 --- a/app/code/Magento/Config/Test/Mftf/Metadata/system_config-meta.xml +++ b/app/code/Magento/Config/Test/Mftf/Metadata/system_config-meta.xml @@ -5,8 +5,9 @@ * See COPYING.txt for license details. */ --> + <operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> <operation name="AdminAccountSharingConfig" dataType="admin_account_sharing_config" type="create" auth="adminFormKey" url="/admin/system_config/save/section/admin/" method="POST"> <object key="groups" dataType="admin_account_sharing_config"> <object key="security" dataType="admin_account_sharing_config"> diff --git a/app/code/Magento/Config/Test/Mftf/Section/CatalogSection.xml b/app/code/Magento/Config/Test/Mftf/Section/CatalogSection.xml index c48f33ba06b..e999dbc42a6 100644 --- a/app/code/Magento/Config/Test/Mftf/Section/CatalogSection.xml +++ b/app/code/Magento/Config/Test/Mftf/Section/CatalogSection.xml @@ -17,5 +17,9 @@ <element name="catalogPriceScopeValue" type="select" selector="//select[@id='catalog_price_scope']/option[text()='{{args}}']" parameterized="true"/> <element name="defaultProductPrice" type="input" selector="#catalog_price_default_product_price"/> <element name="save" type="button" selector="#save"/> + <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="successMessage" type="text" selector="#messages"/> </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 66b40f74d8e..d007c860782 100644 --- a/app/code/Magento/Config/Test/Mftf/Section/GeneralSection.xml +++ b/app/code/Magento/Config/Test/Mftf/Section/GeneralSection.xml @@ -17,6 +17,7 @@ <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"/> + <element name="StoreConfigurationPageSuccessMessage" type="text" selector="#messages [data-ui-id='messages-message-success']"/> </section> <section name="WebSection"> <element name="DefaultLayoutsTab" type="button" selector="#web_default_layouts-head"/> diff --git a/app/code/Magento/Config/Test/Mftf/Test/CheckingCountryDropDownWithOneAllowedCountryTest.xml b/app/code/Magento/Config/Test/Mftf/Test/CheckingCountryDropDownWithOneAllowedCountryTest.xml new file mode 100644 index 00000000000..b0a7ee07dda --- /dev/null +++ b/app/code/Magento/Config/Test/Mftf/Test/CheckingCountryDropDownWithOneAllowedCountryTest.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="CheckingCountryDropDownWithOneAllowedCountryTest"> + <annotations> + <features value="Config"/> + <stories value="MAGETWO-96107: Additional blank option in country dropdown"/> + <title value="Checking country drop-down with one allowed country"/> + <description value="Check country drop-down with one allowed country"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-96133"/> + <group value="configuration"/> + </annotations> + <before> + <createData entity="EnableAdminAccountAllowCountry" stepKey="setAllowedCountries"/> + </before> + <after> + <createData entity="DisableAdminAccountAllowCountry" stepKey="setDefaultValueForAllowCountries"/> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <actionGroup ref="AdminDeleteCustomerActionGroup" stepKey="deleteCustomer"> + <argument name="customerEmail" value="CustomerEntityOne.email"/> + </actionGroup> + <actionGroup ref="AdminClearCustomersFiltersActionGroup" stepKey="clearFilters"/> + <waitForPageLoad stepKey="WaitForPageToLoad"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!--Flush Magento Cache--> + <magentoCLI stepKey="flushCache" command="cache:flush"/> + <!--Create a customer account from Storefront--> + <actionGroup ref="SignUpNewUserFromStorefrontActionGroup" stepKey="createAnAccount"> + <argument name="Customer" value="CustomerEntityOne"/> + </actionGroup> + <click selector="{{CheckoutPaymentSection.addressBook}}" stepKey="goToAddressBook"/> + <click selector="{{StorefrontCustomerAddressSection.country}}" stepKey="clickToExpandCountryDropDown"/> + <see selector="{{StorefrontCustomerAddressSection.country}}" userInput="United States" stepKey="seeSelectedCountry"/> + <dontSee selector="{{StorefrontCustomerAddressSection.country}}" userInput="Brazil" stepKey="canNotSeeSelectedCountry"/> + </test> +</tests> diff --git a/app/code/Magento/Config/Test/Unit/Block/System/Config/FormTest.php b/app/code/Magento/Config/Test/Unit/Block/System/Config/FormTest.php index 93650dd6265..4e260b0fb2b 100644 --- a/app/code/Magento/Config/Test/Unit/Block/System/Config/FormTest.php +++ b/app/code/Magento/Config/Test/Unit/Block/System/Config/FormTest.php @@ -102,6 +102,9 @@ protected function setUp() \Magento\Config\Block\System\Config\Form\Fieldset\Factory::class ); $this->_fieldFactoryMock = $this->createMock(\Magento\Config\Block\System\Config\Form\Field\Factory::class); + $settingCheckerMock = $this->getMockBuilder(SettingChecker::class) + ->disableOriginalConstructor() + ->getMock(); $this->_coreConfigMock = $this->createMock(\Magento\Framework\App\Config\ScopeConfigInterface::class); $this->_backendConfigMock = $this->createMock(\Magento\Config\Model\Config::class); @@ -153,6 +156,7 @@ protected function setUp() 'fieldsetFactory' => $this->_fieldsetFactoryMock, 'fieldFactory' => $this->_fieldFactoryMock, 'context' => $context, + 'settingChecker' => $settingCheckerMock, ]; $objectArguments = $helper->getConstructArguments(\Magento\Config\Block\System\Config\Form::class, $data); @@ -532,7 +536,7 @@ public function testInitFields( $elementVisibilityMock = $this->getMockBuilder(ElementVisibilityInterface::class) ->getMockForAbstractClass(); - $elementVisibilityMock->expects($this->once()) + $elementVisibilityMock->expects($this->any()) ->method('isDisabled') ->willReturn($isDisabled); diff --git a/app/code/Magento/Config/Test/Unit/Console/Command/ConfigSet/DefaultProcessorTest.php b/app/code/Magento/Config/Test/Unit/Console/Command/ConfigSet/DefaultProcessorTest.php index 984e0fe8426..edb76c067bf 100644 --- a/app/code/Magento/Config/Test/Unit/Console/Command/ConfigSet/DefaultProcessorTest.php +++ b/app/code/Magento/Config/Test/Unit/Console/Command/ConfigSet/DefaultProcessorTest.php @@ -7,13 +7,14 @@ use Magento\Config\App\Config\Type\System; use Magento\Config\Console\Command\ConfigSet\DefaultProcessor; +use Magento\Config\Model\Config; +use Magento\Config\Model\Config\Factory as ConfigFactory; use Magento\Framework\App\Config\ConfigPathResolver; use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\DeploymentConfig; use Magento\Store\Model\ScopeInterface; use Magento\Config\Model\PreparedValueFactory; use Magento\Framework\App\Config\Value; -use Magento\Framework\App\Config\ValueInterface; use Magento\Framework\Model\ResourceModel\Db\AbstractDb; use PHPUnit_Framework_MockObject_MockObject as Mock; @@ -55,17 +56,18 @@ class DefaultProcessorTest extends \PHPUnit\Framework\TestCase */ private $resourceModelMock; + /** + * @var ConfigFactory|Mock + */ + private $configFactory; + /** * @inheritdoc */ protected function setUp() { - $this->deploymentConfigMock = $this->getMockBuilder(DeploymentConfig::class) - ->disableOriginalConstructor() - ->getMock(); - $this->configPathResolverMock = $this->getMockBuilder(ConfigPathResolver::class) - ->disableOriginalConstructor() - ->getMock(); + $this->deploymentConfigMock = $this->createMock(DeploymentConfig::class); + $this->configPathResolverMock = $this->createMock(ConfigPathResolver::class); $this->resourceModelMock = $this->getMockBuilder(AbstractDb::class) ->disableOriginalConstructor() ->setMethods(['save']) @@ -74,14 +76,14 @@ protected function setUp() ->disableOriginalConstructor() ->setMethods(['getResource']) ->getMock(); - $this->preparedValueFactoryMock = $this->getMockBuilder(PreparedValueFactory::class) - ->disableOriginalConstructor() - ->getMock(); + $this->preparedValueFactoryMock = $this->createMock(PreparedValueFactory::class); + $this->configFactory = $this->createMock(ConfigFactory::class); $this->model = new DefaultProcessor( $this->preparedValueFactoryMock, $this->deploymentConfigMock, - $this->configPathResolverMock + $this->configPathResolverMock, + $this->configFactory ); } @@ -98,15 +100,16 @@ public function testProcess($path, $value, $scope, $scopeCode) { $this->configMockForProcessTest($path, $scope, $scopeCode); - $this->preparedValueFactoryMock->expects($this->once()) + $config = $this->createMock(Config::class); + $this->configFactory->expects($this->once()) ->method('create') - ->willReturn($this->valueMock); - $this->valueMock->expects($this->once()) - ->method('getResource') - ->willReturn($this->resourceModelMock); - $this->resourceModelMock->expects($this->once()) + ->with(['scope' => $scope, 'scope_code' => $scopeCode]) + ->willReturn($config); + $config->expects($this->once()) + ->method('setDataByPath') + ->with($path, $value); + $config->expects($this->once()) ->method('save') - ->with($this->valueMock) ->willReturnSelf(); $this->model->process($path, $value, $scope, $scopeCode); @@ -124,28 +127,6 @@ public function processDataProvider() ]; } - public function testProcessWithWrongValueInstance() - { - $path = 'test/test/test'; - $scope = ScopeConfigInterface::SCOPE_TYPE_DEFAULT; - $scopeCode = null; - $value = 'value'; - $valueInterfaceMock = $this->getMockBuilder(ValueInterface::class) - ->getMockForAbstractClass(); - - $this->configMockForProcessTest($path, $scope, $scopeCode); - - $this->preparedValueFactoryMock->expects($this->once()) - ->method('create') - ->willReturn($valueInterfaceMock); - $this->valueMock->expects($this->never()) - ->method('getResource'); - $this->resourceModelMock->expects($this->never()) - ->method('save'); - - $this->model->process($path, $value, $scope, $scopeCode); - } - /** * @param string $path * @param string $scope @@ -185,6 +166,9 @@ public function testProcessLockedValue() ->method('resolve') ->willReturn('system/default/test/test/test'); + $this->configFactory->expects($this->never()) + ->method('create'); + $this->model->process($path, $value, ScopeConfigInterface::SCOPE_TYPE_DEFAULT, null); } } diff --git a/app/code/Magento/Config/Test/Unit/Model/ConfigTest.php b/app/code/Magento/Config/Test/Unit/Model/ConfigTest.php index d0568f48ded..bdcb44b756b 100644 --- a/app/code/Magento/Config/Test/Unit/Model/ConfigTest.php +++ b/app/code/Magento/Config/Test/Unit/Model/ConfigTest.php @@ -5,6 +5,8 @@ */ namespace Magento\Config\Test\Unit\Model; +use PHPUnit\Framework\MockObject\MockObject; + /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ @@ -13,130 +15,158 @@ class ConfigTest extends \PHPUnit\Framework\TestCase /** * @var \Magento\Config\Model\Config */ - protected $_model; + private $model; + + /** + * @var \Magento\Framework\Event\ManagerInterface|MockObject + */ + private $eventManagerMock; + + /** + * @var \Magento\Config\Model\Config\Structure\Reader|MockObject + */ + private $structureReaderMock; + + /** + * @var \Magento\Framework\DB\TransactionFactory|MockObject + */ + private $transFactoryMock; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Framework\App\Config\ReinitableConfigInterface|MockObject */ - protected $_eventManagerMock; + private $appConfigMock; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Config\Model\Config\Loader|MockObject */ - protected $_structureReaderMock; + private $configLoaderMock; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Framework\App\Config\ValueFactory|MockObject */ - protected $_transFactoryMock; + private $dataFactoryMock; /** - * @var \Magento\Framework\App\Config\ReinitableConfigInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Store\Model\StoreManagerInterface|MockObject */ - protected $_appConfigMock; + private $storeManager; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Config\Model\Config\Structure|MockObject */ - protected $_applicationMock; + private $configStructure; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Config\Model\Config\Reader\Source\Deployed\SettingChecker|MockObject */ - protected $_configLoaderMock; + private $settingsChecker; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Framework\App\ScopeResolverPool|MockObject */ - protected $_dataFactoryMock; + private $scopeResolverPool; /** - * @var \Magento\Store\Model\StoreManagerInterface + * @var \Magento\Framework\App\ScopeResolverInterface|MockObject */ - protected $_storeManager; + private $scopeResolver; /** - * @var \Magento\Config\Model\Config\Structure + * @var \Magento\Framework\App\ScopeInterface|MockObject */ - protected $_configStructure; + private $scope; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Store\Model\ScopeTypeNormalizer|MockObject */ - private $_settingsChecker; + private $scopeTypeNormalizer; protected function setUp() { - $this->_eventManagerMock = $this->createMock(\Magento\Framework\Event\ManagerInterface::class); - $this->_structureReaderMock = $this->createPartialMock( + $this->eventManagerMock = $this->createMock(\Magento\Framework\Event\ManagerInterface::class); + $this->structureReaderMock = $this->createPartialMock( \Magento\Config\Model\Config\Structure\Reader::class, ['getConfiguration'] ); - $this->_configStructure = $this->createMock(\Magento\Config\Model\Config\Structure::class); + $this->configStructure = $this->createMock(\Magento\Config\Model\Config\Structure::class); - $this->_structureReaderMock->expects( + $this->structureReaderMock->expects( $this->any() )->method( 'getConfiguration' )->will( - $this->returnValue($this->_configStructure) + $this->returnValue($this->configStructure) ); - $this->_transFactoryMock = $this->createPartialMock( + $this->transFactoryMock = $this->createPartialMock( \Magento\Framework\DB\TransactionFactory::class, ['create', 'addObject'] ); - $this->_appConfigMock = $this->createMock(\Magento\Framework\App\Config\ReinitableConfigInterface::class); - $this->_configLoaderMock = $this->createPartialMock( + $this->appConfigMock = $this->createMock(\Magento\Framework\App\Config\ReinitableConfigInterface::class); + $this->configLoaderMock = $this->createPartialMock( \Magento\Config\Model\Config\Loader::class, ['getConfigByPath'] ); - $this->_dataFactoryMock = $this->createMock(\Magento\Framework\App\Config\ValueFactory::class); + $this->dataFactoryMock = $this->createMock(\Magento\Framework\App\Config\ValueFactory::class); - $this->_storeManager = $this->getMockForAbstractClass(\Magento\Store\Model\StoreManagerInterface::class); + $this->storeManager = $this->createMock(\Magento\Store\Model\StoreManagerInterface::class); - $this->_settingsChecker = $this + $this->settingsChecker = $this ->createMock(\Magento\Config\Model\Config\Reader\Source\Deployed\SettingChecker::class); - $this->_model = new \Magento\Config\Model\Config( - $this->_appConfigMock, - $this->_eventManagerMock, - $this->_configStructure, - $this->_transFactoryMock, - $this->_configLoaderMock, - $this->_dataFactoryMock, - $this->_storeManager, - $this->_settingsChecker + $this->scopeResolverPool = $this->createMock(\Magento\Framework\App\ScopeResolverPool::class); + $this->scopeResolver = $this->createMock(\Magento\Framework\App\ScopeResolverInterface::class); + $this->scopeResolverPool->method('get') + ->willReturn($this->scopeResolver); + $this->scope = $this->createMock(\Magento\Framework\App\ScopeInterface::class); + $this->scopeResolver->method('getScope') + ->willReturn($this->scope); + + $this->scopeTypeNormalizer = $this->createMock(\Magento\Store\Model\ScopeTypeNormalizer::class); + + $this->model = new \Magento\Config\Model\Config( + $this->appConfigMock, + $this->eventManagerMock, + $this->configStructure, + $this->transFactoryMock, + $this->configLoaderMock, + $this->dataFactoryMock, + $this->storeManager, + $this->settingsChecker, + [], + $this->scopeResolverPool, + $this->scopeTypeNormalizer ); } public function testSaveDoesNotDoAnythingIfGroupsAreNotPassed() { - $this->_configLoaderMock->expects($this->never())->method('getConfigByPath'); - $this->_model->save(); + $this->configLoaderMock->expects($this->never())->method('getConfigByPath'); + $this->model->save(); } public function testSaveEmptiesNonSetArguments() { - $this->_structureReaderMock->expects($this->never())->method('getConfiguration'); - $this->assertNull($this->_model->getSection()); - $this->assertNull($this->_model->getWebsite()); - $this->assertNull($this->_model->getStore()); - $this->_model->save(); - $this->assertSame('', $this->_model->getSection()); - $this->assertSame('', $this->_model->getWebsite()); - $this->assertSame('', $this->_model->getStore()); + $this->structureReaderMock->expects($this->never())->method('getConfiguration'); + $this->assertNull($this->model->getSection()); + $this->assertNull($this->model->getWebsite()); + $this->assertNull($this->model->getStore()); + $this->model->save(); + $this->assertSame('', $this->model->getSection()); + $this->assertSame('', $this->model->getWebsite()); + $this->assertSame('', $this->model->getStore()); } public function testSaveToCheckAdminSystemConfigChangedSectionEvent() { $transactionMock = $this->createMock(\Magento\Framework\DB\Transaction::class); - $this->_transFactoryMock->expects($this->any())->method('create')->will($this->returnValue($transactionMock)); + $this->transFactoryMock->expects($this->any())->method('create')->will($this->returnValue($transactionMock)); - $this->_configLoaderMock->expects($this->any())->method('getConfigByPath')->will($this->returnValue([])); + $this->configLoaderMock->expects($this->any())->method('getConfigByPath')->will($this->returnValue([])); - $this->_eventManagerMock->expects( + $this->eventManagerMock->expects( $this->at(0) )->method( 'dispatch' @@ -145,7 +175,7 @@ public function testSaveToCheckAdminSystemConfigChangedSectionEvent() $this->arrayHasKey('website') ); - $this->_eventManagerMock->expects( + $this->eventManagerMock->expects( $this->at(0) )->method( 'dispatch' @@ -154,20 +184,20 @@ public function testSaveToCheckAdminSystemConfigChangedSectionEvent() $this->arrayHasKey('store') ); - $this->_model->setGroups(['1' => ['data']]); - $this->_model->save(); + $this->model->setGroups(['1' => ['data']]); + $this->model->save(); } public function testDoNotSaveReadOnlyFields() { $transactionMock = $this->createMock(\Magento\Framework\DB\Transaction::class); - $this->_transFactoryMock->expects($this->any())->method('create')->will($this->returnValue($transactionMock)); + $this->transFactoryMock->expects($this->any())->method('create')->will($this->returnValue($transactionMock)); - $this->_settingsChecker->expects($this->any())->method('isReadOnly')->will($this->returnValue(true)); - $this->_configLoaderMock->expects($this->any())->method('getConfigByPath')->will($this->returnValue([])); + $this->settingsChecker->expects($this->any())->method('isReadOnly')->will($this->returnValue(true)); + $this->configLoaderMock->expects($this->any())->method('getConfigByPath')->will($this->returnValue([])); - $this->_model->setGroups(['1' => ['fields' => ['key' => ['data']]]]); - $this->_model->setSection('section'); + $this->model->setGroups(['1' => ['fields' => ['key' => ['data']]]]); + $this->model->setSection('section'); $group = $this->createMock(\Magento\Config\Model\Config\Structure\Element\Group::class); $group->method('getPath')->willReturn('section/1'); @@ -176,15 +206,15 @@ public function testDoNotSaveReadOnlyFields() $field->method('getGroupPath')->willReturn('section/1'); $field->method('getId')->willReturn('key'); - $this->_configStructure->expects($this->at(0)) + $this->configStructure->expects($this->at(0)) ->method('getElement') ->with('section/1') ->will($this->returnValue($group)); - $this->_configStructure->expects($this->at(1)) + $this->configStructure->expects($this->at(1)) ->method('getElement') ->with('section/1') ->will($this->returnValue($group)); - $this->_configStructure->expects($this->at(2)) + $this->configStructure->expects($this->at(2)) ->method('getElement') ->with('section/1/key') ->will($this->returnValue($field)); @@ -193,28 +223,28 @@ public function testDoNotSaveReadOnlyFields() \Magento\Framework\App\Config\Value::class, ['addData'] ); - $this->_dataFactoryMock->expects($this->any())->method('create')->will($this->returnValue($backendModel)); + $this->dataFactoryMock->expects($this->any())->method('create')->will($this->returnValue($backendModel)); - $this->_transFactoryMock->expects($this->never())->method('addObject'); + $this->transFactoryMock->expects($this->never())->method('addObject'); $backendModel->expects($this->never())->method('addData'); - $this->_model->save(); + $this->model->save(); } public function testSaveToCheckScopeDataSet() { $transactionMock = $this->createMock(\Magento\Framework\DB\Transaction::class); - $this->_transFactoryMock->expects($this->any())->method('create')->will($this->returnValue($transactionMock)); + $this->transFactoryMock->expects($this->any())->method('create')->will($this->returnValue($transactionMock)); - $this->_configLoaderMock->expects($this->any())->method('getConfigByPath')->will($this->returnValue([])); + $this->configLoaderMock->expects($this->any())->method('getConfigByPath')->will($this->returnValue([])); - $this->_eventManagerMock->expects($this->at(0)) + $this->eventManagerMock->expects($this->at(0)) ->method('dispatch') ->with( $this->equalTo('admin_system_config_changed_section_section'), $this->arrayHasKey('website') ); - $this->_eventManagerMock->expects($this->at(0)) + $this->eventManagerMock->expects($this->at(0)) ->method('dispatch') ->with( $this->equalTo('admin_system_config_changed_section_section'), @@ -228,36 +258,51 @@ public function testSaveToCheckScopeDataSet() $field->method('getGroupPath')->willReturn('section/1'); $field->method('getId')->willReturn('key'); - $this->_configStructure->expects($this->at(0)) + $this->configStructure->expects($this->at(0)) ->method('getElement') ->with('section/1') ->will($this->returnValue($group)); - $this->_configStructure->expects($this->at(1)) + $this->configStructure->expects($this->at(1)) ->method('getElement') ->with('section/1') ->will($this->returnValue($group)); - $this->_configStructure->expects($this->at(2)) + $this->configStructure->expects($this->at(2)) ->method('getElement') ->with('section/1/key') ->will($this->returnValue($field)); - $this->_configStructure->expects($this->at(3)) + $this->configStructure->expects($this->at(3)) ->method('getElement') ->with('section/1') ->will($this->returnValue($group)); - $this->_configStructure->expects($this->at(4)) + $this->configStructure->expects($this->at(4)) ->method('getElement') ->with('section/1/key') ->will($this->returnValue($field)); + $this->scopeResolver->expects($this->atLeastOnce()) + ->method('getScope') + ->with('1') + ->willReturn($this->scope); + $this->scope->expects($this->atLeastOnce()) + ->method('getScopeType') + ->willReturn('website'); + $this->scope->expects($this->atLeastOnce()) + ->method('getId') + ->willReturn(1); + $this->scope->expects($this->atLeastOnce()) + ->method('getCode') + ->willReturn('website_code'); + $this->scopeTypeNormalizer->expects($this->atLeastOnce()) + ->method('normalize') + ->with('website') + ->willReturn('websites'); $website = $this->createMock(\Magento\Store\Model\Website::class); - $website->expects($this->any())->method('getCode')->will($this->returnValue('website_code')); - $this->_storeManager->expects($this->any())->method('getWebsite')->will($this->returnValue($website)); - $this->_storeManager->expects($this->any())->method('getWebsites')->will($this->returnValue([$website])); - $this->_storeManager->expects($this->any())->method('isSingleStoreMode')->will($this->returnValue(true)); + $this->storeManager->expects($this->any())->method('getWebsites')->will($this->returnValue([$website])); + $this->storeManager->expects($this->any())->method('isSingleStoreMode')->will($this->returnValue(true)); - $this->_model->setWebsite('website'); - $this->_model->setSection('section'); - $this->_model->setGroups(['1' => ['fields' => ['key' => ['data']]]]); + $this->model->setWebsite('1'); + $this->model->setSection('section'); + $this->model->setGroups(['1' => ['fields' => ['key' => ['data']]]]); $backendModel = $this->createPartialMock( \Magento\Framework\App\Config\Value::class, @@ -270,7 +315,7 @@ public function testSaveToCheckScopeDataSet() 'groups' => [1 => ['fields' => ['key' => ['data']]]], 'group_id' => null, 'scope' => 'websites', - 'scope_id' => 0, + 'scope_id' => 1, 'scope_code' => 'website_code', 'field_config' => null, 'fieldset_data' => ['key' => null], @@ -280,16 +325,16 @@ public function testSaveToCheckScopeDataSet() ->with('section/1/key') ->will($this->returnValue($backendModel)); - $this->_dataFactoryMock->expects($this->any())->method('create')->will($this->returnValue($backendModel)); + $this->dataFactoryMock->expects($this->any())->method('create')->will($this->returnValue($backendModel)); - $this->_model->save(); + $this->model->save(); } public function testSetDataByPath() { $value = 'value'; $path = '<section>/<group>/<field>'; - $this->_model->setDataByPath($path, $value); + $this->model->setDataByPath($path, $value); $expected = [ 'section' => '<section>', 'groups' => [ @@ -300,7 +345,7 @@ public function testSetDataByPath() ], ], ]; - $this->assertSame($expected, $this->_model->getData()); + $this->assertSame($expected, $this->model->getData()); } /** @@ -309,7 +354,7 @@ public function testSetDataByPath() */ public function testSetDataByPathEmpty() { - $this->_model->setDataByPath('', 'value'); + $this->model->setDataByPath('', 'value'); } /** @@ -324,7 +369,7 @@ public function testSetDataByPathWrongDepth($path, $expectedException) $this->expectException('\UnexpectedValueException'); $this->expectExceptionMessage($expectedException); $value = 'value'; - $this->_model->setDataByPath($path, $value); + $this->model->setDataByPath($path, $value); } /** diff --git a/app/code/Magento/Config/etc/adminhtml/di.xml b/app/code/Magento/Config/etc/adminhtml/di.xml index 5e54f177776..189fbdf69a7 100644 --- a/app/code/Magento/Config/etc/adminhtml/di.xml +++ b/app/code/Magento/Config/etc/adminhtml/di.xml @@ -6,7 +6,6 @@ */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> - <preference for="Magento\Config\Model\Config\Structure\SearchInterface" type="Magento\Config\Model\Config\Structure" /> <preference for="Magento\Config\Model\Config\Backend\File\RequestData\RequestDataInterface" type="Magento\Config\Model\Config\Backend\File\RequestData" /> <preference for="Magento\Config\Model\Config\Structure\ElementVisibilityInterface" type="Magento\Config\Model\Config\Structure\ElementVisibilityComposite" /> <type name="Magento\Config\Model\Config\Structure\Element\Iterator\Tab" shared="false" /> diff --git a/app/code/Magento/Config/etc/di.xml b/app/code/Magento/Config/etc/di.xml index a5dd18097fb..87a0e666d2d 100644 --- a/app/code/Magento/Config/etc/di.xml +++ b/app/code/Magento/Config/etc/di.xml @@ -77,6 +77,11 @@ </argument> </arguments> </type> + <type name="Magento\Framework\Lock\Backend\Cache"> + <arguments> + <argument name="cache" xsi:type="object">Magento\Framework\App\Cache\Type\Config</argument> + </arguments> + </type> <type name="Magento\Config\App\Config\Type\System"> <arguments> <argument name="source" xsi:type="object">systemConfigSourceAggregatedProxy</argument> @@ -85,6 +90,7 @@ <argument name="preProcessor" xsi:type="object">Magento\Framework\App\Config\PreProcessorComposite</argument> <argument name="serializer" xsi:type="object">Magento\Framework\Serialize\Serializer\Serialize</argument> <argument name="reader" xsi:type="object">Magento\Config\App\Config\Type\System\Reader\Proxy</argument> + <argument name="locker" xsi:type="object">Magento\Framework\Lock\Backend\Cache</argument> </arguments> </type> <type name="Magento\Config\App\Config\Type\System\Reader"> diff --git a/app/code/Magento/ConfigurableProduct/Model/Plugin/Frontend/ProductIdentitiesExtender.php b/app/code/Magento/ConfigurableProduct/Model/Plugin/Frontend/ProductIdentitiesExtender.php new file mode 100644 index 00000000000..92b7ab0d88e --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Model/Plugin/Frontend/ProductIdentitiesExtender.php @@ -0,0 +1,48 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\ConfigurableProduct\Model\Plugin\Frontend; + +use Magento\ConfigurableProduct\Model\Product\Type\Configurable; +use Magento\Catalog\Model\Product; + +/** + * Extender of product identities for child of configurable products + */ +class ProductIdentitiesExtender +{ + /** + * @var Configurable + */ + private $configurableType; + + /** + * @param Configurable $configurableType + */ + public function __construct(Configurable $configurableType) + { + $this->configurableType = $configurableType; + } + + /** + * Add child identities to product identities + * + * @param Product $subject + * @param array $identities + * @return array + */ + public function afterGetIdentities(Product $subject, array $identities): array + { + foreach ($this->configurableType->getChildrenIds($subject->getId()) as $childIds) { + foreach ($childIds as $childId) { + $identities[] = Product::CACHE_TAG . '_' . $childId; + } + } + + return array_unique($identities); + } +} diff --git a/app/code/Magento/ConfigurableProduct/Model/Product/VariationHandler.php b/app/code/Magento/ConfigurableProduct/Model/Product/VariationHandler.php index 73e7f9053fa..1bd8ef59f0d 100644 --- a/app/code/Magento/ConfigurableProduct/Model/Product/VariationHandler.php +++ b/app/code/Magento/ConfigurableProduct/Model/Product/VariationHandler.php @@ -203,7 +203,9 @@ protected function fillSimpleProductData( $postData['stock_data'] = array_diff_key((array)$parentProduct->getStockData(), array_flip($keysFilter)); if (!isset($postData['stock_data']['is_in_stock'])) { $stockStatus = $parentProduct->getQuantityAndStockStatus(); - $postData['stock_data']['is_in_stock'] = $stockStatus['is_in_stock']; + 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']) @@ -262,6 +264,8 @@ public function duplicateImagesForVariations($productsData) } /** + * Process media gallery for product + * * @param \Magento\Catalog\Model\Product $product * @param array $productData * diff --git a/app/code/Magento/ConfigurableProduct/Plugin/Catalog/Model/Product/Pricing/Renderer/SalableResolver.php b/app/code/Magento/ConfigurableProduct/Plugin/Catalog/Model/Product/Pricing/Renderer/SalableResolver.php index df8782ae422..c828c0929b4 100644 --- a/app/code/Magento/ConfigurableProduct/Plugin/Catalog/Model/Product/Pricing/Renderer/SalableResolver.php +++ b/app/code/Magento/ConfigurableProduct/Plugin/Catalog/Model/Product/Pricing/Renderer/SalableResolver.php @@ -5,7 +5,7 @@ */ namespace Magento\ConfigurableProduct\Plugin\Catalog\Model\Product\Pricing\Renderer; -use Magento\ConfigurableProduct\Pricing\Price\LowestPriceOptionsProviderInterface; +use Magento\ConfigurableProduct\Model\Product\Type\Configurable as TypeConfigurable; /** * A plugin for a salable resolver. @@ -13,17 +13,16 @@ class SalableResolver { /** - * @var LowestPriceOptionsProviderInterface + * @var TypeConfigurable */ - private $lowestPriceOptionsProvider; + private $typeConfigurable; /** - * @param LowestPriceOptionsProviderInterface $lowestPriceOptionsProvider + * @param TypeConfigurable $typeConfigurable */ - public function __construct( - LowestPriceOptionsProviderInterface $lowestPriceOptionsProvider - ) { - $this->lowestPriceOptionsProvider = $lowestPriceOptionsProvider; + public function __construct(TypeConfigurable $typeConfigurable) + { + $this->typeConfigurable = $typeConfigurable; } /** @@ -32,9 +31,7 @@ public function __construct( * @param \Magento\Catalog\Model\Product\Pricing\Renderer\SalableResolver $subject * @param bool $result * @param \Magento\Framework\Pricing\SaleableInterface $salableItem - * * @return bool - * * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function afterIsSalable( @@ -42,8 +39,8 @@ public function afterIsSalable( $result, \Magento\Framework\Pricing\SaleableInterface $salableItem ) { - if ($salableItem->getTypeId() == 'configurable' && $result) { - $result = $salableItem->isSalable(); + if ($salableItem->getTypeId() === TypeConfigurable::TYPE_CODE && $result) { + $result = $this->typeConfigurable->isSalable($salableItem); } return $result; diff --git a/app/code/Magento/ConfigurableProduct/Plugin/SalesRule/Model/Rule/Condition/Product.php b/app/code/Magento/ConfigurableProduct/Plugin/SalesRule/Model/Rule/Condition/Product.php new file mode 100644 index 00000000000..1ed4432347b --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Plugin/SalesRule/Model/Rule/Condition/Product.php @@ -0,0 +1,69 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\ConfigurableProduct\Plugin\SalesRule\Model\Rule\Condition; + +use Magento\ConfigurableProduct\Model\Product\Type\Configurable; + +/** + * Class Product + * + * @package Magento\ConfigurableProduct\Plugin\SalesRule\Model\Rule\Condition + */ +class Product +{ + /** + * Prepare configurable product for validation. + * + * @param \Magento\SalesRule\Model\Rule\Condition\Product $subject + * @param \Magento\Framework\Model\AbstractModel $model + * @return array + */ + public function beforeValidate( + \Magento\SalesRule\Model\Rule\Condition\Product $subject, + \Magento\Framework\Model\AbstractModel $model + ) { + $product = $this->getProductToValidate($subject, $model); + if ($model->getProduct() !== $product) { + // We need to replace product only for validation and keep original product for all other cases. + $clone = clone $model; + $clone->setProduct($product); + $model = $clone; + } + + return [$model]; + } + + /** + * Select proper product for validation. + * + * @param \Magento\SalesRule\Model\Rule\Condition\Product $subject + * @param \Magento\Framework\Model\AbstractModel $model + * + * @return \Magento\Catalog\Api\Data\ProductInterface|\Magento\Catalog\Model\Product + */ + private function getProductToValidate( + \Magento\SalesRule\Model\Rule\Condition\Product $subject, + \Magento\Framework\Model\AbstractModel $model + ) { + /** @var \Magento\Catalog\Model\Product $product */ + $product = $model->getProduct(); + + $attrCode = $subject->getAttribute(); + + /* Check for attributes which are not available for configurable products */ + if ($product->getTypeId() == Configurable::TYPE_CODE && !$product->hasData($attrCode)) { + /** @var \Magento\Catalog\Model\AbstractModel $childProduct */ + $childProduct = current($model->getChildren())->getProduct(); + if ($childProduct->hasData($attrCode)) { + $product = $childProduct; + } + } + + return $product; + } +} diff --git a/app/code/Magento/ConfigurableProduct/Pricing/Render/TierPriceBox.php b/app/code/Magento/ConfigurableProduct/Pricing/Render/TierPriceBox.php index 611523a60b0..447ba16d727 100644 --- a/app/code/Magento/ConfigurableProduct/Pricing/Render/TierPriceBox.php +++ b/app/code/Magento/ConfigurableProduct/Pricing/Render/TierPriceBox.php @@ -5,6 +5,8 @@ */ namespace Magento\ConfigurableProduct\Pricing\Render; +use Magento\Catalog\Pricing\Price\TierPrice; + /** * Responsible for displaying tier price box on configurable product page. * @@ -17,9 +19,27 @@ class TierPriceBox extends FinalPriceBox */ public function toHtml() { - // Hide tier price block in case of MSRP. - if (!$this->isMsrpPriceApplicable()) { + // Hide tier price block in case of MSRP or in case when no options with tier price. + if (!$this->isMsrpPriceApplicable() && $this->isTierPriceApplicable()) { return parent::toHtml(); } } + + /** + * Check if at least one of simple products has tier price. + * + * @return bool + */ + private function isTierPriceApplicable() + { + $product = $this->getSaleableItem(); + foreach ($product->getTypeInstance()->getUsedProducts($product) as $simpleProduct) { + if ($simpleProduct->isSalable() && + !empty($simpleProduct->getPriceInfo()->getPrice(TierPrice::PRICE_CODE)->getTierPriceList()) + ) { + return true; + } + } + return false; + } } diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminAddOptionsToAttributeActionGroup.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminAddOptionsToAttributeActionGroup.xml new file mode 100644 index 00000000000..4328159d6e9 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminAddOptionsToAttributeActionGroup.xml @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="addOptionsToAttributeActionGroup"> + <arguments> + <argument name="option1" defaultValue="colorProductAttribute2"/> + <argument name="option2" defaultValue="colorDefaultProductAttribute1"/> + <argument name="option3" defaultValue="colorProductAttribute3"/> + <argument name="option4" defaultValue="colorProductAttribute1"/> + <argument name="option5" defaultValue="colorDefaultProductAttribute2"/> + </arguments> + <!--Add option 1 to attribute--> + <click selector="{{AdminNewAttributePanel.addOption}}" stepKey="clickAddOption1"/> + <waitForElementVisible selector="{{AdminNewAttributePanel.isDefault('1')}}" time="30" stepKey="waitForOptionRow1" after="clickAddOption1"/> + <fillField selector="{{AdminNewAttributePanel.optionAdminValue('0')}}" userInput="{{option1.name}}" stepKey="fillAdminLabel1" after="waitForOptionRow1"/> + <!--Add option 2 to attribute--> + <click selector="{{AdminNewAttributePanel.addOption}}" stepKey="clickAddOption2" after="fillAdminLabel1"/> + <waitForElementVisible selector="{{AdminNewAttributePanel.isDefault('2')}}" time="30" stepKey="waitForOptionRow2" after="clickAddOption2"/> + <fillField selector="{{AdminNewAttributePanel.optionAdminValue('1')}}" userInput="{{option2.name}}" stepKey="fillAdminLabel2" after="waitForOptionRow2"/> + <!--Add option 3 to attribute--> + <click selector="{{AdminNewAttributePanel.addOption}}" stepKey="clickAddOption3" after="fillAdminLabel2"/> + <waitForElementVisible selector="{{AdminNewAttributePanel.isDefault('3')}}" time="30" stepKey="waitForOptionRow3" after="clickAddOption3"/> + <fillField selector="{{AdminNewAttributePanel.optionAdminValue('2')}}" userInput="{{option3.name}}" stepKey="fillAdminLabel3" after="waitForOptionRow3"/> + <!--Add option 4 to attribute--> + <click selector="{{AdminNewAttributePanel.addOption}}" stepKey="clickAddOption4" after="fillAdminLabel3"/> + <waitForElementVisible selector="{{AdminNewAttributePanel.isDefault('4')}}" time="30" stepKey="waitForOptionRow4" after="clickAddOption4"/> + <fillField selector="{{AdminNewAttributePanel.optionAdminValue('3')}}" userInput="{{option4.name}}" stepKey="fillAdminLabel4" after="waitForOptionRow4"/> + <!--Add option 5 to attribute--> + <click selector="{{AdminNewAttributePanel.addOption}}" stepKey="clickAddOption5" after="fillAdminLabel4"/> + <waitForElementVisible selector="{{AdminNewAttributePanel.isDefault('5')}}" time="30" stepKey="waitForOptionRow5" after="clickAddOption5"/> + <fillField selector="{{AdminNewAttributePanel.optionAdminValue('4')}}" userInput="{{option5.name}}" stepKey="fillAdminLabel5" after="waitForOptionRow5"/> + <!--Save attribute--> + <click selector="{{AdminNewAttributePanel.saveAttribute}}" stepKey="clickSaveAttribute" after="fillAdminLabel5"/> + <waitForPageLoad stepKey="waitForSavingAttribute"/> + <see userInput="You saved the product attribute." stepKey="seeSuccessMessage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminConfigurableProductActionGroup.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminConfigurableProductActionGroup.xml index 0ac1914040d..d2abfc79775 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminConfigurableProductActionGroup.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminConfigurableProductActionGroup.xml @@ -130,4 +130,24 @@ <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="clickOnSaveButton2"/> <click selector="{{AdminChooseAffectedAttributeSetPopup.confirm}}" stepKey="clickOnConfirmInPopup"/> </actionGroup> + + <actionGroup name="createConfigurationsForTwoAttribute" extends="generateConfigurationsByAttributeCode"> + <arguments> + <argument name="secondAttributeCode" type="string"/> + </arguments> + <remove keyForRemoval="clickOnSelectAll"/> + <remove keyForRemoval="clickFilters"/> + <remove keyForRemoval="fillFilterAttributeCodeField"/> + <remove keyForRemoval="clickApplyFiltersButton"/> + <remove keyForRemoval="clickOnFirstCheckbox"/> + + <click selector="{{AdminCreateProductConfigurationsPanel.attributeCheckbox(attributeCode)}}" stepKey="clickOnFirstAttributeCheckbox" after="clickCreateConfigurations"/> + <click selector="{{AdminCreateProductConfigurationsPanel.attributeCheckbox(secondAttributeCode)}}" stepKey="clickOnSecondAttributeCheckbox" after="clickOnFirstAttributeCheckbox"/> + <grabTextFrom selector="{{AdminCreateProductConfigurationsPanel.defaultLabel(attributeCode)}}" stepKey="grabFirstAttributeDefaultLabel" after="clickOnSecondAttributeCheckbox"/> + <grabTextFrom selector="{{AdminCreateProductConfigurationsPanel.defaultLabel(secondAttributeCode)}}" stepKey="grabSecondAttributeDefaultLabel" after="grabFirstAttributeDefaultLabel"/> + <click selector="{{AdminCreateProductConfigurationsPanel.selectAllByAttribute({$grabFirstAttributeDefaultLabel})}}" stepKey="clickOnSelectAllForFistAttribute" after="clickOnNextButton1"/> + <click selector="{{AdminCreateProductConfigurationsPanel.selectAllByAttribute({$grabSecondAttributeDefaultLabel})}}" stepKey="clickOnSelectAllForSecondAttribute" after="clickOnSelectAllForFistAttribute"/> + <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="clickOnSaveButton2"/> + <click selector="{{AdminChooseAffectedAttributeSetPopup.confirm}}" stepKey="clickOnConfirmInPopup"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/ConfigurableProductAttributeNameDesignActionGroup.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/ConfigurableProductAttributeNameDesignActionGroup.xml index 95533057608..c4ad02ee141 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/ConfigurableProductAttributeNameDesignActionGroup.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/ConfigurableProductAttributeNameDesignActionGroup.xml @@ -7,8 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> - + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="GotoCatalogProductsPage"> <!--Click on Catalog item--> @@ -168,5 +167,4 @@ <waitForPageLoad stepKey="waitForAllFilterReset"/> </actionGroup> - </actionGroups> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConfigurableProductAttributeNameDesignData.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConfigurableProductAttributeNameDesignData.xml index 73a668fd2fe..0018f5996c9 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConfigurableProductAttributeNameDesignData.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConfigurableProductAttributeNameDesignData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="NewProductsData" type="user"> <data key="productName" unique="prefix">Shoes</data> <data key="price">60</data> @@ -31,5 +31,4 @@ <data key="configurableProduct">configurable</data> <data key="errorMessage">element.disabled is not a function</data> </entity> - </entities> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminChooseAffectedAttributeSetSection.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminChooseAffectedAttributeSetSection.xml index 42896383529..6e8303e6bae 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminChooseAffectedAttributeSetSection.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminChooseAffectedAttributeSetSection.xml @@ -10,5 +10,6 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminChooseAffectedAttributeSetPopup"> <element name="confirm" type="button" selector="button[data-index='confirm_button']" timeout="30"/> + <element name="closePopUp" type="button" selector="//*[contains(@class,'product_form_product_form_configurable_attribute_set')]//button[@data-role='closeBtn']" timeout="30"/> </section> -</sections> \ No newline at end of file +</sections> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminCreateProductConfigurationsPanelSection.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminCreateProductConfigurationsPanelSection.xml index 7901b6f2290..9b4798c95ec 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminCreateProductConfigurationsPanelSection.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminCreateProductConfigurationsPanelSection.xml @@ -16,6 +16,8 @@ <element name="applyFilters" type="button" selector="button[data-action='grid-filter-apply']" timeout="30"/> <element name="firstCheckbox" type="input" selector="tr[data-repeat-index='0'] .admin__control-checkbox"/> <element name="id" type="text" selector="//tr[contains(@data-repeat-index, '0')]/td[2]/div"/> + <element name="attributeCheckbox" type="checkbox" selector="//div[contains(text(), '{{arg}}')]/ancestor::tr//input[@data-action='select-row']" parameterized="true"/> + <element name="defaultLabel" type="text" selector="//div[contains(text(), '{{arg}}')]/ancestor::tr//td[3]/div[@class='data-grid-cell-content']" parameterized="true"/> <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"/> @@ -35,6 +37,7 @@ <element name="applySingleQuantityToEachSkus" type="radio" selector=".admin__field-label[for='apply-single-inventory-radio']" timeout="30"/> <element name="quantity" type="input" selector="#apply-single-inventory-input"/> <element name="gridLoadingMask" type="text" selector="[data-role='spinner'][data-component*='product_attributes_listing']"/> + <element name="attributeCheckboxByName" type="input" selector="//*[contains(@data-attribute-option-title,'{{arg}}')]//input[@type='checkbox']" parameterized="true"/> <element name="attributeColorCheckbox" type="select" selector="//div[contains(text(),'color') and @class='data-grid-cell-content']/../preceding-sibling::td/label/input"/> </section> </sections> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminNewAttributePanelSection.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminNewAttributePanelSection.xml index 44077888f8b..658e7a5fec9 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminNewAttributePanelSection.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminNewAttributePanelSection.xml @@ -9,6 +9,15 @@ <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminNewAttributePanel"> + <element name="useInSearch" type="select" selector="#is_searchable"/> + <element name="visibleInAdvancedSearch" type="select" selector="#is_visible_in_advanced_search"/> + <element name="comparableOnStorefront" type="select" selector="#is_comparable"/> + <element name="useInLayeredNavigation" type="select" selector="#is_filterable"/> + <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="container" type="text" selector="#create_new_attribute"/> <element name="saveAttribute" type="button" selector="#save"/> <element name="newAttributeIFrame" type="iframe" selector="create_new_attribute_container"/> @@ -20,5 +29,6 @@ <element name="optionAdminValue" type="input" selector="[data-role='options-container'] input[name='option[value][option_{{row}}][0]']" parameterized="true"/> <element name="optionDefaultStoreValue" type="input" selector="[data-role='options-container'] input[name='option[value][option_{{row}}][1]']" parameterized="true"/> <element name="deleteOption" type="button" selector="#delete_button_option_{{row}}" parameterized="true"/> + <element name="deleteOptionByName" type="button" selector="//*[contains(@value, '{{arg}}')]/../following-sibling::td[contains(@id, 'delete_button_container')]/button" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminProductFormConfigurationsSection.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminProductFormConfigurationsSection.xml index f2caef1717e..c5d6abd89ed 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminProductFormConfigurationsSection.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminProductFormConfigurationsSection.xml @@ -18,10 +18,13 @@ <element name="currentVariationsQuantityCells" type="textarea" selector=".admin__control-fields[data-index='quantity_container']"/> <element name="currentVariationsAttributesCells" type="textarea" selector=".admin__control-fields[data-index='attributes']"/> <element name="currentVariationsStatusCells" type="textarea" selector="._no-header[data-index='status']"/> + <element name="firstSKUInConfigurableProductsGrid" type="input" selector="//input[@name='configurable-matrix[0][sku]']"/> <element name="actionsBtn" type="button" selector="(//button[@class='action-select']/span[contains(text(), 'Select')])[{{var1}}]" parameterized="true"/> <element name="removeProductBtn" type="button" selector="//a[text()='Remove Product']"/> <element name="disableProductBtn" type="button" selector="//a[text()='Disable Product']"/> <element name="enableProductBtn" type="button" selector="//a[text()='Enable Product']"/> + <element name="confProductSku" type="input" selector="//*[@name='configurable-matrix[{{arg}}][sku]']" 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"/> </section> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/ConfigurableProductAttributeNameDesignSection.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/ConfigurableProductAttributeNameDesignSection.xml index b3077d9d5d5..ea5638f6816 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/ConfigurableProductAttributeNameDesignSection.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/ConfigurableProductAttributeNameDesignSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="CatalogProductsSection"> <element name="catalogItem" type="button" selector="//*[@id='menu-magento-catalog-catalog']/a/span"/> <element name="productItem" type="button" selector="//*[@data-ui-id='menu-magento-catalog-catalog-products']/a"/> @@ -53,7 +53,6 @@ <element name="saveAttributeButton" type="button" selector="//*[@id='save']"/> <element name="advancedAttributeProperties" type="button" selector="//*[@id='advanced_fieldset-wrapper']//*[contains(text(),'Advanced Attribute Properties')]"/> <element name="attributeCodeField" type="input" selector="//*[@id='attribute_code']"/> - </section> <section name="CreateProductConfigurations"> @@ -64,5 +63,4 @@ <element name="checkboxBlack" type="input" selector="//fieldset[@class='admin__fieldset admin__fieldset-options']//*[contains(text(),'black')]/preceding-sibling::input"/> <element name="errorMessage" type="input" selector="//div[@data-ui-id='messages-message-error']"/> </section> - </sections> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCheckValidatorConfigurableProductTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCheckValidatorConfigurableProductTest.xml new file mode 100644 index 00000000000..dd641fd370b --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCheckValidatorConfigurableProductTest.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="AdminCheckValidatorConfigurableProductTest"> + <annotations> + <stories value="Configurable Product"/> + <title value="Check that validator works correctly when creating Configurations for Configurable Products"/> + <description value="Verify validator works correctly for Configurable Products"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-95995"/> + <useCaseId value="MAGETWO-95834"/> + <group value="ConfigurableProduct"/> + </annotations> + + <before> + <!--Login as admin--> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <!--Create Category--> + <createData entity="ApiCategory" stepKey="createCategory"/> + <!--Create Configurable product--> + <createData entity="ApiConfigurableProduct" stepKey="createConfigProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + </before> + + <after> + <!--Delete created data--> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createConfigProduct" stepKey="deleteConfigProduct"/> + <actionGroup ref="deleteProductBySku" stepKey="deleteProduct"> + <argument name="sku" value="{{ApiConfigurableProduct.name}}-thisIsShortName"/> + </actionGroup> + <conditionalClick selector="{{AdminProductGridFilterSection.clearFilters}}" + dependentSelector="{{AdminProductGridFilterSection.clearFilters}}" visible="true" stepKey="clickClearFilters"/> + <!-- Remove attribute --> + <actionGroup ref="deleteProductAttribute" stepKey="deleteAttribute"> + <argument name="ProductAttribute" value="productDropDownAttribute"/> + </actionGroup> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!-- Find the product that we just created using the product grid --> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="visitAdminProductPage"/> + <waitForPageLoad stepKey="waitForAdminProductPageLoad"/> + <conditionalClick selector="{{AdminProductGridFilterSection.clearFilters}}" + dependentSelector="{{AdminProductGridFilterSection.clearFilters}}" visible="true" stepKey="clickClearFiltersInitial"/> + <actionGroup ref="filterProductGridBySku" stepKey="findCreatedProduct"> + <argument name="product" value="ApiConfigurableProduct"/> + </actionGroup> + <waitForPageLoad stepKey="waitForProductFilterLoad"/> + <click selector="{{AdminProductGridSection.firstRow}}" stepKey="clickOnProductPage"/> + <waitForPageLoad stepKey="waitForProductPageLoad"/> + + <!-- Create configurations based off the Text Swatch we created earlier --> + <click selector="{{AdminProductFormConfigurationsSection.createConfigurations}}" stepKey="clickCreateConfigurations"/> + + <!--Create new attribute--> + <waitForElementVisible stepKey="waitForNewAttributePageOpened" selector="{{AdminCreateProductConfigurationsPanel.createNewAttribute}}"/> + <click selector="{{AdminCreateProductConfigurationsPanel.createNewAttribute}}" stepKey="clickCreateNewAttribute" after="waitForNewAttributePageOpened"/> + <switchToIFrame selector="{{AdminNewAttributePanel.newAttributeIFrame}}" stepKey="enterAttributePanelIFrame" after="clickCreateNewAttribute"/> + <waitForElementVisible selector="{{AdminNewAttributePanel.defaultLabel}}" time="30" stepKey="waitForIframeLoad" after="enterAttributePanelIFrame"/> + <fillField selector="{{AdminNewAttributePanel.defaultLabel}}" userInput="{{productDropDownAttribute.attribute_code}}" stepKey="fillDefaultLabel" after="waitForIframeLoad"/> + <selectOption selector="{{AdminNewAttributePanel.inputType}}" userInput="{{colorProductAttribute.input_type}}" stepKey="selectAttributeInputType" after="fillDefaultLabel"/> + <!--Add option to attribute--> + <click selector="{{AdminNewAttributePanel.addOption}}" stepKey="clickAddOption1" after="selectAttributeInputType"/> + <waitForElementVisible selector="{{AdminNewAttributePanel.isDefault('1')}}" time="30" stepKey="waitForOptionRow1" after="clickAddOption1"/> + <fillField selector="{{AdminNewAttributePanel.optionAdminValue('0')}}" userInput="ThisIsLongNameNameLengthMoreThanSixtyFourThisIsLongNameNameLength" stepKey="fillAdminLabel1" after="waitForOptionRow1"/> + <fillField selector="{{AdminNewAttributePanel.optionDefaultStoreValue('0')}}" userInput="{{colorProductAttribute1.name}}" stepKey="fillDefaultLabel1" after="fillAdminLabel1"/> + + <!--Save attribute--> + <click selector="{{AdminNewAttributePanel.saveAttribute}}" stepKey="clickOnNewAttributePanel"/> + <waitForPageLoad stepKey="waitForSaveAttribute"/> + <switchToIFrame stepKey="switchOutOfIFrame"/> + + <!--Find attribute in grid and select--> + <conditionalClick selector="{{AdminDataGridHeaderSection.clearFilters}}" dependentSelector="{{AdminDataGridHeaderSection.clearFilters}}" visible="true" stepKey="clearExistingFilters"/> + <click selector="{{AdminDataGridHeaderSection.filters}}" stepKey="clickOnFilters"/> + <fillField selector="{{AdminDataGridHeaderSection.attributeCodeFilterInput}}" userInput="{{productDropDownAttribute.attribute_code}}" stepKey="fillFilterAttributeCodeField"/> + <click selector="{{AdminDataGridHeaderSection.applyFilters}}" stepKey="clickApplyFiltersButton"/> + <click selector="{{AdminDataGridTableSection.rowCheckbox('1')}}" stepKey="clickOnFirstCheckbox"/> + <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickNextStep1"/> + <waitForElementVisible selector="{{AdminCreateProductConfigurationsPanel.selectAllByAttribute(productDropDownAttribute.attribute_code)}}" stepKey="waitForNextPageOpened"/> + <click selector="{{AdminCreateProductConfigurationsPanel.selectAllByAttribute(productDropDownAttribute.attribute_code)}}" stepKey="clickSelectAll"/> + <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickNextStep2"/> + <waitForElementVisible selector="{{AdminCreateProductConfigurationsPanel.applySinglePriceToAllSkus}}" stepKey="waitForNextPageOpened2"/> + <click selector="{{AdminCreateProductConfigurationsPanel.applySinglePriceToAllSkus}}" stepKey="clickOnApplySinglePriceToAllSkus"/> + <fillField selector="{{AdminCreateProductConfigurationsPanel.singlePrice}}" userInput="10" stepKey="enterAttributePrice"/> + <click selector="{{AdminCreateProductConfigurationsPanel.applySingleQuantityToEachSkus}}" stepKey="clickOnApplySingleQuantityToEachSku"/> + <fillField selector="{{AdminCreateProductConfigurationsPanel.quantity}}" userInput="100" stepKey="enterAttributeQuantity"/> + <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnNextStep3"/> + <waitForElementVisible selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="waitForNextPageOpened3"/> + <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="generateProducts"/> + <waitForElementVisible selector="{{AdminProductFormActionSection.saveButton}}" stepKey="waitForSaveButtonVisible"/> + <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="saveProduct"/> + <waitForElementVisible selector="{{AdminChooseAffectedAttributeSetPopup.confirm}}" stepKey="waitForPopUpVisible"/> + <click selector="{{AdminChooseAffectedAttributeSetPopup.confirm}}" stepKey="clickOnConfirmInPopup"/> + <dontSeeElement selector="{{AdminMessagesSection.success}}" stepKey="dontSeeSaveProductMessage"/> + + <!--Close modal window--> + <click selector="{{AdminChooseAffectedAttributeSetPopup.closePopUp}}" stepKey="clickOnClosePopup"/> + <waitForElementNotVisible selector="{{AdminChooseAffectedAttributeSetPopup.closePopUp}}" stepKey="waitForDialogClosed"/> + + <!--See that validation message is shown under the fields--> + <scrollTo selector="{{AdminProductFormConfigurationsSection.currentVariationsSkuCells}}" stepKey="scrollTConfigurationTab"/> + <see userInput="Please enter less or equal than 64 symbols." selector="{{AdminProductFormConfigurationsSection.confProductSkuMessage('0')}}" stepKey="SeeValidationMessage"/> + + <!--Edit "SKU" with valid quantity--> + <fillField stepKey="fillValidValue" selector="{{AdminProductFormConfigurationsSection.confProductSku('0')}}" userInput="{{ApiConfigurableProduct.name}}-thisIsShortName"/> + + <!--Click on "Save"--> + <waitForElementVisible selector="{{AdminProductFormActionSection.saveButton}}" stepKey="waitForSaveBtnVisible"/> + <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="saveProductAgain"/> + + <!--Click on "Confirm". Product is saved, success message appears --> + <waitForElementVisible selector="{{AdminChooseAffectedAttributeSetPopup.confirm}}" stepKey="waitPopUpVisible"/> + <click selector="{{AdminChooseAffectedAttributeSetPopup.confirm}}" stepKey="clickOnConfirmPopup"/> + <seeElement selector="{{AdminMessagesSection.success}}" stepKey="seeSaveProductMessage"/> + </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 48f46a1205e..24af7d44e82 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductCreateTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductCreateTest.xml @@ -68,4 +68,71 @@ <see selector="{{StorefrontProductInfoMainSection.productAttributeOptions1}}" userInput="{{colorProductAttribute2.name}}" stepKey="seeInDropDown2"/> <see selector="{{StorefrontProductInfoMainSection.productAttributeOptions1}}" userInput="{{colorProductAttribute3.name}}" stepKey="seeInDropDown3"/> </test> + + <test name="AdminCreateConfigurableProductAfterGettingIncorrectSKUMessageTest"> + <annotations> + <features value="ConfigurableProduct"/> + <stories value="Create, Read, Update, Delete"/> + <title value="admin should be able to create a configurable product after incorrect sku"/> + <description value="admin should be able to create a configurable product after incorrect sku"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-96365"/> + <useCaseId value="MAGETWO-94556"/> + <group value="ConfigurableProduct"/> + </annotations> + + <before> + <createData entity="ApiCategory" stepKey="createCategory"/> + <createData entity="ApiConfigurableProduct" stepKey="createConfigProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <deleteData createDataKey="createConfigProduct" stepKey="deleteConfigProduct"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <amOnPage url="{{AdminProductEditPage.url($$createConfigProduct.id$$)}}" stepKey="goToEditPage"/> + <waitForPageLoad stepKey="waitForProductPage"/> + <conditionalClick selector="{{AdminProductFormConfigurationsSection.sectionHeader}}" dependentSelector="{{AdminProductFormConfigurationsSection.createConfigurations}}" visible="false" stepKey="openConfigurationSection"/> + <click selector="{{AdminProductFormConfigurationsSection.createConfigurations}}" stepKey="openConfigurationPane"/> + <click selector="{{AdminCreateProductConfigurationsPanel.filters}}" stepKey="clickFilters"/> + <fillField selector="{{AdminCreateProductConfigurationsPanel.attributeCode}}" userInput="color" stepKey="fillFilterAttributeCodeField"/> + <click selector="{{AdminCreateProductConfigurationsPanel.applyFilters}}" stepKey="clickApplyFiltersButton"/> + <click selector="{{AdminCreateProductConfigurationsPanel.firstCheckbox}}" stepKey="clickOnFirstCheckbox"/> + <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnNextButton1"/> + <click selector="{{AdminCreateProductConfigurationsPanel.createNewValue}}" stepKey="clickOnCreateNewValue1"/> + <fillField userInput="{{colorProductAttribute2.name}}" selector="{{AdminCreateProductConfigurationsPanel.attributeName}}" stepKey="fillFieldForNewAttribute1"/> + <click selector="{{AdminCreateProductConfigurationsPanel.saveAttribute}}" stepKey="clickOnSaveNewAttribute1"/> + <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnNextButton2"/> + <click selector="{{AdminCreateProductConfigurationsPanel.next}}" stepKey="clickOnNextButton3"/> + <click selector="{{ConfigurableProductSection.generateConfigure}}" stepKey="generateConfigure"/> + <waitForPageLoad stepKey="waitForGenerateConfigure"/> + <grabValueFrom selector="{{AdminProductFormConfigurationsSection.firstSKUInConfigurableProductsGrid}}" stepKey="grabTextFromContent"/> + <fillField stepKey="fillMoreThan64Symbols" selector="{{AdminProductFormConfigurationsSection.firstSKUInConfigurableProductsGrid}}" userInput="01234567890123456789012345678901234567890123456789012345678901234"/> + <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="saveProduct1"/> + <conditionalClick selector="{{AdminChooseAffectedAttributeSetPopup.closePopUp}}" dependentSelector="{{AdminChooseAffectedAttributeSetPopup.closePopUp}}" visible="true" stepKey="clickOnCloseInPopup"/> + <see stepKey="seeErrorMessage" userInput="Please enter less or equal than 64 symbols."/> + <fillField stepKey="fillCorrectSKU" selector="{{AdminProductFormConfigurationsSection.firstSKUInConfigurableProductsGrid}}" userInput="$grabTextFromContent"/> + <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="saveProduct2"/> + <conditionalClick selector="{{AdminChooseAffectedAttributeSetPopup.confirm}}" dependentSelector="{{AdminChooseAffectedAttributeSetPopup.confirm}}" visible="true" stepKey="clickOnConfirmInPopup"/> + <see userInput="You saved the product." stepKey="seeSaveConfirmation"/> + <amOnPage url="{{AdminProductAttributeGridPage.url}}" stepKey="goToProductAttributes"/> + <waitForPageLoad stepKey="waitForProductAttributes"/> + <click selector="{{AdminProductAttributeGridSection.ResetFilter}}" stepKey="resetFiltersOnGrid1"/> + <fillField selector="{{AdminProductAttributeGridSection.FilterByAttributeCode}}" userInput="color" stepKey="fillFilter"/> + <click selector="{{AdminProductAttributeGridSection.Search}}" stepKey="clickSearch"/> + <click selector="{{AdminProductAttributeGridSection.AttributeCode('color')}}" stepKey="clickRowToEdit"/> + <click selector="{{DropdownAttributeOptionsSection.deleteButton(1)}}" stepKey="deleteOption"/> + <click selector="{{AttributePropertiesSection.Save}}" stepKey="saveAttribute"/> + <click selector="{{AdminProductAttributeGridSection.ResetFilter}}" stepKey="resetFiltersOnGrid2"/> + <actionGroup stepKey="deleteProduct1" ref="deleteProductBySku"> + <argument name="sku" value="$grabTextFromContent"/> + </actionGroup> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="visitAdminProductPage"/> + <waitForPageLoad time="60" stepKey="waitForPageLoadInitial"/> + <conditionalClick selector="{{AdminProductGridFilterSection.clearFilters}}" dependentSelector="{{AdminProductGridFilterSection.clearFilters}}" visible="true" stepKey="clickClearFiltersInitial"/> + </test> </tests> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminDeleteConfigurableProductTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminDeleteConfigurableProductTest.xml new file mode 100644 index 00000000000..fb2920be528 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminDeleteConfigurableProductTest.xml @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminDeleteConfigurableProductTest"> + <annotations> + <features value="ConfigurableProduct"/> + <stories value="Delete products"/> + <title value="Delete configurable product test"/> + <description value="Admin should be able to delete a configurable product"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-11020"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="BaseConfigurableProduct" stepKey="createConfigurableProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + </before> + <after> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="deleteProductUsingProductGrid" stepKey="deleteConfigurableProductFilteredBySkuAndName"> + <argument name="product" value="$$createConfigurableProduct$$"/> + </actionGroup> + <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="A total of 1 record(s) have been deleted." stepKey="deleteMessage"/> + <!-- Verify product on Product Page --> + <amOnPage url="{{StorefrontProductPage.url($$createConfigurableProduct.name$$)}}" stepKey="amOnConfigurableProductPage"/> + <see selector="{{StorefrontProductInfoMainSection.productName}}" userInput="Whoops, our bad..." stepKey="seeWhoops"/> + <!-- Search for the product by sku --> + <fillField selector="{{StorefrontQuickSearchSection.searchPhrase}}" userInput="$$createConfigurableProduct.sku$$" stepKey="fillSearchBarByProductSku"/> + <waitForPageLoad stepKey="waitForSearchButton"/> + <click selector="{{StorefrontQuickSearchSection.searchButton}}" stepKey="clickSearchButton"/> + <waitForPageLoad stepKey="waitForSearchResults"/> + <!-- Should not see any search results --> + <dontSee userInput="$$createConfigurableProduct.sku$$" selector="{{StorefrontCatalogSearchMainSection.searchResults}}" stepKey="dontSeeProduct"/> + <see selector="{{StorefrontCatalogSearchMainSection.message}}" userInput="Your search returned no results." stepKey="seeCantFindProductOneMessage"/> + <!-- Go to the category page that we created in the before block --> + <amOnPage url="{{StorefrontCategoryPage.url($$createCategory.name$$)}}" stepKey="onCategoryPage"/> + <!-- Should not see the product --> + <dontSee userInput="$$createConfigurableProduct.name$$" selector="{{StorefrontCategoryMainSection.productsList}}" stepKey="dontSeeProductInCategory"/> + <see selector="{{StorefrontCategoryMainSection.emptyProductMessage}}" userInput="We can't find products matching the selection." stepKey="seeEmptyProductMessage"/> + </test> +</tests> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdvanceCatalogSearchConfigurableTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdvanceCatalogSearchConfigurableTest.xml index 454f9f5f29a..c303e4d19db 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdvanceCatalogSearchConfigurableTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdvanceCatalogSearchConfigurableTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdvanceCatalogSearchConfigurableByNameTest" extends="AdvanceCatalogSearchSimpleProductByNameTest"> <annotations> <features value="ConfigurableProduct"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/ProductsQtyReturnAfterOrderCancelTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/ProductsQtyReturnAfterOrderCancelTest.xml new file mode 100644 index 00000000000..03e1d1b260f --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/ProductsQtyReturnAfterOrderCancelTest.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="ProductsQtyReturnAfterOrderCancel"> + + <annotations> + <features value="ConfigurableProduct"/> + <title value="Product qunatity return after order cancel"/> + <description value="Check Product qunatity return after order cancel"/> + <severity value="CRITICAL"/> + <testCaseId value="MAGETWO-97228"/> + <useCaseId value="MAGETWO-82221"/> + <group value="ConfigurableProduct"/> + </annotations> + + <before> + <createData entity="ApiCategory" stepKey="createCategory"/> + <createData entity="ApiConfigurableProduct" stepKey="createConfigProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <actionGroup ref="LoginAsAdmin" stepKey="login"/> + </before> + + <after> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createConfigProduct" stepKey="deleteConfigProduct"/> + <actionGroup ref="logout" stepKey="amOnLogoutPage"/> + </after> + + <amOnPage url="{{AdminCatalogProductPage.url}}" stepKey="GoToCatalogProductPage1"/> + + <conditionalClick selector="{{AdminProductGridFilterSection.clearFilters}}" dependentSelector="{{AdminProductGridFilterSection.clearFilters}}" visible="true" stepKey="clickClearFiltersInitial"/> + <actionGroup ref="OpenEditProductOnBackendActionGroup" stepKey="openEditProduct"> + <argument name="product" value="$$createConfigProduct$$"/> + </actionGroup> + <fillField selector="{{AdminProductFormSection.productQuantity}}" userInput="100" stepKey="changeProductQuantity"/> + <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="saveChanges"/> + <waitForPageLoad stepKey="waitProductGridToBeLoaded"/> + + <amOnPage url="$$createConfigProduct.sku$$.html" stepKey="navigateToProductPage"/> + <waitForPageLoad stepKey="waitForProductPage"/> + <fillField selector="{{StorefrontProductInfoMainSection.qty}}" userInput="4" stepKey="fillQuantity"/> + + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addToCartFromStorefrontProductPage"> + <argument name="productName" value="$$createConfigProduct.name$$"/> + </actionGroup> + + <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="goToCheckoutFromMinicart"/> + <actionGroup ref="GuestCheckoutFillingShippingSectionActionGroup" stepKey="guestCheckoutFillingShippingSection"> + <argument name="customerVar" value="CustomerEntityOne"/> + <argument name="customerAddressVar" value="CustomerAddressSimple"/> + </actionGroup> + + <actionGroup ref="CheckoutPlaceOrderActionGroup" stepKey="placeOrder"> + <argument name="orderNumberMessage" value="CONST.successGuestCheckoutOrderNumberMessage"/> + <argument name="emailYouMessage" value="CONST.successCheckoutEmailYouMessage" /> + </actionGroup> + + <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber}}" stepKey="grabOrderNumber"/> + + <actionGroup ref="filterOrderGridById" stepKey="filterOrderGridById"> + <argument name="orderId" value="$grabOrderNumber"/> + </actionGroup> + + <click selector="{{AdminOrdersGridSection.firstRow}}" stepKey="clickOrderRow"/> + <click selector="{{AdminOrderDetailsMainActionsSection.invoice}}" stepKey="clickInvoiceButton"/> + <waitForPageLoad stepKey="waitForNewInvoicePageLoad"/> + <fillField selector="{{AdminInvoiceItemsSection.qtyToInvoiceColumn}}" userInput="1" stepKey="ChangeQtyToInvoice"/> + <click selector="{{AdminInvoiceItemsSection.updateQty}}" stepKey="updateQunatity"/> + <waitForPageLoad stepKey="waitPageToBeLoaded"/> + <click selector="{{AdminInvoiceMainActionsSection.submitInvoice}}" stepKey="clickSubmitInvoice"/> + <waitForPageLoad stepKey="waitForSuccessMessageLoad"/> + <click selector="{{AdminOrderDetailsMainActionsSection.ship}}" stepKey="clickShipAction"/> + <waitForPageLoad stepKey="waitOrderDetailToLoad"/> + <fillField selector="{{AdminShipmentItemsSection.itemQtyToShip('1')}}" userInput="1" stepKey="changeItemQtyToShip"/> + <click selector="{{AdminShipmentMainActionsSection.submitShipment}}" stepKey="clickSubmitShipment"/> + <waitForPageLoad stepKey="waitShipmentSectionToLoad"/> + <actionGroup ref="cancelPendingOrder" stepKey="cancelPendingOption"> + <argument name="orderStatus" value="Complete"/> + </actionGroup> + + <see selector="{{AdminOrderItemsOrderedSection.itemQty('1')}}" userInput="Canceled 3" stepKey="seeCanceledQuantity"/> + + <amOnPage url="{{AdminCatalogProductPage.url}}" stepKey="GoToCatalogProductPage"/> + + <actionGroup ref="filterProductGridBySku2" stepKey="filterProductGridBySku"> + <argument name="sku" value="$$createConfigProduct.sku$$"/> + </actionGroup> + <see selector="{{AdminProductGridSection.productGridCell('1', 'Quantity')}}" userInput="99" stepKey="seeProductSkuInGrid"/> + <actionGroup ref="AdminClearFiltersActionGroup" stepKey="clearProductFilters"/> + <actionGroup ref="AdminOrdersGridClearFiltersActionGroup" stepKey="clearOrderFilters"/> + </test> +</tests> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontSortingByPriceForConfigurableWithCatalogRuleAppliedTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontSortingByPriceForConfigurableWithCatalogRuleAppliedTest.xml index 72fce95ade6..57c45ee1e89 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontSortingByPriceForConfigurableWithCatalogRuleAppliedTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontSortingByPriceForConfigurableWithCatalogRuleAppliedTest.xml @@ -153,9 +153,9 @@ <argument name="sortBy" value="price"/> <argument name="sort" value="desc"/> </actionGroup> - <see selector="{{StorefrontCategoryMainSection.lineProductName('1')}}" userInput="$$createSimpleProduct2.name$$" stepKey="seeSimpleProductTwo2"/> - <see selector="{{StorefrontCategoryMainSection.lineProductName('2')}}" userInput="$$createSimpleProduct.name$$" stepKey="seeSimpleProduct2"/> - <see selector="{{StorefrontCategoryMainSection.lineProductName('3')}}" userInput="$$createConfigProduct.name$$" stepKey="seeConfigurableProduct2"/> + <see selector="{{StorefrontCategoryMainSection.lineProductName('1')}}" userInput="$$createConfigProduct.name$$" stepKey="seeConfigurableProduct2"/> + <see selector="{{StorefrontCategoryMainSection.lineProductName('2')}}" userInput="$$createSimpleProduct2.name$$" stepKey="seeSimpleProductTwo2"/> + <see selector="{{StorefrontCategoryMainSection.lineProductName('3')}}" userInput="$$createSimpleProduct.name$$" stepKey="seeSimpleProduct2"/> <!-- Delete the rule --> <amOnPage url="{{CatalogRulePage.url}}" stepKey="goToPriceRulePage"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Plugin/Frontend/ProductIdentitiesExtenderTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Plugin/Frontend/ProductIdentitiesExtenderTest.php new file mode 100644 index 00000000000..b4fb5ccfaa5 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Plugin/Frontend/ProductIdentitiesExtenderTest.php @@ -0,0 +1,77 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\ConfigurableProduct\Test\Unit\Model\Plugin\Frontend; + +use Magento\ConfigurableProduct\Model\Plugin\Frontend\ProductIdentitiesExtender; +use Magento\ConfigurableProduct\Model\Product\Type\Configurable; +use Magento\Catalog\Model\Product; + +/** + * Class ProductIdentitiesExtenderTest + */ +class ProductIdentitiesExtenderTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \PHPUnit_Framework_MockObject_MockObject|Configurable + */ + private $configurableTypeMock; + + /** + * @var ProductIdentitiesExtender + */ + private $plugin; + + /** @var MockObject|\Magento\Catalog\Model\Product */ + private $product; + + protected function setUp() + { + $this->product = $this->getMockBuilder(Product::class) + ->disableOriginalConstructor() + ->setMethods(['getId']) + ->getMock(); + + $this->configurableTypeMock = $this->getMockBuilder(Configurable::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->plugin = new ProductIdentitiesExtender($this->configurableTypeMock); + } + + public function testAfterGetIdentities() + { + $identities = [ + 'SomeCacheId', + 'AnotherCacheId', + ]; + $productId = 12345; + $childIdentities = [ + 0 => [1, 2, 5, 100500] + ]; + $expectedIdentities = [ + 'SomeCacheId', + 'AnotherCacheId', + Product::CACHE_TAG . '_' . 1, + Product::CACHE_TAG . '_' . 2, + Product::CACHE_TAG . '_' . 5, + Product::CACHE_TAG . '_' . 100500, + ]; + + $this->product->expects($this->once()) + ->method('getId') + ->willReturn($productId); + + $this->configurableTypeMock->expects($this->once()) + ->method('getChildrenIds') + ->with($productId) + ->willReturn($childIdentities); + + $productIdentities = $this->plugin->afterGetIdentities($this->product, $identities); + $this->assertEquals($expectedIdentities, $productIdentities); + } +} diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Plugin/Catalog/Model/Product/Pricing/Renderer/SalableResolverTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Plugin/Catalog/Model/Product/Pricing/Renderer/SalableResolverTest.php new file mode 100644 index 00000000000..7ec2ce370ac --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Plugin/Catalog/Model/Product/Pricing/Renderer/SalableResolverTest.php @@ -0,0 +1,82 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\ConfigurableProduct\Test\Unit\Plugin\Catalog\Model\Product\Pricing\Renderer; + +use Magento\Catalog\Model\Product\Pricing\Renderer\SalableResolver; +use Magento\ConfigurableProduct\Model\Product\Type\Configurable as TypeConfigurable; +use Magento\ConfigurableProduct\Plugin\Catalog\Model\Product\Pricing\Renderer\SalableResolver as SalableResolverPlugin; +use Magento\Framework\Pricing\SaleableInterface; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class SalableResolverTest extends TestCase +{ + /** + * @var TypeConfigurable|MockObject + */ + private $typeConfigurable; + + /** + * @var SalableResolverPlugin + */ + private $salableResolver; + + protected function setUp() + { + $this->typeConfigurable = $this->createMock(TypeConfigurable::class); + $this->salableResolver = new SalableResolverPlugin($this->typeConfigurable); + } + + /** + * @param SaleableInterface|MockObject $salableItem + * @param bool $isSalable + * @param bool $typeIsSalable + * @param bool $expectedResult + * @return void + * @dataProvider afterIsSalableDataProvider + */ + public function testAfterIsSalable($salableItem, bool $isSalable, bool $typeIsSalable, bool $expectedResult): void + { + $salableResolver = $this->createMock(SalableResolver::class); + + $this->typeConfigurable->method('isSalable') + ->willReturn($typeIsSalable); + + $result = $this->salableResolver->afterIsSalable($salableResolver, $isSalable, $salableItem); + $this->assertEquals($expectedResult, $result); + } + + /** + * @return array + */ + public function afterIsSalableDataProvider(): array + { + $simpleSalableItem = $this->createMock(SaleableInterface::class); + $simpleSalableItem->expects($this->once()) + ->method('getTypeId') + ->willReturn('simple'); + + $configurableSalableItem = $this->createMock(SaleableInterface::class); + $configurableSalableItem->expects($this->once()) + ->method('getTypeId') + ->willReturn('configurable'); + + return [ + [ + $simpleSalableItem, + true, + false, + true, + ], + [ + $configurableSalableItem, + true, + false, + false, + ], + ]; + } +} diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Plugin/SalesRule/Model/Rule/Condition/ProductTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Plugin/SalesRule/Model/Rule/Condition/ProductTest.php new file mode 100644 index 00000000000..80979148c49 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Plugin/SalesRule/Model/Rule/Condition/ProductTest.php @@ -0,0 +1,226 @@ +<?php declare(strict_types=1); +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\ConfigurableProduct\Test\Unit\Plugin\SalesRule\Model\Rule\Condition; + +use Magento\Backend\Helper\Data; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Product\Type; +use Magento\Catalog\Model\ProductFactory; +use Magento\Catalog\Model\ResourceModel\Product; +use Magento\ConfigurableProduct\Model\Product\Type\Configurable; +use Magento\ConfigurableProduct\Plugin\SalesRule\Model\Rule\Condition\Product as ValidatorPlugin; +use Magento\Directory\Model\CurrencyFactory; +use Magento\Eav\Model\Config; +use Magento\Eav\Model\Entity\AbstractEntity; +use Magento\Eav\Model\ResourceModel\Entity\Attribute\Set\Collection; +use Magento\Framework\App\ScopeResolverInterface; +use Magento\Framework\Locale\Format; +use Magento\Framework\Locale\FormatInterface; +use Magento\Framework\Locale\ResolverInterface; +use Magento\Quote\Model\Quote\Item\AbstractItem; +use Magento\Rule\Model\Condition\Context; +use Magento\SalesRule\Model\Rule\Condition\Product as SalesRuleProduct; + +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.LongVariable) + */ +class ProductTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager + */ + private $objectManager; + + /** + * @var SalesRuleProduct + */ + private $validator; + + /** + * @var \Magento\ConfigurableProduct\Plugin\SalesRule\Model\Rule\Condition\Product + */ + private $validatorPlugin; + + public function setUp() + { + $this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->validator = $this->createValidator(); + $this->validatorPlugin = $this->objectManager->getObject(ValidatorPlugin::class); + } + + /** + * @return \Magento\SalesRule\Model\Rule\Condition\Product + */ + private function createValidator(): SalesRuleProduct + { + /** @var Context|\PHPUnit_Framework_MockObject_MockObject $contextMock */ + $contextMock = $this->getMockBuilder(Context::class) + ->disableOriginalConstructor() + ->getMock(); + /** @var Data|\PHPUnit_Framework_MockObject_MockObject $backendHelperMock */ + $backendHelperMock = $this->getMockBuilder(Data::class) + ->disableOriginalConstructor() + ->getMock(); + /** @var Config|\PHPUnit_Framework_MockObject_MockObject $configMock */ + $configMock = $this->getMockBuilder(Config::class) + ->disableOriginalConstructor() + ->getMock(); + /** @var ProductFactory|\PHPUnit_Framework_MockObject_MockObject $productFactoryMock */ + $productFactoryMock = $this->getMockBuilder(ProductFactory::class) + ->disableOriginalConstructor() + ->getMock(); + /** @var ProductRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject $productRepositoryMock */ + $productRepositoryMock = $this->getMockBuilder(ProductRepositoryInterface::class) + ->getMockForAbstractClass(); + $attributeLoaderInterfaceMock = $this->getMockBuilder(AbstractEntity::class) + ->disableOriginalConstructor() + ->setMethods(['getAttributesByCode']) + ->getMock(); + $attributeLoaderInterfaceMock + ->expects($this->any()) + ->method('getAttributesByCode') + ->willReturn([]); + /** @var Product|\PHPUnit_Framework_MockObject_MockObject $productMock */ + $productMock = $this->getMockBuilder(Product::class) + ->disableOriginalConstructor() + ->setMethods(['loadAllAttributes', 'getConnection', 'getTable']) + ->getMock(); + $productMock->expects($this->any()) + ->method('loadAllAttributes') + ->willReturn($attributeLoaderInterfaceMock); + /** @var Collection|\PHPUnit_Framework_MockObject_MockObject $collectionMock */ + $collectionMock = $this->getMockBuilder(Collection::class) + ->disableOriginalConstructor() + ->getMock(); + /** @var FormatInterface|\PHPUnit_Framework_MockObject_MockObject $formatMock */ + $formatMock = new Format( + $this->getMockBuilder(ScopeResolverInterface::class)->disableOriginalConstructor()->getMock(), + $this->getMockBuilder(ResolverInterface::class)->disableOriginalConstructor()->getMock(), + $this->getMockBuilder(CurrencyFactory::class)->disableOriginalConstructor()->getMock() + ); + + return new SalesRuleProduct( + $contextMock, + $backendHelperMock, + $configMock, + $productFactoryMock, + $productRepositoryMock, + $productMock, + $collectionMock, + $formatMock + ); + } + + public function testChildIsUsedForValidation() + { + $configurableProductMock = $this->createProductMock(); + $configurableProductMock + ->expects($this->any()) + ->method('getTypeId') + ->willReturn(Configurable::TYPE_CODE); + $configurableProductMock + ->expects($this->any()) + ->method('hasData') + ->with($this->equalTo('special_price')) + ->willReturn(false); + + /* @var AbstractItem|\PHPUnit_Framework_MockObject_MockObject $item */ + $item = $this->getMockBuilder(AbstractItem::class) + ->disableOriginalConstructor() + ->setMethods(['setProduct', 'getProduct', 'getChildren']) + ->getMockForAbstractClass(); + $item->expects($this->any()) + ->method('getProduct') + ->willReturn($configurableProductMock); + + $simpleProductMock = $this->createProductMock(); + $simpleProductMock + ->expects($this->any()) + ->method('getTypeId') + ->willReturn(Type::TYPE_SIMPLE); + $simpleProductMock + ->expects($this->any()) + ->method('hasData') + ->with($this->equalTo('special_price')) + ->willReturn(true); + + $childItem = $this->getMockBuilder(AbstractItem::class) + ->disableOriginalConstructor() + ->setMethods(['getProduct']) + ->getMockForAbstractClass(); + $childItem->expects($this->any()) + ->method('getProduct') + ->willReturn($simpleProductMock); + + $item->expects($this->any()) + ->method('getChildren') + ->willReturn([$childItem]); + $item->expects($this->once()) + ->method('setProduct') + ->with($simpleProductMock); + + $this->validator->setAttribute('special_price'); + + $this->validatorPlugin->beforeValidate($this->validator, $item); + } + + /** + * @return Product|\PHPUnit_Framework_MockObject_MockObject + */ + private function createProductMock(): \PHPUnit_Framework_MockObject_MockObject + { + $productMock = $this->getMockBuilder(\Magento\Catalog\Model\Product::class) + ->disableOriginalConstructor() + ->setMethods([ + 'getAttribute', + 'getId', + 'setQuoteItemQty', + 'setQuoteItemPrice', + 'getTypeId', + 'hasData', + ]) + ->getMock(); + $productMock + ->expects($this->any()) + ->method('setQuoteItemQty') + ->willReturnSelf(); + $productMock + ->expects($this->any()) + ->method('setQuoteItemPrice') + ->willReturnSelf(); + + return $productMock; + } + + public function testChildIsNotUsedForValidation() + { + $simpleProductMock = $this->createProductMock(); + $simpleProductMock + ->expects($this->any()) + ->method('getTypeId') + ->willReturn(Type::TYPE_SIMPLE); + $simpleProductMock + ->expects($this->any()) + ->method('hasData') + ->with($this->equalTo('special_price')) + ->willReturn(true); + + /* @var AbstractItem|\PHPUnit_Framework_MockObject_MockObject $item */ + $item = $this->getMockBuilder(AbstractItem::class) + ->disableOriginalConstructor() + ->setMethods(['setProduct', 'getProduct']) + ->getMockForAbstractClass(); + $item->expects($this->any()) + ->method('getProduct') + ->willReturn($simpleProductMock); + + $this->validator->setAttribute('special_price'); + + $this->validatorPlugin->beforeValidate($this->validator, $item); + } +} diff --git a/app/code/Magento/ConfigurableProduct/etc/di.xml b/app/code/Magento/ConfigurableProduct/etc/di.xml index 102ed1314f8..0ae9ffde66f 100644 --- a/app/code/Magento/ConfigurableProduct/etc/di.xml +++ b/app/code/Magento/ConfigurableProduct/etc/di.xml @@ -242,4 +242,7 @@ </argument> </arguments> </type> + <type name="Magento\SalesRule\Model\Rule\Condition\Product"> + <plugin name="apply_rule_on_configurable_children" type="Magento\ConfigurableProduct\Plugin\SalesRule\Model\Rule\Condition\Product" /> + </type> </config> diff --git a/app/code/Magento/ConfigurableProduct/etc/frontend/di.xml b/app/code/Magento/ConfigurableProduct/etc/frontend/di.xml index bb830c36b92..df96829b354 100644 --- a/app/code/Magento/ConfigurableProduct/etc/frontend/di.xml +++ b/app/code/Magento/ConfigurableProduct/etc/frontend/di.xml @@ -10,4 +10,7 @@ <type name="Magento\ConfigurableProduct\Model\ResourceModel\Attribute\OptionSelectBuilderInterface"> <plugin name="Magento_ConfigurableProduct_Plugin_Model_ResourceModel_Attribute_InStockOptionSelectBuilder" type="Magento\ConfigurableProduct\Plugin\Model\ResourceModel\Attribute\InStockOptionSelectBuilder"/> </type> + <type name="Magento\Catalog\Model\Product"> + <plugin name="product_identities_extender" type="Magento\ConfigurableProduct\Model\Plugin\Frontend\ProductIdentitiesExtender" /> + </type> </config> diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/price-configurable.js b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/price-configurable.js index 28e775b984b..6bbab77a3a0 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/price-configurable.js +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/price-configurable.js @@ -53,6 +53,8 @@ define([ if (isConfigurable) { this.disable(); this.clear(); + } else { + this.enable(); } } }); diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/variations.js b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/variations.js index 8d27e3dc58a..6e82fd42692 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/variations.js +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/variations.js @@ -383,12 +383,19 @@ define([ * Chose action for the form save button */ saveFormHandler: function () { - this.serializeData(); + this.formElement().validate(); + + if (this.formElement().source.get('params.invalid') === false) { + this.serializeData(); + } if (this.checkForNewAttributes()) { this.formSaveParams = arguments; this.attributeSetHandlerModal().openModal(); } else { + if (this.validateForm(this.formElement())) { + this.clearOutdatedData(); + } this.formElement().save(arguments[0], arguments[1]); if (this.formElement().source.get('params.invalid')) { @@ -397,6 +404,17 @@ define([ } }, + /** + * @param {Object} formElement + * + * Validates each form element and returns true, if all elements are valid. + */ + validateForm: function (formElement) { + formElement.validate(); + + return !formElement.additionalInvalid && !formElement.source.get('params.invalid'); + }, + /** * Serialize data for specific form fields * @@ -414,12 +432,27 @@ define([ if (this.source.data['configurable-matrix']) { this.source.data['configurable-matrix-serialized'] = JSON.stringify(this.source.data['configurable-matrix']); - delete this.source.data['configurable-matrix']; } if (this.source.data['associated_product_ids']) { this.source.data['associated_product_ids_serialized'] = JSON.stringify(this.source.data['associated_product_ids']); + } + }, + + /** + * Clear outdated data for specific form fields + * + * Outdated fields: + * - configurable-matrix; + * - associated_product_ids. + */ + clearOutdatedData: function () { + if (this.source.data['configurable-matrix']) { + delete this.source.data['configurable-matrix']; + } + + if (this.source.data['associated_product_ids']) { delete this.source.data['associated_product_ids']; } }, 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 6357bbd6c7c..1df84d27a5c 100644 --- a/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js +++ b/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js @@ -376,7 +376,8 @@ define([ basePrice = parseFloat(this.options.spConfig.prices.basePrice.amount), optionFinalPrice, optionPriceDiff, - optionPrices = this.options.spConfig.optionPrices; + optionPrices = this.options.spConfig.optionPrices, + allowedProductMinPrice; this._clearSelect(element); element.options[0] = new Option('', ''); @@ -407,8 +408,8 @@ define([ if (typeof allowedProducts[0] !== 'undefined' && typeof optionPrices[allowedProducts[0]] !== 'undefined') { - - optionFinalPrice = parseFloat(optionPrices[allowedProducts[0]].finalPrice.amount); + allowedProductMinPrice = this._getAllowedProductWithMinPrice(allowedProducts); + optionFinalPrice = parseFloat(optionPrices[allowedProductMinPrice].finalPrice.amount); optionPriceDiff = optionFinalPrice - basePrice; if (optionPriceDiff !== 0) { @@ -489,36 +490,27 @@ define([ _getPrices: function () { var prices = {}, elements = _.toArray(this.options.settings), - hasProductPrice = false, - optionPriceDiff = 0, - allowedProduct, optionPrices, basePrice, optionFinalPrice; + allowedProduct; _.each(elements, function (element) { var selected = element.options[element.selectedIndex], config = selected && selected.config, priceValue = {}; - if (config && config.allowedProducts.length === 1 && !hasProductPrice) { - prices = {}; + if (config && config.allowedProducts.length === 1) { priceValue = this._calculatePrice(config); - hasProductPrice = true; } else if (element.value) { allowedProduct = this._getAllowedProductWithMinPrice(config.allowedProducts); - optionPrices = this.options.spConfig.optionPrices; - basePrice = parseFloat(this.options.spConfig.prices.basePrice.amount); - - if (!_.isEmpty(allowedProduct)) { - optionFinalPrice = parseFloat(optionPrices[allowedProduct].finalPrice.amount); - optionPriceDiff = optionFinalPrice - basePrice; - } - - if (optionPriceDiff !== 0) { - prices = {}; - priceValue = this._calculatePriceDifference(allowedProduct); - } + priceValue = this._calculatePrice({ + 'allowedProducts': [ + allowedProduct + ] + }); } - prices[element.attributeId] = priceValue; + if (!_.isEmpty(priceValue)) { + prices.prices = priceValue; + } }, this); return prices; @@ -539,40 +531,15 @@ define([ _.each(allowedProducts, function (allowedProduct) { optionFinalPrice = parseFloat(optionPrices[allowedProduct].finalPrice.amount); - if (_.isEmpty(product)) { + if (_.isEmpty(product) || optionFinalPrice < optionMinPrice) { optionMinPrice = optionFinalPrice; product = allowedProduct; } - - if (optionFinalPrice < optionMinPrice) { - product = allowedProduct; - } }, this); return product; }, - /** - * Calculate price difference for allowed product - * - * @param {*} allowedProduct - Product - * @returns {*} - * @private - */ - _calculatePriceDifference: function (allowedProduct) { - var displayPrices = $(this.options.priceHolderSelector).priceBox('option').prices, - newPrices = this.options.spConfig.optionPrices[allowedProduct]; - - _.each(displayPrices, function (price, code) { - - if (newPrices[code]) { - displayPrices[code].amount = newPrices[code].amount - displayPrices[code].amount; - } - }); - - return displayPrices; - }, - /** * Returns prices for configured products * diff --git a/app/code/Magento/ConfigurableProductGraphQl/Model/ConfigurableProductTypeResolver.php b/app/code/Magento/ConfigurableProductGraphQl/Model/ConfigurableProductTypeResolver.php index aae39800cdd..eda2ce11daa 100644 --- a/app/code/Magento/ConfigurableProductGraphQl/Model/ConfigurableProductTypeResolver.php +++ b/app/code/Magento/ConfigurableProductGraphQl/Model/ConfigurableProductTypeResolver.php @@ -8,19 +8,25 @@ namespace Magento\ConfigurableProductGraphQl\Model; use Magento\Framework\GraphQl\Query\Resolver\TypeResolverInterface; +use Magento\ConfigurableProduct\Model\Product\Type\Configurable as Type; /** - * {@inheritdoc} + * @inheritdoc */ class ConfigurableProductTypeResolver implements TypeResolverInterface { /** - * {@inheritdoc} + * Configurable product type resolver code */ - public function resolveType(array $data) : string + const TYPE_RESOLVER = 'ConfigurableProduct'; + + /** + * @inheritdoc + */ + public function resolveType(array $data): string { - if (isset($data['type_id']) && $data['type_id'] == 'configurable') { - return 'ConfigurableProduct'; + if (isset($data['type_id']) && $data['type_id'] == Type::TYPE_CODE) { + return self::TYPE_RESOLVER; } return ''; } diff --git a/app/code/Magento/ConfigurableProductGraphQl/Model/Options/Collection.php b/app/code/Magento/ConfigurableProductGraphQl/Model/Options/Collection.php index 90ed5cf5489..36ee00d5533 100644 --- a/app/code/Magento/ConfigurableProductGraphQl/Model/Options/Collection.php +++ b/app/code/Magento/ConfigurableProductGraphQl/Model/Options/Collection.php @@ -124,6 +124,8 @@ private function fetch() : array $this->attributeMap[$productId][$attribute->getId()]['attribute_code'] = $attribute->getProductAttribute()->getAttributeCode(); $this->attributeMap[$productId][$attribute->getId()]['values'] = $attributeData['options']; + $this->attributeMap[$productId][$attribute->getId()]['label'] + = $attribute->getProductAttribute()->getStoreLabel(); } return $this->attributeMap; diff --git a/app/code/Magento/ConfigurableProductGraphQl/Model/Resolver/ConfigurableVariant.php b/app/code/Magento/ConfigurableProductGraphQl/Model/Resolver/ConfigurableVariant.php index a6e39f693b0..3e07fecb2eb 100644 --- a/app/code/Magento/ConfigurableProductGraphQl/Model/Resolver/ConfigurableVariant.php +++ b/app/code/Magento/ConfigurableProductGraphQl/Model/Resolver/ConfigurableVariant.php @@ -71,9 +71,7 @@ public function __construct( } /** - * Fetch and format configurable variants. - * - * {@inheritDoc} + * @inheritdoc */ public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null) { @@ -85,7 +83,7 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value return $this->valueFactory->create($result); } - $this->variantCollection->addParentId((int)$value[$linkField]); + $this->variantCollection->addParentProduct($value['model']); $fields = $this->getProductFields($info); $matchedFields = $this->attributeCollection->getRequestAttributes($fields); $this->variantCollection->addEavAttributes($matchedFields); diff --git a/app/code/Magento/ConfigurableProductGraphQl/Model/Resolver/Variant/Attributes.php b/app/code/Magento/ConfigurableProductGraphQl/Model/Resolver/Variant/Attributes.php index 658e898f09f..9f8f728a85e 100644 --- a/app/code/Magento/ConfigurableProductGraphQl/Model/Resolver/Variant/Attributes.php +++ b/app/code/Magento/ConfigurableProductGraphQl/Model/Resolver/Variant/Attributes.php @@ -7,6 +7,8 @@ namespace Magento\ConfigurableProductGraphQl\Model\Resolver\Variant; +use Magento\Framework\GraphQl\Query\Resolver\ContextInterface; +use Magento\Framework\GraphQl\Query\Resolver\Value; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; use Magento\Framework\GraphQl\Config\Element\Field; use Magento\Framework\GraphQl\Query\ResolverInterface; @@ -17,9 +19,17 @@ class Attributes implements ResolverInterface { /** + * @inheritdoc + * * Format product's option data to conform to GraphQL schema * - * {@inheritdoc} + * @param Field $field + * @param ContextInterface $context + * @param ResolveInfo $info + * @param array|null $value + * @param array|null $args + * @throws \Exception + * @return mixed|Value */ public function resolve( Field $field, @@ -35,12 +45,12 @@ public function resolve( $data = []; foreach ($value['options'] as $option) { $code = $option['attribute_code']; - if (!isset($value['product'][$code])) { + if (!isset($value['product']['model'][$code])) { continue; } foreach ($option['values'] as $optionValue) { - if ($optionValue['value_index'] != $value['product'][$code]) { + if ($optionValue['value_index'] != $value['product']['model'][$code]) { continue; } $data[] = [ diff --git a/app/code/Magento/ConfigurableProductGraphQl/Model/Variant/Collection.php b/app/code/Magento/ConfigurableProductGraphQl/Model/Variant/Collection.php index 0d86e165743..9fda4ec0173 100644 --- a/app/code/Magento/ConfigurableProductGraphQl/Model/Variant/Collection.php +++ b/app/code/Magento/ConfigurableProductGraphQl/Model/Variant/Collection.php @@ -9,9 +9,9 @@ use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Model\Product; -use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Product\CollectionFactory; -use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Product\Collection as ChildCollection; 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; @@ -47,9 +47,9 @@ class Collection private $metadataPool; /** - * @var int[] + * @var Product[] */ - private $parentIds = []; + private $parentProducts = []; /** * @var array @@ -83,19 +83,21 @@ public function __construct( } /** - * Add parent Id to collection filter + * Add parent to collection filter * - * @param int $id + * @param Product $product * @return void */ - public function addParentId(int $id) : void + public function addParentProduct(Product $product) : void { - if (!in_array($id, $this->parentIds) && !empty($this->childrenMap)) { + if (isset($this->parentProducts[$product->getId()])) { + return; + } + + if (!empty($this->childrenMap)) { $this->childrenMap = []; - $this->parentIds[] = $id; - } elseif (!in_array($id, $this->parentIds)) { - $this->parentIds[] = $id; } + $this->parentProducts[$product->getId()] = $product; } /** @@ -130,20 +132,23 @@ public function getChildProductsByParentId(int $id) : array * Fetch all children products from parent id's. * * @return array + * @throws \Exception */ private function fetch() : array { - if (empty($this->parentIds) || !empty($this->childrenMap)) { + if (empty($this->parentProducts) || !empty($this->childrenMap)) { return $this->childrenMap; } $linkField = $this->metadataPool->getMetadata(ProductInterface::class)->getLinkField(); - foreach ($this->parentIds as $id) { + foreach ($this->parentProducts as $product) { + $attributeData = $this->getAttributesCodes($product); /** @var ChildCollection $childCollection */ $childCollection = $this->childCollectionFactory->create(); + $childCollection->addAttributeToSelect($attributeData); + /** @var Product $product */ - $product = $this->productFactory->create(); - $product->setData($linkField, $id); + $product->setData($linkField, $product->getId()); $childCollection->setProductFilter($product); /** @var Product $childProduct */ @@ -160,4 +165,24 @@ private function fetch() : array return $this->childrenMap; } + + /** + * Get attributes code + * + * @param \Magento\Catalog\Model\Product $currentProduct + * @return array + */ + private function getAttributesCodes(Product $currentProduct): array + { + $attributeCodes = []; + $allowAttributes = $currentProduct->getTypeInstance()->getConfigurableAttributes($currentProduct); + foreach ($allowAttributes as $attribute) { + $productAttribute = $attribute->getProductAttribute(); + if (!\in_array($productAttribute->getAttributeCode(), $attributeCodes)) { + $attributeCodes[] = $productAttribute->getAttributeCode(); + } + } + + return $attributeCodes; + } } diff --git a/app/code/Magento/Contact/view/frontend/web/css/source/_module.less b/app/code/Magento/Contact/view/frontend/web/css/source/_module.less new file mode 100644 index 00000000000..0aaec05aa2a --- /dev/null +++ b/app/code/Magento/Contact/view/frontend/web/css/source/_module.less @@ -0,0 +1,42 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. +*/ + +& when (@media-common = true) { + .contact-index-index { + .column:not(.sidebar-main) { + .form.contact { + float: none; + width: 50%; + } + } + + .column:not(.sidebar-additional) { + .form.contact { + float: none; + width: 50%; + } + } + } +} + +// Mobile +.media-width(@extremum, @break) when (@extremum = 'max') and (@break = @screen__m) { + .contact-index-index { + .column:not(.sidebar-main) { + .form.contact { + float: none; + width: 100%; + } + } + + .column:not(.sidebar-additional) { + .form.contact { + float: none; + width: 100%; + } + } + } +} + diff --git a/app/code/Magento/Cron/Test/Unit/Observer/ProcessCronQueueObserverTest.php b/app/code/Magento/Cron/Test/Unit/Observer/ProcessCronQueueObserverTest.php index 632a014a0ab..462dde98f99 100644 --- a/app/code/Magento/Cron/Test/Unit/Observer/ProcessCronQueueObserverTest.php +++ b/app/code/Magento/Cron/Test/Unit/Observer/ProcessCronQueueObserverTest.php @@ -174,7 +174,8 @@ protected function setUp() $this->statFactory = $this->getMockBuilder(StatFactory::class) ->setMethods(['create']) - ->getMockForAbstractClass(); + ->disableOriginalConstructor() + ->getMock(); $this->stat = $this->getMockBuilder(\Magento\Framework\Profiler\Driver\Standard\Stat::class) ->disableOriginalConstructor() diff --git a/app/code/Magento/Customer/Api/AccountManagementInterface.php b/app/code/Magento/Customer/Api/AccountManagementInterface.php index e84da5b9fcd..10fc2349968 100644 --- a/app/code/Magento/Customer/Api/AccountManagementInterface.php +++ b/app/code/Magento/Customer/Api/AccountManagementInterface.php @@ -31,15 +31,13 @@ interface AccountManagementInterface * @param \Magento\Customer\Api\Data\CustomerInterface $customer * @param string $password * @param string $redirectUrl - * @param string[] $extensions * @return \Magento\Customer\Api\Data\CustomerInterface * @throws \Magento\Framework\Exception\LocalizedException */ public function createAccount( \Magento\Customer\Api\Data\CustomerInterface $customer, $password = null, - $redirectUrl = '', - $extensions = [] + $redirectUrl = '' ); /** @@ -50,7 +48,6 @@ public function createAccount( * @param string $hash Password hash that we can save directly * @param string $redirectUrl URL fed to welcome email templates. Can be used by templates to, for example, direct * the customer to a product they were looking at after pressing confirmation link. - * @param string[] $extensions * @return \Magento\Customer\Api\Data\CustomerInterface * @throws \Magento\Framework\Exception\InputException If bad input is provided * @throws \Magento\Framework\Exception\State\InputMismatchException If the provided email is already used @@ -59,8 +56,7 @@ public function createAccount( public function createAccountWithPasswordHash( \Magento\Customer\Api\Data\CustomerInterface $customer, $hash, - $redirectUrl = '', - $extensions = [] + $redirectUrl = '' ); /** diff --git a/app/code/Magento/Customer/Api/CustomerRepositoryInterface.php b/app/code/Magento/Customer/Api/CustomerRepositoryInterface.php index 2133ae5a323..ca9bf4dc7af 100644 --- a/app/code/Magento/Customer/Api/CustomerRepositoryInterface.php +++ b/app/code/Magento/Customer/Api/CustomerRepositoryInterface.php @@ -51,7 +51,7 @@ public function getById($customerId); * Retrieve customers which match a specified criteria. * * This call returns an array of objects, but detailed information about each object’s attributes might not be - * included. See http://devdocs.magento.com/codelinks/attributes.html#CustomerRepositoryInterface to determine + * included. See https://devdocs.magento.com/codelinks/attributes.html#CustomerRepositoryInterface to determine * which call to use to get detailed information about all attributes for an object. * * @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria diff --git a/app/code/Magento/Customer/Api/GroupRepositoryInterface.php b/app/code/Magento/Customer/Api/GroupRepositoryInterface.php index 2f5e637a769..f6ba387e913 100644 --- a/app/code/Magento/Customer/Api/GroupRepositoryInterface.php +++ b/app/code/Magento/Customer/Api/GroupRepositoryInterface.php @@ -42,7 +42,7 @@ public function getById($id); * be filtered by tax class. * * This call returns an array of objects, but detailed information about each object’s attributes might not be - * included. See http://devdocs.magento.com/codelinks/attributes.html#GroupRepositoryInterface to determine + * included. See https://devdocs.magento.com/codelinks/attributes.html#GroupRepositoryInterface to determine * which call to use to get detailed information about all attributes for an object. * * @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria diff --git a/app/code/Magento/Customer/Block/Address/Book.php b/app/code/Magento/Customer/Block/Address/Book.php index 8b38946a063..04669446ffe 100644 --- a/app/code/Magento/Customer/Block/Address/Book.php +++ b/app/code/Magento/Customer/Block/Address/Book.php @@ -6,8 +6,8 @@ namespace Magento\Customer\Block\Address; use Magento\Customer\Api\AddressRepositoryInterface; -use Magento\Customer\Api\CustomerRepositoryInterface; use Magento\Customer\Model\Address\Mapper; +use Magento\Customer\Block\Address\Grid as AddressesGrid; /** * Customer address book block @@ -24,7 +24,7 @@ class Book extends \Magento\Framework\View\Element\Template protected $currentCustomer; /** - * @var CustomerRepositoryInterface + * @var \Magento\Customer\Api\CustomerRepositoryInterface */ protected $customerRepository; @@ -43,33 +43,44 @@ class Book extends \Magento\Framework\View\Element\Template */ protected $addressMapper; + /** + * @var AddressesGrid + */ + private $addressesGrid; + /** * @param \Magento\Framework\View\Element\Template\Context $context - * @param CustomerRepositoryInterface $customerRepository + * @param CustomerRepositoryInterface|null $customerRepository * @param AddressRepositoryInterface $addressRepository * @param \Magento\Customer\Helper\Session\CurrentCustomer $currentCustomer * @param \Magento\Customer\Model\Address\Config $addressConfig * @param Mapper $addressMapper * @param array $data + * @param AddressesGrid|null $addressesGrid + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function __construct( \Magento\Framework\View\Element\Template\Context $context, - CustomerRepositoryInterface $customerRepository, + \Magento\Customer\Api\CustomerRepositoryInterface $customerRepository = null, AddressRepositoryInterface $addressRepository, \Magento\Customer\Helper\Session\CurrentCustomer $currentCustomer, \Magento\Customer\Model\Address\Config $addressConfig, Mapper $addressMapper, - array $data = [] + array $data = [], + Grid $addressesGrid = null ) { - $this->customerRepository = $customerRepository; $this->currentCustomer = $currentCustomer; $this->addressRepository = $addressRepository; $this->_addressConfig = $addressConfig; $this->addressMapper = $addressMapper; + $this->addressesGrid = $addressesGrid ?: \Magento\Framework\App\ObjectManager::getInstance() + ->get(AddressesGrid::class); parent::__construct($context, $data); } /** + * Prepare the Address Book section layout + * * @return $this */ protected function _prepareLayout() @@ -79,14 +90,20 @@ protected function _prepareLayout() } /** + * Generate and return "New Address" URL + * * @return string + * @deprecated not used in this block + * @see \Magento\Customer\Block\Address\Grid::getAddAddressUrl */ public function getAddAddressUrl() { - return $this->getUrl('customer/address/new', ['_secure' => true]); + return $this->addressesGrid->getAddAddressUrl(); } /** + * Generate and return "Back" URL + * * @return string */ public function getBackUrl() @@ -98,24 +115,37 @@ public function getBackUrl() } /** + * Generate and return "Delete" URL + * * @return string + * @deprecated not used in this block + * @see \Magento\Customer\Block\Address\Grid::getDeleteUrl */ public function getDeleteUrl() { - return $this->getUrl('customer/address/delete'); + return $this->addressesGrid->getDeleteUrl(); } /** + * Generate and return "Edit Address" URL. + * + * Address ID passed in parameters + * * @param int $addressId * @return string + * @deprecated not used in this block + * @see \Magento\Customer\Block\Address\Grid::getAddressEditUrl */ public function getAddressEditUrl($addressId) { - return $this->getUrl('customer/address/edit', ['_secure' => true, 'id' => $addressId]); + return $this->addressesGrid->getAddressEditUrl($addressId); } /** + * Determines is the address primary (billing or shipping) + * * @return bool + * @throws \Magento\Framework\Exception\LocalizedException */ public function hasPrimaryAddress() { @@ -123,22 +153,22 @@ public function hasPrimaryAddress() } /** + * Get current additional customer addresses + * + * Will return array of address interfaces if customer have additional addresses and false in other case. + * * @return \Magento\Customer\Api\Data\AddressInterface[]|bool + * @throws \Magento\Framework\Exception\LocalizedException + * @deprecated not used in this block + * @see \Magento\Customer\Block\Address\Grid::getAdditionalAddresses */ public function getAdditionalAddresses() { try { - $addresses = $this->customerRepository->getById($this->currentCustomer->getCustomerId())->getAddresses(); + $addresses = $this->addressesGrid->getAdditionalAddresses(); } catch (\Magento\Framework\Exception\NoSuchEntityException $e) { - return false; - } - $primaryAddressIds = [$this->getDefaultBilling(), $this->getDefaultShipping()]; - foreach ($addresses as $address) { - if (!in_array($address->getId(), $primaryAddressIds)) { - $additional[] = $address; - } } - return empty($additional) ? false : $additional; + return empty($addresses) ? false : $addresses; } /** @@ -158,23 +188,23 @@ public function getAddressHtml(\Magento\Customer\Api\Data\AddressInterface $addr } /** + * Get current customer + * * @return \Magento\Customer\Api\Data\CustomerInterface|null */ public function getCustomer() { - $customer = $this->getData('customer'); - if ($customer === null) { - try { - $customer = $this->customerRepository->getById($this->currentCustomer->getCustomerId()); - } catch (\Magento\Framework\Exception\NoSuchEntityException $e) { - return null; - } - $this->setData('customer', $customer); + $customer = null; + try { + $customer = $this->currentCustomer->getCustomer(); + } catch (\Magento\Framework\Exception\NoSuchEntityException $e) { } return $customer; } /** + * Get customer's default billing address + * * @return int|null */ public function getDefaultBilling() @@ -188,8 +218,11 @@ public function getDefaultBilling() } /** + * Get customer address by ID + * * @param int $addressId * @return \Magento\Customer\Api\Data\AddressInterface|null + * @throws \Magento\Framework\Exception\LocalizedException */ public function getAddressById($addressId) { @@ -201,6 +234,8 @@ public function getAddressById($addressId) } /** + * Get customer's default shipping address + * * @return int|null */ public function getDefaultShipping() diff --git a/app/code/Magento/Customer/Block/Address/Edit.php b/app/code/Magento/Customer/Block/Address/Edit.php index 6a42e9670cc..afefb1138de 100644 --- a/app/code/Magento/Customer/Block/Address/Edit.php +++ b/app/code/Magento/Customer/Block/Address/Edit.php @@ -5,6 +5,7 @@ */ namespace Magento\Customer\Block\Address; +use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Exception\NoSuchEntityException; /** @@ -46,6 +47,11 @@ class Edit extends \Magento\Directory\Block\Data */ protected $dataObjectHelper; + /** + * @var \Magento\Customer\Api\AddressMetadataInterface + */ + private $addressMetadata; + /** * Constructor * @@ -61,6 +67,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 * * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ @@ -76,13 +83,15 @@ public function __construct( \Magento\Customer\Api\Data\AddressInterfaceFactory $addressDataFactory, \Magento\Customer\Helper\Session\CurrentCustomer $currentCustomer, \Magento\Framework\Api\DataObjectHelper $dataObjectHelper, - array $data = [] + array $data = [], + \Magento\Customer\Api\AddressMetadataInterface $addressMetadata = null ) { $this->_customerSession = $customerSession; $this->_addressRepository = $addressRepository; $this->addressDataFactory = $addressDataFactory; $this->currentCustomer = $currentCustomer; $this->dataObjectHelper = $dataObjectHelper; + $this->addressMetadata = $addressMetadata; parent::__construct( $context, $directoryHelper, @@ -103,6 +112,32 @@ protected function _prepareLayout() { parent::_prepareLayout(); + $this->initAddressObject(); + + $this->pageConfig->getTitle()->set($this->getTitle()); + + if ($postedData = $this->_customerSession->getAddressFormData(true)) { + $postedData['region'] = [ + 'region_id' => isset($postedData['region_id']) ? $postedData['region_id'] : null, + 'region' => $postedData['region'], + ]; + $this->dataObjectHelper->populateWithArray( + $this->_address, + $postedData, + \Magento\Customer\Api\Data\AddressInterface::class + ); + } + $this->precheckRequiredAttributes(); + return $this; + } + + /** + * Initialize address object. + * + * @return void + */ + private function initAddressObject() + { // Init address object if ($addressId = $this->getRequest()->getParam('id')) { try { @@ -124,22 +159,26 @@ protected function _prepareLayout() $this->_address->setLastname($customer->getLastname()); $this->_address->setSuffix($customer->getSuffix()); } + } - $this->pageConfig->getTitle()->set($this->getTitle()); - - if ($postedData = $this->_customerSession->getAddressFormData(true)) { - $postedData['region'] = [ - 'region_id' => isset($postedData['region_id']) ? $postedData['region_id'] : null, - 'region' => $postedData['region'], - ]; - $this->dataObjectHelper->populateWithArray( - $this->_address, - $postedData, - \Magento\Customer\Api\Data\AddressInterface::class - ); + /** + * Precheck attributes that may be required in attribute configuration. + * + * @return void + */ + private function precheckRequiredAttributes() + { + $precheckAttributes = $this->getData('check_attributes_on_render'); + $requiredAttributesPrechecked = []; + if (!empty($precheckAttributes) && is_array($precheckAttributes)) { + foreach ($precheckAttributes as $attributeCode) { + $attributeMetadata = $this->addressMetadata->getAttributeMetadata($attributeCode); + if ($attributeMetadata && $attributeMetadata->isRequired()) { + $requiredAttributesPrechecked[$attributeCode] = $attributeCode; + } + } } - - return $this; + $this->setData('required_attributes_prechecked', $requiredAttributesPrechecked); } /** diff --git a/app/code/Magento/Customer/Block/Address/Grid.php b/app/code/Magento/Customer/Block/Address/Grid.php new file mode 100644 index 00000000000..de6767a0ef9 --- /dev/null +++ b/app/code/Magento/Customer/Block/Address/Grid.php @@ -0,0 +1,245 @@ +<?php +declare(strict_types=1); +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Customer\Block\Address; + +use Magento\Customer\Model\ResourceModel\Address\CollectionFactory as AddressCollectionFactory; +use Magento\Directory\Model\CountryFactory; +use Magento\Framework\Exception\NoSuchEntityException; + +/** + * Customer address grid + * + * @api + */ +class Grid extends \Magento\Framework\View\Element\Template +{ + /** + * @var \Magento\Customer\Helper\Session\CurrentCustomer + */ + private $currentCustomer; + + /** + * @var \Magento\Customer\Model\ResourceModel\Address\CollectionFactory + */ + private $addressCollectionFactory; + + /** + * @var \Magento\Customer\Model\ResourceModel\Address\Collection + */ + private $addressCollection; + + /** + * @var CountryFactory + */ + private $countryFactory; + + /** + * @param \Magento\Framework\View\Element\Template\Context $context + * @param \Magento\Customer\Helper\Session\CurrentCustomer $currentCustomer + * @param AddressCollectionFactory $addressCollectionFactory + * @param CountryFactory $countryFactory + * @param array $data + */ + public function __construct( + \Magento\Framework\View\Element\Template\Context $context, + \Magento\Customer\Helper\Session\CurrentCustomer $currentCustomer, + AddressCollectionFactory $addressCollectionFactory, + CountryFactory $countryFactory, + array $data = [] + ) { + $this->currentCustomer = $currentCustomer; + $this->addressCollectionFactory = $addressCollectionFactory; + $this->countryFactory = $countryFactory; + + parent::__construct($context, $data); + } + + /** + * Prepare the Address Book section layout + * + * @return void + * @throws \Magento\Framework\Exception\LocalizedException + */ + protected function _prepareLayout(): void + { + parent::_prepareLayout(); + $this->preparePager(); + } + + /** + * Generate and return "New Address" URL + * + * @return string + */ + public function getAddAddressUrl(): string + { + return $this->getUrl('customer/address/new', ['_secure' => true]); + } + + /** + * Generate and return "Delete" URL + * + * @return string + */ + public function getDeleteUrl(): string + { + return $this->getUrl('customer/address/delete'); + } + + /** + * Generate and return "Edit Address" URL. + * + * Address ID passed in parameters + * + * @param int $addressId + * @return string + */ + public function getAddressEditUrl($addressId): string + { + return $this->getUrl('customer/address/edit', ['_secure' => true, 'id' => $addressId]); + } + + /** + * Get current additional customer addresses + * + * Return array of address interfaces if customer has additional addresses and false in other cases + * + * @return \Magento\Customer\Api\Data\AddressInterface[] + * @throws \Magento\Framework\Exception\LocalizedException + * @throws NoSuchEntityException + */ + public function getAdditionalAddresses(): array + { + $additional = []; + $addresses = $this->getAddressCollection(); + $primaryAddressIds = [$this->getDefaultBilling(), $this->getDefaultShipping()]; + foreach ($addresses as $address) { + if (!in_array((int)$address->getId(), $primaryAddressIds, true)) { + $additional[] = $address->getDataModel(); + } + } + return $additional; + } + + /** + * Get current customer + * + * Return stored customer or get it from session + * + * @return \Magento\Customer\Api\Data\CustomerInterface + */ + public function getCustomer(): \Magento\Customer\Api\Data\CustomerInterface + { + $customer = $this->getData('customer'); + if ($customer === null) { + $customer = $this->currentCustomer->getCustomer(); + $this->setData('customer', $customer); + } + return $customer; + } + + /** + * Get one string street address from the Address DTO passed in parameters + * + * @param \Magento\Customer\Api\Data\AddressInterface $address + * @return string + */ + public function getStreetAddress(\Magento\Customer\Api\Data\AddressInterface $address): string + { + $street = $address->getStreet(); + if (is_array($street)) { + $street = implode(', ', $street); + } + return $street; + } + + /** + * Get country name by $countryCode + * + * Using \Magento\Directory\Model\Country to get country name by $countryCode + * + * @param string $countryCode + * @return string + */ + public function getCountryByCode(string $countryCode): string + { + /** @var \Magento\Directory\Model\Country $country */ + $country = $this->countryFactory->create(); + return $country->loadByCode($countryCode)->getName(); + } + + /** + * Get default billing address + * + * Return address string if address found and null if not + * + * @return int + * @throws \Magento\Framework\Exception\LocalizedException + */ + private function getDefaultBilling(): int + { + $customer = $this->getCustomer(); + + return (int)$customer->getDefaultBilling(); + } + + /** + * Get default shipping address + * + * Return address string if address found and null if not + * + * @return int + * @throws \Magento\Framework\Exception\LocalizedException + */ + private function getDefaultShipping(): int + { + $customer = $this->getCustomer(); + + return (int)$customer->getDefaultShipping(); + } + + /** + * Get pager layout + * + * @return void + * @throws \Magento\Framework\Exception\LocalizedException + */ + private function preparePager(): void + { + $addressCollection = $this->getAddressCollection(); + if (null !== $addressCollection) { + $pager = $this->getLayout()->createBlock( + \Magento\Theme\Block\Html\Pager::class, + 'customer.addresses.pager' + )->setCollection($addressCollection); + $this->setChild('pager', $pager); + } + } + + /** + * Get customer addresses collection. + * + * Filters collection by customer id + * + * @return \Magento\Customer\Model\ResourceModel\Address\Collection + * @throws NoSuchEntityException + */ + private function getAddressCollection(): \Magento\Customer\Model\ResourceModel\Address\Collection + { + if (null === $this->addressCollection) { + if (null === $this->getCustomer()) { + throw new NoSuchEntityException(__('Customer not logged in')); + } + /** @var \Magento\Customer\Model\ResourceModel\Address\Collection $collection */ + $collection = $this->addressCollectionFactory->create(); + $collection->setOrder('entity_id', 'desc') + ->setCustomerFilter([$this->getCustomer()->getId()]); + $this->addressCollection = $collection; + } + return $this->addressCollection; + } +} diff --git a/app/code/Magento/Customer/Block/Adminhtml/Edit/DeleteButton.php b/app/code/Magento/Customer/Block/Adminhtml/Edit/DeleteButton.php index e1bb6feb236..38b2f410d2f 100644 --- a/app/code/Magento/Customer/Block/Adminhtml/Edit/DeleteButton.php +++ b/app/code/Magento/Customer/Block/Adminhtml/Edit/DeleteButton.php @@ -10,6 +10,7 @@ /** * Class DeleteButton + * * @package Magento\Customer\Block\Adminhtml\Edit */ class DeleteButton extends GenericButton implements ButtonProviderInterface @@ -36,6 +37,8 @@ public function __construct( } /** + * Get button data. + * * @return array */ public function getButtonData() @@ -53,12 +56,15 @@ public function getButtonData() ], 'on_click' => '', 'sort_order' => 20, + 'aclResource' => 'Magento_Customer::delete', ]; } return $data; } /** + * Get delete url. + * * @return string */ public function getDeleteUrl() diff --git a/app/code/Magento/Customer/Block/Adminhtml/Edit/InvalidateTokenButton.php b/app/code/Magento/Customer/Block/Adminhtml/Edit/InvalidateTokenButton.php index 180cb3d66ea..ca24ac9356d 100644 --- a/app/code/Magento/Customer/Block/Adminhtml/Edit/InvalidateTokenButton.php +++ b/app/code/Magento/Customer/Block/Adminhtml/Edit/InvalidateTokenButton.php @@ -9,11 +9,14 @@ /** * Class InvalidateTokenButton + * * @package Magento\Customer\Block\Adminhtml\Edit */ class InvalidateTokenButton extends GenericButton implements ButtonProviderInterface { /** + * Get button data. + * * @return array */ public function getButtonData() @@ -27,12 +30,15 @@ public function getButtonData() 'class' => 'invalidate-token', 'on_click' => 'deleteConfirm("' . $deleteConfirmMsg . '", "' . $this->getInvalidateTokenUrl() . '")', 'sort_order' => 65, + 'aclResource' => 'Magento_Customer::invalidate_tokens', ]; } return $data; } /** + * Get invalidate token url. + * * @return string */ public function getInvalidateTokenUrl() diff --git a/app/code/Magento/Customer/Block/Adminhtml/Edit/ResetPasswordButton.php b/app/code/Magento/Customer/Block/Adminhtml/Edit/ResetPasswordButton.php index aa937851168..f8a6b3505ae 100644 --- a/app/code/Magento/Customer/Block/Adminhtml/Edit/ResetPasswordButton.php +++ b/app/code/Magento/Customer/Block/Adminhtml/Edit/ResetPasswordButton.php @@ -27,6 +27,7 @@ public function getButtonData() 'class' => 'reset reset-password', 'on_click' => sprintf("location.href = '%s';", $this->getResetPasswordUrl()), 'sort_order' => 60, + 'aclResource' => 'Magento_Customer::reset_password', ]; } return $data; diff --git a/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/View/Cart.php b/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/View/Cart.php index 3f2c7cda760..1bc6bb1da36 100644 --- a/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/View/Cart.php +++ b/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/View/Cart.php @@ -71,7 +71,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ protected function _construct() { @@ -94,7 +94,7 @@ protected function _prepareCollection() $quote = $this->getQuote(); if ($quote) { - $collection = $quote->getItemsCollection(false); + $collection = $quote->getItemsCollection(true); } else { $collection = $this->_dataCollectionFactory->create(); } @@ -106,7 +106,7 @@ protected function _prepareCollection() } /** - * {@inheritdoc} + * @inheritdoc */ protected function _prepareColumns() { @@ -144,7 +144,7 @@ protected function _prepareColumns() } /** - * {@inheritdoc} + * @inheritdoc */ public function getRowUrl($row) { @@ -152,7 +152,7 @@ public function getRowUrl($row) } /** - * {@inheritdoc} + * @inheritdoc */ public function getHeadersVisibility() { diff --git a/app/code/Magento/Customer/Block/DataProviders/PostCodesPatternsAttributeData.php b/app/code/Magento/Customer/Block/DataProviders/PostCodesPatternsAttributeData.php new file mode 100644 index 00000000000..280948439e1 --- /dev/null +++ b/app/code/Magento/Customer/Block/DataProviders/PostCodesPatternsAttributeData.php @@ -0,0 +1,50 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Customer\Block\DataProviders; + +use Magento\Framework\Serialize\SerializerInterface; +use Magento\Framework\View\Element\Block\ArgumentInterface; +use Magento\Directory\Model\Country\Postcode\Config as PostCodeConfig; + +/** + * Provides postcodes patterns into template. + */ +class PostCodesPatternsAttributeData implements ArgumentInterface +{ + /** + * @var PostCodeConfig + */ + private $postCodeConfig; + + /** + * @var SerializerInterface + */ + private $serializer; + + /** + * Constructor + * + * @param PostCodeConfig $postCodeConfig + * @param SerializerInterface $serializer + */ + public function __construct(PostCodeConfig $postCodeConfig, SerializerInterface $serializer) + { + $this->postCodeConfig = $postCodeConfig; + $this->serializer = $serializer; + } + + /** + * Get serialized post codes + * + * @return string + */ + public function getSerializedPostCodes(): string + { + return $this->serializer->serialize($this->postCodeConfig->getPostCodes()); + } +} diff --git a/app/code/Magento/Customer/Block/Form/Register.php b/app/code/Magento/Customer/Block/Form/Register.php index f31012a52a9..322dd2cbfe9 100644 --- a/app/code/Magento/Customer/Block/Form/Register.php +++ b/app/code/Magento/Customer/Block/Form/Register.php @@ -6,6 +6,7 @@ namespace Magento\Customer\Block\Form; use Magento\Customer\Model\AccountManagement; +use Magento\Newsletter\Observer\PredispatchNewsletterObserver; /** * Customer register form block @@ -86,6 +87,8 @@ public function getConfig($path) } /** + * Prepare layout + * * @return $this */ protected function _prepareLayout() @@ -177,11 +180,13 @@ public function getRegion() */ public function isNewsletterEnabled() { - return $this->_moduleManager->isOutputEnabled('Magento_Newsletter'); + return $this->_moduleManager->isOutputEnabled('Magento_Newsletter') + && $this->getConfig(PredispatchNewsletterObserver::XML_PATH_NEWSLETTER_ACTIVE); } /** * Restore entity data from session + * * Entity and form code must be defined for the form * * @param \Magento\Customer\Model\Metadata\Form $form diff --git a/app/code/Magento/Customer/Block/Widget/Dob.php b/app/code/Magento/Customer/Block/Widget/Dob.php index 936563d5198..55101fb82af 100644 --- a/app/code/Magento/Customer/Block/Widget/Dob.php +++ b/app/code/Magento/Customer/Block/Widget/Dob.php @@ -61,7 +61,7 @@ public function __construct( } /** - * @return void + * @inheritdoc */ public function _construct() { @@ -70,6 +70,8 @@ public function _construct() } /** + * Check if dob attribute enabled in system + * * @return bool */ public function isEnabled() @@ -79,6 +81,8 @@ public function isEnabled() } /** + * Check if dob attribute marked as required + * * @return bool */ public function isRequired() @@ -88,6 +92,8 @@ public function isRequired() } /** + * Set date + * * @param string $date * @return $this */ @@ -135,6 +141,8 @@ protected function applyOutputFilter($value) } /** + * Get day + * * @return string|bool */ public function getDay() @@ -143,6 +151,8 @@ public function getDay() } /** + * Get month + * * @return string|bool */ public function getMonth() @@ -151,6 +161,8 @@ public function getMonth() } /** + * Get year + * * @return string|bool */ public function getYear() @@ -168,6 +180,19 @@ public function getLabel() return __('Date of Birth'); } + /** + * Retrieve store attribute label + * + * @param string $attributeCode + * + * @return string + */ + public function getStoreLabel($attributeCode) + { + $attribute = $this->_getAttribute($attributeCode); + return $attribute ? __($attribute->getStoreLabel()) : ''; + } + /** * Create correct date field * diff --git a/app/code/Magento/Customer/Block/Widget/Gender.php b/app/code/Magento/Customer/Block/Widget/Gender.php index d03c64a54fb..9df3f1072ce 100644 --- a/app/code/Magento/Customer/Block/Widget/Gender.php +++ b/app/code/Magento/Customer/Block/Widget/Gender.php @@ -64,6 +64,7 @@ public function _construct() /** * Check if gender attribute enabled in system + * * @return bool */ public function isEnabled() @@ -73,6 +74,7 @@ public function isEnabled() /** * Check if gender attribute marked as required + * * @return bool */ public function isRequired() @@ -80,6 +82,19 @@ public function isRequired() return $this->_getAttribute('gender') ? (bool)$this->_getAttribute('gender')->isRequired() : false; } + /** + * Retrieve store attribute label + * + * @param string $attributeCode + * + * @return string + */ + public function getStoreLabel($attributeCode) + { + $attribute = $this->_getAttribute($attributeCode); + return $attribute ? __($attribute->getStoreLabel()) : ''; + } + /** * Get current customer from session * @@ -92,6 +107,7 @@ public function getCustomer() /** * Returns options from gender attribute + * * @return OptionInterface[] */ public function getGenderOptions() diff --git a/app/code/Magento/Customer/Block/Widget/Name.php b/app/code/Magento/Customer/Block/Widget/Name.php index d50045f4a40..6f1b051af74 100644 --- a/app/code/Magento/Customer/Block/Widget/Name.php +++ b/app/code/Magento/Customer/Block/Widget/Name.php @@ -55,7 +55,7 @@ public function __construct( } /** - * @return void + * @inheritdoc */ public function _construct() { @@ -245,10 +245,13 @@ public function getStoreLabel($attributeCode) */ public function getAttributeValidationClass($attributeCode) { - return $this->_addressHelper->getAttributeValidationClass($attributeCode); + $attributeMetadata = $this->_getAttribute($attributeCode); + return $attributeMetadata ? $attributeMetadata->getFrontendClass() : ''; } /** + * Check if attribute is required + * * @param string $attributeCode * @return bool */ @@ -259,6 +262,8 @@ private function _isAttributeRequired($attributeCode) } /** + * Check if attribute is visible + * * @param string $attributeCode * @return bool */ diff --git a/app/code/Magento/Customer/Block/Widget/Taxvat.php b/app/code/Magento/Customer/Block/Widget/Taxvat.php index e5c9c01dc3a..e35f04f592a 100644 --- a/app/code/Magento/Customer/Block/Widget/Taxvat.php +++ b/app/code/Magento/Customer/Block/Widget/Taxvat.php @@ -63,4 +63,17 @@ public function isRequired() { return $this->_getAttribute('taxvat') ? (bool)$this->_getAttribute('taxvat')->isRequired() : false; } + + /** + * Retrieve store attribute label + * + * @param string $attributeCode + * + * @return string + */ + public function getStoreLabel($attributeCode) + { + $attribute = $this->_getAttribute($attributeCode); + return $attribute ? __($attribute->getStoreLabel()) : ''; + } } diff --git a/app/code/Magento/Customer/Controller/Account/EditPost.php b/app/code/Magento/Customer/Controller/Account/EditPost.php index 38bc52eac42..4eb41cedea2 100644 --- a/app/code/Magento/Customer/Controller/Account/EditPost.php +++ b/app/code/Magento/Customer/Controller/Account/EditPost.php @@ -7,6 +7,8 @@ namespace Magento\Customer\Controller\Account; +use Magento\Customer\Api\Data\CustomerInterface; +use Magento\Customer\Model\AddressRegistry; use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Customer\Model\AuthenticationInterface; use Magento\Customer\Model\Customer\Mapper; @@ -25,6 +27,7 @@ use Magento\Framework\Escaper; use Magento\Framework\Exception\InputException; use Magento\Framework\Exception\InvalidEmailOrPasswordException; +use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\Exception\State\UserLockedException; use Magento\Customer\Controller\AbstractAccount; use Magento\Framework\Phrase; @@ -85,6 +88,11 @@ class EditPost extends AbstractAccount implements CsrfAwareActionInterface, Http */ private $escaper; + /** + * @var AddressRegistry + */ + private $addressRegistry; + /** * @param Context $context * @param Session $customerSession @@ -93,6 +101,7 @@ class EditPost extends AbstractAccount implements CsrfAwareActionInterface, Http * @param Validator $formKeyValidator * @param CustomerExtractor $customerExtractor * @param Escaper|null $escaper + * @param AddressRegistry|null $addressRegistry */ public function __construct( Context $context, @@ -101,7 +110,8 @@ public function __construct( CustomerRepositoryInterface $customerRepository, Validator $formKeyValidator, CustomerExtractor $customerExtractor, - ?Escaper $escaper = null + ?Escaper $escaper = null, + AddressRegistry $addressRegistry = null ) { parent::__construct($context); $this->session = $customerSession; @@ -110,6 +120,7 @@ public function __construct( $this->formKeyValidator = $formKeyValidator; $this->customerExtractor = $customerExtractor; $this->escaper = $escaper ?: ObjectManager::getInstance()->get(Escaper::class); + $this->addressRegistry = $addressRegistry ?: ObjectManager::getInstance()->get(AddressRegistry::class); } /** @@ -195,6 +206,9 @@ public function execute() // whether a customer enabled change password option $isPasswordChanged = $this->changeCustomerPassword($currentCustomerDataObject->getEmail()); + // No need to validate customer address while editing customer profile + $this->disableAddressValidation($customerCandidateDataObject); + $this->customerRepository->save($customerCandidateDataObject); $this->getEmailNotification()->credentialsChanged( $customerCandidateDataObject, @@ -352,4 +366,18 @@ private function getCustomerMapper() } return $this->customerMapper; } + + /** + * Disable Customer Address Validation + * + * @param CustomerInterface $customer + * @throws NoSuchEntityException + */ + private function disableAddressValidation($customer) + { + foreach ($customer->getAddresses() as $address) { + $addressModel = $this->addressRegistry->retrieve($address->getId()); + $addressModel->setShouldIgnoreValidation(true); + } + } } diff --git a/app/code/Magento/Customer/Controller/Address/FormPost.php b/app/code/Magento/Customer/Controller/Address/FormPost.php index 217af0abd75..25618e31291 100644 --- a/app/code/Magento/Customer/Controller/Address/FormPost.php +++ b/app/code/Magento/Customer/Controller/Address/FormPost.php @@ -26,6 +26,8 @@ use Magento\Framework\View\Result\PageFactory; /** + * Customer Address Form Post Controller + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class FormPost extends \Magento\Customer\Controller\Address implements HttpPostActionInterface @@ -120,8 +122,18 @@ protected function _extractAddress() \Magento\Customer\Api\Data\AddressInterface::class ); $addressDataObject->setCustomerId($this->_getSession()->getCustomerId()) - ->setIsDefaultBilling($this->getRequest()->getParam('default_billing', false)) - ->setIsDefaultShipping($this->getRequest()->getParam('default_shipping', false)); + ->setIsDefaultBilling( + $this->getRequest()->getParam( + 'default_billing', + isset($existingAddressData['default_billing']) ? $existingAddressData['default_billing'] : false + ) + ) + ->setIsDefaultShipping( + $this->getRequest()->getParam( + 'default_shipping', + isset($existingAddressData['default_shipping']) ? $existingAddressData['default_shipping'] : false + ) + ); return $addressDataObject; } diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Customer/InvalidateToken.php b/app/code/Magento/Customer/Controller/Adminhtml/Customer/InvalidateToken.php index 7337d005a73..b69410ecbfc 100644 --- a/app/code/Magento/Customer/Controller/Adminhtml/Customer/InvalidateToken.php +++ b/app/code/Magento/Customer/Controller/Adminhtml/Customer/InvalidateToken.php @@ -7,6 +7,7 @@ namespace Magento\Customer\Controller\Adminhtml\Customer; +use Magento\Framework\App\Action\HttpGetActionInterface; use Magento\Integration\Api\CustomerTokenServiceInterface; use Magento\Customer\Api\AccountManagementInterface; use Magento\Customer\Api\AddressRepositoryInterface; @@ -25,8 +26,15 @@ * @SuppressWarnings(PHPMD.TooManyFields) * @SuppressWarnings(PHPMD.NumberOfChildren) */ -class InvalidateToken extends \Magento\Customer\Controller\Adminhtml\Index +class InvalidateToken extends \Magento\Customer\Controller\Adminhtml\Index implements HttpGetActionInterface { + /** + * Authorization level of a basic admin session + * + * @see _isAllowed() + */ + const ADMIN_RESOURCE = 'Magento_Customer::invalidate_tokens'; + /** * @var CustomerTokenServiceInterface */ diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Index/Delete.php b/app/code/Magento/Customer/Controller/Adminhtml/Index/Delete.php index 15da8b20adb..ab39ca09816 100644 --- a/app/code/Magento/Customer/Controller/Adminhtml/Index/Delete.php +++ b/app/code/Magento/Customer/Controller/Adminhtml/Index/Delete.php @@ -8,8 +8,18 @@ use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Framework\Controller\ResultFactory; +/** + * Delete customer action. + */ class Delete extends \Magento\Customer\Controller\Adminhtml\Index implements HttpPostActionInterface { + /** + * Authorization level of a basic admin session + * + * @see _isAllowed() + */ + const ADMIN_RESOURCE = 'Magento_Customer::delete'; + /** * Delete customer action * diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Index/InlineEdit.php b/app/code/Magento/Customer/Controller/Adminhtml/Index/InlineEdit.php index 3c3808d0a1e..7220de03568 100644 --- a/app/code/Magento/Customer/Controller/Adminhtml/Index/InlineEdit.php +++ b/app/code/Magento/Customer/Controller/Adminhtml/Index/InlineEdit.php @@ -8,10 +8,13 @@ use Magento\Backend\App\Action; use Magento\Customer\Api\CustomerRepositoryInterface; use Magento\Customer\Api\Data\CustomerInterface; +use Magento\Customer\Model\AddressRegistry; use Magento\Customer\Model\EmailNotificationInterface; use Magento\Customer\Ui\Component\Listing\AttributeRepository; use Magento\Framework\App\Action\HttpPostActionInterface; +use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\Message\MessageInterface; +use Magento\Framework\App\ObjectManager; /** * Customer inline edit action @@ -62,6 +65,11 @@ class InlineEdit extends \Magento\Backend\App\Action implements HttpPostActionIn */ private $emailNotification; + /** + * @var AddressRegistry + */ + private $addressRegistry; + /** * @param Action\Context $context * @param CustomerRepositoryInterface $customerRepository @@ -69,6 +77,7 @@ class InlineEdit extends \Magento\Backend\App\Action implements HttpPostActionIn * @param \Magento\Customer\Model\Customer\Mapper $customerMapper * @param \Magento\Framework\Api\DataObjectHelper $dataObjectHelper * @param \Psr\Log\LoggerInterface $logger + * @param AddressRegistry|null $addressRegistry */ public function __construct( Action\Context $context, @@ -76,13 +85,15 @@ public function __construct( \Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory, \Magento\Customer\Model\Customer\Mapper $customerMapper, \Magento\Framework\Api\DataObjectHelper $dataObjectHelper, - \Psr\Log\LoggerInterface $logger + \Psr\Log\LoggerInterface $logger, + AddressRegistry $addressRegistry = null ) { $this->customerRepository = $customerRepository; $this->resultJsonFactory = $resultJsonFactory; $this->customerMapper = $customerMapper; $this->dataObjectHelper = $dataObjectHelper; $this->logger = $logger; + $this->addressRegistry = $addressRegistry ?: ObjectManager::getInstance()->get(AddressRegistry::class); parent::__construct($context); } @@ -219,6 +230,8 @@ protected function updateDefaultBilling(array $data) protected function saveCustomer(CustomerInterface $customer) { try { + // No need to validate customer address during inline edit action + $this->disableAddressValidation($customer); $this->customerRepository->save($customer); } catch (\Magento\Framework\Exception\InputException $e) { $this->getMessageManager()->addError($this->getErrorWithCustomerId($e->getMessage())); @@ -304,4 +317,18 @@ protected function getErrorWithCustomerId($errorText) { return '[Customer ID: ' . $this->getCustomer()->getId() . '] ' . __($errorText); } + + /** + * Disable Customer Address Validation + * + * @param CustomerInterface $customer + * @throws NoSuchEntityException + */ + private function disableAddressValidation($customer) + { + foreach ($customer->getAddresses() as $address) { + $addressModel = $this->addressRegistry->retrieve($address->getId()); + $addressModel->setShouldIgnoreValidation(true); + } + } } diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Index/MassAssignGroup.php b/app/code/Magento/Customer/Controller/Adminhtml/Index/MassAssignGroup.php index a540ad9d7a7..5a9c52bf9b1 100644 --- a/app/code/Magento/Customer/Controller/Adminhtml/Index/MassAssignGroup.php +++ b/app/code/Magento/Customer/Controller/Adminhtml/Index/MassAssignGroup.php @@ -5,6 +5,7 @@ */ namespace Magento\Customer\Controller\Adminhtml\Index; +use Magento\Customer\Model\Customer; use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Backend\App\Action\Context; use Magento\Customer\Model\ResourceModel\Customer\CollectionFactory; @@ -52,6 +53,8 @@ protected function massAction(AbstractCollection $collection) // Verify customer exists $customer = $this->customerRepository->getById($customerId); $customer->setGroupId($this->getRequest()->getParam('group')); + // No need to validate customer and customer address during assigning customer to the group + $this->setIgnoreValidationFlag($customer); $this->customerRepository->save($customer); $customersUpdated++; } @@ -65,4 +68,15 @@ protected function massAction(AbstractCollection $collection) return $resultRedirect; } + + /** + * Set ignore_validation_flag to skip unnecessary address and customer validation + * + * @param Customer $customer + * @return void + */ + private function setIgnoreValidationFlag($customer) + { + $customer->setData('ignore_validation_flag', true); + } } diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Index/MassDelete.php b/app/code/Magento/Customer/Controller/Adminhtml/Index/MassDelete.php index 334018a881f..edaeea6a15e 100644 --- a/app/code/Magento/Customer/Controller/Adminhtml/Index/MassDelete.php +++ b/app/code/Magento/Customer/Controller/Adminhtml/Index/MassDelete.php @@ -18,6 +18,13 @@ */ class MassDelete extends AbstractMassAction implements HttpPostActionInterface { + /** + * Authorization level of a basic admin session + * + * @see _isAllowed() + */ + const ADMIN_RESOURCE = 'Magento_Customer::delete'; + /** * @var CustomerRepositoryInterface */ @@ -40,8 +47,7 @@ public function __construct( } /** - * @param AbstractCollection $collection - * @return \Magento\Backend\Model\View\Result\Redirect + * @inheritdoc */ protected function massAction(AbstractCollection $collection) { diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Index/ResetPassword.php b/app/code/Magento/Customer/Controller/Adminhtml/Index/ResetPassword.php index 3e6046b0d11..1e4fa91cbf8 100644 --- a/app/code/Magento/Customer/Controller/Adminhtml/Index/ResetPassword.php +++ b/app/code/Magento/Customer/Controller/Adminhtml/Index/ResetPassword.php @@ -16,6 +16,13 @@ */ class ResetPassword extends \Magento\Customer\Controller\Adminhtml\Index implements HttpGetActionInterface { + /** + * Authorization level of a basic admin session + * + * @see _isAllowed() + */ + const ADMIN_RESOURCE = 'Magento_Customer::reset_password'; + /** * Reset password handler * diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php b/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php index adb420f9830..cb0343f4ec4 100644 --- a/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php +++ b/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php @@ -5,6 +5,15 @@ */ namespace Magento\Customer\Controller\Adminhtml\Index; +use Magento\Customer\Api\AccountManagementInterface; +use Magento\Customer\Api\AddressRepositoryInterface; +use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Customer\Model\Address\Mapper; +use Magento\Customer\Model\AddressRegistry; +use Magento\Framework\Api\DataObjectHelper; +use Magento\Customer\Api\Data\AddressInterfaceFactory; +use Magento\Customer\Api\Data\CustomerInterfaceFactory; +use Magento\Framework\DataObjectFactory as ObjectFactory; use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; use Magento\Customer\Api\AddressMetadataInterface; use Magento\Customer\Api\CustomerMetadataInterface; @@ -13,6 +22,8 @@ use Magento\Customer\Model\EmailNotificationInterface; use Magento\Customer\Model\Metadata\Form; use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\App\ObjectManager; /** * Save customer action. @@ -26,6 +37,100 @@ class Save extends \Magento\Customer\Controller\Adminhtml\Index implements HttpP */ private $emailNotification; + /** + * @var AddressRegistry + */ + private $addressRegistry; + + /** + * Constructor + * + * @param \Magento\Backend\App\Action\Context $context + * @param \Magento\Framework\Registry $coreRegistry + * @param \Magento\Framework\App\Response\Http\FileFactory $fileFactory + * @param \Magento\Customer\Model\CustomerFactory $customerFactory + * @param \Magento\Customer\Model\AddressFactory $addressFactory + * @param \Magento\Customer\Model\Metadata\FormFactory $formFactory + * @param \Magento\Newsletter\Model\SubscriberFactory $subscriberFactory + * @param \Magento\Customer\Helper\View $viewHelper + * @param \Magento\Framework\Math\Random $random + * @param CustomerRepositoryInterface $customerRepository + * @param \Magento\Framework\Api\ExtensibleDataObjectConverter $extensibleDataObjectConverter + * @param Mapper $addressMapper + * @param AccountManagementInterface $customerAccountManagement + * @param AddressRepositoryInterface $addressRepository + * @param CustomerInterfaceFactory $customerDataFactory + * @param AddressInterfaceFactory $addressDataFactory + * @param \Magento\Customer\Model\Customer\Mapper $customerMapper + * @param \Magento\Framework\Reflection\DataObjectProcessor $dataObjectProcessor + * @param DataObjectHelper $dataObjectHelper + * @param ObjectFactory $objectFactory + * @param \Magento\Framework\View\LayoutFactory $layoutFactory + * @param \Magento\Framework\View\Result\LayoutFactory $resultLayoutFactory + * @param \Magento\Framework\View\Result\PageFactory $resultPageFactory + * @param \Magento\Backend\Model\View\Result\ForwardFactory $resultForwardFactory + * @param \Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory + * @param AddressRegistry|null $addressRegistry + * @SuppressWarnings(PHPMD.ExcessiveParameterList) + */ + public function __construct( + \Magento\Backend\App\Action\Context $context, + \Magento\Framework\Registry $coreRegistry, + \Magento\Framework\App\Response\Http\FileFactory $fileFactory, + \Magento\Customer\Model\CustomerFactory $customerFactory, + \Magento\Customer\Model\AddressFactory $addressFactory, + \Magento\Customer\Model\Metadata\FormFactory $formFactory, + \Magento\Newsletter\Model\SubscriberFactory $subscriberFactory, + \Magento\Customer\Helper\View $viewHelper, + \Magento\Framework\Math\Random $random, + CustomerRepositoryInterface $customerRepository, + \Magento\Framework\Api\ExtensibleDataObjectConverter $extensibleDataObjectConverter, + Mapper $addressMapper, + AccountManagementInterface $customerAccountManagement, + AddressRepositoryInterface $addressRepository, + CustomerInterfaceFactory $customerDataFactory, + AddressInterfaceFactory $addressDataFactory, + \Magento\Customer\Model\Customer\Mapper $customerMapper, + \Magento\Framework\Reflection\DataObjectProcessor $dataObjectProcessor, + DataObjectHelper $dataObjectHelper, + ObjectFactory $objectFactory, + \Magento\Framework\View\LayoutFactory $layoutFactory, + \Magento\Framework\View\Result\LayoutFactory $resultLayoutFactory, + \Magento\Framework\View\Result\PageFactory $resultPageFactory, + \Magento\Backend\Model\View\Result\ForwardFactory $resultForwardFactory, + \Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory, + AddressRegistry $addressRegistry = null + ) { + parent::__construct( + $context, + $coreRegistry, + $fileFactory, + $customerFactory, + $addressFactory, + $formFactory, + $subscriberFactory, + $viewHelper, + $random, + $customerRepository, + $extensibleDataObjectConverter, + $addressMapper, + $customerAccountManagement, + $addressRepository, + $customerDataFactory, + $addressDataFactory, + $customerMapper, + $dataObjectProcessor, + $dataObjectHelper, + $objectFactory, + $layoutFactory, + $resultLayoutFactory, + $resultPageFactory, + $resultForwardFactory, + $resultJsonFactory + ); + $this->addressRegistry = $addressRegistry ?: ObjectManager::getInstance()->get(AddressRegistry::class); + } + /** * Reformat customer account data to be compatible with customer service interface * @@ -195,6 +300,8 @@ public function execute() if ($customerId) { $currentCustomer = $this->_customerRepository->getById($customerId); + // No need to validate customer address while editing customer profile + $this->disableAddressValidation($currentCustomer); $customerData = array_merge( $this->customerMapper->toFlatArray($currentCustomer), $customerData @@ -368,4 +475,18 @@ private function getCurrentCustomerId() return $customerId; } + + /** + * Disable Customer Address Validation + * + * @param CustomerInterface $customer + * @throws NoSuchEntityException + */ + private function disableAddressValidation($customer) + { + foreach ($customer->getAddresses() as $address) { + $addressModel = $this->addressRegistry->retrieve($address->getId()); + $addressModel->setShouldIgnoreValidation(true); + } + } } diff --git a/app/code/Magento/Customer/Helper/Address.php b/app/code/Magento/Customer/Helper/Address.php index 7c81e293258..765c13b2877 100644 --- a/app/code/Magento/Customer/Helper/Address.php +++ b/app/code/Magento/Customer/Helper/Address.php @@ -417,23 +417,4 @@ public function isAttributeVisible($code) } return false; } - - /** - * Retrieve attribute required - * - * @param string $code - * @return bool - * @throws NoSuchEntityException - * @throws \Magento\Framework\Exception\LocalizedException - */ - public function isAttributeRequired($code) - { - $attributeMetadata = $this->_addressMetadataService->getAttributeMetadata($code); - - if ($attributeMetadata) { - return $attributeMetadata->isRequired(); - } - - return false; - } } diff --git a/app/code/Magento/Customer/Model/AccountManagement.php b/app/code/Magento/Customer/Model/AccountManagement.php index d7c5d7f47a4..673300369fe 100644 --- a/app/code/Magento/Customer/Model/AccountManagement.php +++ b/app/code/Magento/Customer/Model/AccountManagement.php @@ -60,6 +60,7 @@ * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @SuppressWarnings(PHPMD.TooManyFields) * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) */ class AccountManagement implements AccountManagementInterface { @@ -524,6 +525,8 @@ private function activateCustomer($customer, $confirmationKey) } $customer->setConfirmation(null); + // No need to validate customer and customer address while activating customer + $this->setIgnoreValidationFlag($customer); $this->customerRepository->save($customer); $this->getEmailNotification()->newAccount( $customer, @@ -683,8 +686,9 @@ public function resetPassword($email, $resetToken, $newPassword) $customer = $this->customerRepository->get($email); } - // No need to validate customer address while saving customer reset password token + // No need to validate customer and customer address while saving customer reset password token $this->disableAddressValidation($customer); + $this->setIgnoreValidationFlag($customer); //Validate Token and new password strength $this->validateResetPasswordToken($customer->getId(), $resetToken); @@ -811,7 +815,7 @@ public function getConfirmationStatus($customerId) /** * @inheritdoc */ - public function createAccount(CustomerInterface $customer, $password = null, $redirectUrl = '', $extensions = []) + public function createAccount(CustomerInterface $customer, $password = null, $redirectUrl = '') { if ($password !== null) { $this->checkPasswordStrength($password); @@ -827,7 +831,7 @@ public function createAccount(CustomerInterface $customer, $password = null, $re } else { $hash = null; } - return $this->createAccountWithPasswordHash($customer, $hash, $redirectUrl, $extensions); + return $this->createAccountWithPasswordHash($customer, $hash, $redirectUrl); } /** @@ -835,12 +839,8 @@ public function createAccount(CustomerInterface $customer, $password = null, $re * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) */ - public function createAccountWithPasswordHash( - CustomerInterface $customer, - $hash, - $redirectUrl = '', - $extensions = [] - ) { + public function createAccountWithPasswordHash(CustomerInterface $customer, $hash, $redirectUrl = '') + { // This logic allows an existing customer to be added to a different store. No new account is created. // The plan is to move this logic into a new method called something like 'registerAccountWithStore' if ($customer->getId()) { @@ -913,7 +913,7 @@ public function createAccountWithPasswordHash( $customer = $this->customerRepository->getById($customer->getId()); $newLinkToken = $this->mathRandom->getUniqueHash(); $this->changeResetPasswordLinkToken($customer, $newLinkToken); - $this->sendEmailConfirmation($customer, $redirectUrl, $extensions); + $this->sendEmailConfirmation($customer, $redirectUrl); return $customer; } @@ -941,12 +941,11 @@ public function getDefaultShippingAddress($customerId) * * @param CustomerInterface $customer * @param string $redirectUrl - * @param array $extensions * @return void * @throws LocalizedException * @throws NoSuchEntityException */ - protected function sendEmailConfirmation(CustomerInterface $customer, $redirectUrl, $extensions = []) + protected function sendEmailConfirmation(CustomerInterface $customer, $redirectUrl) { try { $hash = $this->customerRegistry->retrieveSecureData($customer->getId())->getPasswordHash(); @@ -956,14 +955,7 @@ protected function sendEmailConfirmation(CustomerInterface $customer, $redirectU } elseif ($hash == '') { $templateType = self::NEW_ACCOUNT_EMAIL_REGISTERED_NO_PASSWORD; } - $this->getEmailNotification()->newAccount( - $customer, - $templateType, - $redirectUrl, - $customer->getStoreId(), - null, - $extensions - ); + $this->getEmailNotification()->newAccount($customer, $templateType, $redirectUrl, $customer->getStoreId()); } catch (MailException $e) { // If we are not able to send a new account email, this should be ignored $this->logger->critical($e); @@ -1029,6 +1021,7 @@ private function changePasswordForCustomer($customer, $currentPassword, $newPass $this->checkPasswordStrength($newPassword); $customerSecure->setPasswordHash($this->createPasswordHash($newPassword)); $this->destroyCustomerSessions($customer->getId()); + $this->disableAddressValidation($customer); $this->customerRepository->save($customer); return true; diff --git a/app/code/Magento/Customer/Model/Address.php b/app/code/Magento/Customer/Model/Address.php index 4976ec54660..e9aa2839095 100644 --- a/app/code/Magento/Customer/Model/Address.php +++ b/app/code/Magento/Customer/Model/Address.php @@ -172,12 +172,9 @@ public function updateData(AddressInterface $address) public function getDataModel($defaultBillingAddressId = null, $defaultShippingAddressId = null) { if ($this->getCustomerId() || $this->getParentId()) { - if ($this->getCustomer()->getDefaultBillingAddress()) { - $defaultBillingAddressId = $this->getCustomer()->getDefaultBillingAddress()->getId(); - } - if ($this->getCustomer()->getDefaultShippingAddress()) { - $defaultShippingAddressId = $this->getCustomer()->getDefaultShippingAddress()->getId(); - } + $customer = $this->getCustomer(); + $defaultBillingAddressId = $customer->getDefaultBilling() ?: $defaultBillingAddressId; + $defaultShippingAddressId = $customer->getDefaultShipping() ?: $defaultShippingAddressId; } return parent::getDataModel($defaultBillingAddressId, $defaultShippingAddressId); } diff --git a/app/code/Magento/Customer/Model/Address/AbstractAddress.php b/app/code/Magento/Customer/Model/Address/AbstractAddress.php index 146fec4c79f..d8d0646b30b 100644 --- a/app/code/Magento/Customer/Model/Address/AbstractAddress.php +++ b/app/code/Magento/Customer/Model/Address/AbstractAddress.php @@ -222,7 +222,7 @@ public function getStreet() } /** - * Get steet line by number + * Get street line by number * * @param int $number * @return string diff --git a/app/code/Magento/Customer/Model/Address/AddressModelInterface.php b/app/code/Magento/Customer/Model/Address/AddressModelInterface.php index 0af36e87755..06de3a99a83 100644 --- a/app/code/Magento/Customer/Model/Address/AddressModelInterface.php +++ b/app/code/Magento/Customer/Model/Address/AddressModelInterface.php @@ -15,7 +15,7 @@ interface AddressModelInterface { /** - * Get steet line by number + * Get street line by number * * @param int $number * @return string diff --git a/app/code/Magento/Customer/Model/Config/Backend/Address/Street.php b/app/code/Magento/Customer/Model/Config/Backend/Address/Street.php index fc0fa3ebc07..40a10a1db09 100644 --- a/app/code/Magento/Customer/Model/Config/Backend/Address/Street.php +++ b/app/code/Magento/Customer/Model/Config/Backend/Address/Street.php @@ -87,15 +87,20 @@ public function afterDelete() { $result = parent::afterDelete(); - if ($this->getScope() == \Magento\Store\Model\ScopeInterface::SCOPE_WEBSITES) { - $attribute = $this->_eavConfig->getAttribute('customer_address', 'street'); - $website = $this->_storeManager->getWebsite($this->getScopeCode()); - $attribute->setWebsite($website); - $attribute->load($attribute->getId()); - $attribute->setData('scope_multiline_count', null); - $attribute->save(); - } + $attribute = $this->_eavConfig->getAttribute('customer_address', 'street'); + switch ($this->getScope()) { + case \Magento\Store\Model\ScopeInterface::SCOPE_WEBSITES: + $website = $this->_storeManager->getWebsite($this->getScopeCode()); + $attribute->setWebsite($website); + $attribute->load($attribute->getId()); + $attribute->setData('scope_multiline_count', null); + break; + case ScopeConfigInterface::SCOPE_TYPE_DEFAULT: + $attribute->setData('multiline_count', 2); + break; + } + $attribute->save(); return $result; } } diff --git a/app/code/Magento/Customer/Model/Customer.php b/app/code/Magento/Customer/Model/Customer.php index 972cb63ed45..b00f393f537 100644 --- a/app/code/Magento/Customer/Model/Customer.php +++ b/app/code/Magento/Customer/Model/Customer.php @@ -219,6 +219,13 @@ class Customer extends \Magento\Framework\Model\AbstractModel */ private $accountConfirmation; + /** + * Caching property to store customer address data models by the address ID. + * + * @var array + */ + private $storedAddress; + /** * @param \Magento\Framework\Model\Context $context * @param \Magento\Framework\Registry $registry @@ -314,7 +321,10 @@ public function getDataModel() $addressesData = []; /** @var \Magento\Customer\Model\Address $address */ foreach ($this->getAddresses() as $address) { - $addressesData[] = $address->getDataModel(); + if (!isset($this->storedAddress[$address->getId()])) { + $this->storedAddress[$address->getId()] = $address->getDataModel(); + } + $addressesData[] = $this->storedAddress[$address->getId()]; } $customerDataObject = $this->customerDataFactory->create(); $this->dataObjectHelper->populateWithArray( diff --git a/app/code/Magento/Customer/Model/EmailNotification.php b/app/code/Magento/Customer/Model/EmailNotification.php index 30a9dbedde8..144c24f8e83 100644 --- a/app/code/Magento/Customer/Model/EmailNotification.php +++ b/app/code/Magento/Customer/Model/EmailNotification.php @@ -17,7 +17,7 @@ use Magento\Framework\Exception\LocalizedException; /** - * Class for notification customer. + * Customer email notification * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ @@ -65,8 +65,6 @@ class EmailNotification implements EmailNotificationInterface self::NEW_ACCOUNT_EMAIL_CONFIRMATION => self::XML_PATH_CONFIRM_EMAIL_TEMPLATE, ]; - const CUSTOMER_CONFIRM_URL = 'customer/account/confirm/'; - /**#@-*/ /**#@-*/ @@ -366,7 +364,6 @@ public function passwordResetConfirmation(CustomerInterface $customer) * @param string $backUrl * @param string $storeId * @param string $sendemailStoreId - * @param array $extensions * @return void * @throws LocalizedException */ @@ -375,8 +372,7 @@ public function newAccount( $type = self::NEW_ACCOUNT_EMAIL_REGISTERED, $backUrl = '', $storeId = 0, - $sendemailStoreId = null, - $extensions = [] + $sendemailStoreId = null ) { $types = self::TEMPLATE_TYPES; @@ -394,26 +390,11 @@ public function newAccount( $customerEmailData = $this->getFullCustomerObject($customer); - $templateVars = [ - 'customer' => $customerEmailData, - 'back_url' => $backUrl, - 'store' => $store - ]; - if ($type == self::NEW_ACCOUNT_EMAIL_CONFIRMATION) { - if (empty($extensions)) { - $templateVars['url'] = self::CUSTOMER_CONFIRM_URL; - $templateVars['extensions'] = $extensions; - } else { - $templateVars['url'] = $extensions['url']; - $templateVars['extensions'] = $extensions['extension_info']; - } - } - $this->sendEmailTemplate( $customer, $types[$type], self::XML_PATH_REGISTER_EMAIL_IDENTITY, - $templateVars, + ['customer' => $customerEmailData, 'back_url' => $backUrl, 'store' => $store], $storeId ); } diff --git a/app/code/Magento/Customer/Model/Indexer/CustomerGroupDimensionProvider.php b/app/code/Magento/Customer/Model/Indexer/CustomerGroupDimensionProvider.php index d4e0c5cce34..336e7ab770b 100644 --- a/app/code/Magento/Customer/Model/Indexer/CustomerGroupDimensionProvider.php +++ b/app/code/Magento/Customer/Model/Indexer/CustomerGroupDimensionProvider.php @@ -11,6 +11,9 @@ use Magento\Framework\Indexer\DimensionFactory; use Magento\Framework\Indexer\DimensionProviderInterface; +/** + * Class CustomerGroupDimensionProvider + */ class CustomerGroupDimensionProvider implements DimensionProviderInterface { /** @@ -34,12 +37,19 @@ class CustomerGroupDimensionProvider implements DimensionProviderInterface */ private $dimensionFactory; + /** + * @param CustomerGroupCollectionFactory $collectionFactory + * @param DimensionFactory $dimensionFactory + */ public function __construct(CustomerGroupCollectionFactory $collectionFactory, DimensionFactory $dimensionFactory) { $this->dimensionFactory = $dimensionFactory; $this->collectionFactory = $collectionFactory; } + /** + * @inheritdoc + */ public function getIterator(): \Traversable { foreach ($this->getCustomerGroups() as $customerGroup) { @@ -48,6 +58,8 @@ public function getIterator(): \Traversable } /** + * Get Customer Groups + * * @return array */ private function getCustomerGroups(): array diff --git a/app/code/Magento/Customer/Model/Metadata/AttributeMetadataCache.php b/app/code/Magento/Customer/Model/Metadata/AttributeMetadataCache.php index 5a46fdb9def..8e64fba4a9b 100644 --- a/app/code/Magento/Customer/Model/Metadata/AttributeMetadataCache.php +++ b/app/code/Magento/Customer/Model/Metadata/AttributeMetadataCache.php @@ -12,6 +12,7 @@ use Magento\Framework\App\Cache\StateInterface; use Magento\Framework\App\CacheInterface; use Magento\Framework\Serialize\SerializerInterface; +use Magento\Store\Model\StoreManagerInterface; /** * Cache for attribute metadata @@ -53,6 +54,11 @@ class AttributeMetadataCache */ private $serializer; + /** + * @var StoreManagerInterface + */ + private $storeManager; + /** * Constructor * @@ -60,17 +66,21 @@ class AttributeMetadataCache * @param StateInterface $state * @param SerializerInterface $serializer * @param AttributeMetadataHydrator $attributeMetadataHydrator + * @param StoreManagerInterface $storeManager */ public function __construct( CacheInterface $cache, StateInterface $state, SerializerInterface $serializer, - AttributeMetadataHydrator $attributeMetadataHydrator + AttributeMetadataHydrator $attributeMetadataHydrator, + StoreManagerInterface $storeManager = null ) { $this->cache = $cache; $this->state = $state; $this->serializer = $serializer; $this->attributeMetadataHydrator = $attributeMetadataHydrator; + $this->storeManager = $storeManager ?: \Magento\Framework\App\ObjectManager::getInstance() + ->get(StoreManagerInterface::class); } /** @@ -82,11 +92,12 @@ public function __construct( */ public function load($entityType, $suffix = '') { - if (isset($this->attributes[$entityType . $suffix])) { - return $this->attributes[$entityType . $suffix]; + $storeId = $this->storeManager->getStore()->getId(); + if (isset($this->attributes[$entityType . $suffix . $storeId])) { + return $this->attributes[$entityType . $suffix . $storeId]; } if ($this->isEnabled()) { - $cacheKey = self::ATTRIBUTE_METADATA_CACHE_PREFIX . $entityType . $suffix; + $cacheKey = self::ATTRIBUTE_METADATA_CACHE_PREFIX . $entityType . $suffix . $storeId; $serializedData = $this->cache->load($cacheKey); if ($serializedData) { $attributesData = $this->serializer->unserialize($serializedData); @@ -94,7 +105,7 @@ public function load($entityType, $suffix = '') foreach ($attributesData as $key => $attributeData) { $attributes[$key] = $this->attributeMetadataHydrator->hydrate($attributeData); } - $this->attributes[$entityType . $suffix] = $attributes; + $this->attributes[$entityType . $suffix . $storeId] = $attributes; return $attributes; } } @@ -111,9 +122,10 @@ public function load($entityType, $suffix = '') */ public function save($entityType, array $attributes, $suffix = '') { - $this->attributes[$entityType . $suffix] = $attributes; + $storeId = $this->storeManager->getStore()->getId(); + $this->attributes[$entityType . $suffix . $storeId] = $attributes; if ($this->isEnabled()) { - $cacheKey = self::ATTRIBUTE_METADATA_CACHE_PREFIX . $entityType . $suffix; + $cacheKey = self::ATTRIBUTE_METADATA_CACHE_PREFIX . $entityType . $suffix . $storeId; $attributesData = []; foreach ($attributes as $key => $attribute) { $attributesData[$key] = $this->attributeMetadataHydrator->extract($attribute); diff --git a/app/code/Magento/Customer/Model/Metadata/Form/AbstractData.php b/app/code/Magento/Customer/Model/Metadata/Form/AbstractData.php index 168f00be16e..8e443e93354 100644 --- a/app/code/Magento/Customer/Model/Metadata/Form/AbstractData.php +++ b/app/code/Magento/Customer/Model/Metadata/Form/AbstractData.php @@ -12,6 +12,8 @@ use Magento\Framework\Validator\EmailAddress; /** + * Form Element Abstract Data Model + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ abstract class AbstractData @@ -137,6 +139,7 @@ public function setRequestScope($scope) /** * Set scope visibility + * * Search value only in scope or search value in scope and global * * @param boolean $flag @@ -281,9 +284,14 @@ protected function _validateInputRule($value) ); if ($inputValidation !== null) { + $allowWhiteSpace = false; + switch ($inputValidation) { + case 'alphanum-with-spaces': + $allowWhiteSpace = true; + // Continue to alphanumeric validation case 'alphanumeric': - $validator = new \Zend_Validate_Alnum(true); + $validator = new \Zend_Validate_Alnum($allowWhiteSpace); $validator->setMessage(__('"%1" invalid type entered.', $label), \Zend_Validate_Alnum::INVALID); $validator->setMessage( __('"%1" contains non-alphabetic or non-numeric characters.', $label), diff --git a/app/code/Magento/Customer/Model/ResourceModel/Address.php b/app/code/Magento/Customer/Model/ResourceModel/Address.php index a52c3723108..200eaabe651 100644 --- a/app/code/Magento/Customer/Model/ResourceModel/Address.php +++ b/app/code/Magento/Customer/Model/ResourceModel/Address.php @@ -14,6 +14,7 @@ /** * Class Address + * * @package Magento\Customer\Model\ResourceModel * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ @@ -31,8 +32,8 @@ class Address extends \Magento\Eav\Model\Entity\VersionControl\AbstractEntity /** * @param \Magento\Eav\Model\Entity\Context $context - * @param \Magento\Framework\Model\ResourceModel\Db\VersionControl\Snapshot $entitySnapshot, - * @param \Magento\Framework\Model\ResourceModel\Db\VersionControl\RelationComposite $entityRelationComposite, + * @param \Magento\Framework\Model\ResourceModel\Db\VersionControl\Snapshot $entitySnapshot + * @param \Magento\Framework\Model\ResourceModel\Db\VersionControl\RelationComposite $entityRelationComposite * @param \Magento\Framework\Validator\Factory $validatorFactory * @param \Magento\Customer\Api\CustomerRepositoryInterface $customerRepository * @param array $data @@ -98,6 +99,9 @@ protected function _beforeSave(\Magento\Framework\DataObject $address) */ protected function _validate($address) { + if ($address->getDataByKey('should_ignore_validation')) { + return; + }; $validator = $this->_validatorFactory->createValidator('customer_address', 'save'); if (!$validator->isValid($address)) { @@ -110,7 +114,7 @@ protected function _validate($address) } /** - * {@inheritdoc} + * @inheritdoc */ public function delete($object) { @@ -120,6 +124,8 @@ public function delete($object) } /** + * Get instance of DeleteRelation class + * * @deprecated 100.2.0 * @return DeleteRelation */ @@ -129,6 +135,8 @@ private function getDeleteRelation() } /** + * Get instance of CustomerRegistry class + * * @deprecated 100.2.0 * @return CustomerRegistry */ @@ -138,6 +146,8 @@ private function getCustomerRegistry() } /** + * After delete entity process + * * @param \Magento\Customer\Model\Address $address * @return $this */ diff --git a/app/code/Magento/Customer/Model/ResourceModel/CustomerRepository.php b/app/code/Magento/Customer/Model/ResourceModel/CustomerRepository.php index d3be40bdc5c..b25838e2454 100644 --- a/app/code/Magento/Customer/Model/ResourceModel/CustomerRepository.php +++ b/app/code/Magento/Customer/Model/ResourceModel/CustomerRepository.php @@ -346,7 +346,7 @@ public function getList(SearchCriteriaInterface $searchCriteria) ->joinAttribute('billing_telephone', 'customer_address/telephone', 'default_billing', null, 'left') ->joinAttribute('billing_region', 'customer_address/region', 'default_billing', null, 'left') ->joinAttribute('billing_country_id', 'customer_address/country_id', 'default_billing', null, 'left') - ->joinAttribute('company', 'customer_address/company', 'default_billing', null, 'left'); + ->joinAttribute('billing_company', 'customer_address/company', 'default_billing', null, 'left'); $this->collectionProcessor->process($searchCriteria, $collection); diff --git a/app/code/Magento/Customer/Observer/UpgradeCustomerPasswordObserver.php b/app/code/Magento/Customer/Observer/UpgradeCustomerPasswordObserver.php index eb7e81009c9..26c4c50009b 100644 --- a/app/code/Magento/Customer/Observer/UpgradeCustomerPasswordObserver.php +++ b/app/code/Magento/Customer/Observer/UpgradeCustomerPasswordObserver.php @@ -6,11 +6,15 @@ namespace Magento\Customer\Observer; +use Magento\Customer\Model\Customer; use Magento\Framework\Encryption\EncryptorInterface; use Magento\Framework\Event\ObserverInterface; use Magento\Customer\Api\CustomerRepositoryInterface; use Magento\Customer\Model\CustomerRegistry; +/** + * Class observer UpgradeCustomerPasswordObserver to upgrade customer password hash when customer has logged in + */ class UpgradeCustomerPasswordObserver implements ObserverInterface { /** @@ -61,7 +65,20 @@ public function execute(\Magento\Framework\Event\Observer $observer) if (!$this->encryptor->validateHashVersion($customerSecure->getPasswordHash(), true)) { $customerSecure->setPasswordHash($this->encryptor->getHash($password, true)); + // No need to validate customer and customer address while upgrading customer password + $this->setIgnoreValidationFlag($customer); $this->customerRepository->save($customer); } } + + /** + * Set ignore_validation_flag to skip unnecessary address and customer validation + * + * @param Customer $customer + * @return void + */ + private function setIgnoreValidationFlag($customer) + { + $customer->setData('ignore_validation_flag', true); + } } diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCreateCustomerWithWebsiteAndStoreViewActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCreateCustomerWithWebsiteAndStoreViewActionGroup.xml index 4c458c66ca6..37149e23dc8 100644 --- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCreateCustomerWithWebsiteAndStoreViewActionGroup.xml +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCreateCustomerWithWebsiteAndStoreViewActionGroup.xml @@ -42,4 +42,26 @@ <click stepKey="saveAddress" selector="{{AdminCustomerAddressesSection.saveAddress}}"/> <waitForPageLoad stepKey="waitForAddressSave"/> </actionGroup> + + <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"/> + </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"/> + <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}}"/> + <selectOption stepKey="selectStoreView" selector="{{AdminCustomerAccountInformationSection.storeView}}" userInput="{{storeView}}"/> + <waitForElement selector="{{AdminCustomerAccountInformationSection.storeView}}" stepKey="waitForCustomerStoreViewExpand"/> + <click stepKey="save" selector="{{AdminCustomerAccountInformationSection.saveCustomer}}"/> + <waitForPageLoad stepKey="waitForCustomersPage"/> + <see stepKey="seeSuccessMessage" userInput="You saved the customer."/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCustomerShopingCartActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCustomerShopingCartActionGroup.xml new file mode 100644 index 00000000000..f5d5682e374 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCustomerShopingCartActionGroup.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="AdminAddProductToShoppingCartActionGroup"> + <arguments> + <argument name="productName" type="string"/> + </arguments> + <waitForElementVisible selector="{{AdminCustomerShoppingCartProductItemSection.productItem}}" stepKey="waitForElementVisible"/> + <click selector="{{AdminCustomerShoppingCartProductItemSection.productItem}}" stepKey="expandProductItem"/> + <waitForElementVisible selector="{{AdminCustomerShoppingCartProductItemSection.productNameFilter}}" stepKey="waitForProductFilterFieldVisible"/> + <fillField selector="{{AdminCustomerShoppingCartProductItemSection.productNameFilter}}" stepKey="setProductName" userInput="{{productName}}"/> + <click selector="{{AdminCustomerShoppingCartProductItemSection.searchButton}}" stepKey="clickSearchButton"/> + <waitForAjaxLoad stepKey="waitForAjax"/> + <waitForElementVisible selector="{{AdminCustomerShoppingCartProductItemSection.firstProductCheckbox}}" stepKey="waitForElementCheckboxVisible"/> + <click selector="{{AdminCustomerShoppingCartProductItemSection.firstProductCheckbox}}" stepKey="selectFirstCheckbox"/> + <click selector="{{AdminCustomerShoppingCartProductItemSection.addSelectionsToMyCartButton}}" stepKey="clickAddSelectionsToMyCartButton" after="selectFirstCheckbox"/> + <waitForAjaxLoad stepKey="waitForAjax2"/> + <seeElement stepKey="seeAddedProduct" selector="{{AdminCustomerShoppingCartProductItemSection.addedProductName('productName')}}"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminUpdateCustomerGroupActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminUpdateCustomerGroupActionGroup.xml new file mode 100644 index 00000000000..b1b82fb9fb7 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminUpdateCustomerGroupActionGroup.xml @@ -0,0 +1,38 @@ +<?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="AdminUpdateCustomerGroupByEmailActionGroup"> + <arguments> + <argument name="emailAddress"/> + <argument name="customerGroup" type="string"/> + </arguments> + + <amOnPage url="{{AdminCustomerPage.url}}" stepKey="goToCustomerPage01"/> + + <!-- Start of Action Group: searchAdminDataGridByKeyword --> + <conditionalClick selector="{{AdminDataGridHeaderSection.clearFilters}}" dependentSelector="{{AdminDataGridHeaderSection.clearFilters}}" visible="true" stepKey="clickClearFilters0"/> + <fillField selector="{{AdminDataGridHeaderSection.search}}" userInput="{{emailAddress}}" stepKey="fillKeywordSearchField01"/> + <click selector="{{AdminDataGridHeaderSection.submitSearch}}" stepKey="clickKeywordSearch01"/> + <waitForPageLoad stepKey="waitForPageLoad02"/> + <!-- End of Action Group: searchAdminDataGridByKeyword --> + + <click selector="{{AdminGridRow.editByValue(emailAddress)}}" stepKey="clickOnCustomer01"/> + <waitForPageLoad stepKey="waitForPageLoad03"/> + + <conditionalClick selector="{{AdminCustomerAccountInformationSection.accountInformationTab}}" dependentSelector="{{AdminCustomerAccountInformationSection.accountInformationTab}}" visible="true" stepKey="clickOnAccountInformation01"/> + <waitForPageLoad stepKey="waitForPageLoad04"/> + + <click selector="{{AdminCustomerAccountInformationSection.group}}" stepKey="clickOnCustomerGroup01"/> + <selectOption selector="{{AdminCustomerAccountInformationSection.group}}" userInput="{{customerGroup}}" stepKey="selectCustomerGroup01"/> + + <click selector="{{AdminMainActionsSection.save}}" stepKey="clickOnSave01"/> + <waitForPageLoad stepKey="waitForPageLoad05"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/CreateCustomerActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/CreateCustomerActionGroup.xml similarity index 93% rename from app/code/Magento/Braintree/Test/Mftf/ActionGroup/CreateCustomerActionGroup.xml rename to app/code/Magento/Customer/Test/Mftf/ActionGroup/CreateCustomerActionGroup.xml index a68042127ec..047f656f5ea 100644 --- a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/CreateCustomerActionGroup.xml +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/CreateCustomerActionGroup.xml @@ -5,8 +5,9 @@ * See COPYING.txt for license details. */ --> + <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="CreateCustomerActionGroup"> <click stepKey="openCustomers" selector="{{AdminMenuSection.customers}}"/> <waitForAjaxLoad stepKey="waitForCatalogSubmenu" time="5"/> @@ -39,6 +40,5 @@ <click stepKey="save" selector="{{NewCustomerPageSection.saveCustomer}}"/> <waitForPageLoad stepKey="waitForCustomersPage" time="10"/> <waitForElementVisible selector="{{NewCustomerPageSection.createdSuccessMessage}}" stepKey="waitForSuccessfullyCreatedMessage" time="20"/> - </actionGroup> -</actionGroups> \ No newline at end of file +</actionGroups> diff --git a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/DeleteCustomerActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/DeleteCustomerActionGroup.xml similarity index 100% rename from app/code/Magento/Braintree/Test/Mftf/ActionGroup/DeleteCustomerActionGroup.xml rename to app/code/Magento/Customer/Test/Mftf/ActionGroup/DeleteCustomerActionGroup.xml diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/EditCustomerAddressesFromAdminActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/EditCustomerAddressesFromAdminActionGroup.xml new file mode 100644 index 00000000000..617c895bc12 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/EditCustomerAddressesFromAdminActionGroup.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="EditCustomerAddressesFromAdminActionGroup" > + <arguments> + <argument name="customerAddress"/> + </arguments> + <click selector="{{AdminEditCustomerAddressesSection.addresses}}" stepKey="proceedToAddresses"/> + <click selector="{{AdminEditCustomerAddressesSection.addNewAddress}}" stepKey="addNewAddresses"/> + <waitForPageLoad time="60" stepKey="wait5678" /> + <fillField stepKey="fillPrefixName" userInput="{{customerAddress.prefix}}" selector="{{AdminEditCustomerAddressesSection.prefixName}}"/> + <fillField stepKey="fillMiddleName" userInput="{{customerAddress.middlename}}" selector="{{AdminEditCustomerAddressesSection.middleName}}"/> + <fillField stepKey="fillSuffixName" userInput="{{customerAddress.suffix}}" selector="{{AdminEditCustomerAddressesSection.suffixName}}"/> + <fillField stepKey="fillCompany" userInput="{{customerAddress.company}}" selector="{{AdminEditCustomerAddressesSection.company}}"/> + <fillField stepKey="fillStreetAddress" userInput="{{customerAddress.street}}" selector="{{AdminEditCustomerAddressesSection.streetAddress}}"/> + <fillField stepKey="fillCity" userInput="{{customerAddress.city}}" selector="{{AdminEditCustomerAddressesSection.city}}"/> + <selectOption stepKey="selectCountry" selector="{{AdminEditCustomerAddressesSection.country}}" userInput="{{US_Address_CA.country_id}}"/> + <selectOption stepKey="selectState" selector="{{AdminEditCustomerAddressesSection.state}}" userInput="{{US_Address_CA.state}}"/> + <fillField stepKey="fillZipCode" userInput="{{customerAddress.postcode}}" selector="{{AdminEditCustomerAddressesSection.zipCode}}"/> + <fillField stepKey="fillPhone" userInput="{{customerAddress.telephone}}" selector="{{AdminEditCustomerAddressesSection.phone}}"/> + <fillField stepKey="fillVAT" userInput="{{customerAddress.vat_id}}" selector="{{AdminEditCustomerAddressesSection.vat}}"/> + <click selector="{{AdminEditCustomerAddressesSection.save}}" stepKey="saveAddress"/> + <waitForPageLoad stepKey="waitForAddressSaved"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/OpenEditCustomerFromAdminActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/OpenEditCustomerFromAdminActionGroup.xml index 914cebe99ac..af918e82085 100755 --- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/OpenEditCustomerFromAdminActionGroup.xml +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/OpenEditCustomerFromAdminActionGroup.xml @@ -39,10 +39,9 @@ </actionGroup> <actionGroup name="DeleteCustomerFromAdminActionGroup"> <arguments> - <argument name="customer"/> + <argument name="customer" defaultValue="CustomerEntityOne"/> </arguments> <amOnPage url="{{AdminCustomerPage.url}}" stepKey="navigateToCustomers"/> - <waitForPageLoad stepKey="waitForPageLoad1" /> <conditionalClick selector="{{AdminDataGridHeaderSection.clearFilters}}" dependentSelector="{{AdminDataGridHeaderSection.clearFilters}}" visible="true" stepKey="clickOnButtonToRemoveFiltersIfPresent"/> <fillField selector="{{AdminDataGridHeaderSection.search}}" userInput="{{customer.email}}" stepKey="fillSearch"/> <click selector="{{AdminDataGridHeaderSection.submitSearch}}" stepKey="clickSubmit"/> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/SignUpNewUserFromStorefrontActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/SignUpNewUserFromStorefrontActionGroup.xml index 227085fce9d..76acf6e8659 100644 --- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/SignUpNewUserFromStorefrontActionGroup.xml +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/SignUpNewUserFromStorefrontActionGroup.xml @@ -9,10 +9,9 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="SignUpNewUserFromStorefrontActionGroup"> <arguments> - <argument name="Customer"/> + <argument name="Customer" defaultValue="CustomerEntityOne"/> </arguments> - <amOnPage stepKey="amOnStorefrontPage" url="/"/> - <waitForPageLoad stepKey="waitForStorefrontPage"/> + <amOnPage url="{{StorefrontHomePage.url}}" stepKey="amOnStorefrontPage"/> <click stepKey="clickOnCreateAccountLink" selector="{{StorefrontPanelHeaderSection.createAnAccountLink}}"/> <fillField stepKey="fillFirstName" userInput="{{Customer.firstname}}" selector="{{StorefrontCustomerCreateFormSection.firstnameField}}"/> <fillField stepKey="fillLastName" userInput="{{Customer.lastname}}" selector="{{StorefrontCustomerCreateFormSection.lastnameField}}"/> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontAddCustomerAddressActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontAddCustomerAddressActionGroup.xml new file mode 100644 index 00000000000..a45fcf31f7b --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontAddCustomerAddressActionGroup.xml @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontAddNewCustomerAddressActionGroup"> + <amOnPage url="customer/address/new/" stepKey="OpenCustomerAddNewAddress"/> + <arguments> + <argument name="Address"/> + </arguments> + <fillField stepKey="fillFirstName" userInput="{{Address.firstname}}" selector="{{StorefrontCustomerAddressFormSection.firstName}}"/> + <fillField stepKey="fillLastName" userInput="{{Address.lastname}}" selector="{{StorefrontCustomerAddressFormSection.lastName}}"/> + <fillField stepKey="fillCompanyName" userInput="{{Address.company}}" selector="{{StorefrontCustomerAddressFormSection.company}}"/> + <fillField stepKey="fillPhoneNumber" userInput="{{Address.telephone}}" selector="{{StorefrontCustomerAddressFormSection.phoneNumber}}"/> + <fillField stepKey="fillStreetAddress" userInput="{{Address.street[0]}}" selector="{{StorefrontCustomerAddressFormSection.streetAddress}}"/> + <fillField stepKey="fillCity" userInput="{{Address.city}}" selector="{{StorefrontCustomerAddressFormSection.city}}"/> + <selectOption stepKey="selectState" userInput="{{Address.state}}" selector="{{StorefrontCustomerAddressFormSection.state}}"/> + <fillField stepKey="fillZip" userInput="{{Address.postcode}}" selector="{{StorefrontCustomerAddressFormSection.zip}}"/> + <selectOption stepKey="selectCountry" userInput="{{Address.country}}" selector="{{StorefrontCustomerAddressFormSection.country}}"/> + <click stepKey="saveCustomerAddress" selector="{{StorefrontCustomerAddressFormSection.saveAddress}}"/> + <see userInput="You saved the address." stepKey="verifyAddressAdded"/> + </actionGroup> + <actionGroup name="StorefrontAddCustomerDefaultAddressActionGroup"> + <amOnPage url="customer/address/new/" stepKey="OpenCustomerAddNewAddress"/> + <arguments> + <argument name="Address"/> + </arguments> + <fillField stepKey="fillFirstName" userInput="{{Address.firstname}}" selector="{{StorefrontCustomerAddressFormSection.firstName}}"/> + <fillField stepKey="fillLastName" userInput="{{Address.lastname}}" selector="{{StorefrontCustomerAddressFormSection.lastName}}"/> + <fillField stepKey="fillCompanyName" userInput="{{Address.company}}" selector="{{StorefrontCustomerAddressFormSection.company}}"/> + <fillField stepKey="fillPhoneNumber" userInput="{{Address.telephone}}" selector="{{StorefrontCustomerAddressFormSection.phoneNumber}}"/> + <fillField stepKey="fillStreetAddress" userInput="{{Address.street[0]}}" selector="{{StorefrontCustomerAddressFormSection.streetAddress}}"/> + <fillField stepKey="fillCity" userInput="{{Address.city}}" selector="{{StorefrontCustomerAddressFormSection.city}}"/> + <selectOption stepKey="selectState" userInput="{{Address.state}}" selector="{{StorefrontCustomerAddressFormSection.state}}"/> + <fillField stepKey="fillZip" userInput="{{Address.postcode}}" selector="{{StorefrontCustomerAddressFormSection.zip}}"/> + <selectOption stepKey="selectCountry" userInput="{{Address.country}}" selector="{{StorefrontCustomerAddressFormSection.country}}"/> + <click stepKey="checkUseAsDefaultBillingAddressCheckBox" selector="{{StorefrontCustomerAddressFormSection.useAsDefaultBillingAddressCheckBox}}"/> + <click stepKey="checkUseAsDefaultShippingAddressCheckBox" selector="{{StorefrontCustomerAddressFormSection.useAsDefaultShippingAddressCheckBox}}"/> + <click stepKey="saveCustomerAddress" selector="{{StorefrontCustomerAddressFormSection.saveAddress}}"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <see userInput="You saved the address." stepKey="verifyAddressAdded"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerActionGroup.xml new file mode 100644 index 00000000000..fc5c1b88175 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="CustomerLogoutStorefrontByMenuItemsActionGroup"> + <conditionalClick selector="{{StorefrontPanelHeaderSection.customerWelcome}}" + dependentSelector="{{StorefrontPanelHeaderSection.customerWelcomeMenu}}" + visible="false" + stepKey="clickHeaderCustomerMenuButton" /> + <click selector="{{StorefrontPanelHeaderSection.customerLogoutLink}}" stepKey="clickSignOutButton" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/SwitchAccountActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/SwitchAccountActionGroup.xml similarity index 84% rename from app/code/Magento/Braintree/Test/Mftf/ActionGroup/SwitchAccountActionGroup.xml rename to app/code/Magento/Customer/Test/Mftf/ActionGroup/SwitchAccountActionGroup.xml index 7c774a634b3..4c59edbcb80 100644 --- a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/SwitchAccountActionGroup.xml +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/SwitchAccountActionGroup.xml @@ -5,9 +5,9 @@ * See COPYING.txt for license details. */ --> -<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <!--Sign out--> <actionGroup name="SignOut"> <click selector="{{SignOutSection.admin}}" stepKey="clickToAdminProfile"/> @@ -24,5 +24,4 @@ <fillField userInput="{{NewAdmin.password}}" selector="{{LoginFormSection.password}}" stepKey="fillPassword"/> <click selector="{{LoginFormSection.signIn}}" stepKey="clickLogin"/> </actionGroup> - -</actionGroups> \ No newline at end of file +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/Data/AddressData.xml b/app/code/Magento/Customer/Test/Mftf/Data/AddressData.xml index c7335f90242..da36cf72232 100755 --- a/app/code/Magento/Customer/Test/Mftf/Data/AddressData.xml +++ b/app/code/Magento/Customer/Test/Mftf/Data/AddressData.xml @@ -44,12 +44,29 @@ <data key="city">Austin</data> <data key="state">Texas</data> <data key="country_id">US</data> + <data key="country">United States</data> <data key="postcode">78729</data> <data key="telephone">512-345-6789</data> <data key="default_billing">Yes</data> <data key="default_shipping">Yes</data> <requiredEntity type="region">RegionTX</requiredEntity> </entity> + <entity name="US_Address_TX_Default_Billing" type="address"> + <data key="firstname">John</data> + <data key="lastname">Doe</data> + <data key="company">Magento</data> + <array key="street"> + <item>7700 West Parmer Lane</item> + </array> + <data key="city">Austin</data> + <data key="state">Texas</data> + <data key="country_id">US</data> + <data key="country">United States</data> + <data key="postcode">78729</data> + <data key="telephone">512-345-6789</data> + <data key="default_billing">Yes</data> + <requiredEntity type="region">RegionTX</requiredEntity> + </entity> <entity name="US_Address_NY" type="address"> <data key="firstname">John</data> <data key="lastname">Doe</data> @@ -68,6 +85,23 @@ <requiredEntity type="region">RegionNY</requiredEntity> <data key="country">United States</data> </entity> + <entity name="US_Address_NY_Default_Shipping" type="address"> + <data key="firstname">John</data> + <data key="lastname">Doe</data> + <data key="company">368</data> + <array key="street"> + <item>368 Broadway St.</item> + <item>113</item> + </array> + <data key="city">New York</data> + <data key="state">New York</data> + <data key="country_id">US</data> + <data key="postcode">10001</data> + <data key="telephone">512-345-6789</data> + <data key="default_shipping">Yes</data> + <requiredEntity type="region">RegionNY</requiredEntity> + <data key="country">United States</data> + </entity> <entity name="US_Address_NY_Not_Default_Address" type="address"> <data key="firstname">John</data> <data key="lastname">Doe</data> @@ -95,6 +129,7 @@ <data key="city">Los Angeles</data> <data key="state">California</data> <data key="country_id">US</data> + <data key="country">United States</data> <data key="postcode">90001</data> <data key="telephone">512-345-6789</data> <data key="default_billing">Yes</data> @@ -148,4 +183,12 @@ </array> <data key="state">California</data> </entity> + <entity name="US_Default_Billing_Address_TX" type="address" extends="US_Address_TX"> + <data key="default_billing">false</data> + <data key="default_shipping">true</data> + </entity> + <entity name="US_Default_Shipping_Address_CA" type="address" extends="US_Address_CA"> + <data key="default_billing">true</data> + <data key="default_shipping">false</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 44ac0981d95..b3c0d8d9e00 100644 --- a/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml +++ b/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml @@ -45,6 +45,16 @@ <data key="website_id">0</data> <requiredEntity type="address">US_Address_TX</requiredEntity> </entity> + <entity name="Simple_Customer_Without_Address" type="customer"> + <data key="group_id">1</data> + <data key="email" unique="prefix">John.Doe@example.com</data> + <data key="firstname">John</data> + <data key="lastname">Doe</data> + <data key="fullname">John Doe</data> + <data key="password">pwdTest123!</data> + <data key="store_id">0</data> + <data key="website_id">0</data> + </entity> <entity name="Simple_US_Customer_Multiple_Addresses" type="customer"> <data key="group_id">0</data> <data key="default_billing">true</data> @@ -73,6 +83,20 @@ <requiredEntity type="address">US_Address_NY_Not_Default_Address</requiredEntity> <requiredEntity type="address">UK_Not_Default_Address</requiredEntity> </entity> + <entity name="Simple_US_Customer_With_Different_Billing_Shipping_Addresses" type="customer"> + <data key="group_id">0</data> + <data key="default_billing">true</data> + <data key="default_shipping">true</data> + <data key="email" unique="prefix">John.Doe@example.com</data> + <data key="firstname">John</data> + <data key="lastname">Doe</data> + <data key="fullname">John Doe</data> + <data key="password">pwdTest123!</data> + <data key="store_id">0</data> + <data key="website_id">0</data> + <requiredEntity type="address">US_Address_TX_Default_Billing</requiredEntity> + <requiredEntity type="address">US_Address_NY_Default_Shipping</requiredEntity> + </entity> <entity name="Simple_US_Customer_NY" type="customer"> <data key="group_id">0</data> <data key="default_billing">true</data> @@ -142,4 +166,16 @@ <data key="website_id">0</data> <requiredEntity type="address">UK_Not_Default_Address</requiredEntity> </entity> + <entity name="Customer_With_Different_Default_Billing_Shipping_Addresses" type="customer"> + <data key="group_id">1</data> + <data key="email" unique="prefix">John.Doe@example.com</data> + <data key="firstname">John</data> + <data key="lastname">Doe</data> + <data key="fullname">John Doe</data> + <data key="password">pwdTest123!</data> + <data key="store_id">0</data> + <data key="website_id">0</data> + <requiredEntity type="address">US_Default_Billing_Address_TX</requiredEntity> + <requiredEntity type="address">US_Default_Shipping_Address_CA</requiredEntity> + </entity> </entities> diff --git a/app/code/Magento/Braintree/Test/Mftf/Data/NewCustomerData.xml b/app/code/Magento/Customer/Test/Mftf/Data/NewCustomerData.xml similarity index 77% rename from app/code/Magento/Braintree/Test/Mftf/Data/NewCustomerData.xml rename to app/code/Magento/Customer/Test/Mftf/Data/NewCustomerData.xml index 30345ec31ba..cdd117c2a0b 100644 --- a/app/code/Magento/Braintree/Test/Mftf/Data/NewCustomerData.xml +++ b/app/code/Magento/Customer/Test/Mftf/Data/NewCustomerData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="NewCustomerData" type="braintree_config_state"> <data key="FirstName">Abgar</data> <data key="LastName">Abgaryan</data> @@ -20,5 +20,4 @@ <data key="PhoneNumber">9999</data> <data key="Country">Armenia</data> </entity> - </entities> diff --git a/app/code/Magento/Customer/Test/Mftf/Page/AdminEditCustomerPage.xml b/app/code/Magento/Customer/Test/Mftf/Page/AdminEditCustomerPage.xml index d662c5cef60..9bd382da8eb 100644 --- a/app/code/Magento/Customer/Test/Mftf/Page/AdminEditCustomerPage.xml +++ b/app/code/Magento/Customer/Test/Mftf/Page/AdminEditCustomerPage.xml @@ -13,5 +13,6 @@ <section name="AdminCustomerAddressesGridActionsSection"/> <section name="AdminCustomerAddressesSection"/> <section name="AdminCustomerMainActionsSection"/> + <section name="AdminEditCustomerAddressesSection" /> </page> </pages> diff --git a/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerCreatePage.xml b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerCreatePage.xml index e2ebf638934..0d273da3530 100644 --- a/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerCreatePage.xml +++ b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerCreatePage.xml @@ -9,6 +9,7 @@ <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="StorefrontCustomerCreatePage" url="/customer/account/create/" area="storefront" module="Magento_Customer"> - <section name="StorefrontCustomerCreateFormSection" /> + <section name="StorefrontCustomerCreateFormSection"/> + <section name="StoreFrontCustomerAdvancedAttributesSection"/> </page> </pages> diff --git a/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerLogoutSuccessPage.xml b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerLogoutSuccessPage.xml new file mode 100644 index 00000000000..9c1fc7aa8a8 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerLogoutSuccessPage.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="StorefrontCustomerLogoutSuccessPage" url="customer/account/logoutSuccess/" area="storefront" module="Magento_Customer"/> +</pages> diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/AdminCreateUserSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCreateUserSection.xml similarity index 81% rename from app/code/Magento/Braintree/Test/Mftf/Section/AdminCreateUserSection.xml rename to app/code/Magento/Customer/Test/Mftf/Section/AdminCreateUserSection.xml index 98d748b5a30..376b0b9f66d 100644 --- a/app/code/Magento/Braintree/Test/Mftf/Section/AdminCreateUserSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCreateUserSection.xml @@ -5,7 +5,9 @@ * See COPYING.txt for license details. */ --> -<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminCreateUserSection"> <element name="system" type="input" selector="#menu-magento-backend-system"/> <element name="allUsers" type="input" selector="//span[contains(text(), 'All Users')]"/> @@ -20,4 +22,4 @@ <element name="userRoleTab" type="button" selector="#page_tabs_roles_section"/> <element name="saveButton" type="button" selector="#save"/> </section> -</sections> \ No newline at end of file +</sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAccountInformationSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAccountInformationSection.xml index d5a410164a6..6a3687bb77c 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAccountInformationSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAccountInformationSection.xml @@ -9,6 +9,7 @@ <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminCustomerAccountInformationSection"> + <element name="accountInformationTab" type="button" selector="#tab_customer"/> <element name="statusInactive" type="button" selector=".admin__actions-switch-label"/> <element name="accountInformationTitle" type="text" selector=".admin__page-nav-title"/> <element name="accountInformationButton" type="text" selector="//a/span[text()='Account Information']"/> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerMainActionsSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerMainActionsSection.xml index 3ff880c64e6..0a56763b667 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerMainActionsSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerMainActionsSection.xml @@ -11,5 +11,6 @@ <section name="AdminCustomerMainActionsSection"> <element name="saveButton" type="button" selector="#save" timeout="30"/> <element name="resetPassword" type="button" selector="#resetPassword" timeout="30"/> + <element name="manageShoppingCart" type="button" selector="#manage_quote" timeout="30"/> </section> </sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerShoppingCartSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerShoppingCartSection.xml new file mode 100644 index 00000000000..c4a4d650c1e --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerShoppingCartSection.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminCustomerShoppingCartSection"> + <element name="createOrderButton" type="button" selector="button[title='Create Order']"/> + </section> + + <section name="AdminCustomerShoppingCartProductItemSection"> + <element name="productItem" type="button" selector="#dt-products"/> + <element name="productNameFilter" type="input" selector="#source_products_filter_name"/> + <element name="searchButton" type="button" selector="//*[@id='anchor-content']//button[@title='Search']"/> + <element name="firstProductCheckbox" type="checkbox" selector="//*[@id='source_products_table']/tbody/tr[1]//*[@name='source_products']"/> + <element name="addSelectionsToMyCartButton" type="button" selector="//*[@id='products_search']/div[1]//*[text()='Add selections to my cart']"/> + <element name="addedProductName" type="text" selector="//*[@id='order-items_grid']//*[text()='{{var}}']" parameterized="true"/> + </section> +</sections> diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/AdminDeleteUserSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminDeleteUserSection.xml similarity index 68% rename from app/code/Magento/Braintree/Test/Mftf/Section/AdminDeleteUserSection.xml rename to app/code/Magento/Customer/Test/Mftf/Section/AdminDeleteUserSection.xml index bf2e2b44eb6..0ba197999be 100644 --- a/app/code/Magento/Braintree/Test/Mftf/Section/AdminDeleteUserSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminDeleteUserSection.xml @@ -5,11 +5,13 @@ * See COPYING.txt for license details. */ --> -<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminDeleteUserSection"> <element name="theUser" selector="//td[contains(text(), 'John')]" type="button"/> <element name="password" selector="#user_current_password" type="input"/> <element name="delete" selector="//button/span[contains(text(), 'Delete User')]" type="button"/> <element name="confirm" selector="//*[@class='action-primary action-accept']" type="button"/> </section> -</sections> \ No newline at end of file +</sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminEditCustomerAddressesSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminEditCustomerAddressesSection.xml new file mode 100644 index 00000000000..04d6c4dc2a0 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminEditCustomerAddressesSection.xml @@ -0,0 +1,32 @@ +<?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="AdminEditCustomerAddressesSection"> + <element name="addresses" type="button" selector="//span[text()='Addresses']" timeout="30"/> + <element name="addNewAddress" type="button" selector="//span[text()='Add New Address']"/> + <element name="defaultBillingAddress" type="text" selector="input[name='default_billing']"/> + <element name="defaultShippingAddress" type="text" selector="input[name='default_shipping']"/> + <element name="prefixName" type="text" selector="input[name='prefix']"/> + <element name="firstName" type="text" selector="input[name='firstname']" /> + <element name="middleName" type="text" selector="input[name='middlename']" /> + <element name="lastName" type="text" selector="input[name='lastname']" /> + <element name="suffixName" type="text" selector="input[name='suffix']" /> + <element name="company" type="text" selector="input[name='company']" /> + <element name="streetAddress" type="text" selector="input[name='street[0]']" /> + <element name="city" type="text" selector="//*[@class='modal-component']//input[@name='city']" /> + <element name="country" type="select" selector="//*[@class='modal-component']//select[@name='country_id']" /> + <element name="state" type="select" selector="//*[@class='modal-component']//select[@name='region_id']" /> + <element name="zipCode" type="text" selector="//*[@class='modal-component']//input[@name='postcode']" /> + <element name="phone" type="text" selector="//*[@class='modal-component']//input[@name='telephone']" /> + <element name="vat" type="text" selector="input[name='vat_id']" /> + <element name="save" type="button" selector="//button[@title='Save']" /> + </section> + +</sections> diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/AdminUserGridSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminUserGridSection.xml similarity index 74% rename from app/code/Magento/Braintree/Test/Mftf/Section/AdminUserGridSection.xml rename to app/code/Magento/Customer/Test/Mftf/Section/AdminUserGridSection.xml index 9564bc61f79..7c4a76871d5 100644 --- a/app/code/Magento/Braintree/Test/Mftf/Section/AdminUserGridSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminUserGridSection.xml @@ -5,7 +5,9 @@ * See COPYING.txt for license details. */ --> -<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminUserGridSection"> <element name="usernameFilterTextField" type="input" selector="#permissionsUserGrid_filter_username"/> <element name="searchButton" type="button" selector=".admin__data-grid-header button[title=Search]"/> @@ -14,4 +16,4 @@ <element name="searchResultFirstRow" type="text" selector=".data-grid>tbody>tr"/> <element name="successMessage" type="text" selector=".message-success"/> </section> -</sections> \ No newline at end of file +</sections> diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/CustomersPageSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/CustomersPageSection.xml similarity index 84% rename from app/code/Magento/Braintree/Test/Mftf/Section/CustomersPageSection.xml rename to app/code/Magento/Customer/Test/Mftf/Section/CustomersPageSection.xml index e4a75b1b6a8..60c63538719 100644 --- a/app/code/Magento/Braintree/Test/Mftf/Section/CustomersPageSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/CustomersPageSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="CustomersPageSection"> <element name="addNewCustomerButton" type="button" selector="//*[@id='add']"/> <element name="customerCheckbox" type="button" selector="//*[contains(text(),'{{args}}')]/parent::td/preceding-sibling::td/label[@class='data-grid-checkbox-cell-inner']" parameterized="true"/> diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/CustomersSubmenuSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/CustomersSubmenuSection.xml similarity index 68% rename from app/code/Magento/Braintree/Test/Mftf/Section/CustomersSubmenuSection.xml rename to app/code/Magento/Customer/Test/Mftf/Section/CustomersSubmenuSection.xml index 937afb83da9..6eeef1ba9da 100644 --- a/app/code/Magento/Braintree/Test/Mftf/Section/CustomersSubmenuSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/CustomersSubmenuSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="CustomersSubmenuSection"> <element name="allCustomers" type="button" selector="//li[@id='menu-magento-customer-customer']//li[@data-ui-id='menu-magento-customer-customer-manage']"/> </section> diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/NewCustomerPageSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/NewCustomerPageSection.xml similarity index 92% rename from app/code/Magento/Braintree/Test/Mftf/Section/NewCustomerPageSection.xml rename to app/code/Magento/Customer/Test/Mftf/Section/NewCustomerPageSection.xml index d302f9c7d0c..abb8aa6c1d8 100644 --- a/app/code/Magento/Braintree/Test/Mftf/Section/NewCustomerPageSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/NewCustomerPageSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="NewCustomerPageSection"> <element name="associateToWebsite" type="select" selector="//*[@class='admin__field-control _with-tooltip']//*[@class='admin__control-select']"/> <element name="group" type="select" selector="//div[@class='admin__field-control admin__control-fields required']//div[@class='admin__field-control']//select[@class='admin__control-select']"/> @@ -28,6 +28,5 @@ <element name="phoneNumber" type="input" selector="//input[contains(@name, 'telephone')]"/> <element name="saveCustomer" type="button" selector="//button[@title='Save Customer']"/> <element name="createdSuccessMessage" type="button" selector="//div[@data-ui-id='messages-message-success']"/> - </section> </sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAccountInformationSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAccountInformationSection.xml new file mode 100644 index 00000000000..59da4e9279a --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAccountInformationSection.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="StorefrontCustomerAccountInformationSection"> + <element name="firstName" type="input" selector="#firstname"/> + <element name="lastName" type="input" selector="#lastname"/> + <element name="changeEmail" type="checkbox" selector="#change_email"/> + <element name="changePassword" type="checkbox" selector="#change_password"/> + <element name="testAddedAttributeFiled" type="input" selector="//input[contains(@id,'{{var}}')]" parameterized="true"/> + <element name="saveButton" type="button" selector="#form-validate .action.save.primary"/> + </section> +</sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAddressFormSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAddressFormSection.xml new file mode 100644 index 00000000000..112ced1bc37 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAddressFormSection.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="StorefrontCustomerAddressFormSection"> + <element name="firstName" type="input" selector="//form[@class='form-address-edit']//input[contains(@name, 'firstname')]"/> + <element name="lastName" type="input" selector="//form[@class='form-address-edit']//input[contains(@name, 'lastname')]"/> + <element name="company" type="input" selector="//form[@class='form-address-edit']//input[contains(@name, 'company')]"/> + <element name="phoneNumber" type="input" selector="//form[@class='form-address-edit']//input[contains(@name, 'telephone')]"/> + <element name="streetAddress" type="input" selector="//form[@class='form-address-edit']//input[contains(@name, 'street')]"/> + <element name="city" type="input" selector="//form[@class='form-address-edit']//input[contains(@name, 'city')]"/> + <element name="state" type="select" selector="//form[@class='form-address-edit']//select[contains(@name, 'region_id')]"/> + <element name="zip" type="input" selector="//form[@class='form-address-edit']//input[contains(@name, 'postcode')]"/> + <element name="country" type="select" selector="//form[@class='form-address-edit']//select[contains(@name, 'country_id')]"/> + <element name="useAsDefaultBillingAddressCheckBox" type="input" selector="//form[@class='form-address-edit']//input[@name='default_billing']"/> + <element name="useAsDefaultShippingAddressCheckBox" type="input" selector="//form[@class='form-address-edit']//input[@name='default_shipping']"/> + <element name="saveAddress" type="button" selector="//button[@title='Save Address']" timeout="30"/> + </section> +</sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAddressesSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAddressesSection.xml index 05bbc559def..29a2f549274 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAddressesSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAddressesSection.xml @@ -9,7 +9,13 @@ <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontCustomerAddressesSection"> - <element name="addressesList" type="text" selector=".block-addresses-list" /> - <element name="deleteAdditionalAddress" type="button" selector="//ol[@class='items addresses']/li[@class='item'][{{var}}]//a[@class='action delete']" parameterized="true"/> + <element name="defaultBillingAddress" type="text" selector=".box-address-billing" /> + <element name="editDefaultBillingAddress" type="text" selector="//div[@class='box-actions']//span[text()='Change Billing Address']" timeout="30"/> + <element name="defaultShippingAddress" type="text" selector=".box-address-shipping" /> + <element name="editDefaultShippingAddress" type="text" selector="//div[@class='box-actions']//span[text()='Change Shipping Address']" timeout="30"/> + <element name="addressesList" type="text" selector=".additional-addresses" /> + <element name="deleteAdditionalAddress" type="button" selector="//tbody//tr[{{var}}]//a[@class='action delete']" parameterized="true"/> + <element name="editAdditionalAddress" type="button" selector="//tbody//tr[{{var}}]//a[@class='action edit']" parameterized="true" timeout="30"/> + <element name="addNewAddress" type="button" selector="//span[text()='Add New Address']"/> </section> </sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerCreateFormSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerCreateFormSection.xml index 2b5662cdd62..ee14ee5c165 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerCreateFormSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerCreateFormSection.xml @@ -11,9 +11,23 @@ <section name="StorefrontCustomerCreateFormSection"> <element name="firstnameField" type="input" selector="#firstname"/> <element name="lastnameField" type="input" selector="#lastname"/> + <element name="lastnameLabel" type="text" selector="//label[@for='lastname']"/> <element name="emailField" type="input" selector="#email_address"/> <element name="passwordField" type="input" selector="#password"/> <element name="confirmPasswordField" type="input" selector="#password-confirmation"/> <element name="createAccountButton" type="button" selector="button.action.submit.primary" timeout="30"/> </section> + <section name="StoreFrontCustomerAdvancedAttributesSection"> + <element name="textFieldAttribute" type="input" selector="//input[@id='{{var}}']" parameterized="true" /> + <element name="textAreaAttribute" type="input" selector="//textarea[@id='{{var}}']" parameterized="true" /> + <element name="multiLineFirstAttribute" type="input" selector="//input[@id='{{var}}_0']" parameterized="true" /> + <element name="multiLineSecondAttribute" type="input" selector="//input[@id='{{var}}_1']" parameterized="true" /> + <element name="datedAttribute" type="input" selector="//input[@id='{{var}}']" parameterized="true" /> + <element name="dropDownAttribute" type="select" selector="//select[@id='{{var}}']" parameterized="true" /> + <element name="dropDownOptionAttribute" type="text" selector="//*[@id='{{var}}']/option[2]" parameterized="true" /> + <element name="multiSelectFirstOptionAttribute" type="text" selector="//select[@id='{{var}}']/option[3]" parameterized="true" /> + <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"/> + </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 74821930310..0e31f0e0c77 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerSidebarSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerSidebarSection.xml @@ -10,5 +10,6 @@ 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()='{{var1}}']" parameterized="true"/> + <element name="sidebarCurrentTab" type="text" selector="//div[@id='block-collapsible-nav']//strong[contains(text(), '{{var}}')]" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontPanelHeaderSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontPanelHeaderSection.xml index a0c83f5bc49..1955c6a417b 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontPanelHeaderSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontPanelHeaderSection.xml @@ -12,5 +12,8 @@ <element name="WelcomeMessage" type="text" selector=".greet.welcome span"/> <element name="createAnAccountLink" type="select" selector=".panel.header li:nth-child(3)" timeout="30"/> <element name="notYouLink" type="button" selector=".greet.welcome span a"/> + <element name="customerWelcome" type="text" selector=".panel.header .customer-welcome"/> + <element name="customerWelcomeMenu" type="text" selector=".panel.header .customer-welcome .customer-menu"/> + <element name="customerLogoutLink" type="text" selector=".panel.header .customer-welcome .customer-menu .authorization-link a" timeout="30"/> </section> </sections> diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/SwitchAccountSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/SwitchAccountSection.xml similarity index 78% rename from app/code/Magento/Braintree/Test/Mftf/Section/SwitchAccountSection.xml rename to app/code/Magento/Customer/Test/Mftf/Section/SwitchAccountSection.xml index 3a07cbc6dd1..4442e317694 100644 --- a/app/code/Magento/Braintree/Test/Mftf/Section/SwitchAccountSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/SwitchAccountSection.xml @@ -7,8 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> - + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="LoginFormSection"> <element name="username" type="input" selector="#username"/> <element name="password" type="input" selector="#login"/> @@ -19,6 +18,4 @@ <element name="admin" type="button" selector=".admin__action-dropdown-text"/> <element name="logout" type="button" selector="//*[contains(text(), 'Sign Out')]"/> </section> - </sections> - diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontAddCustomerAddressTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontAddCustomerAddressTest.xml new file mode 100644 index 00000000000..413bbfd06a5 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontAddCustomerAddressTest.xml @@ -0,0 +1,121 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontAddNewCustomerAddressTest"> + <annotations> + <features value="Customer address"/> + <stories value="Implement handling of large number of addresses on storefront Address book"/> + <title value="Storefront - My account - Address Book - add new address"/> + <description value="Storefront user should be able to create a new address via the storefront"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-97364"/> + <group value="customer"/> + <group value="create"/> + </annotations> + <before> + <createData entity="Simple_Customer_Without_Address" stepKey="createCustomer"/> + </before> + <after> + <deleteData createDataKey="createCustomer" stepKey="DeleteCustomer"/> + <amOnPage url="admin/admin/auth/logout/" stepKey="AmOnLogoutPage"/> + </after> + + <!--Log in to Storefront as Customer 1 --> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="signUp"> + <argument name="Customer" value="$$createCustomer$$"/> + </actionGroup> + <actionGroup ref="StorefrontAddNewCustomerAddressActionGroup" stepKey="AddNewAddress"> + <argument name="Address" value="US_Address_TX"/> + </actionGroup> + <see userInput="{{US_Address_TX.street[0]}}" + selector="{{StorefrontCustomerAddressesSection.defaultBillingAddress}}" stepKey="checkNewAddressesStreetOnDefaultBilling"/> + <see userInput="{{US_Address_TX.city}}" + selector="{{StorefrontCustomerAddressesSection.defaultBillingAddress}}" stepKey="checkNewAddressesCityOnDefaultBilling"/> + <see userInput="{{US_Address_TX.postcode}}" + selector="{{StorefrontCustomerAddressesSection.defaultBillingAddress}}" stepKey="checkNewAddressesPostcodeOnDefaultBilling"/> + <see userInput="{{US_Address_TX.street[0]}}" + selector="{{StorefrontCustomerAddressesSection.defaultShippingAddress}}" stepKey="checkNewAddressesStreetOnDefaultShipping"/> + <see userInput="{{US_Address_TX.city}}" + selector="{{StorefrontCustomerAddressesSection.defaultShippingAddress}}" stepKey="checkNewAddressesCityOnDefaultShipping"/> + <see userInput="{{US_Address_TX.postcode}}" + selector="{{StorefrontCustomerAddressesSection.defaultShippingAddress}}" stepKey="checkNewAddressesPostcodeOnDefaultShipping"/> + </test> + <test name="StorefrontAddCustomerDefaultAddressTest"> + <annotations> + <features value="Customer address"/> + <stories value="Implement handling of large number of addresses on storefront Address book"/> + <title value="Storefront - My account - Address Book - add new default billing/shipping address"/> + <description value="Storefront user should be able to create a new default address via the storefront"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-97364"/> + <group value="customer"/> + <group value="create"/> + </annotations> + <before> + <createData entity="Simple_US_Customer" stepKey="createCustomer"/> + </before> + <after> + <deleteData createDataKey="createCustomer" stepKey="DeleteCustomer"/> + </after> + + <!--Log in to Storefront as Customer 1 --> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="signUp"> + <argument name="Customer" value="$$createCustomer$$"/> + </actionGroup> + <actionGroup ref="StorefrontAddCustomerDefaultAddressActionGroup" stepKey="AddNewDefaultAddress"> + <argument name="Address" value="US_Address_TX"/> + </actionGroup> + <see userInput="{{US_Address_TX.street[0]}}" + selector="{{StorefrontCustomerAddressesSection.defaultBillingAddress}}" stepKey="checkNewAddressesStreetOnDefaultBilling"/> + <see userInput="{{US_Address_TX.city}}" + selector="{{StorefrontCustomerAddressesSection.defaultBillingAddress}}" stepKey="checkNewAddressesCityOnDefaultBilling"/> + <see userInput="{{US_Address_TX.postcode}}" + selector="{{StorefrontCustomerAddressesSection.defaultBillingAddress}}" stepKey="checkNewAddressesPostcodeOnDefaultBilling"/> + <see userInput="{{US_Address_TX.street[0]}}" + selector="{{StorefrontCustomerAddressesSection.defaultShippingAddress}}" stepKey="checkNewAddressesStreetOnDefaultShipping"/> + <see userInput="{{US_Address_TX.city}}" + selector="{{StorefrontCustomerAddressesSection.defaultShippingAddress}}" stepKey="checkNewAddressesCityOnDefaultShipping"/> + <see userInput="{{US_Address_TX.postcode}}" + selector="{{StorefrontCustomerAddressesSection.defaultShippingAddress}}" stepKey="checkNewAddressesPostcodeOnDefaultShipping"/> + </test> + <test name="StorefrontAddCustomerNonDefaultAddressTest"> + <annotations> + <features value="Customer address"/> + <stories value="Implement handling of large number of addresses on storefront Address book"/> + <title value="Storefront - My account - Address Book - add new non default billing/shipping address"/> + <description value="Storefront user should be able to create a new non default address via the storefront"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-97500"/> + <group value="customer"/> + <group value="create"/> + </annotations> + <before> + <createData entity="Simple_US_Customer_NY" stepKey="createCustomer"/> + </before> + <after> + <deleteData createDataKey="createCustomer" stepKey="DeleteCustomer"/> + <amOnPage url="admin/admin/auth/logout/" stepKey="AmOnLogoutPage"/> + </after> + + <!--Log in to Storefront as Customer 1 --> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="signUp"> + <argument name="Customer" value="$$createCustomer$$"/> + </actionGroup> + <actionGroup ref="StorefrontAddNewCustomerAddressActionGroup" stepKey="AddNewNonDefaultAddress"> + <argument name="Address" value="US_Address_TX"/> + </actionGroup> + <see userInput="{{US_Address_TX.street[0]}}" + selector="{{StorefrontCustomerAddressesSection.addressesList}}" stepKey="checkNewAddressesStreetOnDefaultShipping"/> + <see userInput="{{US_Address_TX.city}}" + selector="{{StorefrontCustomerAddressesSection.addressesList}}" stepKey="checkNewAddressesCityOnDefaultShipping"/> + <see userInput="{{US_Address_TX.postcode}}" + selector="{{StorefrontCustomerAddressesSection.addressesList}}" stepKey="checkNewAddressesPostcodeOnDefaultShipping"/> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontUpdateCustomerAddressTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontUpdateCustomerAddressTest.xml new file mode 100644 index 00000000000..d9d1c9f2e05 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontUpdateCustomerAddressTest.xml @@ -0,0 +1,122 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontUpdateCustomerDefaultBillingAddressFromBlockTest"> + <annotations> + <features value="Customer address"/> + <stories value="Implement handling of large number of addresses on storefront Address book"/> + <title value="Add default customer address via the Storefront6"/> + <description value="Storefront user should be able to create a new default address via the storefront"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-97501"/> + <group value="customer"/> + <group value="update"/> + </annotations> + <before> + <createData entity="Simple_US_Customer_With_Different_Billing_Shipping_Addresses" stepKey="createCustomer"/> + </before> + <after> + <deleteData createDataKey="createCustomer" stepKey="DeleteCustomer"/> + </after> + + <!--Log in to Storefront as Customer 1 --> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="signUp"> + <argument name="Customer" value="$$createCustomer$$"/> + </actionGroup> + <amOnPage url="customer/address/" stepKey="OpenCustomerAddNewAddress"/> + <click stepKey="ClickEditDefaultBillingAddress" selector="{{StorefrontCustomerAddressesSection.editDefaultBillingAddress}}"/> + <fillField stepKey="fillFirstName" userInput="EditedFirstNameBilling" selector="{{StorefrontCustomerAddressFormSection.firstName}}"/> + <fillField stepKey="fillLastName" userInput="EditedLastNameBilling" selector="{{StorefrontCustomerAddressFormSection.lastName}}"/> + <click stepKey="saveCustomerAddress" selector="{{StorefrontCustomerAddressFormSection.saveAddress}}"/> + <see userInput="You saved the address." stepKey="verifyAddressAdded"/> + <see userInput="EditedFirstNameBilling" + selector="{{StorefrontCustomerAddressesSection.defaultBillingAddress}}" stepKey="checkNewAddressesFirstNameOnDefaultBilling"/> + <see userInput="EditedLastNameBilling" + selector="{{StorefrontCustomerAddressesSection.defaultBillingAddress}}" stepKey="checkNewAddressesLastNameOnDefaultBilling"/> + <see userInput="{{US_Address_NY_Default_Shipping.firstname}}" + selector="{{StorefrontCustomerAddressesSection.defaultShippingAddress}}" stepKey="checkNewAddressesFirstNameOnDefaultShipping"/> + <see userInput="{{US_Address_NY_Default_Shipping.lastname}}" + selector="{{StorefrontCustomerAddressesSection.defaultShippingAddress}}" stepKey="checkNewAddressesLastNameOnDefaultShipping"/> + </test> + <test name="StorefrontUpdateCustomerDefaultShippingAddressFromBlockTest"> + <annotations> + <features value="Customer address"/> + <stories value="Implement handling of large number of addresses on storefront Address book"/> + <title value="Add default customer address via the Storefront611"/> + <description value="Storefront user should be able to create a new default address via the storefront"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-97501"/> + <group value="customer"/> + <group value="update"/> + <skip> + <issueId value="MAGETWO-97504"/> + </skip> + </annotations> + <before> + <createData entity="Simple_US_Customer_With_Different_Billing_Shipping_Addresses" stepKey="createCustomer"/> + </before> + <after> + <deleteData createDataKey="createCustomer" stepKey="DeleteCustomer"/> + </after> + + <!--Log in to Storefront as Customer 1 --> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="signUp"> + <argument name="Customer" value="$$createCustomer$$"/> + </actionGroup> + <amOnPage url="customer/address/" stepKey="OpenCustomerAddNewAddress"/> + <click stepKey="ClickEditDefaultShippingAddress" selector="{{StorefrontCustomerAddressesSection.editDefaultShippingAddress}}"/> + <fillField stepKey="fillFirstName" userInput="EditedFirstNameShipping" selector="{{StorefrontCustomerAddressFormSection.firstName}}"/> + <fillField stepKey="fillLastName" userInput="EditedLastNameShipping" selector="{{StorefrontCustomerAddressFormSection.lastName}}"/> + <click stepKey="saveCustomerAddress" selector="{{StorefrontCustomerAddressFormSection.saveAddress}}"/> + <see userInput="You saved the address." stepKey="verifyAddressAdded"/> + <see userInput="EditedFirstNameShipping" + selector="{{StorefrontCustomerAddressesSection.defaultShippingAddress}}" stepKey="checkNewAddressesFirstNameOnDefaultShipping"/> + <see userInput="EditedLastNameShipping" + selector="{{StorefrontCustomerAddressesSection.defaultBillingAddress}}" stepKey="checkNewAddressesLastNameOnDefaultShipping"/> + <see userInput="{{US_Address_TX_Default_Billing.firstname}}" + selector="{{StorefrontCustomerAddressesSection.defaultBillingAddress}}" stepKey="checkNewAddressesFirstNameOnDefaultBilling"/> + <see userInput="{{US_Address_TX_Default_Billing.lastname}}" + selector="{{StorefrontCustomerAddressesSection.defaultBillingAddress}}" stepKey="checkNewAddressesLastNameOnDefaultBilling"/> + </test> + <test name="StorefrontUpdateCustomerAddressFromGridTest"> + <annotations> + <features value="Customer address"/> + <stories value="Add default customer address via the Storefront7"/> + <title value="Add default customer address via the Storefront7"/> + <description value="Storefront user should be able to create a new default address via the storefront2"/> + <severity value="CRITICAL"/> + <testCaseId value="MAGETWO-97502"/> + <group value="customer"/> + <group value="update"/> + </annotations> + <before> + <createData entity="Simple_US_Customer_Multiple_Addresses" stepKey="createCustomer"/> + </before> + <after> + <deleteData createDataKey="createCustomer" stepKey="DeleteCustomer"/> + </after> + + <!--Log in to Storefront as Customer 1 --> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="signUp"> + <argument name="Customer" value="$$createCustomer$$"/> + </actionGroup> + + <amOnPage url="customer/address/" stepKey="OpenCustomerAddNewAddress"/> + <click selector="{{StorefrontCustomerAddressesSection.editAdditionalAddress('1')}}" stepKey="editAdditionalAddress"/> + <fillField stepKey="fillFirstName" userInput="EditedFirstName" selector="{{StorefrontCustomerAddressFormSection.firstName}}"/> + <fillField stepKey="fillLastName" userInput="EditedLastName" selector="{{StorefrontCustomerAddressFormSection.lastName}}"/> + <click stepKey="saveCustomerAddress" selector="{{StorefrontCustomerAddressFormSection.saveAddress}}"/> + <see userInput="You saved the address." stepKey="verifyAddressAdded"/> + <see userInput="EditedFirstName" + selector="{{StorefrontCustomerAddressesSection.addressesList}}" stepKey="checkNewAddressFirstNameOnGrid"/> + <see userInput="EditedLastName" + selector="{{StorefrontCustomerAddressesSection.addressesList}}" stepKey="checkNewAddressLastNameOnGrid"/> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Customer/Test/Unit/Block/Address/GridTest.php b/app/code/Magento/Customer/Test/Unit/Block/Address/GridTest.php new file mode 100644 index 00000000000..31bcc376123 --- /dev/null +++ b/app/code/Magento/Customer/Test/Unit/Block/Address/GridTest.php @@ -0,0 +1,196 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Customer\Test\Unit\Block\Address; + +use Magento\Customer\Model\ResourceModel\Address\CollectionFactory; + +/** + * Unit tests for \Magento\Customer\Block\Address\Grid class + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class GridTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager + */ + private $objectManager; + + /** + * @var \Magento\Customer\Helper\Session\CurrentCustomer|\PHPUnit_Framework_MockObject_MockObject + */ + private $addressCollectionFactory; + + /** + * @var \Magento\Customer\Model\ResourceModel\Address\CollectionFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $currentCustomer; + + /** + * @var \Magento\Directory\Model\CountryFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $countryFactory; + + /** + * @var \Magento\Framework\UrlInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $urlBuilder; + + /** + * @var \Magento\Customer\Block\Address\Grid + */ + private $gridBlock; + + protected function setUp() + { + $this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $this->currentCustomer = $this->getMockBuilder(\Magento\Customer\Helper\Session\CurrentCustomer::class) + ->disableOriginalConstructor() + ->setMethods(['getCustomer']) + ->getMock(); + + $this->addressCollectionFactory = $this->getMockBuilder(CollectionFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + + $this->countryFactory = $this->getMockBuilder(\Magento\Directory\Model\CountryFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + + $this->urlBuilder = $this->getMockForAbstractClass(\Magento\Framework\UrlInterface::class); + + $this->gridBlock = $this->objectManager->getObject( + \Magento\Customer\Block\Address\Grid::class, + [ + 'addressCollectionFactory' => $this->addressCollectionFactory, + 'currentCustomer' => $this->currentCustomer, + 'countryFactory' => $this->countryFactory, + '_urlBuilder' => $this->urlBuilder + ] + ); + } + + /** + * Test for \Magento\Customer\Block\Address\Book::getChildHtml method with 'pager' argument + */ + public function testGetChildHtml() + { + $customerId = 1; + + /** @var \Magento\Framework\View\Element\BlockInterface|\PHPUnit_Framework_MockObject_MockObject $block */ + $block = $this->getMockBuilder(\Magento\Framework\View\Element\BlockInterface::class) + ->setMethods(['setCollection']) + ->getMockForAbstractClass(); + /** @var $layout \Magento\Framework\View\LayoutInterface|\PHPUnit_Framework_MockObject_MockObject */ + $layout = $this->getMockForAbstractClass(\Magento\Framework\View\LayoutInterface::class); + /** @var \Magento\Customer\Api\Data\CustomerInterface|\PHPUnit_Framework_MockObject_MockObject $customer */ + $customer = $this->getMockForAbstractClass(\Magento\Customer\Api\Data\CustomerInterface::class); + /** @var \PHPUnit_Framework_MockObject_MockObject */ + $addressCollection = $this->getMockBuilder(\Magento\Customer\Model\ResourceModel\Address\Collection::class) + ->disableOriginalConstructor() + ->setMethods(['setOrder', 'setCustomerFilter', 'load']) + ->getMock(); + + $layout->expects($this->atLeastOnce())->method('getChildName')->with('NameInLayout', 'pager') + ->willReturn('ChildName'); + $layout->expects($this->atLeastOnce())->method('renderElement')->with('ChildName', true) + ->willReturn('OutputString'); + $layout->expects($this->atLeastOnce())->method('createBlock') + ->with(\Magento\Theme\Block\Html\Pager::class, 'customer.addresses.pager')->willReturn($block); + $customer->expects($this->atLeastOnce())->method('getId')->willReturn($customerId); + $this->currentCustomer->expects($this->atLeastOnce())->method('getCustomer')->willReturn($customer); + $addressCollection->expects($this->atLeastOnce())->method('setOrder')->with('entity_id', 'desc') + ->willReturnSelf(); + $addressCollection->expects($this->atLeastOnce())->method('setCustomerFilter')->with([$customerId]) + ->willReturnSelf(); + $this->addressCollectionFactory->expects($this->atLeastOnce())->method('create') + ->willReturn($addressCollection); + $block->expects($this->atLeastOnce())->method('setCollection')->with($addressCollection)->willReturnSelf(); + $this->gridBlock->setNameInLayout('NameInLayout'); + $this->gridBlock->setLayout($layout); + $this->assertEquals('OutputString', $this->gridBlock->getChildHtml('pager')); + } + + /** + * Test for \Magento\Customer\Block\Address\Grid::getAddressEditUrl method + */ + public function testGetAddAddressUrl() + { + $addressId = 1; + $expectedUrl = 'expected_url'; + $this->urlBuilder->expects($this->atLeastOnce())->method('getUrl') + ->with('customer/address/edit', ['_secure' => true, 'id' => $addressId]) + ->willReturn($expectedUrl); + $this->assertEquals($expectedUrl, $this->gridBlock->getAddressEditUrl($addressId)); + } + + public function testGetAdditionalAddresses() + { + $customerId = 1; + /** @var \Magento\Customer\Api\Data\CustomerInterface|\PHPUnit_Framework_MockObject_MockObject $customer */ + $customer = $this->getMockForAbstractClass(\Magento\Customer\Api\Data\CustomerInterface::class); + /** @var \PHPUnit_Framework_MockObject_MockObject */ + $addressCollection = $this->getMockBuilder(\Magento\Customer\Model\ResourceModel\Address\Collection::class) + ->disableOriginalConstructor() + ->setMethods(['setOrder', 'setCustomerFilter', 'load', 'getIterator']) + ->getMock(); + $addressDataModel = $this->getMockForAbstractClass(\Magento\Customer\Api\Data\AddressInterface::class); + $address = $this->getMockBuilder(\Magento\Customer\Model\Address::class) + ->disableOriginalConstructor() + ->setMethods(['getId', 'getDataModel']) + ->getMock(); + $collection = [$address, $address, $address]; + $address->expects($this->exactly(3))->method('getId') + ->willReturnOnConsecutiveCalls(1, 2, 3); + $address->expects($this->atLeastOnce())->method('getDataModel')->willReturn($addressDataModel); + $customer->expects($this->atLeastOnce())->method('getId')->willReturn($customerId); + $customer->expects($this->atLeastOnce())->method('getDefaultBilling')->willReturn('1'); + $customer->expects($this->atLeastOnce())->method('getDefaultShipping')->willReturn('2'); + + $this->currentCustomer->expects($this->atLeastOnce())->method('getCustomer')->willReturn($customer); + $addressCollection->expects($this->atLeastOnce())->method('setOrder')->with('entity_id', 'desc') + ->willReturnSelf(); + $addressCollection->expects($this->atLeastOnce())->method('setCustomerFilter')->with([$customerId]) + ->willReturnSelf(); + $addressCollection->expects($this->atLeastOnce())->method('getIterator') + ->willReturn(new \ArrayIterator($collection)); + $this->addressCollectionFactory->expects($this->atLeastOnce())->method('create') + ->willReturn($addressCollection); + + $this->assertEquals($addressDataModel, $this->gridBlock->getAdditionalAddresses()[0]); + } + + /** + * Test for \Magento\Customer\ViewModel\CustomerAddress::getStreetAddress method + */ + public function testGetStreetAddress() + { + $street = ['Line 1', 'Line 2']; + $expectedAddress = 'Line 1, Line 2'; + $address = $this->getMockForAbstractClass(\Magento\Customer\Api\Data\AddressInterface::class); + $address->expects($this->atLeastOnce())->method('getStreet')->willReturn($street); + $this->assertEquals($expectedAddress, $this->gridBlock->getStreetAddress($address)); + } + + /** + * Test for \Magento\Customer\ViewModel\CustomerAddress::getCountryByCode method + */ + public function testGetCountryByCode() + { + $countryId = 'US'; + $countryName = 'United States'; + $country = $this->getMockBuilder(\Magento\Directory\Model\Country::class) + ->disableOriginalConstructor() + ->setMethods(['loadByCode', 'getName']) + ->getMock(); + $this->countryFactory->expects($this->atLeastOnce())->method('create')->willReturn($country); + $country->expects($this->atLeastOnce())->method('loadByCode')->with($countryId)->willReturnSelf(); + $country->expects($this->atLeastOnce())->method('getName')->willReturn($countryName); + $this->assertEquals($countryName, $this->gridBlock->getCountryByCode($countryId)); + } +} diff --git a/app/code/Magento/Customer/Test/Unit/Block/Form/RegisterTest.php b/app/code/Magento/Customer/Test/Unit/Block/Form/RegisterTest.php index f1629d61fe9..d234ebfb334 100644 --- a/app/code/Magento/Customer/Test/Unit/Block/Form/RegisterTest.php +++ b/app/code/Magento/Customer/Test/Unit/Block/Form/RegisterTest.php @@ -7,6 +7,7 @@ use Magento\Customer\Block\Form\Register; use Magento\Customer\Model\AccountManagement; +use Magento\Newsletter\Observer\PredispatchNewsletterObserver; /** * Test class for \Magento\Customer\Block\Form\Register. @@ -274,12 +275,13 @@ public function testGetRegionNull() } /** - * @param $isNewsletterEnabled - * @param $expectedValue + * @param boolean $isNewsletterEnabled + * @param string $isNewsletterActive + * @param boolean $expectedValue * * @dataProvider isNewsletterEnabledProvider */ - public function testIsNewsletterEnabled($isNewsletterEnabled, $expectedValue) + public function testIsNewsletterEnabled($isNewsletterEnabled, $isNewsletterActive, $expectedValue) { $this->_moduleManager->expects( $this->once() @@ -290,6 +292,17 @@ public function testIsNewsletterEnabled($isNewsletterEnabled, $expectedValue) )->will( $this->returnValue($isNewsletterEnabled) ); + + $this->_scopeConfig->expects( + $this->any() + )->method( + 'getValue' + )->with( + PredispatchNewsletterObserver::XML_PATH_NEWSLETTER_ACTIVE + )->will( + $this->returnValue($isNewsletterActive) + ); + $this->assertEquals($expectedValue, $this->_block->isNewsletterEnabled()); } @@ -298,7 +311,7 @@ public function testIsNewsletterEnabled($isNewsletterEnabled, $expectedValue) */ public function isNewsletterEnabledProvider() { - return [[true, true], [false, false]]; + return [[true, true, true], [true, false, false], [false, true, false], [false, false, false]]; } /** diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Address/FormPostTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Address/FormPostTest.php index c2a795fc950..7ae55f44421 100644 --- a/app/code/Magento/Customer/Test/Unit/Controller/Address/FormPostTest.php +++ b/app/code/Magento/Customer/Test/Unit/Controller/Address/FormPostTest.php @@ -455,14 +455,20 @@ public function testExecute( $regionCode, $newRegionId, $newRegion, - $newRegionCode + $newRegionCode, + $existingDefaultBilling = false, + $existingDefaultShipping = false, + $setDefaultBilling = false, + $setDefaultShipping = false ): void { $existingAddressData = [ 'country_id' => $countryId, 'region_id' => $regionId, 'region' => $region, 'region_code' => $regionCode, - 'customer_id' => $customerId + 'customer_id' => $customerId, + 'default_billing' => $existingDefaultBilling, + 'default_shipping' => $existingDefaultShipping, ]; $newAddressData = [ 'country_id' => $countryId, @@ -486,8 +492,8 @@ public function testExecute( ->method('getParam') ->willReturnMap([ ['id', null, $addressId], - ['default_billing', false, $addressId], - ['default_shipping', false, $addressId], + ['default_billing', $existingDefaultBilling, $setDefaultBilling], + ['default_shipping', $existingDefaultShipping, $setDefaultShipping], ]); $this->addressRepository->expects($this->once()) @@ -565,11 +571,11 @@ public function testExecute( ->willReturnSelf(); $this->addressData->expects($this->once()) ->method('setIsDefaultBilling') - ->with() + ->with($setDefaultBilling) ->willReturnSelf(); $this->addressData->expects($this->once()) ->method('setIsDefaultShipping') - ->with() + ->with($setDefaultShipping) ->willReturnSelf(); $this->messageManager->expects($this->once()) @@ -628,11 +634,11 @@ public function dataProviderTestExecute(): array [1, 1, 1, 2, null, null, 12, null, null], [1, 1, 1, 2, 'Alaska', null, 12, null, 'CA'], - [1, 1, 1, 2, 'Alaska', 'AK', 12, 'California', null], + [1, 1, 1, 2, 'Alaska', 'AK', 12, 'California', null, true, true, true, false], - [1, 1, 1, 2, null, null, 12, null, null], - [1, 1, 1, 2, 'Alaska', null, 12, null, 'CA'], - [1, 1, 1, 2, 'Alaska', 'AK', 12, 'California', null], + [1, 1, 1, 2, null, null, 12, null, null, false, false, true, false], + [1, 1, 1, 2, 'Alaska', null, 12, null, 'CA', true, false, true, false], + [1, 1, 1, 2, 'Alaska', 'AK', 12, 'California', null, true, true, true, true], ]; } 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 78d9dd70035..45e64f6557d 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 @@ -5,10 +5,14 @@ */ namespace Magento\Customer\Test\Unit\Controller\Adminhtml\Index; +use Magento\Customer\Model\AddressRegistry; use Magento\Customer\Model\EmailNotificationInterface; +use Magento\Framework\DataObject; use Magento\Framework\Message\MessageInterface; /** + * Unit tests for Inline customer edit + * * @SuppressWarnings(PHPMD.TooManyFields) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ @@ -68,14 +72,27 @@ class InlineEditTest extends \PHPUnit\Framework\TestCase /** @var EmailNotificationInterface|\PHPUnit_Framework_MockObject_MockObject */ private $emailNotification; + /** @var AddressRegistry|\PHPUnit_Framework_MockObject_MockObject */ + private $addressRegistry; + /** @var array */ private $items; + /** + * Sets up mocks + * + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ protected function setUp() { $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - $this->request = $this->getMockForAbstractClass(\Magento\Framework\App\RequestInterface::class, [], '', false); + $this->request = $this->getMockForAbstractClass( + \Magento\Framework\App\RequestInterface::class, + [], + '', + false + ); $this->messageManager = $this->getMockForAbstractClass( \Magento\Framework\Message\ManagerInterface::class, [], @@ -125,8 +142,12 @@ protected function setUp() '', false ); - $this->logger = $this->getMockForAbstractClass(\Psr\Log\LoggerInterface::class, [], '', false); - + $this->logger = $this->getMockForAbstractClass( + \Psr\Log\LoggerInterface::class, + [], + '', + false + ); $this->emailNotification = $this->getMockBuilder(EmailNotificationInterface::class) ->disableOriginalConstructor() ->getMock(); @@ -138,6 +159,7 @@ protected function setUp() 'messageManager' => $this->messageManager, ] ); + $this->addressRegistry = $this->createMock(\Magento\Customer\Model\AddressRegistry::class); $this->controller = $objectManager->getObject( \Magento\Customer\Controller\Adminhtml\Index\InlineEdit::class, [ @@ -150,6 +172,7 @@ protected function setUp() 'addressDataFactory' => $this->addressDataFactory, 'addressRepository' => $this->addressRepository, 'logger' => $this->logger, + 'addressRegistry' => $this->addressRegistry ] ); $reflection = new \ReflectionClass(get_class($this->controller)); @@ -166,6 +189,8 @@ protected function setUp() } /** + * Prepare mocks for tests + * * @param int $populateSequence */ protected function prepareMocksForTesting($populateSequence = 0) @@ -204,6 +229,9 @@ protected function prepareMocksForTesting($populateSequence = 0) ->willReturn(12); } + /** + * Prepare mocks for update customers default billing address use case + */ protected function prepareMocksForUpdateDefaultBilling() { $this->prepareMocksForProcessAddressData(); @@ -212,12 +240,15 @@ protected function prepareMocksForUpdateDefaultBilling() 'firstname' => 'Firstname', 'lastname' => 'Lastname', ]; - $this->customerData->expects($this->once()) + $this->customerData->expects($this->exactly(2)) ->method('getAddresses') ->willReturn([$this->address]); $this->address->expects($this->once()) ->method('isDefaultBilling') ->willReturn(true); + $this->addressRegistry->expects($this->once()) + ->method('retrieve') + ->willReturn(new DataObject()); $this->dataObjectHelper->expects($this->at(0)) ->method('populateWithArray') ->with( @@ -227,6 +258,9 @@ protected function prepareMocksForUpdateDefaultBilling() ); } + /** + * Prepare mocks for processing customers address data use case + */ protected function prepareMocksForProcessAddressData() { $this->customerData->expects($this->once()) @@ -237,6 +271,9 @@ protected function prepareMocksForProcessAddressData() ->willReturn('Lastname'); } + /** + * Prepare mocks for error messages processing test + */ protected function prepareMocksForErrorMessagesProcessing() { $this->messageManager->expects($this->atLeastOnce()) @@ -261,6 +298,9 @@ protected function prepareMocksForErrorMessagesProcessing() ->willReturnSelf(); } + /** + * Unit test for updating customers billing address use case + */ public function testExecuteWithUpdateBilling() { $this->prepareMocksForTesting(1); @@ -281,6 +321,9 @@ public function testExecuteWithUpdateBilling() $this->assertSame($this->resultJson, $this->controller->execute()); } + /** + * Unit test for creating customer with empty data use case + */ public function testExecuteWithoutItems() { $this->resultJsonFactory->expects($this->once()) @@ -305,6 +348,9 @@ public function testExecuteWithoutItems() $this->assertSame($this->resultJson, $this->controller->execute()); } + /** + * Unit test for verifying Localized Exception during inline edit + */ public function testExecuteLocalizedException() { $exception = new \Magento\Framework\Exception\LocalizedException(__('Exception message')); @@ -312,6 +358,9 @@ public function testExecuteLocalizedException() $this->customerData->expects($this->once()) ->method('getDefaultBilling') ->willReturn(false); + $this->customerData->expects($this->once()) + ->method('getAddresses') + ->willReturn([]); $this->customerRepository->expects($this->once()) ->method('save') ->with($this->customerData) @@ -327,6 +376,9 @@ public function testExecuteLocalizedException() $this->assertSame($this->resultJson, $this->controller->execute()); } + /** + * Unit test for verifying Execute Exception during inline edit + */ public function testExecuteException() { $exception = new \Exception('Exception message'); @@ -334,6 +386,9 @@ public function testExecuteException() $this->customerData->expects($this->once()) ->method('getDefaultBilling') ->willReturn(false); + $this->customerData->expects($this->once()) + ->method('getAddresses') + ->willReturn([]); $this->customerRepository->expects($this->once()) ->method('save') ->with($this->customerData) diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/MassAssignGroupTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/MassAssignGroupTest.php index 884aab711d1..10144bdc318 100644 --- a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/MassAssignGroupTest.php +++ b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/MassAssignGroupTest.php @@ -11,6 +11,7 @@ /** * Class MassAssignGroupTest + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class MassAssignGroupTest extends \PHPUnit\Framework\TestCase @@ -70,12 +71,17 @@ class MassAssignGroupTest extends \PHPUnit\Framework\TestCase */ protected $customerRepositoryMock; + /** + * @inheritdoc + */ protected function setUp() { $objectManagerHelper = new ObjectManagerHelper($this); $this->contextMock = $this->createMock(\Magento\Backend\App\Action\Context::class); - $resultRedirectFactory = $this->createMock(\Magento\Backend\Model\View\Result\RedirectFactory::class); + $resultRedirectFactory = $this->createMock( + \Magento\Backend\Model\View\Result\RedirectFactory::class + ); $this->responseMock = $this->createMock(\Magento\Framework\App\ResponseInterface::class); $this->requestMock = $this->getMockBuilder(\Magento\Framework\App\Request\Http::class) ->disableOriginalConstructor()->getMock(); @@ -129,7 +135,8 @@ protected function setUp() $this->customerCollectionFactoryMock->expects($this->once()) ->method('create') ->willReturn($this->customerCollectionMock); - $this->customerRepositoryMock = $this->getMockBuilder(\Magento\Customer\Api\CustomerRepositoryInterface::class) + $this->customerRepositoryMock = $this + ->getMockBuilder(\Magento\Customer\Api\CustomerRepositoryInterface::class) ->getMockForAbstractClass(); $this->massAction = $objectManagerHelper->getObject( \Magento\Customer\Controller\Adminhtml\Index\MassAssignGroup::class, @@ -142,12 +149,18 @@ protected function setUp() ); } + /** + * Unit test to verify mass customer group assignment use case + * + * @throws \Magento\Framework\Exception\LocalizedException + */ public function testExecute() { $customersIds = [10, 11, 12]; - $customerMock = $this->getMockBuilder( - \Magento\Customer\Api\Data\CustomerInterface::class - )->getMockForAbstractClass(); + $customerMock = $this->getMockBuilder(\Magento\Customer\Api\Data\CustomerInterface::class) + ->setMethods(['setData']) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); $this->customerCollectionMock->expects($this->any()) ->method('getAllIds') ->willReturn($customersIds); @@ -168,6 +181,11 @@ public function testExecute() $this->massAction->execute(); } + /** + * Unit test to verify expected error during mass customer group assignment use case + * + * @throws \Magento\Framework\Exception\LocalizedException + */ public function testExecuteWithException() { $customersIds = [10, 11, 12]; diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/SaveTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/SaveTest.php index c52d5b2fb37..8d802e907a8 100644 --- a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/SaveTest.php +++ b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/SaveTest.php @@ -14,6 +14,8 @@ use Magento\Framework\Controller\Result\Redirect; /** + * Testing Save Customer use case from admin page + * * @SuppressWarnings(PHPMD.TooManyFields) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @covers \Magento\Customer\Controller\Adminhtml\Index\Save @@ -435,6 +437,10 @@ public function testExecuteWithExistentCustomer() $customerEmail = 'customer@email.com'; $customerMock->expects($this->once())->method('getEmail')->willReturn($customerEmail); + $customerMock->expects($this->once()) + ->method('getAddresses') + ->willReturn([]); + $this->emailNotificationMock->expects($this->once()) ->method('credentialsChanged') ->with($customerMock, $customerEmail) diff --git a/app/code/Magento/Customer/Test/Unit/Helper/AddressTest.php b/app/code/Magento/Customer/Test/Unit/Helper/AddressTest.php index 2c49a81676c..0818d94afe5 100644 --- a/app/code/Magento/Customer/Test/Unit/Helper/AddressTest.php +++ b/app/code/Magento/Customer/Test/Unit/Helper/AddressTest.php @@ -415,32 +415,6 @@ public function isAttributeVisibleDataProvider() ]; } - /** - * Test is required filed by attribute code - * - * @param string $attributeCode - * @param bool $isMetadataExists - * @dataProvider isAttributeRequiredDataProvider - * @covers \Magento\Customer\Helper\Address::isAttributeRequired() - * @return void - */ - public function testIsAttributeRequired($attributeCode, $isMetadataExists) - { - $attributeMetadata = null; - if ($isMetadataExists) { - $attributeMetadata = $this->getMockBuilder(\Magento\Customer\Api\Data\AttributeMetadataInterface::class) - ->getMockForAbstractClass(); - $attributeMetadata->expects($this->once()) - ->method('isRequired') - ->willReturn(true); - } - $this->addressMetadataService->expects($this->once()) - ->method('getAttributeMetadata') - ->with($attributeCode) - ->willReturn($attributeMetadata); - $this->assertEquals($isMetadataExists, $this->helper->isAttributeRequired($attributeCode)); - } - /** * Data provider for test testIsAttributeRequire * diff --git a/app/code/Magento/Customer/Test/Unit/Model/AccountManagementTest.php b/app/code/Magento/Customer/Test/Unit/Model/AccountManagementTest.php index 0273c445bdd..22c9d90c086 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/AccountManagementTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/AccountManagementTest.php @@ -1472,12 +1472,15 @@ public function testChangePassword() $passwordHash = '1a2b3f4c'; $this->reInitModel(); - $customer = $this->getMockBuilder(Customer::class) + $customer = $this->getMockBuilder(CustomerInterface::class) ->disableOriginalConstructor() ->getMock(); $customer->expects($this->any()) ->method('getId') ->willReturn($customerId); + $customer->expects($this->once()) + ->method('getAddresses') + ->willReturn([]); $this->customerRepository ->expects($this->once()) diff --git a/app/code/Magento/Customer/Test/Unit/Model/CustomerTest.php b/app/code/Magento/Customer/Test/Unit/Model/CustomerTest.php index cb396e32509..65831069aa1 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/CustomerTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/CustomerTest.php @@ -13,9 +13,12 @@ use Magento\Customer\Model\Customer; use Magento\Customer\Model\AccountConfirmation; +use Magento\Customer\Model\ResourceModel\Address\CollectionFactory as AddressCollectionFactory; +use Magento\Customer\Api\Data\CustomerInterfaceFactory; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.TooManyFields) */ class CustomerTest extends \PHPUnit\Framework\TestCase { @@ -68,6 +71,21 @@ class CustomerTest extends \PHPUnit\Framework\TestCase */ private $accountConfirmation; + /** + * @var AddressCollectionFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $addressesFactory; + + /** + * @var CustomerInterfaceFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $customerDataFactory; + + /** + * @var \Magento\Framework\Api\DataObjectHelper|\PHPUnit_Framework_MockObject_MockObject + */ + private $dataObjectHelper; + protected function setUp() { $this->_website = $this->createMock(\Magento\Store\Model\Website::class); @@ -100,6 +118,19 @@ protected function setUp() $this->_encryptor = $this->createMock(\Magento\Framework\Encryption\EncryptorInterface::class); $helper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); $this->accountConfirmation = $this->createMock(AccountConfirmation::class); + $this->addressesFactory = $this->getMockBuilder(AddressCollectionFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + $this->customerDataFactory = $this->getMockBuilder(CustomerInterfaceFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + $this->dataObjectHelper = $this->getMockBuilder(\Magento\Framework\Api\DataObjectHelper::class) + ->disableOriginalConstructor() + ->setMethods(['populateWithArray']) + ->getMock(); + $this->_model = $helper->getObject( \Magento\Customer\Model\Customer::class, [ @@ -112,7 +143,10 @@ protected function setUp() 'registry' => $this->registryMock, 'resource' => $this->resourceMock, 'dataObjectProcessor' => $this->dataObjectProcessor, - 'accountConfirmation' => $this->accountConfirmation + 'accountConfirmation' => $this->accountConfirmation, + '_addressesFactory' => $this->addressesFactory, + 'customerDataFactory' => $this->customerDataFactory, + 'dataObjectHelper' => $this->dataObjectHelper ] ); } @@ -186,13 +220,13 @@ public function testSendNewAccountEmailWithoutStoreId() ->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', + 'website_id' => 1, + 'store_id' => 1, + 'email' => 'email@example.com', + 'firstname' => 'FirstName', + 'lastname' => 'LastName', + 'middlename' => 'MiddleName', + 'prefix' => 'Name Prefix', ]); $this->_model->sendNewAccountEmail('registered'); } @@ -310,4 +344,43 @@ public function testUpdateData() $this->assertEquals($this->_model->getData(), $expectedResult); } + + /** + * Test for the \Magento\Customer\Model\Customer::getDataModel() method + */ + public function testGetDataModel() + { + $customerId = 1; + $this->_model->setEntityId($customerId); + $this->_model->setId($customerId); + $addressDataModel = $this->getMockForAbstractClass(\Magento\Customer\Api\Data\AddressInterface::class); + $address = $this->getMockBuilder(\Magento\Customer\Model\Address::class) + ->disableOriginalConstructor() + ->setMethods(['setCustomer', 'getDataModel']) + ->getMock(); + $address->expects($this->atLeastOnce())->method('getDataModel')->willReturn($addressDataModel); + $addresses = new \ArrayIterator([$address, $address]); + $addressCollection = $this->getMockBuilder(\Magento\Customer\Model\ResourceModel\Address\Collection::class) + ->disableOriginalConstructor() + ->setMethods(['setCustomerFilter', 'addAttributeToSelect', 'getIterator', 'getItems']) + ->getMock(); + $addressCollection->expects($this->atLeastOnce())->method('setCustomerFilter')->willReturnSelf(); + $addressCollection->expects($this->atLeastOnce())->method('addAttributeToSelect')->willReturnSelf(); + $addressCollection->expects($this->atLeastOnce())->method('getIterator') + ->willReturn($addresses); + $addressCollection->expects($this->atLeastOnce())->method('getItems') + ->willReturn($addresses); + $this->addressesFactory->expects($this->atLeastOnce())->method('create')->willReturn($addressCollection); + $customerDataObject = $this->getMockForAbstractClass(\Magento\Customer\Api\Data\CustomerInterface::class); + $this->customerDataFactory->expects($this->atLeastOnce())->method('create')->willReturn($customerDataObject); + $this->dataObjectHelper->expects($this->atLeastOnce())->method('populateWithArray') + ->with($customerDataObject, $this->_model->getData(), \Magento\Customer\Api\Data\CustomerInterface::class) + ->willReturnSelf(); + $customerDataObject->expects($this->atLeastOnce())->method('setAddresses') + ->with([$addressDataModel, $addressDataModel]) + ->willReturnSelf(); + $customerDataObject->expects($this->atLeastOnce())->method('setId')->with($customerId)->willReturnSelf(); + $this->_model->getDataModel(); + $this->assertEquals($customerDataObject, $this->_model->getDataModel()); + } } diff --git a/app/code/Magento/Customer/Test/Unit/Model/Metadata/AttributeMetadataCacheTest.php b/app/code/Magento/Customer/Test/Unit/Model/Metadata/AttributeMetadataCacheTest.php index 658472d13ab..83915731ea5 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/Metadata/AttributeMetadataCacheTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/Metadata/AttributeMetadataCacheTest.php @@ -15,7 +15,14 @@ use Magento\Framework\App\CacheInterface; use Magento\Framework\Serialize\SerializerInterface; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Store\Api\Data\StoreInterface; +use Magento\Store\Model\StoreManagerInterface; +/** + * AttributeMetadataCache Test + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class AttributeMetadataCacheTest extends \PHPUnit\Framework\TestCase { /** @@ -43,6 +50,16 @@ class AttributeMetadataCacheTest extends \PHPUnit\Framework\TestCase */ private $attributeMetadataCache; + /** + * @var StoreInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $storeMock; + + /** + * @var StoreManagerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $storeManagerMock; + protected function setUp() { $objectManager = new ObjectManager($this); @@ -50,13 +67,18 @@ protected function setUp() $this->stateMock = $this->createMock(StateInterface::class); $this->serializerMock = $this->createMock(SerializerInterface::class); $this->attributeMetadataHydratorMock = $this->createMock(AttributeMetadataHydrator::class); + $this->storeMock = $this->createMock(StoreInterface::class); + $this->storeManagerMock = $this->createMock(StoreManagerInterface::class); + $this->storeManagerMock->method('getStore')->willReturn($this->storeMock); + $this->storeMock->method('getId')->willReturn(1); $this->attributeMetadataCache = $objectManager->getObject( AttributeMetadataCache::class, [ 'cache' => $this->cacheMock, 'state' => $this->stateMock, 'serializer' => $this->serializerMock, - 'attributeMetadataHydrator' => $this->attributeMetadataHydratorMock + 'attributeMetadataHydrator' => $this->attributeMetadataHydratorMock, + 'storeManager' => $this->storeManagerMock ] ); } @@ -80,7 +102,8 @@ public function testLoadNoCache() { $entityType = 'EntityType'; $suffix = 'none'; - $cacheKey = AttributeMetadataCache::ATTRIBUTE_METADATA_CACHE_PREFIX . $entityType . $suffix; + $storeId = 1; + $cacheKey = AttributeMetadataCache::ATTRIBUTE_METADATA_CACHE_PREFIX . $entityType . $suffix . $storeId; $this->stateMock->expects($this->once()) ->method('isEnabled') ->with(Type::TYPE_IDENTIFIER) @@ -96,7 +119,8 @@ public function testLoad() { $entityType = 'EntityType'; $suffix = 'none'; - $cacheKey = AttributeMetadataCache::ATTRIBUTE_METADATA_CACHE_PREFIX . $entityType . $suffix; + $storeId = 1; + $cacheKey = AttributeMetadataCache::ATTRIBUTE_METADATA_CACHE_PREFIX . $entityType . $suffix . $storeId; $serializedString = 'serialized string'; $attributeMetadataOneData = [ 'attribute_code' => 'attribute_code', @@ -156,7 +180,8 @@ public function testSave() { $entityType = 'EntityType'; $suffix = 'none'; - $cacheKey = AttributeMetadataCache::ATTRIBUTE_METADATA_CACHE_PREFIX . $entityType . $suffix; + $storeId = 1; + $cacheKey = AttributeMetadataCache::ATTRIBUTE_METADATA_CACHE_PREFIX . $entityType . $suffix . $storeId; $serializedString = 'serialized string'; $attributeMetadataOneData = [ 'attribute_code' => 'attribute_code', diff --git a/app/code/Magento/Customer/Test/Unit/Model/Metadata/Form/AbstractDataTest.php b/app/code/Magento/Customer/Test/Unit/Model/Metadata/Form/AbstractDataTest.php index e4dc22ba40e..5b4b50ca821 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/Metadata/Form/AbstractDataTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/Metadata/Form/AbstractDataTest.php @@ -205,37 +205,32 @@ public function applyOutputFilterDataProvider() } /** + * Tests input validation rules. + * * @param null|string $value * @param null|string $label * @param null|string $inputValidation * @param bool|array $expectedOutput * @dataProvider validateInputRuleDataProvider */ - public function testValidateInputRule($value, $label, $inputValidation, $expectedOutput) + public function testValidateInputRule($value, $label, $inputValidation, $expectedOutput): void { $validationRule = $this->getMockBuilder(\Magento\Customer\Api\Data\ValidationRuleInterface::class) ->disableOriginalConstructor() ->setMethods(['getName', 'getValue']) ->getMockForAbstractClass(); - $validationRule->expects($this->any()) - ->method('getName') - ->will($this->returnValue('input_validation')); - $validationRule->expects($this->any()) - ->method('getValue') - ->will($this->returnValue($inputValidation)); - - $this->_attributeMock->expects($this->any())->method('getStoreLabel')->will($this->returnValue($label)); - $this->_attributeMock->expects( - $this->any() - )->method( - 'getValidationRules' - )->will( - $this->returnValue( - [ - $validationRule, - ] - ) - ); + + $validationRule->method('getName') + ->willReturn('input_validation'); + + $validationRule->method('getValue') + ->willReturn($inputValidation); + + $this->_attributeMock->method('getStoreLabel') + ->willReturn($label); + + $this->_attributeMock->method('getValidationRules') + ->willReturn([$validationRule]); $this->assertEquals($expectedOutput, $this->_model->validateInputRule($value)); } @@ -256,6 +251,16 @@ public function validateInputRuleDataProvider() \Zend_Validate_Alnum::NOT_ALNUM => '"mylabel" contains non-alphabetic or non-numeric characters.' ] ], + [ + 'abc qaz', + 'mylabel', + 'alphanumeric', + [ + \Zend_Validate_Alnum::NOT_ALNUM => '"mylabel" contains non-alphabetic or non-numeric characters.' + ] + ], + ['abcqaz', 'mylabel', 'alphanumeric', true], + ['abc qaz', 'mylabel', 'alphanum-with-spaces', true], [ '!@#$', 'mylabel', 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 9d2411dd4c5..05953b09b8c 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/ResourceModel/CustomerRepositoryTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/ResourceModel/CustomerRepositoryTest.php @@ -618,7 +618,7 @@ public function testGetList() ->willReturnSelf(); $collection->expects($this->at(7)) ->method('joinAttribute') - ->with('company', 'customer_address/company', 'default_billing', null, 'left') + ->with('billing_company', 'customer_address/company', 'default_billing', null, 'left') ->willReturnSelf(); $this->collectionProcessorMock->expects($this->once()) ->method('process') diff --git a/app/code/Magento/Customer/Test/Unit/Observer/UpgradeCustomerPasswordObserverTest.php b/app/code/Magento/Customer/Test/Unit/Observer/UpgradeCustomerPasswordObserverTest.php index 8971f155f78..188bbde71c1 100644 --- a/app/code/Magento/Customer/Test/Unit/Observer/UpgradeCustomerPasswordObserverTest.php +++ b/app/code/Magento/Customer/Test/Unit/Observer/UpgradeCustomerPasswordObserverTest.php @@ -7,6 +7,9 @@ use Magento\Customer\Observer\UpgradeCustomerPasswordObserver; +/** + * Class UpgradeCustomerPasswordObserverTest for testing upgrade password observer + */ class UpgradeCustomerPasswordObserverTest extends \PHPUnit\Framework\TestCase { /** @@ -29,9 +32,13 @@ class UpgradeCustomerPasswordObserverTest extends \PHPUnit\Framework\TestCase */ protected $customerRegistry; + /** + * @inheritdoc + */ protected function setUp() { - $this->customerRepository = $this->getMockBuilder(\Magento\Customer\Api\CustomerRepositoryInterface::class) + $this->customerRepository = $this + ->getMockBuilder(\Magento\Customer\Api\CustomerRepositoryInterface::class) ->getMockForAbstractClass(); $this->customerRegistry = $this->getMockBuilder(\Magento\Customer\Model\CustomerRegistry::class) ->disableOriginalConstructor() @@ -47,6 +54,9 @@ protected function setUp() ); } + /** + * Unit test for verifying customers password upgrade observer + */ public function testUpgradeCustomerPassword() { $customerId = '1'; @@ -57,6 +67,8 @@ public function testUpgradeCustomerPassword() ->setMethods(['getId']) ->getMock(); $customer = $this->getMockBuilder(\Magento\Customer\Api\Data\CustomerInterface::class) + ->setMethods(['setData']) + ->disableOriginalConstructor() ->getMockForAbstractClass(); $customerSecure = $this->getMockBuilder(\Magento\Customer\Model\Data\CustomerSecure::class) ->disableOriginalConstructor() diff --git a/app/code/Magento/Customer/Test/Unit/Ui/Component/Listing/Column/ValidationRulesTest.php b/app/code/Magento/Customer/Test/Unit/Ui/Component/Listing/Column/ValidationRulesTest.php index 130b3acd11e..07b0a760432 100644 --- a/app/code/Magento/Customer/Test/Unit/Ui/Component/Listing/Column/ValidationRulesTest.php +++ b/app/code/Magento/Customer/Test/Unit/Ui/Component/Listing/Column/ValidationRulesTest.php @@ -18,12 +18,6 @@ class ValidationRulesTest extends \PHPUnit\Framework\TestCase protected function setUp() { - $this->validationRules = $this->getMockBuilder( - \Magento\Customer\Ui\Component\Listing\Column\ValidationRules::class - ) - ->disableOriginalConstructor() - ->getMock(); - $this->validationRule = $this->getMockBuilder(\Magento\Customer\Api\Data\ValidationRuleInterface::class) ->disableOriginalConstructor() ->getMock(); @@ -31,20 +25,26 @@ protected function setUp() $this->validationRules = new ValidationRules(); } - public function testGetValidationRules() + /** + * Tests input validation rules + * + * @param String $validationRule - provided input validation rules + * @param String $validationClass - expected input validation class + * @dataProvider validationRulesDataProvider + */ + public function testGetValidationRules(String $validationRule, String $validationClass): void { $expectsRules = [ 'required-entry' => true, - 'validate-number' => true, + $validationClass => true, ]; - $this->validationRule->expects($this->atLeastOnce()) - ->method('getName') + $this->validationRule->method('getName') ->willReturn('input_validation'); - $this->validationRule->expects($this->atLeastOnce()) - ->method('getValue') - ->willReturn('numeric'); - $this->assertEquals( + $this->validationRule->method('getValue') + ->willReturn($validationRule); + + self::assertEquals( $expectsRules, $this->validationRules->getValidationRules( true, @@ -56,6 +56,23 @@ public function testGetValidationRules() ); } + /** + * Provides possible validation rules. + * + * @return array + */ + public function validationRulesDataProvider(): array + { + return [ + ['alpha', 'validate-alpha'], + ['numeric', 'validate-number'], + ['alphanumeric', 'validate-alphanum'], + ['alphanum-with-spaces', 'validate-alphanum-with-spaces'], + ['url', 'validate-url'], + ['email', 'validate-email'] + ]; + } + public function testGetValidationRulesWithOnlyRequiredRule() { $expectsRules = [ diff --git a/app/code/Magento/Customer/Ui/Component/Listing/Column/ValidationRules.php b/app/code/Magento/Customer/Ui/Component/Listing/Column/ValidationRules.php index b8f83421a6d..6befec8e942 100644 --- a/app/code/Magento/Customer/Ui/Component/Listing/Column/ValidationRules.php +++ b/app/code/Magento/Customer/Ui/Component/Listing/Column/ValidationRules.php @@ -7,6 +7,9 @@ use Magento\Customer\Api\Data\ValidationRuleInterface; +/** + * Provides validation classes according to corresponding rules. + */ class ValidationRules { /** @@ -16,6 +19,7 @@ class ValidationRules 'alpha' => 'validate-alpha', 'numeric' => 'validate-number', 'alphanumeric' => 'validate-alphanum', + 'alphanum-with-spaces' => 'validate-alphanum-with-spaces', 'url' => 'validate-url', 'email' => 'validate-email', ]; diff --git a/app/code/Magento/Customer/etc/acl.xml b/app/code/Magento/Customer/etc/acl.xml index e8e6219bef4..1d45aa6445d 100644 --- a/app/code/Magento/Customer/etc/acl.xml +++ b/app/code/Magento/Customer/etc/acl.xml @@ -10,7 +10,13 @@ <resources> <resource id="Magento_Backend::admin"> <resource id="Magento_Customer::customer" title="Customers" translate="title" sortOrder="40"> - <resource id="Magento_Customer::manage" title="All Customers" translate="title" sortOrder="10" /> + <resource id="Magento_Customer::manage" title="All Customers" translate="title" sortOrder="10"> + <resource id="Magento_Customer::actions" title="Actions" translate="title" sortOrder="10"> + <resource id="Magento_Customer::delete" title="Delete" translate="title" sortOrder="10" /> + <resource id="Magento_Customer::reset_password" title="Reset password" translate="title" sortOrder="20" /> + <resource id="Magento_Customer::invalidate_tokens" title="Invalidate tokens" translate="title" sortOrder="30" /> + </resource> + </resource> <resource id="Magento_Customer::online" title="Now Online" translate="title" sortOrder="20" /> <resource id="Magento_Customer::group" title="Customer Groups" translate="title" sortOrder="30" /> </resource> diff --git a/app/code/Magento/Customer/view/frontend/email/account_new_confirmation.html b/app/code/Magento/Customer/view/frontend/email/account_new_confirmation.html index 9b183d63471..010087ace2d 100644 --- a/app/code/Magento/Customer/view/frontend/email/account_new_confirmation.html +++ b/app/code/Magento/Customer/view/frontend/email/account_new_confirmation.html @@ -9,9 +9,7 @@ "var this.getUrl($store, 'customer/account/confirm/', [_query:[id:$customer.id, key:$customer.confirmation, back_url:$back_url]])":"Account Confirmation URL", "var this.getUrl($store, 'customer/account/')":"Customer Account URL", "var customer.email":"Customer Email", -"var customer.name":"Customer Name", -"var extensions":"Extensions", -"var url":"Url" +"var customer.name":"Customer Name" } @--> {{template config_path="design/email/header_template"}} @@ -25,7 +23,7 @@ <table class="inner-wrapper" border="0" cellspacing="0" cellpadding="0" align="center"> <tr> <td align="center"> - <a href="{{var this.getUrl($store,$url,[_query:[id:$customer.id,key:$customer.confirmation,extensions:$extensions,back_url:$back_url],_nosid:1])}}" target="_blank">{{trans "Confirm Your Account"}}</a> + <a href="{{var this.getUrl($store,'customer/account/confirm/',[_query:[id:$customer.id,key:$customer.confirmation,back_url:$back_url],_nosid:1])}}" target="_blank">{{trans "Confirm Your Account"}}</a> </td> </tr> </table> diff --git a/app/code/Magento/Customer/view/frontend/layout/customer_address_form.xml b/app/code/Magento/Customer/view/frontend/layout/customer_address_form.xml index f053805409f..f5ee2b347a5 100644 --- a/app/code/Magento/Customer/view/frontend/layout/customer_address_form.xml +++ b/app/code/Magento/Customer/view/frontend/layout/customer_address_form.xml @@ -20,6 +20,7 @@ <block class="Magento\Customer\Block\Address\Edit" name="customer_address_edit" template="Magento_Customer::address/edit.phtml" cacheable="false"> <arguments> <argument name="attribute_data" xsi:type="object">Magento\Customer\Block\DataProviders\AddressAttributeData</argument> + <argument name="post_code_config" xsi:type="object">Magento\Customer\Block\DataProviders\PostCodesPatternsAttributeData</argument> </arguments> </block> </referenceContainer> diff --git a/app/code/Magento/Customer/view/frontend/layout/customer_address_index.xml b/app/code/Magento/Customer/view/frontend/layout/customer_address_index.xml index bad120e4627..2c5e5b98e5f 100644 --- a/app/code/Magento/Customer/view/frontend/layout/customer_address_index.xml +++ b/app/code/Magento/Customer/view/frontend/layout/customer_address_index.xml @@ -13,6 +13,7 @@ </referenceBlock> <referenceContainer name="content"> <block class="Magento\Customer\Block\Address\Book" name="address_book" template="Magento_Customer::address/book.phtml" cacheable="false"/> + <block class="Magento\Customer\Block\Address\Grid" name="address_grid" template="Magento_Customer::address/grid.phtml" cacheable="false"/> </referenceContainer> </body> </page> diff --git a/app/code/Magento/Customer/view/frontend/templates/address/book.phtml b/app/code/Magento/Customer/view/frontend/templates/address/book.phtml index 45c13d93574..9d09a090dea 100644 --- a/app/code/Magento/Customer/view/frontend/templates/address/book.phtml +++ b/app/code/Magento/Customer/view/frontend/templates/address/book.phtml @@ -62,47 +62,3 @@ <?php endif ?> </div> </div> - -<div class="block block-addresses-list"> - <div class="block-title"><strong><?= $block->escapeHtml(__('Additional Address Entries')) ?></strong></div> - <div class="block-content"> - <?php if ($_pAddsses = $block->getAdditionalAddresses()): ?> - <ol class="items addresses"> - <?php foreach ($_pAddsses as $_address): ?> - <li class="item"> - <address> - <?= $block->getAddressHtml($_address) ?><br /> - </address> - <div class="item actions"> - <a class="action edit" href="<?= $block->escapeUrl($block->getUrl('customer/address/edit', ['id' => $_address->getId()])) ?>"><span><?= $block->escapeHtml(__('Edit Address')) ?></span></a> - <a class="action delete" href="#" role="delete-address" data-address="<?= $block->escapeHtmlAttr($_address->getId()) ?>"><span><?= $block->escapeHtml(__('Delete Address')) ?></span></a> - </div> - </li> - <?php endforeach; ?> - </ol> - <?php else: ?> - <p class="empty"><?= $block->escapeHtml(__('You have no other address entries in your address book.')) ?></p> - <?php endif ?> - </div> -</div> - -<div class="actions-toolbar"> - <div class="primary"> - <button type="button" role="add-address" title="<?= $block->escapeHtmlAttr(__('Add New Address')) ?>" class="action primary add"><span><?= $block->escapeHtml(__('Add New Address')) ?></span></button> - </div> - <div class="secondary"> - <a class="action back" href="<?= $block->escapeUrl($block->getBackUrl()) ?>"><span><?= $block->escapeHtml(__('Back')) ?></span></a> - </div> -</div> -<script type="text/x-magento-init"> - { - ".page-main": { - "address": { - "deleteAddress": "li.item a[role='delete-address']", - "deleteUrlPrefix": "<?= $block->escapeJs($block->escapeUrl($block->getDeleteUrl())) ?>id/", - "addAddress": "button[role='add-address']", - "addAddressLocation": "<?= $block->escapeJs($block->escapeUrl($block->getAddAddressUrl())) ?>" - } - } - } -</script> diff --git a/app/code/Magento/Customer/view/frontend/templates/address/edit.phtml b/app/code/Magento/Customer/view/frontend/templates/address/edit.phtml index faa15f72402..df3f0004108 100644 --- a/app/code/Magento/Customer/view/frontend/templates/address/edit.phtml +++ b/app/code/Magento/Customer/view/frontend/templates/address/edit.phtml @@ -126,6 +126,9 @@ title="<?= /* @noEscape */ $block->getAttributeData()->getFrontendLabel('postcode') ?>" id="zip" class="input-text validate-zip-international <?= $block->escapeHtmlAttr($this->helper('Magento\Customer\Helper\Address')->getAttributeValidationClass('postcode')) ?>"> + <div role="alert" class="message warning" style="display:none"> + <span></span> + </div> </div> </div> <div class="field country required"> @@ -184,7 +187,9 @@ <script type="text/x-magento-init"> { "#form-validate": { - "addressValidation": {} + "addressValidation": { + "postCodes": <?= /* @noEscape */ $block->getPostCodeConfig()->getSerializedPostCodes(); ?> + } }, "#country": { "regionUpdater": { diff --git a/app/code/Magento/Customer/view/frontend/templates/address/grid.phtml b/app/code/Magento/Customer/view/frontend/templates/address/grid.phtml new file mode 100644 index 00000000000..7af5b5af2e7 --- /dev/null +++ b/app/code/Magento/Customer/view/frontend/templates/address/grid.phtml @@ -0,0 +1,82 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +// @codingStandardsIgnoreFile + +/** @var \Magento\Customer\Block\Address\Grid $block */ +$customerAddressView = $block->getData('customer_address'); +?> + +<div class="block block-addresses-list"> + <div class="block-title"><strong><?= $block->escapeHtml(__('Additional Address Entries')) ?></strong></div> + <div class="block-content"> + <?php if ($_pAddsses = $block->getAdditionalAddresses()): ?> + + <div class="table-wrapper additional-addresses"> + <table class="data table table-additional-addresses-items history" id="additional-addresses-table"> + <caption class="table-caption"><?= /* @escapeNotVerified */ __('Additional addresses') ?></caption> + <thead> + <tr> + <th scope="col" class="col firstname"><?= /* @escapeNotVerified */ __('First Name') ?></th> + <th scope="col" class="col lastname"><?= /* @escapeNotVerified */ __('Last Name') ?></th> + <th scope="col" class="col streetaddress"><?= /* @escapeNotVerified */ __('Street Address') ?></th> + <th scope="col" class="col city"><?= /* @escapeNotVerified */ __('City') ?></th> + <th scope="col" class="col country"><?= /* @escapeNotVerified */ __('Country') ?></th> + <th scope="col" class="col state"><?= /* @escapeNotVerified */ __('State') ?></th> + <th scope="col" class="col zip"><?= /* @escapeNotVerified */ __('Zip/Postal Code') ?></th> + <th scope="col" class="col phone"><?= /* @escapeNotVerified */ __('Phone') ?></th> + <th scope="col" class="col actions"> </th> + </tr> + </thead> + <tbody> + <?php foreach ($_pAddsses as $address): ?> + <tr> + <td data-th="<?= $block->escapeHtml(__('First Name')) ?>" class="col firstname"><?= $block->escapeHtml($address->getFirstname()) ?></td> + <td data-th="<?= $block->escapeHtml(__('Last Name')) ?>" class="col lastname"><?= $block->escapeHtml($address->getLastname()) ?></td> + <td data-th="<?= $block->escapeHtml(__('Street Address')) ?>" class="col streetaddress"><?= $block->escapeHtml($block->getStreetAddress($address)) ?></td> + <td data-th="<?= $block->escapeHtml(__('City')) ?>" class="col city"><?= $block->escapeHtml($address->getCity()) ?></td> + <td data-th="<?= $block->escapeHtml(__('Country')) ?>" class="col country"><?= /* @escapeNotVerified */ $block->getCountryByCode($address->getCountryId()) ?></td> + <td data-th="<?= $block->escapeHtml(__('State')) ?>" class="col state"><?= /* @escapeNotVerified */ $address->getRegion()->getRegion() ?></td> + <td data-th="<?= $block->escapeHtml(__('Zip/Postal Code')) ?>" class="col zip"><?= $block->escapeHtml($address->getPostcode()) ?></td> + <td data-th="<?= $block->escapeHtml(__('Phone')) ?>" class="col phone"><?= $block->escapeHtml($address->getTelephone()) ?></td> + <td data-th="<?= $block->escapeHtml(__('Actions')) ?>" class="col actions"> + <a class="action edit" href="<?= $block->escapeUrl($block->getUrl('customer/address/edit', ['id' => $address->getId()])) ?>"><span><?= $block->escapeHtml(__('Edit')) ?></span></a> + <a class="action delete" href="#" role="delete-address" data-address="<?= $block->escapeHtmlAttr($address->getId()) ?>"><span><?= $block->escapeHtml(__('Delete')) ?></span></a> + </td> + </tr> + <?php endforeach; ?> + </tbody> + </table> + </div> + <?php if ($block->getChildHtml('pager')): ?> + <div class="customer-addresses-toolbar toolbar bottom"><?= $block->getChildHtml('pager') ?></div> + <?php endif ?> + <?php else: ?> + <p class="empty"><?= $block->escapeHtml(__('You have no other address entries in your address book.')) ?></p> + <?php endif ?> + </div> +</div> + +<div class="actions-toolbar"> + <div class="primary"> + <button type="button" role="add-address" title="<?= $block->escapeHtmlAttr(__('Add New Address')) ?>" class="action primary add"><span><?= $block->escapeHtml(__('Add New Address')) ?></span></button> + </div> + <div class="secondary"> + <a class="action back" href="<?= $block->escapeUrl($block->getBackUrl()) ?>"><span><?= $block->escapeHtml(__('Back')) ?></span></a> + </div> +</div> +<script type="text/x-magento-init"> + { + ".page-main": { + "address": { + "deleteAddress": "td a[role='delete-address']", + "deleteUrlPrefix": "<?= $block->escapeJs($block->escapeUrl($block->getDeleteUrl())) ?>id/", + "addAddress": "button[role='add-address']", + "addAddressLocation": "<?= $block->escapeJs($block->escapeUrl($block->getAddAddressUrl())) ?>" + } + } + } +</script> diff --git a/app/code/Magento/Customer/view/frontend/templates/widget/dob.phtml b/app/code/Magento/Customer/view/frontend/templates/widget/dob.phtml index 0a37896b810..31510a65ef7 100644 --- a/app/code/Magento/Customer/view/frontend/templates/widget/dob.phtml +++ b/app/code/Magento/Customer/view/frontend/templates/widget/dob.phtml @@ -29,7 +29,7 @@ $fieldCssClass = 'field date field-' . $block->getHtmlId(); $fieldCssClass .= $block->isRequired() ? ' required' : ''; ?> <div class="<?= $block->escapeHtmlAttr($fieldCssClass) ?>"> - <label class="label" for="<?= $block->escapeHtmlAttr($block->getHtmlId()) ?>"><span><?= $block->escapeHtml($block->getLabel()) ?></span></label> + <label class="label" for="<?= $block->escapeHtmlAttr($block->getHtmlId()) ?>"><span><?= $block->escapeHtml($block->getStoreLabel('dob')) ?></span></label> <div class="control customer-dob"> <?= $block->getFieldHtml() ?> <?php if ($_message = $block->getAdditionalDescription()) : ?> diff --git a/app/code/Magento/Customer/view/frontend/templates/widget/gender.phtml b/app/code/Magento/Customer/view/frontend/templates/widget/gender.phtml index d60f0968ad4..8b45618a891 100644 --- a/app/code/Magento/Customer/view/frontend/templates/widget/gender.phtml +++ b/app/code/Magento/Customer/view/frontend/templates/widget/gender.phtml @@ -9,9 +9,9 @@ /** @var \Magento\Customer\Block\Widget\Gender $block */ ?> <div class="field gender<?php if ($block->isRequired()) echo ' required' ?>"> - <label class="label" for="<?= $block->escapeHtmlAttr($block->getFieldId('gender')) ?>"><span><?= $block->escapeHtml(__('Gender')) ?></span></label> + <label class="label" for="<?= $block->escapeHtmlAttr($block->getFieldId('gender')) ?>"><span><?= $block->escapeHtml($block->getStoreLabel('gender')) ?></span></label> <div class="control"> - <select id="<?= $block->escapeHtmlAttr($block->getFieldId('gender')) ?>" name="<?= $block->escapeHtmlAttr($block->getFieldName('gender')) ?>" title="<?= $block->escapeHtmlAttr(__('Gender')) ?>"<?php if ($block->isRequired()):?> class="validate-select" data-validate="{required:true}"<?php endif; ?>> + <select id="<?= $block->escapeHtmlAttr($block->getFieldId('gender')) ?>" name="<?= $block->escapeHtmlAttr($block->getFieldName('gender')) ?>" title="<?= $block->escapeHtmlAttr($block->getStoreLabel('gender')) ?>"<?php if ($block->isRequired()):?> class="validate-select" data-validate="{required:true}"<?php endif; ?>> <?php $options = $block->getGenderOptions(); ?> <?php $value = $block->getGender();?> <?php foreach ($options as $option):?> diff --git a/app/code/Magento/Customer/view/frontend/templates/widget/taxvat.phtml b/app/code/Magento/Customer/view/frontend/templates/widget/taxvat.phtml index 4b3681d4d8f..bb60845a64e 100644 --- a/app/code/Magento/Customer/view/frontend/templates/widget/taxvat.phtml +++ b/app/code/Magento/Customer/view/frontend/templates/widget/taxvat.phtml @@ -9,8 +9,8 @@ /** @var \Magento\Customer\Block\Widget\Taxvat $block */ ?> <div class="field taxvat<?php if ($block->isRequired()) echo ' required'; ?>"> - <label class="label" for="<?= $block->escapeHtmlAttr($block->getFieldId('taxvat')) ?>"><span><?= $block->escapeHtml(__('Tax/VAT number')) ?></span></label> + <label class="label" for="<?= $block->escapeHtmlAttr($block->getFieldId('taxvat')) ?>"><span><?= $block->escapeHtml($block->getStoreLabel('taxvat')) ?></span></label> <div class="control"> - <input type="text" id="<?= $block->escapeHtmlAttr($block->getFieldId('taxvat')) ?>" name="<?= $block->escapeHtmlAttr($block->getFieldName('taxvat')) ?>" value="<?= $block->escapeHtmlAttr($block->getTaxvat()) ?>" title="<?= $block->escapeHtmlAttr(__('Tax/VAT number')) ?>" class="input-text <?= $block->escapeHtmlAttr($this->helper('Magento\Customer\Helper\Address')->getAttributeValidationClass('taxvat')) ?>" <?php if ($block->isRequired()) echo ' data-validate="{required:true}"' ?>> + <input type="text" id="<?= $block->escapeHtmlAttr($block->getFieldId('taxvat')) ?>" name="<?= $block->escapeHtmlAttr($block->getFieldName('taxvat')) ?>" value="<?= $block->escapeHtmlAttr($block->getTaxvat()) ?>" title="<?= $block->escapeHtmlAttr($block->getStoreLabel('taxvat')) ?>" class="input-text <?= $block->escapeHtmlAttr($this->helper('Magento\Customer\Helper\Address')->getAttributeValidationClass('taxvat')) ?>" <?php if ($block->isRequired()) echo ' data-validate="{required:true}"' ?>> </div> </div> diff --git a/app/code/Magento/Customer/view/frontend/templates/widget/telephone.phtml b/app/code/Magento/Customer/view/frontend/templates/widget/telephone.phtml index 1b61dc45573..6367bf10bba 100644 --- a/app/code/Magento/Customer/view/frontend/templates/widget/telephone.phtml +++ b/app/code/Magento/Customer/view/frontend/templates/widget/telephone.phtml @@ -19,7 +19,7 @@ <?php $_validationClass = $block->escapeHtmlAttr( $this->helper('Magento\Customer\Helper\Address') - ->getAttributeValidationClass('fax') + ->getAttributeValidationClass('telephone') ); ?> <input type="text" diff --git a/app/code/Magento/Customer/view/frontend/web/js/addressValidation.js b/app/code/Magento/Customer/view/frontend/web/js/addressValidation.js index be2960701de..c014b814ea9 100644 --- a/app/code/Magento/Customer/view/frontend/web/js/addressValidation.js +++ b/app/code/Magento/Customer/view/frontend/web/js/addressValidation.js @@ -5,25 +5,38 @@ define([ 'jquery', + 'underscore', + 'mageUtils', + 'mage/translate', + 'Magento_Checkout/js/model/postcode-validator', 'jquery/ui', 'validation' -], function ($) { +], function ($, __, utils, $t, postCodeValidator) { 'use strict'; $.widget('mage.addressValidation', { options: { selectors: { - button: '[data-action=save-address]' + button: '[data-action=save-address]', + zip: '#zip', + country: 'select[name="country_id"]:visible' } }, + zipInput: null, + countrySelect: null, + /** * Validation creation + * * @protected */ _create: function () { var button = $(this.options.selectors.button, this.element); + this.zipInput = $(this.options.selectors.zip, this.element); + this.countrySelect = $(this.options.selectors.country, this.element); + this.element.validation({ /** @@ -36,6 +49,75 @@ define([ form.submit(); } }); + + this._addPostCodeValidation(); + }, + + /** + * Add postcode validation + * + * @protected + */ + _addPostCodeValidation: function () { + var self = this; + + this.zipInput.on('keyup', __.debounce(function (event) { + var valid = self._validatePostCode(event.target.value); + + self._renderValidationResult(valid); + }, 500) + ); + + this.countrySelect.on('change', function () { + var valid = self._validatePostCode(self.zipInput.val()); + + self._renderValidationResult(valid); + }); + }, + + /** + * Validate post code value. + * + * @protected + * @param {String} postCode - post code + * @return {Boolean} Whether is post code valid + */ + _validatePostCode: function (postCode) { + var countryId = this.countrySelect.val(); + + if (postCode === null) { + return true; + } + + return postCodeValidator.validate(postCode, countryId, this.options.postCodes); + }, + + /** + * Renders warning messages for invalid post code. + * + * @protected + * @param {Boolean} valid + */ + _renderValidationResult: function (valid) { + var warnMessage, + alertDiv = this.zipInput.next(); + + if (!valid) { + 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.'); + } + + alertDiv.children(':first').text(warnMessage); + + if (valid) { + alertDiv.hide(); + } else { + alertDiv.show(); + } } }); diff --git a/app/code/Magento/Customer/view/frontend/web/js/customer-data.js b/app/code/Magento/Customer/view/frontend/web/js/customer-data.js index 39e3f8d95ee..66d37cb84e9 100644 --- a/app/code/Magento/Customer/view/frontend/web/js/customer-data.js +++ b/app/code/Magento/Customer/view/frontend/web/js/customer-data.js @@ -216,6 +216,9 @@ define([ $.cookieStorage.set(privateContentVersion, privateContent); } $.localStorage.set(privateContentVersion, privateContent); + _.each(dataProvider.getFromStorage(storage.keys()), function (sectionData, sectionName) { + buffer.notify(sectionName, sectionData); + }); this.reload([], false); isLoading = true; } else if (expiredSectionNames.length > 0) { diff --git a/app/code/Magento/CustomerAnalytics/README.md b/app/code/Magento/CustomerAnalytics/README.md index 8c64ce97629..9658a8e7d90 100644 --- a/app/code/Magento/CustomerAnalytics/README.md +++ b/app/code/Magento/CustomerAnalytics/README.md @@ -1,3 +1,3 @@ # Magento_CustomerAnalytics module -The Magento_CustomerAnalytics module configures data definitions for a data collection related to the Customer module entities to be used in [Advanced Reporting](http://devdocs.magento.com/guides/v2.2/advanced-reporting/modules.html). +The Magento_CustomerAnalytics module configures data definitions for a data collection related to the Customer module entities to be used in [Advanced Reporting](https://devdocs.magento.com/guides/v2.2/advanced-reporting/modules.html). diff --git a/app/code/Magento/CustomerGraphQl/Model/Customer/CreateAccount.php b/app/code/Magento/CustomerGraphQl/Model/Customer/CreateAccount.php new file mode 100644 index 00000000000..4a4b5c86352 --- /dev/null +++ b/app/code/Magento/CustomerGraphQl/Model/Customer/CreateAccount.php @@ -0,0 +1,85 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CustomerGraphQl\Model\Customer; + +use Magento\Customer\Api\AccountManagementInterface; +use Magento\Customer\Api\Data\CustomerInterface; +use Magento\Customer\Api\Data\CustomerInterfaceFactory; +use Magento\Framework\Api\DataObjectHelper; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Store\Model\StoreManagerInterface; + +/** + * Class CreateAccount creates new customer account + */ +class CreateAccount +{ + /** + * @var DataObjectHelper + */ + private $dataObjectHelper; + + /** + * @var CustomerInterfaceFactory + */ + private $customerFactory; + + /** + * @var AccountManagementInterface + */ + private $accountManagement; + + /** + * @var StoreManagerInterface + */ + private $storeManager; + + /** + * @param DataObjectHelper $dataObjectHelper + * @param CustomerInterfaceFactory $customerFactory + * @param StoreManagerInterface $storeManager + * @param AccountManagementInterface $accountManagement + */ + public function __construct( + DataObjectHelper $dataObjectHelper, + CustomerInterfaceFactory $customerFactory, + StoreManagerInterface $storeManager, + AccountManagementInterface $accountManagement + ) { + $this->dataObjectHelper = $dataObjectHelper; + $this->customerFactory = $customerFactory; + $this->accountManagement = $accountManagement; + $this->storeManager = $storeManager; + } + + /** + * Creates new customer account + * + * @param array $args + * @return CustomerInterface + * @throws LocalizedException + * @throws NoSuchEntityException + */ + public function execute(array $args): CustomerInterface + { + $customerDataObject = $this->customerFactory->create(); + $this->dataObjectHelper->populateWithArray( + $customerDataObject, + $args['input'], + CustomerInterface::class + ); + $store = $this->storeManager->getStore(); + $customerDataObject->setWebsiteId($store->getWebsiteId()); + $customerDataObject->setStoreId($store->getId()); + + $password = array_key_exists('password', $args['input']) ? $args['input']['password'] : null; + + return $this->accountManagement->createAccount($customerDataObject, $password); + } +} diff --git a/app/code/Magento/CustomerGraphQl/Model/Customer/SetUpUserContext.php b/app/code/Magento/CustomerGraphQl/Model/Customer/SetUpUserContext.php new file mode 100644 index 00000000000..1fcf1c0d7c1 --- /dev/null +++ b/app/code/Magento/CustomerGraphQl/Model/Customer/SetUpUserContext.php @@ -0,0 +1,30 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CustomerGraphQl\Model\Customer; + +use Magento\Customer\Api\Data\CustomerInterface; +use Magento\Framework\GraphQl\Query\Resolver\ContextInterface; +use Magento\Authorization\Model\UserContextInterface; + +/** + * Set up user context after creating new customer account + */ +class SetUpUserContext +{ + /** + * Set up user context after creating new customer account + * + * @param ContextInterface $context + * @param CustomerInterface $customer + */ + public function execute(ContextInterface $context, CustomerInterface $customer) + { + $context->setUserId((int)$customer->getId()); + $context->setUserType(UserContextInterface::USER_TYPE_CUSTOMER); + } +} diff --git a/app/code/Magento/CustomerGraphQl/Model/Resolver/CreateCustomer.php b/app/code/Magento/CustomerGraphQl/Model/Resolver/CreateCustomer.php new file mode 100644 index 00000000000..299045c6b62 --- /dev/null +++ b/app/code/Magento/CustomerGraphQl/Model/Resolver/CreateCustomer.php @@ -0,0 +1,95 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CustomerGraphQl\Model\Resolver; + +use Magento\CustomerGraphQl\Model\Customer\ChangeSubscriptionStatus; +use Magento\CustomerGraphQl\Model\Customer\CreateAccount; +use Magento\CustomerGraphQl\Model\Customer\CustomerDataProvider; +use Magento\CustomerGraphQl\Model\Customer\SetUpUserContext; +use Magento\Framework\Exception\State\InputMismatchException; +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\Framework\Validator\Exception as ValidatorException; + +/** + * Create customer account resolver + */ +class CreateCustomer implements ResolverInterface +{ + /** + * @var CustomerDataProvider + */ + private $customerDataProvider; + + /** + * @var ChangeSubscriptionStatus + */ + private $changeSubscriptionStatus; + + /** + * @var CreateAccount + */ + private $createAccount; + + /** + * @var SetUpUserContext + */ + private $setUpUserContext; + + /** + * @param CustomerDataProvider $customerDataProvider + * @param ChangeSubscriptionStatus $changeSubscriptionStatus + * @param SetUpUserContext $setUpUserContext + * @param CreateAccount $createAccount + */ + public function __construct( + CustomerDataProvider $customerDataProvider, + ChangeSubscriptionStatus $changeSubscriptionStatus, + SetUpUserContext $setUpUserContext, + CreateAccount $createAccount + ) { + $this->customerDataProvider = $customerDataProvider; + $this->changeSubscriptionStatus = $changeSubscriptionStatus; + $this->createAccount = $createAccount; + $this->setUpUserContext = $setUpUserContext; + } + + /** + * @inheritdoc + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + if (!isset($args['input']) || !is_array($args['input']) || empty($args['input'])) { + throw new GraphQlInputException(__('"input" value should be specified')); + } + try { + $customer = $this->createAccount->execute($args); + $customerId = (int)$customer->getId(); + $this->setUpUserContext->execute($context, $customer); + if (array_key_exists('is_subscribed', $args['input'])) { + if ($args['input']['is_subscribed']) { + $this->changeSubscriptionStatus->execute($customerId, true); + } + } + $data = $this->customerDataProvider->getCustomerById($customerId); + } catch (ValidatorException $e) { + throw new GraphQlInputException(__($e->getMessage())); + } catch (InputMismatchException $e) { + throw new GraphQlInputException(__($e->getMessage())); + } + + return ['customer' => $data]; + } +} diff --git a/app/code/Magento/CustomerGraphQl/Model/Resolver/UpdateCustomerAddress.php b/app/code/Magento/CustomerGraphQl/Model/Resolver/UpdateCustomerAddress.php index 7bae40e4cc5..833ab2e4502 100644 --- a/app/code/Magento/CustomerGraphQl/Model/Resolver/UpdateCustomerAddress.php +++ b/app/code/Magento/CustomerGraphQl/Model/Resolver/UpdateCustomerAddress.php @@ -109,6 +109,10 @@ private function updateCustomerAddress(int $customerId, int $addressId, array $a { $address = $this->getCustomerAddressForUser->execute($addressId, $customerId); $this->dataObjectHelper->populateWithArray($address, $addressData, AddressInterface::class); + if (isset($addressData['region']['region_id'])) { + $address->setRegionId($address->getRegion()->getRegionId()); + } + return $this->addressRepository->save($address); } } diff --git a/app/code/Magento/CustomerGraphQl/etc/schema.graphqls b/app/code/Magento/CustomerGraphQl/etc/schema.graphqls index c92753b9622..f4a417fe2f0 100644 --- a/app/code/Magento/CustomerGraphQl/etc/schema.graphqls +++ b/app/code/Magento/CustomerGraphQl/etc/schema.graphqls @@ -8,7 +8,8 @@ type Query { type Mutation { generateCustomerToken(email: String!, password: String!): CustomerToken @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\GenerateCustomerToken") @doc(description:"Retrieve the customer token") changeCustomerPassword(currentPassword: String!, newPassword: String!): Customer @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\ChangePassword") @doc(description:"Changes the password for the logged-in customer") - updateCustomer (input: UpdateCustomerInput!): UpdateCustomerOutput @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\UpdateCustomer") @doc(description:"Update the customer's personal information") + createCustomer (input: CustomerInput!): CustomerOutput @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\CreateCustomer") @doc(description:"Create customer account") + updateCustomer (input: CustomerInput!): CustomerOutput @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\UpdateCustomer") @doc(description:"Update the customer's personal information") revokeCustomerToken: RevokeCustomerTokenOutput @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\RevokeCustomerToken") @doc(description:"Revoke the customer token") createCustomerAddress(input: CustomerAddressInput!): CustomerAddress @resolver(class: "Magento\\CustomerGraphQl\\Model\\Resolver\\CreateCustomerAddress") @doc(description: "Create customer address") updateCustomerAddress(id: Int!, input: CustomerAddressInput): CustomerAddress @resolver(class: "Magento\\CustomerGraphQl\\Model\\Resolver\\UpdateCustomerAddress") @doc(description: "Update customer address") @@ -50,15 +51,21 @@ type CustomerToken { token: String @doc(description: "The customer token") } -input UpdateCustomerInput { - firstname: String - lastname: String - email: String - password: String - is_subscribed: Boolean +input CustomerInput { + prefix: String @doc(description: "An honorific, such as Dr., Mr., or Mrs.") + firstname: String @doc(description: "The customer's first name") + middlename: String @doc(description: "The customer's middle name") + lastname: String @doc(description: "The customer's family name") + suffix: String @doc(description: "A value such as Sr., Jr., or III") + email: String @doc(description: "The customer's email address. Required") + dob: String @doc(description: "The customer's date of birth") + taxvat: String @doc(description: "The customer's Tax/VAT number (for corporate customers)") + gender: Int @doc(description: "The customer's gender(Male - 1, Female - 2)") + password: String @doc(description: "The customer's password") + is_subscribed: Boolean @doc(description: "Indicates whether the customer is subscribed to the company's newsletter") } -type UpdateCustomerOutput { +type CustomerOutput { customer: Customer! } @@ -365,4 +372,4 @@ enum CountryCodeEnum @doc(description: "The list of countries codes") { YE @doc(description: "Yemen") ZM @doc(description: "Zambia") ZW @doc(description: "Zimbabwe") -} \ No newline at end of file +} diff --git a/app/code/Magento/Deploy/Model/Filesystem.php b/app/code/Magento/Deploy/Model/Filesystem.php index 59880d2d669..bc19ee28785 100644 --- a/app/code/Magento/Deploy/Model/Filesystem.php +++ b/app/code/Magento/Deploy/Model/Filesystem.php @@ -29,8 +29,8 @@ class Filesystem * Access permissions to the files are set during deploy Magento 2, directly after * uploading code of Magento. Also it is possible to specify the value * of inverse mask for setting access permissions to files generated by Magento. - * @link http://devdocs.magento.com/guides/v2.0/install-gde/install/post-install-umask.html - * @link http://devdocs.magento.com/guides/v2.0/install-gde/prereq/file-system-perms.html + * @link https://devdocs.magento.com/guides/v2.0/install-gde/install/post-install-umask.html + * @link https://devdocs.magento.com/guides/v2.0/install-gde/prereq/file-system-perms.html */ const PERMISSIONS_FILE = 0640; @@ -41,8 +41,8 @@ class Filesystem * Access permissions to the directories are set during deploy Magento 2, directly after * uploading code of Magento. Also it is possible to specify the value * of inverse mask for setting access permissions to directories generated by Magento. - * @link http://devdocs.magento.com/guides/v2.0/install-gde/install/post-install-umask.html - * @link http://devdocs.magento.com/guides/v2.0/install-gde/prereq/file-system-perms.html + * @link https://devdocs.magento.com/guides/v2.0/install-gde/install/post-install-umask.html + * @link https://devdocs.magento.com/guides/v2.0/install-gde/prereq/file-system-perms.html */ const PERMISSIONS_DIR = 0750; @@ -150,6 +150,8 @@ public function __construct( * * @param OutputInterface $output * @return void + * @throws LocalizedException + * @throws \Exception */ public function regenerateStatic( OutputInterface $output @@ -164,9 +166,12 @@ public function regenerateStatic( DirectoryList::STATIC_VIEW ] ); - + + $this->reinitCacheDirectories(); // Trigger code generation $this->compile($output); + + $this->reinitCacheDirectories(); // Trigger static assets compilation and deployment $this->deployStaticContent($output); } @@ -217,6 +222,7 @@ private function getAdminUserInterfaceLocales() * * @return array * @throws \InvalidArgumentException if unknown locale is provided by the store configuration + * @throws \Magento\Framework\Exception\FileSystemException */ private function getUsedLocales() { @@ -249,13 +255,6 @@ function ($locale) { protected function compile(OutputInterface $output) { $output->writeln('Starting compilation'); - $this->cleanupFilesystem( - [ - DirectoryList::CACHE, - DirectoryList::GENERATED_CODE, - DirectoryList::GENERATED_METADATA, - ] - ); $cmd = $this->functionCallPath . 'setup:di:compile'; /** @@ -279,6 +278,7 @@ protected function compile(OutputInterface $output) * * @param array $directoryCodeList * @return void + * @throws \Magento\Framework\Exception\FileSystemException */ public function cleanupFilesystem($directoryCodeList) { @@ -320,8 +320,9 @@ public function cleanupFilesystem($directoryCodeList) * Access permissions to the files and directories are set during deploy Magento 2, directly after * uploading code of Magento. Also it is possible to specify the value * of inverse mask for setting access permissions to files and directories generated by Magento. - * @link http://devdocs.magento.com/guides/v2.0/install-gde/install/post-install-umask.html - * @link http://devdocs.magento.com/guides/v2.0/install-gde/prereq/file-system-perms.html + * @link https://devdocs.magento.com/guides/v2.0/install-gde/install/post-install-umask.html + * @link https://devdocs.magento.com/guides/v2.0/install-gde/prereq/file-system-perms.html + * @throws \Magento\Framework\Exception\FileSystemException */ protected function changePermissions($directoryCodeList, $dirPermissions, $filePermissions) { @@ -345,8 +346,9 @@ protected function changePermissions($directoryCodeList, $dirPermissions, $fileP * Access permissions to the files and directories are set during deploy Magento 2, directly after * uploading code of Magento. Also it is possible to specify the value * of inverse mask for setting access permissions to files and directories generated by Magento. - * @link http://devdocs.magento.com/guides/v2.0/install-gde/install/post-install-umask.html - * @link http://devdocs.magento.com/guides/v2.0/install-gde/prereq/file-system-perms.html + * @link https://devdocs.magento.com/guides/v2.0/install-gde/install/post-install-umask.html + * @link https://devdocs.magento.com/guides/v2.0/install-gde/prereq/file-system-perms.html + * @throws \Magento\Framework\Exception\FileSystemException */ public function lockStaticResources() { @@ -361,4 +363,15 @@ public function lockStaticResources() self::PERMISSIONS_FILE ); } + + /** + * Flush cache and restore the basic cache directories. + * + * @throws LocalizedException + */ + private function reinitCacheDirectories() + { + $command = $this->functionCallPath . 'cache:flush'; + $this->shell->execute($command); + } } diff --git a/app/code/Magento/Deploy/Test/Unit/Model/FilesystemTest.php b/app/code/Magento/Deploy/Test/Unit/Model/FilesystemTest.php index 00f70c6527a..d3ff594fa61 100644 --- a/app/code/Magento/Deploy/Test/Unit/Model/FilesystemTest.php +++ b/app/code/Magento/Deploy/Test/Unit/Model/FilesystemTest.php @@ -69,6 +69,9 @@ class FilesystemTest extends \PHPUnit\Framework\TestCase */ private $cmdPrefix; + /** + * @inheritdoc + */ protected function setUp() { $objectManager = new ObjectManager($this); @@ -124,6 +127,9 @@ protected function setUp() $this->cmdPrefix = PHP_BINARY . ' -f ' . BP . DIRECTORY_SEPARATOR . 'bin' . DIRECTORY_SEPARATOR . 'magento '; } + /** + * @throws \Magento\Framework\Exception\LocalizedException + */ public function testRegenerateStatic() { $storeLocales = ['fr_FR', 'de_DE', 'nl_NL']; @@ -131,18 +137,16 @@ public function testRegenerateStatic() ->willReturn($storeLocales); $setupDiCompileCmd = $this->cmdPrefix . 'setup:di:compile'; - $this->shell->expects(self::at(0)) - ->method('execute') - ->with($setupDiCompileCmd); - $this->initAdminLocaleMock('en_US'); $usedLocales = ['fr_FR', 'de_DE', 'nl_NL', 'en_US']; + $cacheFlushCmd = $this->cmdPrefix . 'cache:flush'; $staticContentDeployCmd = $this->cmdPrefix . 'setup:static-content:deploy -f ' . implode(' ', $usedLocales); - $this->shell->expects(self::at(1)) + $this->shell + ->expects($this->exactly(4)) ->method('execute') - ->with($staticContentDeployCmd); + ->withConsecutive([$cacheFlushCmd], [$setupDiCompileCmd], [$cacheFlushCmd], [$staticContentDeployCmd]); $this->output->expects(self::at(0)) ->method('writeln') @@ -166,6 +170,7 @@ public function testRegenerateStatic() * @return void * @expectedException \InvalidArgumentException * @expectedExceptionMessage ;echo argument has invalid value, run info:language:list for list of available locales + * @throws \Magento\Framework\Exception\LocalizedException */ public function testGenerateStaticForNotAllowedStoreViewLocale() { @@ -184,6 +189,7 @@ public function testGenerateStaticForNotAllowedStoreViewLocale() * @return void * @expectedException \InvalidArgumentException * @expectedExceptionMessage ;echo argument has invalid value, run info:language:list for list of available locales + * @throws \Magento\Framework\Exception\LocalizedException */ public function testGenerateStaticForNotAllowedAdminLocale() { diff --git a/app/code/Magento/Deploy/etc/di.xml b/app/code/Magento/Deploy/etc/di.xml index fd604aa1b39..0c32baebf12 100644 --- a/app/code/Magento/Deploy/etc/di.xml +++ b/app/code/Magento/Deploy/etc/di.xml @@ -71,18 +71,4 @@ </argument> </arguments> </type> - <type name="Magento\Deploy\App\Mode\ConfigProvider"> - <arguments> - <argument name="config" xsi:type="array"> - <item name="developer" xsi:type="array"> - <item name="production" xsi:type="array"> - <item name="dev/debug/debug_logging" xsi:type="string">0</item> - </item> - <item name="developer" xsi:type="array"> - <item name="dev/debug/debug_logging" xsi:type="null" /> - </item> - </item> - </argument> - </arguments> - </type> </config> diff --git a/app/code/Magento/Developer/Model/Logger/Handler/Debug.php b/app/code/Magento/Developer/Model/Logger/Handler/Debug.php index 9bfee42fa6a..ba98524bb66 100644 --- a/app/code/Magento/Developer/Model/Logger/Handler/Debug.php +++ b/app/code/Magento/Developer/Model/Logger/Handler/Debug.php @@ -5,10 +5,10 @@ */ namespace Magento\Developer\Model\Logger\Handler; +use Magento\Config\Setup\ConfigOptionsList; use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\State; use Magento\Framework\Filesystem\DriverInterface; -use Magento\Store\Model\ScopeInterface; use Magento\Framework\App\DeploymentConfig; /** @@ -37,6 +37,7 @@ class Debug extends \Magento\Framework\Logger\Handler\Debug * @param ScopeConfigInterface $scopeConfig * @param DeploymentConfig $deploymentConfig * @param string $filePath + * @throws \Exception */ public function __construct( DriverInterface $filesystem, @@ -53,16 +54,32 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function isHandling(array $record) { if ($this->deploymentConfig->isAvailable()) { return parent::isHandling($record) - && $this->scopeConfig->getValue('dev/debug/debug_logging', ScopeInterface::SCOPE_STORE); + && $this->isLoggingEnabled(); } return parent::isHandling($record); } + + /** + * Check that logging functionality is enabled. + * + * @return bool + */ + private function isLoggingEnabled(): bool + { + $configValue = $this->deploymentConfig->get(ConfigOptionsList::CONFIG_PATH_DEBUG_LOGGING); + if ($configValue === null) { + $isEnabled = $this->state->getMode() !== State::MODE_PRODUCTION; + } else { + $isEnabled = (bool)$configValue; + } + return $isEnabled; + } } diff --git a/app/code/Magento/Developer/Model/Logger/Handler/Syslog.php b/app/code/Magento/Developer/Model/Logger/Handler/Syslog.php index 6dc276a696f..3f5ff586403 100644 --- a/app/code/Magento/Developer/Model/Logger/Handler/Syslog.php +++ b/app/code/Magento/Developer/Model/Logger/Handler/Syslog.php @@ -7,22 +7,19 @@ namespace Magento\Developer\Model\Logger\Handler; +use Magento\Config\Setup\ConfigOptionsList; use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\DeploymentConfig; /** - * Enable/disable syslog logging based on the store config setting. + * Enable/disable syslog logging based on the deployment config setting. */ class Syslog extends \Magento\Framework\Logger\Handler\Syslog { - public const CONFIG_PATH = 'dev/syslog/syslog_logging'; - /** - * Scope config. - * - * @var ScopeConfigInterface + * @deprecated configuration value has been removed. */ - private $scopeConfig; + public const CONFIG_PATH = 'dev/syslog/syslog_logging'; /** * Deployment config. @@ -35,6 +32,7 @@ class Syslog extends \Magento\Framework\Logger\Handler\Syslog * @param ScopeConfigInterface $scopeConfig Scope config * @param DeploymentConfig $deploymentConfig Deployment config * @param string $ident The string ident to be added to each message + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function __construct( ScopeConfigInterface $scopeConfig, @@ -42,8 +40,6 @@ public function __construct( string $ident ) { parent::__construct($ident); - - $this->scopeConfig = $scopeConfig; $this->deploymentConfig = $deploymentConfig; } @@ -53,7 +49,18 @@ public function __construct( public function isHandling(array $record): bool { return parent::isHandling($record) - && $this->deploymentConfig->isAvailable() - && $this->scopeConfig->getValue(self::CONFIG_PATH); + && $this->deploymentConfig->isDbAvailable() + && $this->isLoggingEnabled(); + } + + /** + * Check that logging functionality is enabled. + * + * @return bool + */ + private function isLoggingEnabled(): bool + { + $configValue = $this->deploymentConfig->get(ConfigOptionsList::CONFIG_PATH_SYSLOG_LOGGING); + return (bool)$configValue; } } diff --git a/app/code/Magento/Developer/Model/Setup/Declaration/Schema/WhitelistGenerator.php b/app/code/Magento/Developer/Model/Setup/Declaration/Schema/WhitelistGenerator.php index 5cdcc6eb99a..b752eaa111f 100644 --- a/app/code/Magento/Developer/Model/Setup/Declaration/Schema/WhitelistGenerator.php +++ b/app/code/Magento/Developer/Model/Setup/Declaration/Schema/WhitelistGenerator.php @@ -206,12 +206,15 @@ private function getElementsWithAutogeneratedName(Schema $schema, string $tableN foreach ($tableData[$elementType] as $tableElementData) { if ($tableElementData['type'] === 'foreign') { $referenceTable = $schema->getTableByName($tableElementData['referenceTable']); - $constraintName = $this->elementNameResolver->getFullFKName( - $table, - $table->getColumnByName($tableElementData['column']), - $referenceTable, - $referenceTable->getColumnByName($tableElementData['referenceColumn']) - ); + $column = $table->getColumnByName($tableElementData['column']); + $referenceColumn = $referenceTable->getColumnByName($tableElementData['referenceColumn']); + $constraintName = ($column !== false && $referenceColumn !== false) ? + $this->elementNameResolver->getFullFKName( + $table, + $column, + $referenceTable, + $referenceColumn + ) : null; } else { $constraintName = $this->elementNameResolver->getFullIndexName( $table, @@ -219,7 +222,9 @@ private function getElementsWithAutogeneratedName(Schema $schema, string $tableN $tableElementData['type'] ); } - $declaredStructure[$elementType][$constraintName] = true; + if ($constraintName) { + $declaredStructure[$elementType][$constraintName] = true; + } } } diff --git a/app/code/Magento/Developer/Test/Unit/Model/Logger/Handler/DebugTest.php b/app/code/Magento/Developer/Test/Unit/Model/Logger/Handler/DebugTest.php index c116775d582..1c729c933ec 100644 --- a/app/code/Magento/Developer/Test/Unit/Model/Logger/Handler/DebugTest.php +++ b/app/code/Magento/Developer/Test/Unit/Model/Logger/Handler/DebugTest.php @@ -51,6 +51,9 @@ class DebugTest extends \PHPUnit\Framework\TestCase */ private $deploymentConfigMock; + /** + * @inheritdoc + */ protected function setUp() { $this->filesystemMock = $this->getMockBuilder(DriverInterface::class) @@ -80,70 +83,95 @@ protected function setUp() $this->model->setFormatter($this->formatterMock); } - public function testHandle() + /** + * @return void + */ + public function testHandleEnabledInDeveloperMode() { $this->deploymentConfigMock->expects($this->once()) ->method('isAvailable') ->willReturn(true); - $this->stateMock->expects($this->never()) - ->method('getMode'); - $this->scopeConfigMock->expects($this->once()) - ->method('getValue') - ->with('dev/debug/debug_logging', ScopeInterface::SCOPE_STORE, null) - ->willReturn(true); + $this->stateMock + ->expects($this->once()) + ->method('getMode') + ->willReturn(State::MODE_DEVELOPER); + $this->scopeConfigMock + ->expects($this->never()) + ->method('getValue'); $this->assertTrue($this->model->isHandling(['formatted' => false, 'level' => Logger::DEBUG])); } - public function testHandleDisabledByProduction() + /** + * @return void + */ + public function testHandleEnabledInDefaultMode() { $this->deploymentConfigMock->expects($this->once()) ->method('isAvailable') ->willReturn(true); - $this->stateMock->expects($this->never()) - ->method('getMode'); - $this->scopeConfigMock->expects($this->once()) + $this->stateMock + ->expects($this->once()) + ->method('getMode') + ->willReturn(State::MODE_DEFAULT); + $this->scopeConfigMock + ->expects($this->never()) ->method('getValue'); - $this->assertFalse($this->model->isHandling(['formatted' => false, 'level' => Logger::DEBUG])); + $this->assertTrue($this->model->isHandling(['formatted' => false, 'level' => Logger::DEBUG])); } - public function testHandleDisabledByConfig() + /** + * @return void + */ + public function testHandleDisabledByProduction() { $this->deploymentConfigMock->expects($this->once()) ->method('isAvailable') ->willReturn(true); - $this->stateMock->expects($this->never()) - ->method('getMode'); - $this->scopeConfigMock->expects($this->once()) - ->method('getValue') - ->with('dev/debug/debug_logging', ScopeInterface::SCOPE_STORE, null) - ->willReturn(false); + $this->stateMock + ->expects($this->once()) + ->method('getMode') + ->willReturn(State::MODE_PRODUCTION); + $this->scopeConfigMock + ->expects($this->never()) + ->method('getValue'); $this->assertFalse($this->model->isHandling(['formatted' => false, 'level' => Logger::DEBUG])); } + /** + * @return void + */ public function testHandleDisabledByLevel() { $this->deploymentConfigMock->expects($this->once()) ->method('isAvailable') ->willReturn(true); - $this->stateMock->expects($this->never()) - ->method('getMode'); - $this->scopeConfigMock->expects($this->never()) + $this->stateMock + ->expects($this->never()) + ->method('getMode') + ->willReturn(State::MODE_DEVELOPER); + $this->scopeConfigMock + ->expects($this->never()) ->method('getValue'); $this->assertFalse($this->model->isHandling(['formatted' => false, 'level' => Logger::API])); } + /** + * @return void + */ public function testDeploymentConfigIsNotAvailable() { $this->deploymentConfigMock->expects($this->once()) ->method('isAvailable') ->willReturn(false); - $this->stateMock->expects($this->never()) + $this->stateMock + ->expects($this->never()) ->method('getMode'); - $this->scopeConfigMock->expects($this->never()) + $this->scopeConfigMock + ->expects($this->never()) ->method('getValue'); $this->assertTrue($this->model->isHandling(['formatted' => false, 'level' => Logger::DEBUG])); diff --git a/app/code/Magento/Developer/Test/Unit/Model/Logger/Handler/SyslogTest.php b/app/code/Magento/Developer/Test/Unit/Model/Logger/Handler/SyslogTest.php index c744645b670..06c19d3f2e8 100644 --- a/app/code/Magento/Developer/Test/Unit/Model/Logger/Handler/SyslogTest.php +++ b/app/code/Magento/Developer/Test/Unit/Model/Logger/Handler/SyslogTest.php @@ -7,6 +7,7 @@ namespace Magento\Developer\Test\Unit\Model\Logger\Handler; +use Magento\Config\Setup\ConfigOptionsList; use Magento\Developer\Model\Logger\Handler\Syslog; use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\DeploymentConfig; @@ -34,6 +35,9 @@ class SyslogTest extends TestCase */ private $deploymentConfigMock; + /** + * @inheritdoc + */ protected function setUp() { $this->scopeConfigMock = $this->getMockForAbstractClass(ScopeConfigInterface::class); @@ -46,35 +50,48 @@ protected function setUp() ); } + /** + * @return void + */ public function testIsHandling(): void { $record = [ 'level' => Monolog::DEBUG, ]; - $this->scopeConfigMock->expects($this->once()) - ->method('getValue') - ->with(Syslog::CONFIG_PATH) - ->willReturn('1'); - $this->deploymentConfigMock->expects($this->once()) - ->method('isAvailable') + $this->scopeConfigMock + ->expects($this->never()) + ->method('getValue'); + $this->deploymentConfigMock + ->expects($this->once()) + ->method('isDbAvailable') ->willReturn(true); + $this->deploymentConfigMock + ->expects($this->once()) + ->method('get') + ->with(ConfigOptionsList::CONFIG_PATH_SYSLOG_LOGGING) + ->willReturn(1); $this->assertTrue( $this->model->isHandling($record) ); } + /** + * @return void + */ public function testIsHandlingNotInstalled(): void { $record = [ 'level' => Monolog::DEBUG, ]; - $this->scopeConfigMock->expects($this->never()) + $this->scopeConfigMock + ->expects($this->never()) ->method('getValue'); - $this->deploymentConfigMock->expects($this->once()) - ->method('isAvailable') + $this->deploymentConfigMock + ->expects($this->once()) + ->method('isDbAvailable') ->willReturn(false); $this->assertFalse( @@ -82,19 +99,27 @@ public function testIsHandlingNotInstalled(): void ); } + /** + * @return void + */ public function testIsHandlingDisabled(): void { $record = [ 'level' => Monolog::DEBUG, ]; - $this->scopeConfigMock->expects($this->once()) - ->method('getValue') - ->with(Syslog::CONFIG_PATH) - ->willReturn('0'); - $this->deploymentConfigMock->expects($this->once()) - ->method('isAvailable') + $this->scopeConfigMock + ->expects($this->never()) + ->method('getValue'); + $this->deploymentConfigMock + ->expects($this->once()) + ->method('isDbAvailable') ->willReturn(true); + $this->deploymentConfigMock + ->expects($this->once()) + ->method('get') + ->with(ConfigOptionsList::CONFIG_PATH_SYSLOG_LOGGING) + ->willReturn(0); $this->assertFalse( $this->model->isHandling($record) diff --git a/app/code/Magento/Developer/etc/adminhtml/system.xml b/app/code/Magento/Developer/etc/adminhtml/system.xml index 4ebc45f1a2c..c64abd6eae7 100644 --- a/app/code/Magento/Developer/etc/adminhtml/system.xml +++ b/app/code/Magento/Developer/etc/adminhtml/system.xml @@ -25,20 +25,6 @@ <backend_model>Magento\Developer\Model\Config\Backend\AllowedIps</backend_model> </field> </group> - <group id="debug" type="text" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="1"> - <field id="debug_logging" translate="label comment" type="select" sortOrder="30" showInDefault="1" showInWebsite="0" showInStore="0"> - <label>Log to File</label> - <comment>Not available in production mode.</comment> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - </field> - </group> - <group id="syslog" translate="label" type="text" sortOrder="40" showInDefault="1" showInWebsite="1" showInStore="1"> - <label>Syslog</label> - <field id="syslog_logging" translate="label comment" type="select" sortOrder="10" showInDefault="1" showInWebsite="0" showInStore="0"> - <label>Log to Syslog</label> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> - </field> - </group> </section> </system> </config> diff --git a/app/code/Magento/Dhl/Model/Carrier.php b/app/code/Magento/Dhl/Model/Carrier.php index d997db6ac1a..42716d73373 100644 --- a/app/code/Magento/Dhl/Model/Carrier.php +++ b/app/code/Magento/Dhl/Model/Carrier.php @@ -7,6 +7,7 @@ namespace Magento\Dhl\Model; use Magento\Catalog\Model\Product\Type; +use Magento\Framework\App\ProductMetadataInterface; use Magento\Framework\Module\Dir; use Magento\Sales\Exception\DocumentValidationException; use Magento\Sales\Model\Order\Shipment; @@ -56,6 +57,13 @@ class Carrier extends \Magento\Dhl\Model\AbstractDhl implements \Magento\Shippin */ const CODE = 'dhl'; + /** + * DHL service prefixes used for message reference + */ + private const SERVICE_PREFIX_QUOTE = 'QUOT'; + private const SERVICE_PREFIX_SHIPVAL = 'SHIP'; + private const SERVICE_PREFIX_TRACKING = 'TRCK'; + /** * Rate request data * @@ -206,6 +214,11 @@ class Carrier extends \Magento\Dhl\Model\AbstractDhl implements \Magento\Shippin */ private $xmlValidator; + /** + * @var ProductMetadataInterface + */ + private $productMetadata; + /** * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig * @param \Magento\Quote\Model\Quote\Address\RateResult\ErrorFactory $rateErrorFactory @@ -232,7 +245,8 @@ class Carrier extends \Magento\Dhl\Model\AbstractDhl implements \Magento\Shippin * @param \Magento\Framework\Stdlib\DateTime $dateTime * @param \Magento\Framework\HTTP\ZendClientFactory $httpClientFactory * @param array $data - * @param \Magento\Dhl\Model\Validator\XmlValidator $xmlValidator + * @param \Magento\Dhl\Model\Validator\XmlValidator|null $xmlValidator + * @param ProductMetadataInterface|null $productMetadata * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -261,7 +275,8 @@ public function __construct( \Magento\Framework\Stdlib\DateTime $dateTime, \Magento\Framework\HTTP\ZendClientFactory $httpClientFactory, array $data = [], - \Magento\Dhl\Model\Validator\XmlValidator $xmlValidator = null + \Magento\Dhl\Model\Validator\XmlValidator $xmlValidator = null, + ProductMetadataInterface $productMetadata = null ) { $this->readFactory = $readFactory; $this->_carrierHelper = $carrierHelper; @@ -295,6 +310,8 @@ public function __construct( } $this->xmlValidator = $xmlValidator ?: \Magento\Framework\App\ObjectManager::getInstance()->get(XmlValidator::class); + $this->productMetadata = $productMetadata + ?: \Magento\Framework\App\ObjectManager::getInstance()->get(ProductMetadataInterface::class); } /** @@ -983,18 +1000,29 @@ protected function _getQuotesFromServer($request) protected function _buildQuotesRequestXml() { $rawRequest = $this->_rawRequest; - $xmlStr = '<?xml version = "1.0" encoding = "UTF-8"?>' . - '<p:DCTRequest xmlns:p="http://www.dhl.com" xmlns:p1="http://www.dhl.com/datatypes" ' . - 'xmlns:p2="http://www.dhl.com/DCTRequestdatatypes" ' . + + $xmlStr = '<?xml version="1.0" encoding = "UTF-8"?>' . + '<req:DCTRequest schemaVersion="2.0" ' . + 'xmlns:req="http://www.dhl.com" ' . 'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ' . - 'xsi:schemaLocation="http://www.dhl.com DCT-req.xsd "/>'; + 'xsi:schemaLocation="http://www.dhl.com DCT-req_global-2.0.xsd"/>'; + $xml = $this->_xmlElFactory->create(['data' => $xmlStr]); $nodeGetQuote = $xml->addChild('GetQuote', '', ''); $nodeRequest = $nodeGetQuote->addChild('Request'); $nodeServiceHeader = $nodeRequest->addChild('ServiceHeader'); - $nodeServiceHeader->addChild('SiteID', (string)$this->getConfigData('id')); - $nodeServiceHeader->addChild('Password', (string)$this->getConfigData('password')); + $nodeServiceHeader->addChild('MessageTime', $this->buildMessageTimestamp()); + $nodeServiceHeader->addChild( + 'MessageReference', + $this->buildMessageReference(self::SERVICE_PREFIX_QUOTE) + ); + $nodeServiceHeader->addChild('SiteID', (string) $this->getConfigData('id')); + $nodeServiceHeader->addChild('Password', (string) $this->getConfigData('password')); + + $nodeMetaData = $nodeRequest->addChild('MetaData'); + $nodeMetaData->addChild('SoftwareName', $this->buildSoftwareName()); + $nodeMetaData->addChild('SoftwareVersion', $this->buildSoftwareVersion()); $nodeFrom = $nodeGetQuote->addChild('From'); $nodeFrom->addChild('CountryCode', $rawRequest->getOrigCountryId()); @@ -1386,44 +1414,37 @@ protected function _doRequest() { $rawRequest = $this->_request; - $originRegion = $this->getCountryParams( - $this->_scopeConfig->getValue( - Shipment::XML_PATH_STORE_COUNTRY_ID, - \Magento\Store\Model\ScopeInterface::SCOPE_STORE, - $this->getStore() - ) - )->getRegion(); - - if (!$originRegion) { - throw new \Magento\Framework\Exception\LocalizedException(__('Wrong Region')); - } - - if ($originRegion == 'AM') { - $originRegion = ''; - } - $xmlStr = '<?xml version="1.0" encoding="UTF-8"?>' . - '<req:ShipmentValidateRequest' . - $originRegion . + '<req:ShipmentRequest' . ' xmlns:req="http://www.dhl.com"' . ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' . - ' xsi:schemaLocation="http://www.dhl.com ship-val-req' . - ($originRegion ? '_' . - $originRegion : '') . - '.xsd" />'; + ' xsi:schemaLocation="http://www.dhl.com ship-val-global-req-6.0.xsd"' . + ' schemaVersion="6.0" />'; $xml = $this->_xmlElFactory->create(['data' => $xmlStr]); $nodeRequest = $xml->addChild('Request', '', ''); $nodeServiceHeader = $nodeRequest->addChild('ServiceHeader'); + $nodeServiceHeader->addChild('MessageTime', $this->buildMessageTimestamp()); + // MessageReference must be 28 to 32 chars. + $nodeServiceHeader->addChild( + 'MessageReference', + $this->buildMessageReference(self::SERVICE_PREFIX_SHIPVAL) + ); $nodeServiceHeader->addChild('SiteID', (string)$this->getConfigData('id')); $nodeServiceHeader->addChild('Password', (string)$this->getConfigData('password')); - if (!$originRegion) { - $xml->addChild('RequestedPickupTime', 'N', ''); - } - if ($originRegion !== 'AP') { - $xml->addChild('NewShipper', 'N', ''); + $originRegion = $this->getCountryParams( + $this->_scopeConfig->getValue( + Shipment::XML_PATH_STORE_COUNTRY_ID, + \Magento\Store\Model\ScopeInterface::SCOPE_STORE, + $this->getStore() + ) + )->getRegion(); + if ($originRegion) { + $xml->addChild('RegionCode', $originRegion, ''); } + $xml->addChild('RequestedPickupTime', 'N', ''); + $xml->addChild('NewShipper', 'N', ''); $xml->addChild('LanguageCode', 'EN', ''); $xml->addChild('PiecesEnabled', 'Y', ''); @@ -1465,8 +1486,9 @@ protected function _doRequest() } $nodeConsignee->addChild('City', $rawRequest->getRecipientAddressCity()); - if ($originRegion !== 'AP') { - $nodeConsignee->addChild('Division', $rawRequest->getRecipientAddressStateOrProvinceCode()); + $recipientAddressStateOrProvinceCode = $rawRequest->getRecipientAddressStateOrProvinceCode(); + if ($recipientAddressStateOrProvinceCode) { + $nodeConsignee->addChild('Division', $recipientAddressStateOrProvinceCode); } $nodeConsignee->addChild('PostalCode', $rawRequest->getRecipientAddressPostalCode()); $nodeConsignee->addChild('CountryCode', $rawRequest->getRecipientAddressCountryCode()); @@ -1510,15 +1532,13 @@ protected function _doRequest() $nodeReference->addChild('ReferenceType', 'St'); /** Shipment Details */ - $this->_shipmentDetails($xml, $rawRequest, $originRegion); + $this->_shipmentDetails($xml, $rawRequest); /** Shipper */ $nodeShipper = $xml->addChild('Shipper', '', ''); $nodeShipper->addChild('ShipperID', (string)$this->getConfigData('account')); $nodeShipper->addChild('CompanyName', $rawRequest->getShipperContactCompanyName()); - if ($originRegion !== 'AP') { - $nodeShipper->addChild('RegisteredAccount', (string)$this->getConfigData('account')); - } + $nodeShipper->addChild('RegisteredAccount', (string)$this->getConfigData('account')); $address = $rawRequest->getShipperAddressStreet1() . ' ' . $rawRequest->getShipperAddressStreet2(); $address = $this->string->split($address, 35, false, true); @@ -1531,8 +1551,9 @@ protected function _doRequest() } $nodeShipper->addChild('City', $rawRequest->getShipperAddressCity()); - if ($originRegion !== 'AP') { - $nodeShipper->addChild('Division', $rawRequest->getShipperAddressStateOrProvinceCode()); + $shipperAddressStateOrProvinceCode = $rawRequest->getShipperAddressStateOrProvinceCode(); + if ($shipperAddressStateOrProvinceCode) { + $nodeShipper->addChild('Division', $shipperAddressStateOrProvinceCode); } $nodeShipper->addChild('PostalCode', $rawRequest->getShipperAddressPostalCode()); $nodeShipper->addChild('CountryCode', $rawRequest->getShipperAddressCountryCode()); @@ -1584,19 +1605,13 @@ protected function _doRequest() * @return void * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ protected function _shipmentDetails($xml, $rawRequest, $originRegion = '') { $nodeShipmentDetails = $xml->addChild('ShipmentDetails', '', ''); $nodeShipmentDetails->addChild('NumberOfPieces', count($rawRequest->getPackages())); - if ($originRegion) { - $nodeShipmentDetails->addChild( - 'CurrencyCode', - $this->_storeManager->getWebsite($this->_request->getWebsiteId())->getBaseCurrencyCode() - ); - } - $nodePieces = $nodeShipmentDetails->addChild('Pieces', '', ''); /* @@ -1615,18 +1630,12 @@ protected function _shipmentDetails($xml, $rawRequest, $originRegion = '') } $nodePiece->addChild('PieceID', ++$i); $nodePiece->addChild('PackageType', $packageType); - $nodePiece->addChild('Weight', sprintf('%.1f', $package['params']['weight'])); + $nodePiece->addChild('Weight', sprintf('%.3f', $package['params']['weight'])); $params = $package['params']; if ($params['width'] && $params['length'] && $params['height']) { - if (!$originRegion) { - $nodePiece->addChild('Width', round($params['width'])); - $nodePiece->addChild('Height', round($params['height'])); - $nodePiece->addChild('Depth', round($params['length'])); - } else { - $nodePiece->addChild('Depth', round($params['length'])); - $nodePiece->addChild('Width', round($params['width'])); - $nodePiece->addChild('Height', round($params['height'])); - } + $nodePiece->addChild('Width', round($params['width'])); + $nodePiece->addChild('Height', round($params['height'])); + $nodePiece->addChild('Depth', round($params['length'])); } $content = []; foreach ($package['items'] as $item) { @@ -1635,58 +1644,40 @@ protected function _shipmentDetails($xml, $rawRequest, $originRegion = '') $nodePiece->addChild('PieceContents', substr(implode(',', $content), 0, 34)); } - if (!$originRegion) { - $nodeShipmentDetails->addChild('Weight', sprintf('%.1f', $rawRequest->getPackageWeight())); - $nodeShipmentDetails->addChild('WeightUnit', substr($this->_getWeightUnit(), 0, 1)); - $nodeShipmentDetails->addChild('GlobalProductCode', $rawRequest->getShippingMethod()); - $nodeShipmentDetails->addChild('LocalProductCode', $rawRequest->getShippingMethod()); - $nodeShipmentDetails->addChild('Date', $this->_coreDate->date('Y-m-d')); - $nodeShipmentDetails->addChild('Contents', 'DHL Parcel'); - /** - * The DoorTo Element defines the type of delivery service that applies to the shipment. - * The valid values are DD (Door to Door), DA (Door to Airport) , AA and DC (Door to - * Door non-compliant) - */ - $nodeShipmentDetails->addChild('DoorTo', 'DD'); - $nodeShipmentDetails->addChild('DimensionUnit', substr($this->_getDimensionUnit(), 0, 1)); - if ($package['params']['container'] == self::DHL_CONTENT_TYPE_NON_DOC) { - $packageType = 'CP'; - } - $nodeShipmentDetails->addChild('PackageType', $packageType); - if ($this->isDutiable($rawRequest->getOrigCountryId(), $rawRequest->getDestCountryId())) { - $nodeShipmentDetails->addChild('IsDutiable', 'Y'); - } - $nodeShipmentDetails->addChild( - 'CurrencyCode', - $this->_storeManager->getWebsite($this->_request->getWebsiteId())->getBaseCurrencyCode() - ); - } else { - if ($package['params']['container'] == self::DHL_CONTENT_TYPE_NON_DOC) { - $packageType = 'CP'; - } - $nodeShipmentDetails->addChild('PackageType', $packageType); - $nodeShipmentDetails->addChild('Weight', sprintf('%.3f', $rawRequest->getPackageWeight())); - $nodeShipmentDetails->addChild('DimensionUnit', substr($this->_getDimensionUnit(), 0, 1)); - $nodeShipmentDetails->addChild('WeightUnit', substr($this->_getWeightUnit(), 0, 1)); - $nodeShipmentDetails->addChild('GlobalProductCode', $rawRequest->getShippingMethod()); - $nodeShipmentDetails->addChild('LocalProductCode', $rawRequest->getShippingMethod()); - - /** - * The DoorTo Element defines the type of delivery service that applies to the shipment. - * The valid values are DD (Door to Door), DA (Door to Airport) , AA and DC (Door to - * Door non-compliant) - */ - $nodeShipmentDetails->addChild('DoorTo', 'DD'); - $nodeShipmentDetails->addChild('Date', $this->_coreDate->date('Y-m-d')); - $nodeShipmentDetails->addChild('Contents', 'DHL Parcel TEST'); + $nodeShipmentDetails->addChild('Weight', sprintf('%.3f', $rawRequest->getPackageWeight())); + $nodeShipmentDetails->addChild('WeightUnit', substr($this->_getWeightUnit(), 0, 1)); + $nodeShipmentDetails->addChild('GlobalProductCode', $rawRequest->getShippingMethod()); + $nodeShipmentDetails->addChild('LocalProductCode', $rawRequest->getShippingMethod()); + $nodeShipmentDetails->addChild( + 'Date', + $this->_coreDate->date('Y-m-d', strtotime('now + 1day')) + ); + $nodeShipmentDetails->addChild('Contents', 'DHL Parcel'); + /** + * The DoorTo Element defines the type of delivery service that applies to the shipment. + * The valid values are DD (Door to Door), DA (Door to Airport) , AA and DC (Door to + * Door non-compliant) + */ + $nodeShipmentDetails->addChild('DoorTo', 'DD'); + $nodeShipmentDetails->addChild('DimensionUnit', substr($this->_getDimensionUnit(), 0, 1)); + if ($package['params']['container'] == self::DHL_CONTENT_TYPE_NON_DOC) { + $packageType = 'CP'; + } + $nodeShipmentDetails->addChild('PackageType', $packageType); + if ($this->isDutiable($rawRequest->getOrigCountryId(), $rawRequest->getDestCountryId())) { + $nodeShipmentDetails->addChild('IsDutiable', 'Y'); } + $nodeShipmentDetails->addChild( + 'CurrencyCode', + $this->_storeManager->getWebsite($this->_request->getWebsiteId())->getBaseCurrencyCode() + ); } /** * Get tracking * * @param string|string[] $trackings - * @return Result|null + * @return \Magento\Shipping\Model\Tracking\Result|null */ public function getTracking($trackings) { @@ -1710,12 +1701,15 @@ protected function _getXMLTracking($trackings) '<req:KnownTrackingRequest' . ' xmlns:req="http://www.dhl.com"' . ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' . - ' xsi:schemaLocation="http://www.dhl.com TrackingRequestKnown.xsd" />'; + ' xsi:schemaLocation="http://www.dhl.com TrackingRequestKnown-1.0.xsd"' . + ' schemaVersion="1.0" />'; $xml = $this->_xmlElFactory->create(['data' => $xmlStr]); $requestNode = $xml->addChild('Request', '', ''); $serviceHeaderNode = $requestNode->addChild('ServiceHeader', '', ''); + $serviceHeaderNode->addChild('MessageTime', $this->buildMessageTimestamp()); + $serviceHeaderNode->addChild('MessageReference', $this->buildMessageReference(self::SERVICE_PREFIX_TRACKING)); $serviceHeaderNode->addChild('SiteID', (string)$this->getConfigData('id')); $serviceHeaderNode->addChild('Password', (string)$this->getConfigData('password')); @@ -1959,12 +1953,14 @@ protected function _prepareShippingLabelContent(\SimpleXMLElement $xml) } /** + * Verify if the shipment is dutiable + * * @param string $origCountryId * @param string $destCountryId * * @return bool */ - protected function isDutiable($origCountryId, $destCountryId) + protected function isDutiable($origCountryId, $destCountryId) : bool { $this->_checkDomesticStatus($origCountryId, $destCountryId); @@ -1972,4 +1968,59 @@ protected function isDutiable($origCountryId, $destCountryId) self::DHL_CONTENT_TYPE_NON_DOC == $this->getConfigData('content_type') || !$this->_isDomestic; } + + /** + * Builds a datetime string to be used as the MessageTime in accordance to the expected format. + * + * @param string|null $datetime + * @return string + */ + private function buildMessageTimestamp(string $datetime = null): string + { + return $this->_coreDate->date(\DATE_RFC3339, $datetime); + } + + /** + * Builds a string to be used as the MessageReference. + * + * @param string $servicePrefix + * @return string + * @throws \Magento\Framework\Exception\LocalizedException + */ + private function buildMessageReference(string $servicePrefix): string + { + $validPrefixes = [ + self::SERVICE_PREFIX_QUOTE, + self::SERVICE_PREFIX_SHIPVAL, + self::SERVICE_PREFIX_TRACKING + ]; + + if (!in_array($servicePrefix, $validPrefixes)) { + throw new \Magento\Framework\Exception\LocalizedException( + __("Invalid service prefix \"$servicePrefix\" provided while attempting to build MessageReference") + ); + } + + return str_replace('.', '', uniqid("MAGE_{$servicePrefix}_", true)); + } + + /** + * Builds a string to be used as the request SoftwareName. + * + * @return string + */ + private function buildSoftwareName(): string + { + return substr($this->productMetadata->getName(), 0, 30); + } + + /** + * Builds a string to be used as the request SoftwareVersion. + * + * @return string + */ + private function buildSoftwareVersion(): string + { + return substr($this->productMetadata->getVersion(), 0, 10); + } } diff --git a/app/code/Magento/Dhl/Test/Unit/Model/CarrierTest.php b/app/code/Magento/Dhl/Test/Unit/Model/CarrierTest.php index 96c76a17bc3..ac458024fb6 100644 --- a/app/code/Magento/Dhl/Test/Unit/Model/CarrierTest.php +++ b/app/code/Magento/Dhl/Test/Unit/Model/CarrierTest.php @@ -8,12 +8,14 @@ use Magento\Dhl\Model\Carrier; use Magento\Dhl\Model\Validator\XmlValidator; use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\ProductMetadataInterface; use Magento\Framework\Filesystem\Directory\Read; use Magento\Framework\Filesystem\Directory\ReadFactory; use Magento\Framework\HTTP\ZendClient; use Magento\Framework\HTTP\ZendClientFactory; use Magento\Framework\Locale\ResolverInterface; use Magento\Framework\Module\Dir\Reader; +use Magento\Framework\Stdlib\DateTime\DateTime; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Framework\Xml\Security; use Magento\Quote\Model\Quote\Address\RateRequest; @@ -89,6 +91,16 @@ class CarrierTest extends \PHPUnit\Framework\TestCase */ private $logger; + /** + * @var DateTime|MockObject + */ + private $coreDateMock; + + /** + * @var ProductMetadataInterface + */ + private $productMetadataMock; + /** * @inheritdoc */ @@ -117,14 +129,6 @@ protected function setUp() $this->scope = $this->getMockForAbstractClass(ScopeConfigInterface::class); - $xmlElFactory = $this->getXmlFactory(); - $rateFactory = $this->getRateFactory(); - $rateMethodFactory = $this->getRateMethodFactory(); - $httpClientFactory = $this->getHttpClientFactory(); - $configReader = $this->getConfigReader(); - $readFactory = $this->getReadFactory(); - $storeManager = $this->getStoreManager(); - $this->error = $this->getMockBuilder(Error::class) ->setMethods(['setCarrier', 'setCarrierTitle', 'setErrorMessage']) ->getMock(); @@ -135,31 +139,45 @@ protected function setUp() $this->errorFactory->method('create') ->willReturn($this->error); - $carrierHelper = $this->getCarrierHelper(); - $this->xmlValidator = $this->getMockBuilder(XmlValidator::class) ->disableOriginalConstructor() ->getMock(); $this->logger = $this->getMockForAbstractClass(LoggerInterface::class); + $this->coreDateMock = $this->getMockBuilder(DateTime::class) + ->disableOriginalConstructor() + ->getMock(); + $this->coreDateMock->method('date') + ->willReturn('currentTime'); + + $this->productMetadataMock = $this->getMockBuilder(ProductMetadataInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->productMetadataMock->method('getName') + ->willReturn('Software_Product_Name_30_Char_123456789'); + $this->productMetadataMock->method('getVersion') + ->willReturn('10Char_Ver123456789'); + $this->model = $this->objectManager->getObject( Carrier::class, [ 'scopeConfig' => $this->scope, - 'xmlSecurity' => new Security(), - 'logger' => $this->logger, - 'xmlElFactory' => $xmlElFactory, - 'rateFactory' => $rateFactory, 'rateErrorFactory' => $this->errorFactory, - 'rateMethodFactory' => $rateMethodFactory, - 'httpClientFactory' => $httpClientFactory, - 'readFactory' => $readFactory, - 'storeManager' => $storeManager, - 'configReader' => $configReader, - 'carrierHelper' => $carrierHelper, + 'logger' => $this->logger, + 'xmlSecurity' => new Security(), + 'xmlElFactory' => $this->getXmlFactory(), + 'rateFactory' => $this->getRateFactory(), + 'rateMethodFactory' => $this->getRateMethodFactory(), + 'carrierHelper' => $this->getCarrierHelper(), + 'configReader' => $this->getConfigReader(), + 'storeManager' => $this->getStoreManager(), + 'readFactory' => $this->getReadFactory(), + 'httpClientFactory' => $this->getHttpClientFactory(), 'data' => ['id' => 'dhl', 'store' => '1'], 'xmlValidator' => $this->xmlValidator, + 'coreDate' => $this->coreDateMock, + 'productMetadata' => $this->productMetadataMock ] ); } @@ -183,7 +201,7 @@ public function scopeConfigGetValue($path) 'carriers/dhl/content_type' => 'N', 'carriers/dhl/nondoc_methods' => '1,3,4,8,P,Q,E,F,H,J,M,V,Y', 'carriers/dhl/showmethod' => 1, - 'carriers/dhl/title' => 'dhl Title', + 'carriers/dhl/title' => 'DHL Title', 'carriers/dhl/specificerrmsg' => 'dhl error message', 'carriers/dhl/unit_of_measure' => 'K', 'carriers/dhl/size' => '1', @@ -191,7 +209,7 @@ public function scopeConfigGetValue($path) 'carriers/dhl/width' => '1.6', 'carriers/dhl/depth' => '1.6', 'carriers/dhl/debug' => 1, - 'shipping/origin/country_id' => 'GB', + 'shipping/origin/country_id' => 'GB' ]; return isset($pathMap[$path]) ? $pathMap[$path] : null; } @@ -247,8 +265,14 @@ protected function _invokePrepareShippingLabelContent(\SimpleXMLElement $xml) return $method->invoke($model, $xml); } + /** + * Tests that valid rates are returned when sending a quotes request. + */ public function testCollectRates() { + $requestData = require __DIR__ . '/_files/dhl_quote_request_data.php'; + $responseXml = file_get_contents(__DIR__ . '/_files/dhl_quote_response.xml'); + $this->scope->method('getValue') ->willReturnCallback([$this, 'scopeConfigGetValue']); @@ -256,13 +280,14 @@ public function testCollectRates() ->willReturn(true); $this->httpResponse->method('getBody') - ->willReturn(file_get_contents(__DIR__ . '/_files/success_dhl_response_rates.xml')); + ->willReturn($responseXml); - /** @var RateRequest $request */ - $request = $this->objectManager->getObject( - RateRequest::class, - require __DIR__ . '/_files/rates_request_data_dhl.php' - ); + $this->coreDateMock->method('date') + ->willReturnCallback(function () { + return date(\DATE_RFC3339); + }); + + $request = $this->objectManager->getObject(RateRequest::class, $requestData); $reflectionClass = new \ReflectionObject($this->httpClient); $rawPostData = $reflectionClass->getProperty('raw_post_data'); @@ -272,13 +297,27 @@ public function testCollectRates() ->method('debug') ->with($this->stringContains('<SiteID>****</SiteID><Password>****</Password>')); - self::assertNotEmpty($this->model->collectRates($request)->getAllRates()); - self::assertContains('<Weight>18.223</Weight>', $rawPostData->getValue($this->httpClient)); - self::assertContains('<Height>0.630</Height>', $rawPostData->getValue($this->httpClient)); - self::assertContains('<Width>0.630</Width>', $rawPostData->getValue($this->httpClient)); - self::assertContains('<Depth>0.630</Depth>', $rawPostData->getValue($this->httpClient)); + $expectedRates = require __DIR__ . '/_files/dhl_quote_response_rates.php'; + $actualRates = $this->model->collectRates($request)->getAllRates(); + + self::assertEquals(count($expectedRates), count($actualRates)); + + foreach ($actualRates as $i => $actualRate) { + $actualRate = $actualRate->getData(); + unset($actualRate['method_title']); + self::assertEquals($expectedRates[$i], $actualRate); + } + + $requestXml = $rawPostData->getValue($this->httpClient); + self::assertContains('<Weight>18.223</Weight>', $requestXml); + self::assertContains('<Height>0.630</Height>', $requestXml); + self::assertContains('<Width>0.630</Width>', $requestXml); + self::assertContains('<Depth>0.630</Depth>', $requestXml); } + /** + * Tests that an error is returned when attempting to collect rates for an inactive shipping method. + */ public function testCollectRatesErrorMessage() { $this->scope->method('getValue') @@ -296,23 +335,28 @@ public function testCollectRatesErrorMessage() $this->assertSame($this->error, $this->model->collectRates($request)); } - public function testCollectRatesFail() - { - $this->scope->expects($this->once())->method('isSetFlag')->willReturn(true); - - $request = new RateRequest(); - $request->setPackageWeight(1); - - $this->assertFalse(false, $this->model->collectRates($request)); - } - /** * Test request to shipment sends valid xml values. + * + * @param string $origCountryId + * @param string $expectedRegionCode + * @dataProvider requestToShipmentDataProvider */ - public function testRequestToShipment() + public function testRequestToShipment(string $origCountryId, string $expectedRegionCode) { + $expectedRequestXml = file_get_contents(__DIR__ . '/_files/shipment_request.xml'); + $scopeConfigValueMap = [ + ['carriers/dhl/account', 'store', null, '1234567890'], + ['carriers/dhl/gateway_url', 'store', null, 'https://xmlpi-ea.dhl.com/XMLShippingServlet'], + ['carriers/dhl/id', 'store', null, 'some ID'], + ['carriers/dhl/password', 'store', null, 'some password'], + ['carriers/dhl/content_type', 'store', null, 'N'], + ['carriers/dhl/nondoc_methods', 'store', null, '1,3,4,8,P,Q,E,F,H,J,M,V,Y'], + ['shipping/origin/country_id', 'store', null, $origCountryId], + ]; + $this->scope->method('getValue') - ->willReturnCallback([$this, 'scopeConfigGetValue']); + ->willReturnMap($scopeConfigValueMap); $this->httpResponse->method('getBody') ->willReturn(utf8_encode(file_get_contents(__DIR__ . '/_files/response_shipping_label.xml'))); @@ -352,7 +396,7 @@ public function testRequestToShipment() $this->request->method('getPackages') ->willReturn($packages); $this->request->method('getOrigCountryId') - ->willReturn('GB'); + ->willReturn($origCountryId); $this->request->method('setPackages') ->willReturnSelf(); $this->request->method('setPackageWeight') @@ -382,7 +426,17 @@ public function testRequestToShipment() $rawPostData->setAccessible(true); $this->assertNotNull($result); - $this->assertContains('<Weight>0.454</Weight>', $rawPostData->getValue($this->httpClient)); + $requestXml = $rawPostData->getValue($this->httpClient); + $requestElement = new Element($requestXml); + $this->assertEquals($expectedRegionCode, $requestElement->RegionCode->__toString()); + $requestElement->RegionCode = 'Checked'; + $messageReference = $requestElement->Request->ServiceHeader->MessageReference->__toString(); + $this->assertStringStartsWith('MAGE_SHIP_', $messageReference); + $this->assertGreaterThanOrEqual(28, strlen($messageReference)); + $this->assertLessThanOrEqual(32, strlen($messageReference)); + $requestElement->Request->ServiceHeader->MessageReference = 'MAGE_SHIP_28TO32_Char_CHECKED'; + $expectedRequestElement = new Element($expectedRequestXml); + $this->assertXmlStringEqualsXmlString($expectedRequestElement->asXML(), $requestElement->asXML()); } /** @@ -394,86 +448,14 @@ public function requestToShipmentDataProvider() { return [ [ - 'GB' + 'GB', 'EU' ], [ - null + 'SG', 'AP' ] ]; } - /** - * Test that shipping label request for origin country from AP region doesn't contain restricted fields. - * - * @return void - */ - public function testShippingLabelRequestForAsiaPacificRegion() - { - $this->scope->method('getValue') - ->willReturnMap( - [ - ['shipping/origin/country_id', ScopeInterface::SCOPE_STORE, null, 'SG'], - ['carriers/dhl/gateway_url', ScopeInterface::SCOPE_STORE, null, 'https://xmlpi-ea.dhl.com'], - ] - ); - - $this->httpResponse->method('getBody') - ->willReturn(utf8_encode(file_get_contents(__DIR__ . '/_files/response_shipping_label.xml'))); - - $packages = [ - 'package' => [ - 'params' => [ - 'width' => '1', - 'length' => '1', - 'height' => '1', - 'dimension_units' => 'INCH', - 'weight_units' => 'POUND', - 'weight' => '0.45', - 'customs_value' => '10.00', - 'container' => Carrier::DHL_CONTENT_TYPE_NON_DOC, - ], - 'items' => [ - 'item1' => [ - 'name' => 'item_name', - ], - ], - ], - ]; - - $this->request->method('getPackages')->willReturn($packages); - $this->request->method('getOrigCountryId')->willReturn('SG'); - $this->request->method('setPackages')->willReturnSelf(); - $this->request->method('setPackageWeight')->willReturnSelf(); - $this->request->method('setPackageValue')->willReturnSelf(); - $this->request->method('setValueWithDiscount')->willReturnSelf(); - $this->request->method('setPackageCustomsValue')->willReturnSelf(); - - $result = $this->model->requestToShipment($this->request); - - $reflectionClass = new \ReflectionObject($this->httpClient); - $rawPostData = $reflectionClass->getProperty('raw_post_data'); - $rawPostData->setAccessible(true); - - $this->assertNotNull($result); - $requestXml = $rawPostData->getValue($this->httpClient); - - $this->assertNotContains( - 'NewShipper', - $requestXml, - 'NewShipper is restricted field for AP region' - ); - $this->assertNotContains( - 'Division', - $requestXml, - 'Division is restricted field for AP region' - ); - $this->assertNotContains( - 'RegisteredAccount', - $requestXml, - 'RegisteredAccount is restricted field for AP region' - ); - } - /** * @dataProvider dhlProductsDataProvider * @@ -537,6 +519,107 @@ public function dhlProductsDataProvider() : array ]; } + /** + * Tests that the built MessageReference string is of the appropriate format. + * + * @dataProvider buildMessageReferenceDataProvider + * @param $servicePrefix + * @throws \ReflectionException + */ + public function testBuildMessageReference($servicePrefix) + { + $method = new \ReflectionMethod($this->model, 'buildMessageReference'); + $method->setAccessible(true); + + $messageReference = $method->invoke($this->model, $servicePrefix); + $this->assertGreaterThanOrEqual(28, strlen($messageReference)); + $this->assertLessThanOrEqual(32, strlen($messageReference)); + } + + /** + * @return array + */ + public function buildMessageReferenceDataProvider() + { + return [ + 'quote_prefix' => ['QUOT'], + 'shipval_prefix' => ['SHIP'], + 'tracking_prefix' => ['TRCK'] + ]; + } + + /** + * Tests that an exception is thrown when an invalid service prefix is provided. + * + * @expectedException \Magento\Framework\Exception\LocalizedException + * @expectedExceptionMessage Invalid service prefix + */ + public function testBuildMessageReferenceInvalidPrefix() + { + $method = new \ReflectionMethod($this->model, 'buildMessageReference'); + $method->setAccessible(true); + + $method->invoke($this->model, 'INVALID'); + } + + /** + * Tests that the built software name string is of the appropriate format. + * + * @dataProvider buildSoftwareNameDataProvider + * @param $productName + * @throws \ReflectionException + */ + public function testBuildSoftwareName($productName) + { + $method = new \ReflectionMethod($this->model, 'buildSoftwareName'); + $method->setAccessible(true); + + $this->productMetadataMock->method('getName')->willReturn($productName); + + $softwareName = $method->invoke($this->model); + $this->assertLessThanOrEqual(30, strlen($softwareName)); + } + + /** + * @return array + */ + public function buildSoftwareNameDataProvider() + { + return [ + 'valid_length' => ['Magento'], + 'exceeds_length' => ['Product_Name_Longer_Than_30_Char'] + ]; + } + + /** + * Tests that the built software version string is of the appropriate format. + * + * @dataProvider buildSoftwareVersionProvider + * @param $productVersion + * @throws \ReflectionException + */ + public function testBuildSoftwareVersion($productVersion) + { + $method = new \ReflectionMethod($this->model, 'buildSoftwareVersion'); + $method->setAccessible(true); + + $this->productMetadataMock->method('getVersion')->willReturn($productVersion); + + $softwareVersion = $method->invoke($this->model); + $this->assertLessThanOrEqual(10, strlen($softwareVersion)); + } + + /** + * @return array + */ + public function buildSoftwareVersionProvider() + { + return [ + 'valid_length' => ['2.3.1'], + 'exceeds_length' => ['dev-MC-1000'] + ]; + } + /** * Creates mock for XML factory. * @@ -595,14 +678,18 @@ private function getRateMethodFactory(): MockObject ->disableOriginalConstructor() ->setMethods(['create']) ->getMock(); - $rateMethod = $this->getMockBuilder(Method::class) - ->disableOriginalConstructor() - ->setMethods(['setPrice']) - ->getMock(); - $rateMethod->method('setPrice') - ->willReturnSelf(); + $rateMethodFactory->method('create') - ->willReturn($rateMethod); + ->willReturnCallback(function () { + $rateMethod = $this->getMockBuilder(Method::class) + ->disableOriginalConstructor() + ->setMethods(['setPrice']) + ->getMock(); + $rateMethod->method('setPrice') + ->willReturnSelf(); + + return $rateMethod; + }); return $rateMethodFactory; } diff --git a/app/code/Magento/Dhl/Test/Unit/Model/_files/countries.xml b/app/code/Magento/Dhl/Test/Unit/Model/_files/countries.xml index 3f28111f229..792465ce459 100644 --- a/app/code/Magento/Dhl/Test/Unit/Model/_files/countries.xml +++ b/app/code/Magento/Dhl/Test/Unit/Model/_files/countries.xml @@ -1,4 +1,4 @@ -<?xml version="1.0" encoding="UTF-8"?> +<?xml version="1.0"?> <!-- /** * Copyright © Magento, Inc. All rights reserved. @@ -83,7 +83,7 @@ <currency>EUR</currency> <weight_unit>KG</weight_unit> <measure_unit>CM</measure_unit> - <region>EA</region> + <region>EU</region> <name>Austria</name> <domestic>1</domestic> </AT> @@ -132,7 +132,7 @@ <currency>EUR</currency> <weight_unit>KG</weight_unit> <measure_unit>CM</measure_unit> - <region>EA</region> + <region>EU</region> <name>Belgium</name> <domestic>1</domestic> </BE> @@ -146,7 +146,7 @@ <currency>BGN</currency> <weight_unit>KG</weight_unit> <measure_unit>CM</measure_unit> - <region>EA</region> + <region>EU</region> <name>Bulgaria</name> <domestic>1</domestic> </BG> @@ -257,7 +257,7 @@ <currency>CHF</currency> <weight_unit>KG</weight_unit> <measure_unit>CM</measure_unit> - <region>EA</region> + <region>EU</region> <name>Switzerland</name> </CH> <CI> @@ -331,7 +331,7 @@ <currency>CZK</currency> <weight_unit>KG</weight_unit> <measure_unit>CM</measure_unit> - <region>EA</region> + <region>EU</region> <name>Czech Republic, The</name> <domestic>1</domestic> </CZ> @@ -339,7 +339,7 @@ <currency>EUR</currency> <weight_unit>KG</weight_unit> <measure_unit>CM</measure_unit> - <region>EA</region> + <region>EU</region> <name>Germany</name> <domestic>1</domestic> </DE> @@ -353,7 +353,7 @@ <currency>DKK</currency> <weight_unit>KG</weight_unit> <measure_unit>CM</measure_unit> - <region>EA</region> + <region>EU</region> <name>Denmark</name> <domestic>1</domestic> </DK> @@ -389,7 +389,7 @@ <currency>EEK</currency> <weight_unit>KG</weight_unit> <measure_unit>CM</measure_unit> - <region>EA</region> + <region>EU</region> <name>Estonia</name> <domestic>1</domestic> </EE> @@ -410,7 +410,7 @@ <currency>EUR</currency> <weight_unit>KG</weight_unit> <measure_unit>CM</measure_unit> - <region>EA</region> + <region>EU</region> <name>Spain</name> <domestic>1</domestic> </ES> @@ -424,7 +424,7 @@ <currency>EUR</currency> <weight_unit>KG</weight_unit> <measure_unit>CM</measure_unit> - <region>EA</region> + <region>EU</region> <name>Finland</name> <domestic>1</domestic> </FI> @@ -457,7 +457,7 @@ <currency>EUR</currency> <weight_unit>KG</weight_unit> <measure_unit>CM</measure_unit> - <region>EA</region> + <region>EU</region> <name>France</name> <domestic>1</domestic> </FR> @@ -471,7 +471,7 @@ <currency>GBP</currency> <weight_unit>KG</weight_unit> <measure_unit>CM</measure_unit> - <region>EA</region> + <region>EU</region> <name>United Kingdom</name> <domestic>1</domestic> </GB> @@ -549,7 +549,7 @@ <currency>EUR</currency> <weight_unit>KG</weight_unit> <measure_unit>CM</measure_unit> - <region>EA</region> + <region>EU</region> <name>Greece</name> <domestic>1</domestic> </GR> @@ -612,7 +612,7 @@ <currency>HUF</currency> <weight_unit>KG</weight_unit> <measure_unit>CM</measure_unit> - <region>EA</region> + <region>EU</region> <name>Hungary</name> <domestic>1</domestic> </HU> @@ -633,7 +633,7 @@ <currency>EUR</currency> <weight_unit>KG</weight_unit> <measure_unit>CM</measure_unit> - <region>EA</region> + <region>EU</region> <name>Ireland, Republic Of</name> <domestic>1</domestic> </IE> @@ -668,14 +668,14 @@ <currency>ISK</currency> <weight_unit>KG</weight_unit> <measure_unit>CM</measure_unit> - <region>EA</region> + <region>EU</region> <name>Iceland</name> </IS> <IT> <currency>EUR</currency> <weight_unit>KG</weight_unit> <measure_unit>CM</measure_unit> - <region>EA</region> + <region>EU</region> <name>Italy</name> <domestic>1</domestic> </IT> @@ -834,7 +834,7 @@ <currency>LTL</currency> <weight_unit>KG</weight_unit> <measure_unit>CM</measure_unit> - <region>EA</region> + <region>EU</region> <name>Lithuania</name> <domestic>1</domestic> </LT> @@ -842,7 +842,7 @@ <currency>EUR</currency> <weight_unit>KG</weight_unit> <measure_unit>CM</measure_unit> - <region>EA</region> + <region>EU</region> <name>Luxembourg</name> <domestic>1</domestic> </LU> @@ -850,7 +850,7 @@ <currency>LVL</currency> <weight_unit>KG</weight_unit> <measure_unit>CM</measure_unit> - <region>EA</region> + <region>EU</region> <name>Latvia</name> <domestic>1</domestic> </LV> @@ -1039,7 +1039,7 @@ <currency>EUR</currency> <weight_unit>KG</weight_unit> <measure_unit>CM</measure_unit> - <region>EA</region> + <region>EU</region> <name>Netherlands, The</name> <domestic>1</domestic> </NL> @@ -1047,7 +1047,7 @@ <currency>NOK</currency> <weight_unit>KG</weight_unit> <measure_unit>CM</measure_unit> - <region>EA</region> + <region>EU</region> <name>Norway</name> </NO> <NP> @@ -1127,7 +1127,7 @@ <currency>PLN</currency> <weight_unit>KG</weight_unit> <measure_unit>CM</measure_unit> - <region>EA</region> + <region>EU</region> <name>Poland</name> <domestic>1</domestic> </PL> @@ -1142,7 +1142,7 @@ <currency>EUR</currency> <weight_unit>KG</weight_unit> <measure_unit>CM</measure_unit> - <region>EA</region> + <region>EU</region> <name>Portugal</name> <domestic>1</domestic> </PT> @@ -1177,7 +1177,7 @@ <currency>RON</currency> <weight_unit>KG</weight_unit> <measure_unit>CM</measure_unit> - <region>EA</region> + <region>EU</region> <name>Romania</name> <domestic>1</domestic> </RO> @@ -1231,7 +1231,7 @@ <currency>SEK</currency> <weight_unit>KG</weight_unit> <measure_unit>CM</measure_unit> - <region>EA</region> + <region>EU</region> <name>Sweden</name> <domestic>1</domestic> </SE> @@ -1246,7 +1246,7 @@ <currency>EUR</currency> <weight_unit>KG</weight_unit> <measure_unit>CM</measure_unit> - <region>EA</region> + <region>EU</region> <name>Slovenia</name> <domestic>1</domestic> </SI> @@ -1254,7 +1254,7 @@ <currency>EUR</currency> <weight_unit>KG</weight_unit> <measure_unit>CM</measure_unit> - <region>EA</region> + <region>EU</region> <name>Slovakia</name> <domestic>1</domestic> </SK> diff --git a/app/code/Magento/Dhl/Test/Unit/Model/_files/rates_request_data_dhl.php b/app/code/Magento/Dhl/Test/Unit/Model/_files/dhl_quote_request_data.php similarity index 92% rename from app/code/Magento/Dhl/Test/Unit/Model/_files/rates_request_data_dhl.php rename to app/code/Magento/Dhl/Test/Unit/Model/_files/dhl_quote_request_data.php index 32e9c78886c..f762b997352 100644 --- a/app/code/Magento/Dhl/Test/Unit/Model/_files/rates_request_data_dhl.php +++ b/app/code/Magento/Dhl/Test/Unit/Model/_files/dhl_quote_request_data.php @@ -24,10 +24,9 @@ 'limit_carrier' => null, 'base_subtotal_incl_tax' => '5', 'orig_country_id' => 'US', - 'country_id' => 'US', - 'region_id' => '12', - 'city' => 'Fremont', - 'postcode' => '94538', + 'orig_region_id' => '12', + 'orig_city' => 'Fremont', + 'orig_postcode' => '94538', 'dhl_id' => 'MAGEN_8501', 'dhl_password' => 'QR2GO1U74X', 'dhl_account' => '799909537', diff --git a/app/code/Magento/Dhl/Test/Unit/Model/_files/success_dhl_response_rates.xml b/app/code/Magento/Dhl/Test/Unit/Model/_files/dhl_quote_response.xml similarity index 84% rename from app/code/Magento/Dhl/Test/Unit/Model/_files/success_dhl_response_rates.xml rename to app/code/Magento/Dhl/Test/Unit/Model/_files/dhl_quote_response.xml index b529e86ef15..74217079664 100644 --- a/app/code/Magento/Dhl/Test/Unit/Model/_files/success_dhl_response_rates.xml +++ b/app/code/Magento/Dhl/Test/Unit/Model/_files/dhl_quote_response.xml @@ -5,9 +5,9 @@ * See COPYING.txt for license details. */ --> - -<res:DCTResponse xmlns:res="http://www.dhl.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://www.dhl.com DCT-Response.xsd"> +<res:DCTResponse xmlns:res="http://www.dhl.com" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.dhl.com DCT-Response_global-2.0.xsd"> <GetQuoteResponse> <Response> <ServiceHeader> @@ -16,15 +16,15 @@ </ServiceHeader> </Response> <BkgDetails> - <OriginServiceArea> - <FacilityCode>NUQ</FacilityCode> - <ServiceAreaCode>NUQ</ServiceAreaCode> - </OriginServiceArea> - <DestinationServiceArea> - <FacilityCode>BER</FacilityCode> - <ServiceAreaCode>BER</ServiceAreaCode> - </DestinationServiceArea> <QtdShp> + <OriginServiceArea> + <FacilityCode>NUQ</FacilityCode> + <ServiceAreaCode>NUQ</ServiceAreaCode> + </OriginServiceArea> + <DestinationServiceArea> + <FacilityCode>BER</FacilityCode> + <ServiceAreaCode>BER</ServiceAreaCode> + </DestinationServiceArea> <GlobalProductCode>E</GlobalProductCode> <LocalProductCode>E</LocalProductCode> <ProductShortName>EXPRESS 9:00</ProductShortName> @@ -42,9 +42,10 @@ <TotalTransitDays>2</TotalTransitDays> <PickupPostalLocAddDays>0</PickupPostalLocAddDays> <DeliveryPostalLocAddDays>0</DeliveryPostalLocAddDays> - <PickupNonDHLCourierCode /> - <DeliveryNonDHLCourierCode /> - <DeliveryDate>2014-01-13</DeliveryDate> + <DeliveryDate> + <DlvyDateTime>2014-01-13 11:59:00</DlvyDateTime> + <DeliveryDateTimeOffset>+00:00</DeliveryDateTimeOffset> + </DeliveryDate> <DeliveryTime>PT9H</DeliveryTime> <DimensionalWeight>2.205</DimensionalWeight> <WeightUnit>LB</WeightUnit> @@ -101,8 +102,19 @@ <TotalTaxAmount>0.000</TotalTaxAmount> <WeightChargeTax>0.000</WeightChargeTax> </QtdSInAdCur> + <PickupWindowEarliestTime>09:00:00</PickupWindowEarliestTime> + <PickupWindowLatestTime>17:00:00</PickupWindowLatestTime> + <BookingCutoffOffset>PT1H</BookingCutoffOffset> </QtdShp> <QtdShp> + <OriginServiceArea> + <FacilityCode>NUQ</FacilityCode> + <ServiceAreaCode>NUQ</ServiceAreaCode> + </OriginServiceArea> + <DestinationServiceArea> + <FacilityCode>BER</FacilityCode> + <ServiceAreaCode>BER</ServiceAreaCode> + </DestinationServiceArea> <GlobalProductCode>Q</GlobalProductCode> <LocalProductCode>Q</LocalProductCode> <ProductShortName>MEDICAL EXPRESS</ProductShortName> @@ -120,9 +132,10 @@ <TotalTransitDays>2</TotalTransitDays> <PickupPostalLocAddDays>0</PickupPostalLocAddDays> <DeliveryPostalLocAddDays>0</DeliveryPostalLocAddDays> - <PickupNonDHLCourierCode /> - <DeliveryNonDHLCourierCode /> - <DeliveryDate>2014-01-13</DeliveryDate> + <DeliveryDate> + <DlvyDateTime>2014-01-13 11:59:00</DlvyDateTime> + <DeliveryDateTimeOffset>+00:00</DeliveryDateTimeOffset> + </DeliveryDate> <DeliveryTime>PT9H</DeliveryTime> <DimensionalWeight>2.205</DimensionalWeight> <WeightUnit>LB</WeightUnit> @@ -179,8 +192,19 @@ <TotalTaxAmount>0.000</TotalTaxAmount> <WeightChargeTax>0.000</WeightChargeTax> </QtdSInAdCur> + <PickupWindowEarliestTime>09:00:00</PickupWindowEarliestTime> + <PickupWindowLatestTime>17:00:00</PickupWindowLatestTime> + <BookingCutoffOffset>PT1H</BookingCutoffOffset> </QtdShp> <QtdShp> + <OriginServiceArea> + <FacilityCode>NUQ</FacilityCode> + <ServiceAreaCode>NUQ</ServiceAreaCode> + </OriginServiceArea> + <DestinationServiceArea> + <FacilityCode>BER</FacilityCode> + <ServiceAreaCode>BER</ServiceAreaCode> + </DestinationServiceArea> <GlobalProductCode>Y</GlobalProductCode> <LocalProductCode>Y</LocalProductCode> <ProductShortName>EXPRESS 12:00</ProductShortName> @@ -198,9 +222,10 @@ <TotalTransitDays>2</TotalTransitDays> <PickupPostalLocAddDays>0</PickupPostalLocAddDays> <DeliveryPostalLocAddDays>0</DeliveryPostalLocAddDays> - <PickupNonDHLCourierCode /> - <DeliveryNonDHLCourierCode /> - <DeliveryDate>2014-01-13</DeliveryDate> + <DeliveryDate> + <DlvyDateTime>2014-01-13 11:59:00</DlvyDateTime> + <DeliveryDateTimeOffset>+00:00</DeliveryDateTimeOffset> + </DeliveryDate> <DeliveryTime>PT12H</DeliveryTime> <DimensionalWeight>2.205</DimensionalWeight> <WeightUnit>LB</WeightUnit> @@ -257,8 +282,19 @@ <TotalTaxAmount>0.000</TotalTaxAmount> <WeightChargeTax>0.000</WeightChargeTax> </QtdSInAdCur> + <PickupWindowEarliestTime>09:00:00</PickupWindowEarliestTime> + <PickupWindowLatestTime>17:00:00</PickupWindowLatestTime> + <BookingCutoffOffset>PT1H</BookingCutoffOffset> </QtdShp> <QtdShp> + <OriginServiceArea> + <FacilityCode>NUQ</FacilityCode> + <ServiceAreaCode>NUQ</ServiceAreaCode> + </OriginServiceArea> + <DestinationServiceArea> + <FacilityCode>BER</FacilityCode> + <ServiceAreaCode>BER</ServiceAreaCode> + </DestinationServiceArea> <GlobalProductCode>3</GlobalProductCode> <LocalProductCode>3</LocalProductCode> <ProductShortName>B2C</ProductShortName> @@ -275,9 +311,10 @@ <TotalTransitDays>2</TotalTransitDays> <PickupPostalLocAddDays>0</PickupPostalLocAddDays> <DeliveryPostalLocAddDays>0</DeliveryPostalLocAddDays> - <PickupNonDHLCourierCode /> - <DeliveryNonDHLCourierCode /> - <DeliveryDate>2014-01-13</DeliveryDate> + <DeliveryDate> + <DlvyDateTime>2014-01-13 11:59:00</DlvyDateTime> + <DeliveryDateTimeOffset>+00:00</DeliveryDateTimeOffset> + </DeliveryDate> <DeliveryTime>PT23H59M</DeliveryTime> <DimensionalWeight>2.205</DimensionalWeight> <WeightUnit>LB</WeightUnit> @@ -309,8 +346,19 @@ <TotalTaxAmount>0.000</TotalTaxAmount> <WeightChargeTax>0.000</WeightChargeTax> </QtdSInAdCur> + <PickupWindowEarliestTime>09:00:00</PickupWindowEarliestTime> + <PickupWindowLatestTime>17:00:00</PickupWindowLatestTime> + <BookingCutoffOffset>PT1H</BookingCutoffOffset> </QtdShp> <QtdShp> + <OriginServiceArea> + <FacilityCode>NUQ</FacilityCode> + <ServiceAreaCode>NUQ</ServiceAreaCode> + </OriginServiceArea> + <DestinationServiceArea> + <FacilityCode>BER</FacilityCode> + <ServiceAreaCode>BER</ServiceAreaCode> + </DestinationServiceArea> <GlobalProductCode>P</GlobalProductCode> <LocalProductCode>P</LocalProductCode> <ProductShortName>EXPRESS WORLDWIDE</ProductShortName> @@ -328,9 +376,10 @@ <TotalTransitDays>2</TotalTransitDays> <PickupPostalLocAddDays>0</PickupPostalLocAddDays> <DeliveryPostalLocAddDays>0</DeliveryPostalLocAddDays> - <PickupNonDHLCourierCode /> - <DeliveryNonDHLCourierCode /> - <DeliveryDate>2014-01-13</DeliveryDate> + <DeliveryDate> + <DlvyDateTime>2014-01-13 11:59:00</DlvyDateTime> + <DeliveryDateTimeOffset>+00:00</DeliveryDateTimeOffset> + </DeliveryDate> <DeliveryTime>PT23H59M</DeliveryTime> <DimensionalWeight>2.205</DimensionalWeight> <WeightUnit>LB</WeightUnit> @@ -387,6 +436,9 @@ <TotalTaxAmount>0.000</TotalTaxAmount> <WeightChargeTax>0.000</WeightChargeTax> </QtdSInAdCur> + <PickupWindowEarliestTime>09:00:00</PickupWindowEarliestTime> + <PickupWindowLatestTime>17:00:00</PickupWindowLatestTime> + <BookingCutoffOffset>PT1H</BookingCutoffOffset> </QtdShp> </BkgDetails> <Srvs> diff --git a/app/code/Magento/Dhl/Test/Unit/Model/_files/dhl_quote_response_rates.php b/app/code/Magento/Dhl/Test/Unit/Model/_files/dhl_quote_response_rates.php new file mode 100644 index 00000000000..ddd7b2e4f97 --- /dev/null +++ b/app/code/Magento/Dhl/Test/Unit/Model/_files/dhl_quote_response_rates.php @@ -0,0 +1,31 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +return [ + [ + 'carrier' => 'dhl', + 'carrier_title' => 'DHL Title', + 'cost' => 45.85, + 'method' => 'E' + ], + [ + 'carrier' => 'dhl', + 'carrier_title' => 'DHL Title', + 'cost' => 35.26, + 'method' => 'Q' + ], + [ + 'carrier' => 'dhl', + 'carrier_title' => 'DHL Title', + 'cost' => 37.38, + 'method' => 'Y' + ], + [ + 'carrier' => 'dhl', + 'carrier_title' => 'DHL Title', + 'cost' => 35.26, + 'method' => 'P' + ] +]; diff --git a/app/code/Magento/Dhl/Test/Unit/Model/_files/shipment_request.xml b/app/code/Magento/Dhl/Test/Unit/Model/_files/shipment_request.xml new file mode 100644 index 00000000000..d411041c960 --- /dev/null +++ b/app/code/Magento/Dhl/Test/Unit/Model/_files/shipment_request.xml @@ -0,0 +1,93 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<req:ShipmentRequest xmlns:req="http://www.dhl.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.dhl.com ship-val-global-req-6.0.xsd" schemaVersion="6.0"> + <Request xmlns=""> + <ServiceHeader> + <MessageTime>currentTime</MessageTime> + <MessageReference>MAGE_SHIP_28TO32_Char_CHECKED</MessageReference> + <SiteID>some ID</SiteID> + <Password>some password</Password> + </ServiceHeader> + </Request> + <RegionCode xmlns="">CHECKED</RegionCode> + <RequestedPickupTime xmlns="">N</RequestedPickupTime> + <NewShipper xmlns="">N</NewShipper> + <LanguageCode xmlns="">EN</LanguageCode> + <PiecesEnabled xmlns="">Y</PiecesEnabled> + <Billing xmlns=""> + <ShipperAccountNumber>1234567890</ShipperAccountNumber> + <ShippingPaymentType>S</ShippingPaymentType> + <BillingAccountNumber>1234567890</BillingAccountNumber> + <DutyPaymentType>S</DutyPaymentType> + <DutyAccountNumber>1234567890</DutyAccountNumber> + </Billing> + <Consignee xmlns=""> + <CompanyName/> + <AddressLine/> + <City/> + <PostalCode/> + <CountryCode/> + <CountryName/> + <Contact> + <PersonName/> + <PhoneNumber/> + </Contact> + </Consignee> + <Commodity xmlns=""> + <CommodityCode>1</CommodityCode> + </Commodity> + <Dutiable xmlns=""> + <DeclaredValue>10.00</DeclaredValue> + <DeclaredCurrency>USD</DeclaredCurrency> + </Dutiable> + <Reference xmlns=""> + <ReferenceID>shipment reference</ReferenceID> + <ReferenceType>St</ReferenceType> + </Reference> + <ShipmentDetails xmlns=""> + <NumberOfPieces>1</NumberOfPieces> + <Pieces xmlns=""> + <Piece xmlns=""> + <PieceID>1</PieceID> + <PackageType>CP</PackageType> + <Weight>0.454</Weight> + <Width>3</Width> + <Height>3</Height> + <Depth>3</Depth> + <PieceContents>item_name</PieceContents> + </Piece> + </Pieces> + <Weight>0.454</Weight> + <WeightUnit>K</WeightUnit> + <GlobalProductCode/> + <LocalProductCode/> + <Date>currentTime</Date> + <Contents>DHL Parcel</Contents> + <DoorTo>DD</DoorTo> + <DimensionUnit>C</DimensionUnit> + <PackageType>CP</PackageType> + <IsDutiable>Y</IsDutiable> + <CurrencyCode>USD</CurrencyCode> + </ShipmentDetails> + <Shipper xmlns=""> + <ShipperID>1234567890</ShipperID> + <CompanyName/> + <RegisteredAccount>1234567890</RegisteredAccount> + <AddressLine/> + <City/> + <PostalCode/> + <CountryCode/> + <CountryName/> + <Contact xmlns=""> + <PersonName/> + <PhoneNumber/> + </Contact> + </Shipper> + <LabelImageFormat xmlns="">PDF</LabelImageFormat> +</req:ShipmentRequest> diff --git a/app/code/Magento/Dhl/etc/countries.xml b/app/code/Magento/Dhl/etc/countries.xml index 48837dbefb5..792465ce459 100644 --- a/app/code/Magento/Dhl/etc/countries.xml +++ b/app/code/Magento/Dhl/etc/countries.xml @@ -83,7 +83,7 @@ <currency>EUR</currency> <weight_unit>KG</weight_unit> <measure_unit>CM</measure_unit> - <region>EA</region> + <region>EU</region> <name>Austria</name> <domestic>1</domestic> </AT> @@ -132,7 +132,7 @@ <currency>EUR</currency> <weight_unit>KG</weight_unit> <measure_unit>CM</measure_unit> - <region>EA</region> + <region>EU</region> <name>Belgium</name> <domestic>1</domestic> </BE> @@ -146,7 +146,7 @@ <currency>BGN</currency> <weight_unit>KG</weight_unit> <measure_unit>CM</measure_unit> - <region>EA</region> + <region>EU</region> <name>Bulgaria</name> <domestic>1</domestic> </BG> @@ -257,7 +257,7 @@ <currency>CHF</currency> <weight_unit>KG</weight_unit> <measure_unit>CM</measure_unit> - <region>EA</region> + <region>EU</region> <name>Switzerland</name> </CH> <CI> @@ -331,7 +331,7 @@ <currency>CZK</currency> <weight_unit>KG</weight_unit> <measure_unit>CM</measure_unit> - <region>EA</region> + <region>EU</region> <name>Czech Republic, The</name> <domestic>1</domestic> </CZ> @@ -339,7 +339,7 @@ <currency>EUR</currency> <weight_unit>KG</weight_unit> <measure_unit>CM</measure_unit> - <region>EA</region> + <region>EU</region> <name>Germany</name> <domestic>1</domestic> </DE> @@ -353,7 +353,7 @@ <currency>DKK</currency> <weight_unit>KG</weight_unit> <measure_unit>CM</measure_unit> - <region>EA</region> + <region>EU</region> <name>Denmark</name> <domestic>1</domestic> </DK> @@ -389,7 +389,7 @@ <currency>EEK</currency> <weight_unit>KG</weight_unit> <measure_unit>CM</measure_unit> - <region>EA</region> + <region>EU</region> <name>Estonia</name> <domestic>1</domestic> </EE> @@ -410,7 +410,7 @@ <currency>EUR</currency> <weight_unit>KG</weight_unit> <measure_unit>CM</measure_unit> - <region>EA</region> + <region>EU</region> <name>Spain</name> <domestic>1</domestic> </ES> @@ -424,7 +424,7 @@ <currency>EUR</currency> <weight_unit>KG</weight_unit> <measure_unit>CM</measure_unit> - <region>EA</region> + <region>EU</region> <name>Finland</name> <domestic>1</domestic> </FI> @@ -457,7 +457,7 @@ <currency>EUR</currency> <weight_unit>KG</weight_unit> <measure_unit>CM</measure_unit> - <region>EA</region> + <region>EU</region> <name>France</name> <domestic>1</domestic> </FR> @@ -471,7 +471,7 @@ <currency>GBP</currency> <weight_unit>KG</weight_unit> <measure_unit>CM</measure_unit> - <region>EA</region> + <region>EU</region> <name>United Kingdom</name> <domestic>1</domestic> </GB> @@ -549,7 +549,7 @@ <currency>EUR</currency> <weight_unit>KG</weight_unit> <measure_unit>CM</measure_unit> - <region>EA</region> + <region>EU</region> <name>Greece</name> <domestic>1</domestic> </GR> @@ -612,7 +612,7 @@ <currency>HUF</currency> <weight_unit>KG</weight_unit> <measure_unit>CM</measure_unit> - <region>EA</region> + <region>EU</region> <name>Hungary</name> <domestic>1</domestic> </HU> @@ -633,7 +633,7 @@ <currency>EUR</currency> <weight_unit>KG</weight_unit> <measure_unit>CM</measure_unit> - <region>EA</region> + <region>EU</region> <name>Ireland, Republic Of</name> <domestic>1</domestic> </IE> @@ -668,14 +668,14 @@ <currency>ISK</currency> <weight_unit>KG</weight_unit> <measure_unit>CM</measure_unit> - <region>EA</region> + <region>EU</region> <name>Iceland</name> </IS> <IT> <currency>EUR</currency> <weight_unit>KG</weight_unit> <measure_unit>CM</measure_unit> - <region>EA</region> + <region>EU</region> <name>Italy</name> <domestic>1</domestic> </IT> @@ -834,7 +834,7 @@ <currency>LTL</currency> <weight_unit>KG</weight_unit> <measure_unit>CM</measure_unit> - <region>EA</region> + <region>EU</region> <name>Lithuania</name> <domestic>1</domestic> </LT> @@ -842,7 +842,7 @@ <currency>EUR</currency> <weight_unit>KG</weight_unit> <measure_unit>CM</measure_unit> - <region>EA</region> + <region>EU</region> <name>Luxembourg</name> <domestic>1</domestic> </LU> @@ -850,7 +850,7 @@ <currency>LVL</currency> <weight_unit>KG</weight_unit> <measure_unit>CM</measure_unit> - <region>EA</region> + <region>EU</region> <name>Latvia</name> <domestic>1</domestic> </LV> @@ -1039,7 +1039,7 @@ <currency>EUR</currency> <weight_unit>KG</weight_unit> <measure_unit>CM</measure_unit> - <region>EA</region> + <region>EU</region> <name>Netherlands, The</name> <domestic>1</domestic> </NL> @@ -1047,7 +1047,7 @@ <currency>NOK</currency> <weight_unit>KG</weight_unit> <measure_unit>CM</measure_unit> - <region>EA</region> + <region>EU</region> <name>Norway</name> </NO> <NP> @@ -1127,7 +1127,7 @@ <currency>PLN</currency> <weight_unit>KG</weight_unit> <measure_unit>CM</measure_unit> - <region>EA</region> + <region>EU</region> <name>Poland</name> <domestic>1</domestic> </PL> @@ -1142,7 +1142,7 @@ <currency>EUR</currency> <weight_unit>KG</weight_unit> <measure_unit>CM</measure_unit> - <region>EA</region> + <region>EU</region> <name>Portugal</name> <domestic>1</domestic> </PT> @@ -1177,7 +1177,7 @@ <currency>RON</currency> <weight_unit>KG</weight_unit> <measure_unit>CM</measure_unit> - <region>EA</region> + <region>EU</region> <name>Romania</name> <domestic>1</domestic> </RO> @@ -1231,7 +1231,7 @@ <currency>SEK</currency> <weight_unit>KG</weight_unit> <measure_unit>CM</measure_unit> - <region>EA</region> + <region>EU</region> <name>Sweden</name> <domestic>1</domestic> </SE> @@ -1246,7 +1246,7 @@ <currency>EUR</currency> <weight_unit>KG</weight_unit> <measure_unit>CM</measure_unit> - <region>EA</region> + <region>EU</region> <name>Slovenia</name> <domestic>1</domestic> </SI> @@ -1254,7 +1254,7 @@ <currency>EUR</currency> <weight_unit>KG</weight_unit> <measure_unit>CM</measure_unit> - <region>EA</region> + <region>EU</region> <name>Slovakia</name> <domestic>1</domestic> </SK> diff --git a/app/code/Magento/Directory/Model/Config/Source/WeightUnit.php b/app/code/Magento/Directory/Model/Config/Source/WeightUnit.php index 4e2758b362a..c8c42b95204 100644 --- a/app/code/Magento/Directory/Model/Config/Source/WeightUnit.php +++ b/app/code/Magento/Directory/Model/Config/Source/WeightUnit.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Directory\Model\Config\Source; /** @@ -14,10 +15,13 @@ class WeightUnit implements \Magento\Framework\Option\ArrayInterface { /** - * {@inheritdoc} + * @inheritdoc */ public function toOptionArray() { - return [['value' => 'lbs', 'label' => __('lbs')], ['value' => 'kgs', 'label' => __('kgs')]]; + return [ + ['value' => 'lbs', 'label' => __('lbs')], + ['value' => 'kgs', 'label' => __('kgs')] + ]; } } diff --git a/app/code/Magento/Directory/Model/ResourceModel/Country/Collection.php b/app/code/Magento/Directory/Model/ResourceModel/Country/Collection.php index f90a7b3b519..827a32dcea4 100644 --- a/app/code/Magento/Directory/Model/ResourceModel/Country/Collection.php +++ b/app/code/Magento/Directory/Model/ResourceModel/Country/Collection.php @@ -205,6 +205,7 @@ public function getItemById($countryId) /** * Add filter by country code to collection. + * * $countryCode can be either array of country codes or string representing one country code. * $iso can be either array containing 'iso2', 'iso3' values or string with containing one of that values directly. * The collection will contain countries where at least one of country $iso fields matches $countryCode. @@ -297,7 +298,7 @@ public function toOptionArray($emptyLabel = ' ') } $options[] = $option; } - if ($emptyLabel !== false && count($options) > 0) { + if ($emptyLabel !== false && count($options) > 1) { array_unshift($options, ['value' => '', 'label' => $emptyLabel]); } diff --git a/app/code/Magento/DirectoryGraphQl/Model/Resolver/Countries.php b/app/code/Magento/DirectoryGraphQl/Model/Resolver/Countries.php new file mode 100644 index 00000000000..dc788801f3e --- /dev/null +++ b/app/code/Magento/DirectoryGraphQl/Model/Resolver/Countries.php @@ -0,0 +1,63 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\DirectoryGraphQl\Model\Resolver; + +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Query\ResolverInterface; +use Magento\Framework\Reflection\DataObjectProcessor; +use Magento\Directory\Api\CountryInformationAcquirerInterface; +use Magento\Directory\Api\Data\CountryInformationInterface; + +/** + * Countries field resolver, used for GraphQL request processing. + */ +class Countries implements ResolverInterface +{ + /** + * @var DataObjectProcessor + */ + private $dataProcessor; + + /** + * @var CountryInformationAcquirerInterface + */ + private $countryInformationAcquirer; + + /** + * @param DataObjectProcessor $dataProcessor + * @param CountryInformationAcquirerInterface $countryInformationAcquirer + */ + public function __construct( + DataObjectProcessor $dataProcessor, + CountryInformationAcquirerInterface $countryInformationAcquirer + ) { + $this->dataProcessor = $dataProcessor; + $this->countryInformationAcquirer = $countryInformationAcquirer; + } + + /** + * @inheritdoc + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + $countries = $this->countryInformationAcquirer->getCountriesInfo(); + + $output = []; + foreach ($countries as $country) { + $output[] = $this->dataProcessor->buildOutputDataArray($country, CountryInformationInterface::class); + } + + return $output; + } +} diff --git a/app/code/Magento/DirectoryGraphQl/Model/Resolver/Country.php b/app/code/Magento/DirectoryGraphQl/Model/Resolver/Country.php new file mode 100644 index 00000000000..ea39f12a7bc --- /dev/null +++ b/app/code/Magento/DirectoryGraphQl/Model/Resolver/Country.php @@ -0,0 +1,67 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\DirectoryGraphQl\Model\Resolver; + +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Query\ResolverInterface; +use Magento\Framework\Reflection\DataObjectProcessor; +use Magento\Directory\Api\CountryInformationAcquirerInterface; +use Magento\Directory\Api\Data\CountryInformationInterface; + +/** + * Country field resolver, used for GraphQL request processing. + */ +class Country implements ResolverInterface +{ + /** + * @var DataObjectProcessor + */ + private $dataProcessor; + + /** + * @var CountryInformationAcquirerInterface + */ + private $countryInformationAcquirer; + + /** + * @param DataObjectProcessor $dataProcessor + * @param CountryInformationAcquirerInterface $countryInformationAcquirer + */ + public function __construct( + DataObjectProcessor $dataProcessor, + CountryInformationAcquirerInterface $countryInformationAcquirer + ) { + $this->dataProcessor = $dataProcessor; + $this->countryInformationAcquirer = $countryInformationAcquirer; + } + + /** + * @inheritdoc + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + try { + $country = $this->countryInformationAcquirer->getCountryInfo($args['id']); + } catch (NoSuchEntityException $exception) { + throw new GraphQlNoSuchEntityException(__($exception->getMessage()), $exception); + } + + return $this->dataProcessor->buildOutputDataArray( + $country, + CountryInformationInterface::class + ); + } +} diff --git a/app/code/Magento/DirectoryGraphQl/Model/Resolver/Currency.php b/app/code/Magento/DirectoryGraphQl/Model/Resolver/Currency.php new file mode 100644 index 00000000000..fb2db6c312a --- /dev/null +++ b/app/code/Magento/DirectoryGraphQl/Model/Resolver/Currency.php @@ -0,0 +1,59 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\DirectoryGraphQl\Model\Resolver; + +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Query\ResolverInterface; +use Magento\Framework\Reflection\DataObjectProcessor; +use Magento\Directory\Api\CurrencyInformationAcquirerInterface; +use Magento\Directory\Api\Data\CurrencyInformationInterface; + +/** + * Currency field resolver, used for GraphQL request processing. + */ +class Currency implements ResolverInterface +{ + /** + * @var DataObjectProcessor + */ + private $dataProcessor; + + /** + * @var CurrencyInformationAcquirerInterface + */ + private $currencyInformationAcquirer; + + /** + * @param DataObjectProcessor $dataProcessor + * @param CurrencyInformationAcquirerInterface $currencyInformationAcquirer + */ + public function __construct( + DataObjectProcessor $dataProcessor, + CurrencyInformationAcquirerInterface $currencyInformationAcquirer + ) { + $this->dataProcessor = $dataProcessor; + $this->currencyInformationAcquirer = $currencyInformationAcquirer; + } + + /** + * @inheritdoc + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + return $this->dataProcessor->buildOutputDataArray( + $this->currencyInformationAcquirer->getCurrencyInfo(), + CurrencyInformationInterface::class + ); + } +} diff --git a/app/code/Magento/DirectoryGraphQl/README.md b/app/code/Magento/DirectoryGraphQl/README.md new file mode 100644 index 00000000000..1a5c969b39e --- /dev/null +++ b/app/code/Magento/DirectoryGraphQl/README.md @@ -0,0 +1,4 @@ +# DirectoryGraphQl + +**DirectoryGraphQl** provides type and resolver information for the GraphQl module +to generate directory information endpoints. diff --git a/app/code/Magento/DirectoryGraphQl/Test/Mftf/README.md b/app/code/Magento/DirectoryGraphQl/Test/Mftf/README.md new file mode 100644 index 00000000000..8e2e188c1fe --- /dev/null +++ b/app/code/Magento/DirectoryGraphQl/Test/Mftf/README.md @@ -0,0 +1,3 @@ +# Directory Graph Ql Functional Tests + +The Functional Test Module for **Magento Directory Graph Ql** module. diff --git a/app/code/Magento/DirectoryGraphQl/composer.json b/app/code/Magento/DirectoryGraphQl/composer.json new file mode 100644 index 00000000000..0a81102a927 --- /dev/null +++ b/app/code/Magento/DirectoryGraphQl/composer.json @@ -0,0 +1,25 @@ +{ + "name": "magento/module-directory-graph-ql", + "description": "N/A", + "type": "magento2-module", + "require": { + "php": "~7.1.3||~7.2.0", + "magento/module-directory": "*", + "magento/framework": "*" + }, + "suggest": { + "magento/module-graph-ql": "*" + }, + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "Magento\\DirectoryGraphQl\\": "" + } + } +} diff --git a/app/code/Magento/DirectoryGraphQl/etc/module.xml b/app/code/Magento/DirectoryGraphQl/etc/module.xml new file mode 100644 index 00000000000..5d6ec613f36 --- /dev/null +++ b/app/code/Magento/DirectoryGraphQl/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_DirectoryGraphQl"/> +</config> diff --git a/app/code/Magento/DirectoryGraphQl/etc/schema.graphqls b/app/code/Magento/DirectoryGraphQl/etc/schema.graphqls new file mode 100644 index 00000000000..40ef6975fad --- /dev/null +++ b/app/code/Magento/DirectoryGraphQl/etc/schema.graphqls @@ -0,0 +1,37 @@ +# Copyright © Magento, Inc. All rights reserved. +# See COPYING.txt for license details. + +type Query { + currency: Currency @resolver(class: "Magento\\DirectoryGraphQl\\Model\\Resolver\\Currency") @doc(description: "The currency query returns information about store currency.") + countries: [Country] @resolver(class: "Magento\\DirectoryGraphQl\\Model\\Resolver\\Countries") @doc(description: "The countries query provides information for all countries.") + country (id: String): Country @resolver(class: "Magento\\DirectoryGraphQl\\Model\\Resolver\\Country") @doc(description: "The countries query provides information for a single country.") +} + +type Currency { + base_currency_code: String + base_currency_symbol: String + default_display_currecy_code: String + default_display_currecy_symbol: String + available_currency_codes: [String] + exchange_rates: [ExchangeRate] +} + +type ExchangeRate { + currency_to: String + rate: Float +} + +type Country { + id: String + two_letter_abbreviation: String + three_letter_abbreviation: String + full_name_locale: String + full_name_english: String + available_regions: [Region] +} + +type Region { + id: Int + code: String + name: String +} diff --git a/app/code/Magento/DirectoryGraphQl/registration.php b/app/code/Magento/DirectoryGraphQl/registration.php new file mode 100644 index 00000000000..6bb7fd8d4e4 --- /dev/null +++ b/app/code/Magento/DirectoryGraphQl/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_DirectoryGraphQl', __DIR__); diff --git a/app/code/Magento/Downloadable/Block/Adminhtml/Catalog/Product/Composite/Fieldset/Downloadable.php b/app/code/Magento/Downloadable/Block/Adminhtml/Catalog/Product/Composite/Fieldset/Downloadable.php index f44d9ffe2b7..973d52e865d 100644 --- a/app/code/Magento/Downloadable/Block/Adminhtml/Catalog/Product/Composite/Fieldset/Downloadable.php +++ b/app/code/Magento/Downloadable/Block/Adminhtml/Catalog/Product/Composite/Fieldset/Downloadable.php @@ -4,16 +4,15 @@ * See COPYING.txt for license details. */ -/** - * Adminhtml block for fieldset of downloadable product - * - * @author Magento Core Team <core@magentocommerce.com> - */ namespace Magento\Downloadable\Block\Adminhtml\Catalog\Product\Composite\Fieldset; /** + * Adminhtml block for fieldset of downloadable product + * * @api * @since 100.0.2 + * @deprecated + * @see \Magento\Downloadable\Ui\DataProvider\Product\Form\Modifier\Composite */ class Downloadable extends \Magento\Downloadable\Block\Catalog\Product\Links { diff --git a/app/code/Magento/Downloadable/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable.php b/app/code/Magento/Downloadable/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable.php index e2694b3b93b..8fdf1d39530 100644 --- a/app/code/Magento/Downloadable/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable.php +++ b/app/code/Magento/Downloadable/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable.php @@ -15,6 +15,8 @@ * Adminhtml catalog product downloadable items tab and form * * @author Magento Core Team <core@magentocommerce.com> + * @deprecated + * @see \Magento\Downloadable\Ui\DataProvider\Product\Form\Modifier\Composite */ class Downloadable extends Widget implements TabInterface { @@ -134,6 +136,8 @@ public function isHidden() } /** + * Get group code + * * @return string */ public function getGroupCode() @@ -152,6 +156,8 @@ public function getContentTabId() } /** + * Is downloadable + * * @return bool */ public function isDownloadable() @@ -160,7 +166,7 @@ public function isDownloadable() } /** - * @return $this + * @inheritdoc */ protected function _prepareLayout() { diff --git a/app/code/Magento/Downloadable/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/Links.php b/app/code/Magento/Downloadable/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/Links.php index 947e6dc1e83..47c66c98fc8 100644 --- a/app/code/Magento/Downloadable/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/Links.php +++ b/app/code/Magento/Downloadable/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/Links.php @@ -10,6 +10,9 @@ * * @author Magento Core Team <core@magentocommerce.com> * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * + * @deprecated + * @see \Magento\Downloadable\Ui\DataProvider\Product\Form\Modifier\Links */ class Links extends \Magento\Backend\Block\Template { @@ -434,6 +437,8 @@ public function getConfig() } /** + * Is single store mode + * * @return bool */ public function isSingleStoreMode() @@ -442,8 +447,11 @@ public function isSingleStoreMode() } /** + * Get base currency code + * * @param null|string|bool|int|\Magento\Store\Model\Store $storeId $storeId * @return string + * @throws \Magento\Framework\Exception\NoSuchEntityException */ public function getBaseCurrencyCode($storeId) { @@ -451,8 +459,11 @@ public function getBaseCurrencyCode($storeId) } /** + * Get base currency symbol + * * @param null|string|bool|int|\Magento\Store\Model\Store $storeId $storeId * @return string + * @throws \Magento\Framework\Exception\NoSuchEntityException */ public function getBaseCurrencySymbol($storeId) { diff --git a/app/code/Magento/Downloadable/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/Samples.php b/app/code/Magento/Downloadable/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/Samples.php index 3c86bfb2f8d..f245aeeeead 100644 --- a/app/code/Magento/Downloadable/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/Samples.php +++ b/app/code/Magento/Downloadable/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/Samples.php @@ -9,6 +9,9 @@ * Adminhtml catalog product downloadable items tab links section * * @author Magento Core Team <core@magentocommerce.com> + * + * @deprecated + * @see \Magento\Downloadable\Ui\DataProvider\Product\Form\Modifier\Samples */ class Samples extends \Magento\Backend\Block\Widget { diff --git a/app/code/Magento/Downloadable/Controller/Adminhtml/Downloadable/Product/Edit/Form.php b/app/code/Magento/Downloadable/Controller/Adminhtml/Downloadable/Product/Edit/Form.php index 1ef72f1deec..fe430566d63 100644 --- a/app/code/Magento/Downloadable/Controller/Adminhtml/Downloadable/Product/Edit/Form.php +++ b/app/code/Magento/Downloadable/Controller/Adminhtml/Downloadable/Product/Edit/Form.php @@ -6,6 +6,13 @@ */ namespace Magento\Downloadable\Controller\Adminhtml\Downloadable\Product\Edit; +/** + * Class Form + * + * @deprecated since downloadable information rendering moved to UI components. + * @see \Magento\Downloadable\Ui\DataProvider\Product\Form\Modifier\Composite + * @package Magento\Downloadable\Controller\Adminhtml\Downloadable\Product\Edit + */ class Form extends \Magento\Catalog\Controller\Adminhtml\Product\Edit { /** diff --git a/app/code/Magento/Downloadable/Test/Mftf/Data/LinkData.xml b/app/code/Magento/Downloadable/Test/Mftf/Data/LinkData.xml index 38ac2c99e47..08f1c234935 100644 --- a/app/code/Magento/Downloadable/Test/Mftf/Data/LinkData.xml +++ b/app/code/Magento/Downloadable/Test/Mftf/Data/LinkData.xml @@ -31,6 +31,28 @@ <data key="file">magento-logo.png</data> <data key="sample">https://static.magento.com/sites/all/themes/mag_redesign/images/magento-logo.svg</data> </entity> + <entity name="downloadableLink1" type="downloadable_link"> + <data key="title" unique="suffix">link-1</data> + <data key="price">2.43</data> + <data key="number_of_downloads">2</data> + <data key="sample_type">url</data> + <data key="sample_url">http://example.com</data> + <data key="link_type">url</data> + <data key="link_url">http://example.com</data> + <data key="is_shareable">0</data> + <data key="sort_order">1</data> + </entity> + <entity name="downloadableLink2" type="downloadable_link"> + <data key="title" unique="suffix">link-2</data> + <data key="price">3</data> + <data key="number_of_downloads">3</data> + <data key="sample_type">url</data> + <data key="sample_url">http://example.com</data> + <data key="link_type">url</data> + <data key="link_url">http://example.com</data> + <data key="is_shareable">1</data> + <data key="sort_order">2</data> + </entity> <entity name="downloadableSampleFile" type="downloadable_sample"> <data key="title" unique="suffix">SampleFile</data> <data key="file_type">Upload File</data> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Data/ProductData.xml b/app/code/Magento/Downloadable/Test/Mftf/Data/ProductData.xml index 6a91b60dcb5..4bed31d9f85 100644 --- a/app/code/Magento/Downloadable/Test/Mftf/Data/ProductData.xml +++ b/app/code/Magento/Downloadable/Test/Mftf/Data/ProductData.xml @@ -19,6 +19,20 @@ <data key="status">1</data> <data key="urlKey" unique="suffix">downloadableproduct</data> </entity> + <entity name="DownloadableProductWithTwoLink" type="product"> + <data key="sku" unique="suffix">downloadableproduct</data> + <data key="type_id">downloadable</data> + <data key="attribute_set_id">4</data> + <data key="name" unique="suffix">DownloadableProduct</data> + <data key="price">50.99</data> + <data key="quantity">100</data> + <data key="weight">0</data> + <data key="status">1</data> + <data key="urlKey" unique="suffix">downloadableproduct</data> + <requiredEntity type="custom_attribute_array">CustomAttributeCategoryIds</requiredEntity> + <requiredEntity type="downloadable_link">downloadableLink1</requiredEntity> + <requiredEntity type="downloadable_link">downloadableLink2</requiredEntity> + </entity> <entity name="ApiDownloadableProduct" type="product"> <data key="sku" unique="suffix">api-downloadable-product</data> <data key="type_id">downloadable</data> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminAddDefaultVideoDownloadableProductTest.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminAddDefaultVideoDownloadableProductTest.xml index 88dcca09587..a7acdfded29 100644 --- a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminAddDefaultVideoDownloadableProductTest.xml +++ b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminAddDefaultVideoDownloadableProductTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminAddDefaultVideoDownloadableProductTest" extends="AdminAddDefaultVideoSimpleProductTest"> <annotations> <features value="Downloadable"/> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminDeleteDownloadableProductTest.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminDeleteDownloadableProductTest.xml new file mode 100644 index 00000000000..d7e93d3429b --- /dev/null +++ b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminDeleteDownloadableProductTest.xml @@ -0,0 +1,57 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminDeleteDownloadableProductTest"> + <annotations> + <features value="Downloadable"/> + <title value="Delete Downloadable Product"/> + <description value="Admin should be able to delete a downloadable product"/> + <testCaseId value="MC-11018"/> + <severity value="CRITICAL"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="DownloadableProductWithTwoLink" stepKey="createDownloadableProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="downloadableLink1" stepKey="addDownloadableLink1"> + <requiredEntity createDataKey="createDownloadableProduct"/> + </createData> + <createData entity="downloadableLink2" stepKey="addDownloadableLink2"> + <requiredEntity createDataKey="createDownloadableProduct"/> + </createData> + </before> + <after> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="deleteProductUsingProductGrid" stepKey="deleteDownloadableProductFilteredBySkuAndName"> + <argument name="product" value="$$createDownloadableProduct$$"/> + </actionGroup> + <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="A total of 1 record(s) have been deleted." stepKey="deleteMessage"/> + <!--Verify product on Product Page --> + <amOnPage url="{{StorefrontProductPage.url($$createDownloadableProduct.name$$)}}" stepKey="amOnDownloadableProductPage"/> + <see selector="{{StorefrontProductInfoMainSection.productName}}" userInput="Whoops, our bad..." stepKey="seeWhoops"/> + <!-- Search for the product by sku --> + <fillField selector="{{StorefrontQuickSearchSection.searchPhrase}}" userInput="$$createDownloadableProduct.sku$$" stepKey="fillSearchBarByProductSku"/> + <waitForPageLoad stepKey="waitForSearchButton"/> + <click selector="{{StorefrontQuickSearchSection.searchButton}}" stepKey="clickSearchButton"/> + <waitForPageLoad stepKey="waitForSearchResults"/> + <!-- Should not see any search results --> + <dontSee userInput="$$createDownloadableProduct.sku$$" selector="{{StorefrontCatalogSearchMainSection.searchResults}}" stepKey="dontSeeProduct"/> + <see selector="{{StorefrontCatalogSearchMainSection.message}}" userInput="Your search returned no results." stepKey="seeCantFindProductOneMessage"/> + <!-- Go to the category page that we created in the before block --> + <amOnPage url="{{StorefrontCategoryPage.url($$createCategory.name$$)}}" stepKey="onCategoryPage"/> + <!-- Should not see the product --> + <dontSee userInput="$$createDownloadableProduct.name$$" selector="{{StorefrontCategoryMainSection.productsList}}" stepKey="dontSeeProductInCategory"/> + <see selector="{{StorefrontCategoryMainSection.emptyProductMessage}}" userInput="We can't find products matching the selection." stepKey="seeEmptyProductMessage"/> + </test> +</tests> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/AdvanceCatalogSearchDownloadableProductTest.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/AdvanceCatalogSearchDownloadableProductTest.xml index af5d20b075d..66177b6875d 100644 --- a/app/code/Magento/Downloadable/Test/Mftf/Test/AdvanceCatalogSearchDownloadableProductTest.xml +++ b/app/code/Magento/Downloadable/Test/Mftf/Test/AdvanceCatalogSearchDownloadableProductTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdvanceCatalogSearchDownloadableByNameTest" extends="AdvanceCatalogSearchSimpleProductByNameTest"> <annotations> <features value="Downloadable"/> diff --git a/app/code/Magento/Downloadable/Test/Unit/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/LinksTest.php b/app/code/Magento/Downloadable/Test/Unit/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/LinksTest.php index 8b9900d747c..06b29fce1cd 100644 --- a/app/code/Magento/Downloadable/Test/Unit/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/LinksTest.php +++ b/app/code/Magento/Downloadable/Test/Unit/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/LinksTest.php @@ -5,6 +5,14 @@ */ namespace Magento\Downloadable\Test\Unit\Block\Adminhtml\Catalog\Product\Edit\Tab\Downloadable; +/** + * Class LinksTest + * + * @package Magento\Downloadable\Test\Unit\Block\Adminhtml\Catalog\Product\Edit\Tab\Downloadable + * + * @deprecated + * @see \Magento\Downloadable\Ui\DataProvider\Product\Form\Modifier\Links + */ class LinksTest extends \PHPUnit\Framework\TestCase { /** diff --git a/app/code/Magento/Downloadable/Test/Unit/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/SamplesTest.php b/app/code/Magento/Downloadable/Test/Unit/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/SamplesTest.php index e850923bbd0..f0423606add 100644 --- a/app/code/Magento/Downloadable/Test/Unit/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/SamplesTest.php +++ b/app/code/Magento/Downloadable/Test/Unit/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/SamplesTest.php @@ -5,6 +5,14 @@ */ namespace Magento\Downloadable\Test\Unit\Block\Adminhtml\Catalog\Product\Edit\Tab\Downloadable; +/** + * Class SamplesTest + * + * @package Magento\Downloadable\Test\Unit\Block\Adminhtml\Catalog\Product\Edit\Tab\Downloadable + * + * @deprecated + * @see \Magento\Downloadable\Ui\DataProvider\Product\Form\Modifier\Samples + */ class SamplesTest extends \PHPUnit\Framework\TestCase { /** diff --git a/app/code/Magento/Downloadable/view/adminhtml/layout/catalog_product_downloadable.xml b/app/code/Magento/Downloadable/view/adminhtml/layout/catalog_product_downloadable.xml index 19b485f0b78..0352c98bfa5 100644 --- a/app/code/Magento/Downloadable/view/adminhtml/layout/catalog_product_downloadable.xml +++ b/app/code/Magento/Downloadable/view/adminhtml/layout/catalog_product_downloadable.xml @@ -5,6 +5,10 @@ * See COPYING.txt for license details. */ --> +<!-- +@deprecated Adminhtml Blocks extending for Downloadable products have neen moved to UI components +@see \Magento\Downloadable\Ui\DataProvider\Product\Form\Modifier\Composite +--> <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <update handle="downloadable_items"/> <body> diff --git a/app/code/Magento/Downloadable/view/adminhtml/layout/catalog_product_simple.xml b/app/code/Magento/Downloadable/view/adminhtml/layout/catalog_product_simple.xml index 843f9b40256..d424db980f7 100644 --- a/app/code/Magento/Downloadable/view/adminhtml/layout/catalog_product_simple.xml +++ b/app/code/Magento/Downloadable/view/adminhtml/layout/catalog_product_simple.xml @@ -5,6 +5,10 @@ * See COPYING.txt for license details. */ --> +<!-- +@deprecated Adminhtml Blocks extending for Downloadable products have neen moved to UI components +@see \Magento\Downloadable\Ui\DataProvider\Product\Form\Modifier\Composite +--> <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <update handle="downloadable_items"/> </page> diff --git a/app/code/Magento/Downloadable/view/adminhtml/layout/catalog_product_view_type_downloadable.xml b/app/code/Magento/Downloadable/view/adminhtml/layout/catalog_product_view_type_downloadable.xml index d1e551ff1c9..9c88e1ba15c 100644 --- a/app/code/Magento/Downloadable/view/adminhtml/layout/catalog_product_view_type_downloadable.xml +++ b/app/code/Magento/Downloadable/view/adminhtml/layout/catalog_product_view_type_downloadable.xml @@ -5,6 +5,10 @@ * See COPYING.txt for license details. */ --> +<!-- +@deprecated Adminhtml Blocks extending for Downloadable products have neen moved to UI components +@see \Magento\Downloadable\Ui\DataProvider\Product\Form\Modifier\Composite +--> <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> <referenceBlock name="product.composite.fieldset"> diff --git a/app/code/Magento/Downloadable/view/adminhtml/layout/catalog_product_virtual.xml b/app/code/Magento/Downloadable/view/adminhtml/layout/catalog_product_virtual.xml index 843f9b40256..d424db980f7 100644 --- a/app/code/Magento/Downloadable/view/adminhtml/layout/catalog_product_virtual.xml +++ b/app/code/Magento/Downloadable/view/adminhtml/layout/catalog_product_virtual.xml @@ -5,6 +5,10 @@ * See COPYING.txt for license details. */ --> +<!-- +@deprecated Adminhtml Blocks extending for Downloadable products have neen moved to UI components +@see \Magento\Downloadable\Ui\DataProvider\Product\Form\Modifier\Composite +--> <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <update handle="downloadable_items"/> </page> diff --git a/app/code/Magento/Downloadable/view/adminhtml/layout/downloadable_items.xml b/app/code/Magento/Downloadable/view/adminhtml/layout/downloadable_items.xml index 958e922334d..fec90e7049b 100644 --- a/app/code/Magento/Downloadable/view/adminhtml/layout/downloadable_items.xml +++ b/app/code/Magento/Downloadable/view/adminhtml/layout/downloadable_items.xml @@ -5,6 +5,10 @@ * See COPYING.txt for license details. */ --> +<!-- +@deprecated Adminhtml Blocks extending for Downloadable products have neen moved to UI components +@see \Magento\Downloadable\Ui\DataProvider\Product\Form\Modifier\Composite +--> <layout xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_layout.xsd"> <!--<referenceContainer name="product_form">--> <!--<block name="downloadable_items" class="Magento\Downloadable\Block\Adminhtml\Catalog\Product\Edit\Tab\Downloadable">--> diff --git a/app/code/Magento/Downloadable/view/adminhtml/templates/product/composite/fieldset/downloadable.phtml b/app/code/Magento/Downloadable/view/adminhtml/templates/product/composite/fieldset/downloadable.phtml index c86eb56a390..6ac6ecdfa65 100644 --- a/app/code/Magento/Downloadable/view/adminhtml/templates/product/composite/fieldset/downloadable.phtml +++ b/app/code/Magento/Downloadable/view/adminhtml/templates/product/composite/fieldset/downloadable.phtml @@ -4,6 +4,7 @@ * See COPYING.txt for license details. */ +// @deprecated // @codingStandardsIgnoreFile ?> diff --git a/app/code/Magento/Downloadable/view/adminhtml/templates/product/edit/downloadable.phtml b/app/code/Magento/Downloadable/view/adminhtml/templates/product/edit/downloadable.phtml index 7dc547c5e27..a4443edb08e 100644 --- a/app/code/Magento/Downloadable/view/adminhtml/templates/product/edit/downloadable.phtml +++ b/app/code/Magento/Downloadable/view/adminhtml/templates/product/edit/downloadable.phtml @@ -4,6 +4,7 @@ * See COPYING.txt for license details. */ +// @deprecated // @codingStandardsIgnoreFile ?> diff --git a/app/code/Magento/Downloadable/view/adminhtml/templates/product/edit/downloadable/links.phtml b/app/code/Magento/Downloadable/view/adminhtml/templates/product/edit/downloadable/links.phtml index 3ec6010218f..c86019d9cd2 100644 --- a/app/code/Magento/Downloadable/view/adminhtml/templates/product/edit/downloadable/links.phtml +++ b/app/code/Magento/Downloadable/view/adminhtml/templates/product/edit/downloadable/links.phtml @@ -4,6 +4,7 @@ * See COPYING.txt for license details. */ +// @deprecated // @codingStandardsIgnoreFile ?> diff --git a/app/code/Magento/Downloadable/view/adminhtml/templates/product/edit/downloadable/samples.phtml b/app/code/Magento/Downloadable/view/adminhtml/templates/product/edit/downloadable/samples.phtml index 3645a184df2..947d1d0b38b 100644 --- a/app/code/Magento/Downloadable/view/adminhtml/templates/product/edit/downloadable/samples.phtml +++ b/app/code/Magento/Downloadable/view/adminhtml/templates/product/edit/downloadable/samples.phtml @@ -4,6 +4,7 @@ * See COPYING.txt for license details. */ +// @deprecated // @codingStandardsIgnoreFile ?> diff --git a/app/code/Magento/DownloadableGraphQl/Model/DownloadableProductTypeResolver.php b/app/code/Magento/DownloadableGraphQl/Model/DownloadableProductTypeResolver.php index 59c007d9107..4bef5d3d57b 100644 --- a/app/code/Magento/DownloadableGraphQl/Model/DownloadableProductTypeResolver.php +++ b/app/code/Magento/DownloadableGraphQl/Model/DownloadableProductTypeResolver.php @@ -8,19 +8,21 @@ namespace Magento\DownloadableGraphQl\Model; use Magento\Framework\GraphQl\Query\Resolver\TypeResolverInterface; +use Magento\Downloadable\Model\Product\Type as Type; /** - * {@inheritdoc} + * @inheritdoc */ class DownloadableProductTypeResolver implements TypeResolverInterface { + const DOWNLOADABLE_PRODUCT = 'DownloadableProduct'; /** - * {@inheritdoc} + * @inheritdoc */ public function resolveType(array $data) : string { - if (isset($data['type_id']) && $data['type_id'] == 'downloadable') { - return 'DownloadableProduct'; + if (isset($data['type_id']) && $data['type_id'] == Type::TYPE_DOWNLOADABLE) { + return self::DOWNLOADABLE_PRODUCT; } return ''; } diff --git a/app/code/Magento/DownloadableGraphQl/Model/Resolver/CustomerDownloadableProducts.php b/app/code/Magento/DownloadableGraphQl/Model/Resolver/CustomerDownloadableProducts.php new file mode 100644 index 00000000000..b981e028856 --- /dev/null +++ b/app/code/Magento/DownloadableGraphQl/Model/Resolver/CustomerDownloadableProducts.php @@ -0,0 +1,83 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\DownloadableGraphQl\Model\Resolver; + +use Magento\DownloadableGraphQl\Model\ResourceModel\GetPurchasedDownloadableProducts; +use Magento\Framework\GraphQl\Query\Resolver\ContextInterface; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Query\ResolverInterface; +use Magento\Framework\UrlInterface; + +/** + * @inheritdoc + * + * Returns available downloadable products for customer + */ +class CustomerDownloadableProducts implements ResolverInterface +{ + /** + * @var GetPurchasedDownloadableProducts + */ + private $getPurchasedDownloadableProducts; + + /** + * @var UrlInterface + */ + private $urlBuilder; + + /** + * @param GetPurchasedDownloadableProducts $getPurchasedDownloadableProducts + * @param UrlInterface $urlBuilder + */ + public function __construct( + GetPurchasedDownloadableProducts $getPurchasedDownloadableProducts, + UrlInterface $urlBuilder + ) { + $this->getPurchasedDownloadableProducts = $getPurchasedDownloadableProducts; + $this->urlBuilder = $urlBuilder; + } + + /** + * @inheritdoc + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + $currentUserId = $context->getUserId(); + $purchasedProducts = $this->getPurchasedDownloadableProducts->execute($currentUserId); + $productsData = []; + + /* The fields names are hardcoded since there's no existing name reference in the code */ + foreach ($purchasedProducts as $purchasedProduct) { + if ($purchasedProduct['number_of_downloads_bought']) { + $remainingDownloads = $purchasedProduct['number_of_downloads_bought'] - + $purchasedProduct['number_of_downloads_used']; + } else { + $remainingDownloads = __('Unlimited'); + } + + $productsData[] = [ + 'order_increment_id' => $purchasedProduct['order_increment_id'], + 'date' => $purchasedProduct['created_at'], + 'status' => $purchasedProduct['status'], + 'download_url' => $this->urlBuilder->getUrl( + 'downloadable/download/link', + ['id' => $purchasedProduct['link_hash'], '_secure' => true] + ), + 'remaining_downloads' => $remainingDownloads + ]; + } + + return ['items' => $productsData]; + } +} diff --git a/app/code/Magento/DownloadableGraphQl/Model/ResourceModel/GetPurchasedDownloadableProducts.php b/app/code/Magento/DownloadableGraphQl/Model/ResourceModel/GetPurchasedDownloadableProducts.php new file mode 100644 index 00000000000..e8c29e90609 --- /dev/null +++ b/app/code/Magento/DownloadableGraphQl/Model/ResourceModel/GetPurchasedDownloadableProducts.php @@ -0,0 +1,58 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\DownloadableGraphQl\Model\ResourceModel; + +use Magento\Framework\App\ResourceConnection; +use Magento\Downloadable\Model\Link\Purchased\Item; + +/** + * Class GetPurchasedDownloadableProducts + * + * The model returns all purchased products for the specified customer + */ +class GetPurchasedDownloadableProducts +{ + /** + * @var ResourceConnection + */ + private $resourceConnection; + + /** + * @param ResourceConnection $resourceConnection + */ + public function __construct( + ResourceConnection $resourceConnection + ) { + $this->resourceConnection = $resourceConnection; + } + + /** + * Return available purchased products for customer + * + * @param int $customerId + * @return array + */ + public function execute(int $customerId): array + { + $connection = $this->resourceConnection->getConnection(); + $allowedItemsStatuses = [Item::LINK_STATUS_PENDING_PAYMENT, Item::LINK_STATUS_PAYMENT_REVIEW]; + $downloadablePurchasedTable = $connection->getTableName('downloadable_link_purchased'); + + /* The fields names are hardcoded since there's no existing name reference in the code */ + $selectQuery = $connection->select() + ->from($downloadablePurchasedTable) + ->joinLeft( + ['item' => $connection->getTableName('downloadable_link_purchased_item')], + "$downloadablePurchasedTable.purchased_id = item.purchased_id" + ) + ->where("$downloadablePurchasedTable.customer_id = ?", $customerId) + ->where('item.status NOT IN (?)', $allowedItemsStatuses); + + return $connection->fetchAll($selectQuery); + } +} diff --git a/app/code/Magento/DownloadableGraphQl/etc/schema.graphqls b/app/code/Magento/DownloadableGraphQl/etc/schema.graphqls index 8e877ffe836..e2cacdf7608 100644 --- a/app/code/Magento/DownloadableGraphQl/etc/schema.graphqls +++ b/app/code/Magento/DownloadableGraphQl/etc/schema.graphqls @@ -1,6 +1,22 @@ # Copyright © Magento, Inc. All rights reserved. # See COPYING.txt for license details. +type Query { + customerDownloadableProducts: CustomerDownloadableProducts @resolver(class: "Magento\\DownloadableGraphQl\\Model\\Resolver\\CustomerDownloadableProducts") @doc(description: "The query returns the contents of a customer's downloadable products") +} + +type CustomerDownloadableProducts { + items: [CustomerDownloadableProduct] @doc(description: "List of purchased downloadable items") +} + +type CustomerDownloadableProduct { + order_increment_id: String + date: String + status: String + download_url: String + remaining_downloads: String +} + type DownloadableProduct implements ProductInterface, CustomizableProductInterface @doc(description: "DownloadableProduct defines a product that the customer downloads") { downloadable_product_samples: [DownloadableProductSamples] @resolver(class: "Magento\\DownloadableGraphQl\\Model\\Resolver\\Product\\DownloadableOptions") @doc(description: "An array containing information about samples of this downloadable product.") downloadable_product_links: [DownloadableProductLinks] @resolver(class: "Magento\\DownloadableGraphQl\\Model\\Resolver\\Product\\DownloadableOptions") @doc(description: "An array containing information about the links for this downloadable product") diff --git a/app/code/Magento/Eav/Api/AttributeSetRepositoryInterface.php b/app/code/Magento/Eav/Api/AttributeSetRepositoryInterface.php index 8bd65ba85ab..be4b5a3fa0f 100644 --- a/app/code/Magento/Eav/Api/AttributeSetRepositoryInterface.php +++ b/app/code/Magento/Eav/Api/AttributeSetRepositoryInterface.php @@ -17,7 +17,7 @@ interface AttributeSetRepositoryInterface * Retrieve list of Attribute Sets * * This call returns an array of objects, but detailed information about each object’s attributes might not be - * included. See http://devdocs.magento.com/codelinks/attributes.html#AttributeSetRepositoryInterface to determine + * included. See https://devdocs.magento.com/codelinks/attributes.html#AttributeSetRepositoryInterface to determine * which call to use to get detailed information about all attributes for an object. * * @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria diff --git a/app/code/Magento/Eav/Model/Attribute/Data/AbstractData.php b/app/code/Magento/Eav/Model/Attribute/Data/AbstractData.php index 3189041d7f7..3bc87ed9775 100644 --- a/app/code/Magento/Eav/Model/Attribute/Data/AbstractData.php +++ b/app/code/Magento/Eav/Model/Attribute/Data/AbstractData.php @@ -143,6 +143,7 @@ public function setRequestScope($scope) /** * Set scope visibility + * * Search value only in scope or search value in scope and global * * @param bool $flag @@ -296,7 +297,7 @@ protected function _applyOutputFilter($value) * Validate value by attribute input validation rule * * @param string $value - * @return string|true + * @return array|true * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ @@ -311,9 +312,13 @@ protected function _validateInputRule($value) if (!empty($validateRules['input_validation'])) { $label = $this->getAttribute()->getStoreLabel(); + $allowWhiteSpace = false; switch ($validateRules['input_validation']) { + case 'alphanum-with-spaces': + $allowWhiteSpace = true; + // Continue to alphanumeric validation case 'alphanumeric': - $validator = new \Zend_Validate_Alnum(true); + $validator = new \Zend_Validate_Alnum($allowWhiteSpace); $validator->setMessage(__('"%1" invalid type entered.', $label), \Zend_Validate_Alnum::INVALID); $validator->setMessage( __('"%1" contains non-alphabetic or non-numeric characters.', $label), diff --git a/app/code/Magento/Eav/Model/Attribute/Data/File.php b/app/code/Magento/Eav/Model/Attribute/Data/File.php index 1b2cac32598..f14e01accef 100644 --- a/app/code/Magento/Eav/Model/Attribute/Data/File.php +++ b/app/code/Magento/Eav/Model/Attribute/Data/File.php @@ -146,7 +146,7 @@ protected function _validateByRules($value) return $this->_fileValidator->getMessages(); } - if (!empty($value['tmp_name']) && !is_uploaded_file($value['tmp_name'])) { + if (empty($value['tmp_name'])) { return [__('"%1" is not a valid file.', $label)]; } diff --git a/app/code/Magento/Eav/Model/Attribute/Data/Text.php b/app/code/Magento/Eav/Model/Attribute/Data/Text.php index 0a49e012690..c5167821fdf 100644 --- a/app/code/Magento/Eav/Model/Attribute/Data/Text.php +++ b/app/code/Magento/Eav/Model/Attribute/Data/Text.php @@ -72,20 +72,17 @@ public function validateValue($value) return true; } - if (empty($value) && $value !== '0') { + if (empty($value) && $value !== '0' && $attribute->getDefaultValue() === null) { $label = __($attribute->getStoreLabel()); $errors[] = __('"%1" is a required value.', $label); } - $result = $this->validateLength($attribute, $value); - if (count($result) !== 0) { - $errors = array_merge($errors, $result); - } + $validateLengthResult = $this->validateLength($attribute, $value); + $errors = array_merge($errors, $validateLengthResult); + + $validateInputRuleResult = $this->validateInputRule($value); + $errors = array_merge($errors, $validateInputRuleResult); - $result = $this->_validateInputRule($value); - if ($result !== true) { - $errors = array_merge($errors, $result); - } if (count($errors) == 0) { return true; } @@ -141,7 +138,7 @@ public function outputValue($format = \Magento\Eav\Model\AttributeDataFactory::O * @param string $value * @return array errors */ - private function validateLength(\Magento\Eav\Model\Attribute $attribute, $value): array + private function validateLength(\Magento\Eav\Model\Attribute $attribute, string $value): array { $errors = []; $length = $this->_string->strlen(trim($value)); @@ -162,4 +159,16 @@ private function validateLength(\Magento\Eav\Model\Attribute $attribute, $value) return $errors; } + + /** + * Validate value by attribute input validation rule. + * + * @param string $value + * @return array + */ + private function validateInputRule(string $value): array + { + $result = $this->_validateInputRule($value); + return \is_array($result) ? $result : []; + } } diff --git a/app/code/Magento/Eav/Model/Entity/AbstractEntity.php b/app/code/Magento/Eav/Model/Entity/AbstractEntity.php index 0522ea04321..d0a5e8de53a 100644 --- a/app/code/Magento/Eav/Model/Entity/AbstractEntity.php +++ b/app/code/Magento/Eav/Model/Entity/AbstractEntity.php @@ -10,6 +10,7 @@ use Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend; use Magento\Eav\Model\Entity\Attribute\Frontend\AbstractFrontend; use Magento\Eav\Model\Entity\Attribute\Source\AbstractSource; +use Magento\Eav\Model\Entity\Attribute\UniqueValidationInterface; use Magento\Framework\App\Config\Element; use Magento\Framework\DataObject; use Magento\Framework\DB\Adapter\DuplicateException; @@ -215,12 +216,21 @@ abstract class AbstractEntity extends AbstractResource implements EntityInterfac */ protected $objectRelationProcessor; + /** + * @var UniqueValidationInterface + */ + private $uniqueValidator; + /** * @param Context $context * @param array $data + * @param UniqueValidationInterface|null $uniqueValidator */ - public function __construct(Context $context, $data = []) - { + public function __construct( + Context $context, + $data = [], + UniqueValidationInterface $uniqueValidator = null + ) { $this->_eavConfig = $context->getEavConfig(); $this->_resource = $context->getResource(); $this->_attrSetEntity = $context->getAttributeSetEntity(); @@ -229,6 +239,8 @@ public function __construct(Context $context, $data = []) $this->_universalFactory = $context->getUniversalFactory(); $this->transactionManager = $context->getTransactionManager(); $this->objectRelationProcessor = $context->getObjectRelationProcessor(); + $this->uniqueValidator = $uniqueValidator ?: + ObjectManager::getInstance()->get(UniqueValidationInterface::class); parent::__construct(); $properties = get_object_vars($this); foreach ($data as $key => $value) { @@ -488,6 +500,7 @@ public function addAttribute(AbstractAttribute $attribute, $object = null) /** * Get attributes by scope * + * @param string $suffix * @return array */ private function getAttributesByScope($suffix) @@ -958,12 +971,8 @@ public function checkAttributeUniqueValue(AbstractAttribute $attribute, $object) $data = $connection->fetchCol($select, $bind); - $objectId = $object->getData($entityIdField); - if ($objectId) { - if (isset($data[0])) { - return $data[0] == $objectId; - } - return true; + if ($object->getData($entityIdField)) { + return $this->uniqueValidator->validate($attribute, $object, $this, $entityIdField, $data); } return !count($data); @@ -1972,7 +1981,8 @@ public function afterDelete(DataObject $object) /** * Load attributes for object - * if the object will not pass all attributes for this entity type will be loaded + * + * If the object will not pass all attributes for this entity type will be loaded * * @param array $attributes * @param AbstractEntity|null $object diff --git a/app/code/Magento/Eav/Model/Entity/Attribute.php b/app/code/Magento/Eav/Model/Entity/Attribute.php index c605f3ce17e..06a4abb9858 100644 --- a/app/code/Magento/Eav/Model/Entity/Attribute.php +++ b/app/code/Magento/Eav/Model/Entity/Attribute.php @@ -3,7 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - namespace Magento\Eav\Model\Entity; use Magento\Framework\Api\AttributeValueFactory; @@ -295,6 +294,12 @@ public function beforeSave() } } + if ($this->getFrontendInput() == 'media_image') { + if (!$this->getFrontendModel()) { + $this->setFrontendModel(\Magento\Catalog\Model\Product\Attribute\Frontend\Image::class); + } + } + if ($this->getBackendType() == 'gallery') { if (!$this->getBackendModel()) { $this->setBackendModel(\Magento\Eav\Model\Entity\Attribute\Backend\DefaultBackend::class); @@ -316,7 +321,7 @@ public function afterSave() } /** - * @return $this + * @inheritdoc * @since 100.0.7 */ public function afterDelete() diff --git a/app/code/Magento/Eav/Model/Entity/Attribute/Frontend/AbstractFrontend.php b/app/code/Magento/Eav/Model/Entity/Attribute/Frontend/AbstractFrontend.php index 3d4c9e89a03..2c7ea1ab926 100644 --- a/app/code/Magento/Eav/Model/Entity/Attribute/Frontend/AbstractFrontend.php +++ b/app/code/Magento/Eav/Model/Entity/Attribute/Frontend/AbstractFrontend.php @@ -20,6 +20,8 @@ use Magento\Eav\Model\Entity\Attribute\Source\BooleanFactory; /** + * EAV entity attribute form renderer. + * * @api * @since 100.0.2 */ @@ -234,6 +236,9 @@ protected function _getInputValidateClass() case 'alphanumeric': $class = 'validate-alphanum'; break; + case 'alphanum-with-spaces': + $class = 'validate-alphanum-with-spaces'; + break; case 'numeric': $class = 'validate-digits'; break; diff --git a/app/code/Magento/Eav/Model/Entity/Attribute/UniqueValidationInterface.php b/app/code/Magento/Eav/Model/Entity/Attribute/UniqueValidationInterface.php new file mode 100644 index 00000000000..b68e79d7b7d --- /dev/null +++ b/app/code/Magento/Eav/Model/Entity/Attribute/UniqueValidationInterface.php @@ -0,0 +1,33 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Eav\Model\Entity\Attribute; + +use Magento\Framework\DataObject; +use Magento\Eav\Model\Entity\AbstractEntity; + +/** + * Interface for unique attribute validator + */ +interface UniqueValidationInterface +{ + /** + * Validate if attribute value is unique + * + * @param AbstractAttribute $attribute + * @param DataObject $object + * @param AbstractEntity $entity + * @param string $entityLinkField + * @param array $entityIds + * @return bool + */ + public function validate( + AbstractAttribute $attribute, + DataObject $object, + AbstractEntity $entity, + $entityLinkField, + array $entityIds + ); +} diff --git a/app/code/Magento/Eav/Model/Entity/Attribute/UniqueValidator.php b/app/code/Magento/Eav/Model/Entity/Attribute/UniqueValidator.php new file mode 100644 index 00000000000..b1888b42bef --- /dev/null +++ b/app/code/Magento/Eav/Model/Entity/Attribute/UniqueValidator.php @@ -0,0 +1,31 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Eav\Model\Entity\Attribute; + +use Magento\Framework\DataObject; +use Magento\Eav\Model\Entity\AbstractEntity; + +/** + * Class for validate unique attribute value + */ +class UniqueValidator implements UniqueValidationInterface +{ + /** + * @inheritdoc + */ + public function validate( + AbstractAttribute $attribute, + DataObject $object, + AbstractEntity $entity, + $entityLinkField, + array $entityIds + ) { + if (isset($entityIds[0])) { + return $entityIds[0] == $object->getData($entityLinkField); + } + return true; + } +} diff --git a/app/code/Magento/Eav/Model/Entity/Type.php b/app/code/Magento/Eav/Model/Entity/Type.php index 80fcfd4ab58..444d58bf546 100644 --- a/app/code/Magento/Eav/Model/Entity/Type.php +++ b/app/code/Magento/Eav/Model/Entity/Type.php @@ -167,12 +167,8 @@ public function getAttributeCollection($setId = null) */ protected function _getAttributeCollection() { - $collection = $this->_attributeFactory->create()->getCollection(); - $objectsModel = $this->getAttributeModel(); - if ($objectsModel) { - $collection->setModel($objectsModel); - } - + $collection = $this->_universalFactory->create($this->getEntityAttributeCollection()); + $collection->setItemObjectClass($this->getAttributeModel()); return $collection; } diff --git a/app/code/Magento/Eav/Test/Unit/Model/Attribute/Data/TextTest.php b/app/code/Magento/Eav/Test/Unit/Model/Attribute/Data/TextTest.php index bbbe712b2bb..331d1e6216a 100644 --- a/app/code/Magento/Eav/Test/Unit/Model/Attribute/Data/TextTest.php +++ b/app/code/Magento/Eav/Test/Unit/Model/Attribute/Data/TextTest.php @@ -6,12 +6,15 @@ namespace Magento\Eav\Test\Unit\Model\Attribute\Data; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Stdlib\StringUtils; + class TextTest extends \PHPUnit\Framework\TestCase { /** * @var \Magento\Eav\Model\Attribute\Data\Text */ - protected $_model; + private $model; /** * {@inheritDoc} @@ -21,10 +24,10 @@ protected function setUp() $locale = $this->createMock(\Magento\Framework\Stdlib\DateTime\TimezoneInterface::class); $localeResolver = $this->createMock(\Magento\Framework\Locale\ResolverInterface::class); $logger = $this->createMock(\Psr\Log\LoggerInterface::class); - $helper = $this->createMock(\Magento\Framework\Stdlib\StringUtils::class); + $helper = new StringUtils; - $this->_model = new \Magento\Eav\Model\Attribute\Data\Text($locale, $logger, $localeResolver, $helper); - $this->_model->setAttribute( + $this->model = new \Magento\Eav\Model\Attribute\Data\Text($locale, $logger, $localeResolver, $helper); + $this->model->setAttribute( $this->createAttribute( [ 'store_label' => 'Test', @@ -41,7 +44,7 @@ protected function setUp() */ protected function tearDown() { - $this->_model = null; + $this->model = null; } /** @@ -51,7 +54,7 @@ public function testValidateValueString(): void { $inputValue = '0'; $expectedResult = true; - $this->assertEquals($expectedResult, $this->_model->validateValue($inputValue)); + self::assertEquals($expectedResult, $this->model->validateValue($inputValue)); } /** @@ -61,8 +64,8 @@ public function testValidateValueInteger(): void { $inputValue = 0; $expectedResult = ['"Test" is a required value.']; - $result = $this->_model->validateValue($inputValue); - $this->assertEquals($expectedResult, [(string)$result[0]]); + $result = $this->model->validateValue($inputValue); + self::assertEquals($expectedResult, [(string)$result[0]]); } /** @@ -79,12 +82,106 @@ public function testWithoutLengthValidation(): void ]; $defaultAttributeData['validate_rules']['min_text_length'] = 2; - $this->_model->setAttribute($this->createAttribute($defaultAttributeData)); - $this->assertEquals($expectedResult, $this->_model->validateValue('t')); + $this->model->setAttribute($this->createAttribute($defaultAttributeData)); + self::assertEquals($expectedResult, $this->model->validateValue('t')); $defaultAttributeData['validate_rules']['max_text_length'] = 3; - $this->_model->setAttribute($this->createAttribute($defaultAttributeData)); - $this->assertEquals($expectedResult, $this->_model->validateValue('test')); + $this->model->setAttribute($this->createAttribute($defaultAttributeData)); + self::assertEquals($expectedResult, $this->model->validateValue('test')); + } + + /** + * Test of alphanumeric validation. + * + * @param {String} $value - provided value + * @param {Boolean|Array} $expectedResult - validation result + * @return void + * @throws LocalizedException + * @dataProvider alphanumDataProvider + */ + public function testAlphanumericValidation($value, $expectedResult): void + { + $defaultAttributeData = [ + 'store_label' => 'Test', + 'attribute_code' => 'test', + 'is_required' => 1, + 'validate_rules' => [ + 'min_text_length' => 0, + 'max_text_length' => 10, + 'input_validation' => 'alphanumeric' + ], + ]; + + $this->model->setAttribute($this->createAttribute($defaultAttributeData)); + self::assertEquals($expectedResult, $this->model->validateValue($value)); + } + + /** + * Provides possible input values. + * + * @return array + */ + public function alphanumDataProvider(): array + { + return [ + ['QazWsx', true], + ['QazWsx123', true], + ['QazWsx 123', + [\Zend_Validate_Alnum::NOT_ALNUM => '"Test" contains non-alphabetic or non-numeric characters.'] + ], + ['QazWsx_123', + [\Zend_Validate_Alnum::NOT_ALNUM => '"Test" contains non-alphabetic or non-numeric characters.'] + ], + ['QazWsx12345', [ + __('"%1" length must be equal or less than %2 characters.', 'Test', 10)] + ], + ]; + } + + /** + * Test of alphanumeric validation with spaces. + * + * @param {String} $value - provided value + * @param {Boolean|Array} $expectedResult - validation result + * @return void + * @throws LocalizedException + * @dataProvider alphanumWithSpacesDataProvider + */ + public function testAlphanumericValidationWithSpaces($value, $expectedResult): void + { + $defaultAttributeData = [ + 'store_label' => 'Test', + 'attribute_code' => 'test', + 'is_required' => 1, + 'validate_rules' => [ + 'min_text_length' => 0, + 'max_text_length' => 10, + 'input_validation' => 'alphanum-with-spaces' + ], + ]; + + $this->model->setAttribute($this->createAttribute($defaultAttributeData)); + self::assertEquals($expectedResult, $this->model->validateValue($value)); + } + + /** + * Provides possible input values. + * + * @return array + */ + public function alphanumWithSpacesDataProvider(): array + { + return [ + ['QazWsx', true], + ['QazWsx123', true], + ['QazWsx 123', true], + ['QazWsx_123', + [\Zend_Validate_Alnum::NOT_ALNUM => '"Test" contains non-alphabetic or non-numeric characters.'] + ], + ['QazWsx12345', [ + __('"%1" length must be equal or less than %2 characters.', 'Test', 10)] + ], + ]; } /** diff --git a/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/Frontend/DefaultFrontendTest.php b/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/Frontend/DefaultFrontendTest.php index a61c9ef4474..fd4f7472b2f 100644 --- a/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/Frontend/DefaultFrontendTest.php +++ b/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/Frontend/DefaultFrontendTest.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Eav\Test\Unit\Model\Entity\Attribute\Frontend; use Magento\Eav\Model\Entity\Attribute\Frontend\DefaultFrontend; @@ -13,43 +15,44 @@ use Magento\Framework\App\CacheInterface; use Magento\Eav\Model\Entity\Attribute\AbstractAttribute; use Magento\Eav\Model\Entity\Attribute\Source\AbstractSource; +use PHPUnit\Framework\MockObject\MockObject; class DefaultFrontendTest extends \PHPUnit\Framework\TestCase { /** * @var DefaultFrontend */ - protected $model; + private $model; /** - * @var BooleanFactory|\PHPUnit_Framework_MockObject_MockObject + * @var BooleanFactory | MockObject */ - protected $booleanFactory; + private $booleanFactory; /** - * @var Serializer|\PHPUnit_Framework_MockObject_MockObject + * @var Serializer| MockObject */ - private $serializerMock; + private $serializer; /** - * @var StoreManagerInterface|\PHPUnit_Framework_MockObject_MockObject + * @var StoreManagerInterface | MockObject */ - private $storeManagerMock; + private $storeManager; /** - * @var StoreInterface|\PHPUnit_Framework_MockObject_MockObject + * @var StoreInterface | MockObject */ - private $storeMock; + private $store; /** - * @var CacheInterface|\PHPUnit_Framework_MockObject_MockObject + * @var CacheInterface | MockObject */ - private $cacheMock; + private $cache; /** - * @var AbstractAttribute|\PHPUnit_Framework_MockObject_MockObject + * @var AbstractAttribute | MockObject */ - private $attributeMock; + private $attribute; /** * @var array @@ -57,10 +60,13 @@ class DefaultFrontendTest extends \PHPUnit\Framework\TestCase private $cacheTags; /** - * @var AbstractSource|\PHPUnit_Framework_MockObject_MockObject + * @var AbstractSource | MockObject */ - private $sourceMock; + private $source; + /** + * @inheritdoc + */ protected function setUp() { $this->cacheTags = ['tag1', 'tag2']; @@ -68,111 +74,108 @@ protected function setUp() $this->booleanFactory = $this->getMockBuilder(BooleanFactory::class) ->disableOriginalConstructor() ->getMock(); - $this->serializerMock = $this->getMockBuilder(Serializer::class) + $this->serializer = $this->getMockBuilder(Serializer::class) ->getMock(); - $this->storeManagerMock = $this->getMockBuilder(StoreManagerInterface::class) + $this->storeManager = $this->getMockBuilder(StoreManagerInterface::class) ->getMockForAbstractClass(); - $this->storeMock = $this->getMockBuilder(StoreInterface::class) + $this->store = $this->getMockBuilder(StoreInterface::class) ->getMockForAbstractClass(); - $this->cacheMock = $this->getMockBuilder(CacheInterface::class) + $this->cache = $this->getMockBuilder(CacheInterface::class) ->getMockForAbstractClass(); - $this->attributeMock = $this->getMockBuilder(AbstractAttribute::class) - ->disableOriginalConstructor() - ->setMethods(['getAttributeCode', 'getSource']) - ->getMockForAbstractClass(); - $this->sourceMock = $this->getMockBuilder(AbstractSource::class) + $this->attribute = $this->createAttribute(); + $this->source = $this->getMockBuilder(AbstractSource::class) ->disableOriginalConstructor() ->setMethods(['getAllOptions']) ->getMockForAbstractClass(); - $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - $this->model = $objectManager->getObject( - DefaultFrontend::class, - [ - '_attrBooleanFactory' => $this->booleanFactory, - 'cache' => $this->cacheMock, - 'storeManager' => $this->storeManagerMock, - 'serializer' => $this->serializerMock, - '_attribute' => $this->attributeMock, - 'cacheTags' => $this->cacheTags - ] + $this->model = new DefaultFrontend( + $this->booleanFactory, + $this->cache, + null, + $this->cacheTags, + $this->storeManager, + $this->serializer ); + + $this->model->setAttribute($this->attribute); } public function testGetClassEmpty() { - $attributeMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute\AbstractAttribute::class) - ->disableOriginalConstructor() - ->setMethods([ - 'getIsRequired', - 'getFrontendClass', - 'getValidateRules', - ]) - ->getMock(); - $attributeMock->expects($this->once()) - ->method('getIsRequired') + /** @var AbstractAttribute | MockObject $attribute */ + $attribute = $this->createAttribute(); + $attribute->method('getIsRequired') ->willReturn(false); - $attributeMock->expects($this->once()) - ->method('getFrontendClass') + $attribute->method('getFrontendClass') ->willReturn(''); - $attributeMock->expects($this->exactly(2)) + $attribute->expects($this->exactly(2)) ->method('getValidateRules') ->willReturn(''); - $this->model->setAttribute($attributeMock); - $this->assertEmpty($this->model->getClass()); + $this->model->setAttribute($attribute); + + self::assertEmpty($this->model->getClass()); } - public function testGetClass() + /** + * Validates generated html classes. + * + * @param String $validationRule + * @param String $expectedClass + * @return void + * @dataProvider validationRulesDataProvider + */ + public function testGetClass(String $validationRule, String $expectedClass): void { - $attributeMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute\AbstractAttribute::class) - ->disableOriginalConstructor() - ->setMethods([ - 'getIsRequired', - 'getFrontendClass', - 'getValidateRules', - ]) - ->getMock(); - $attributeMock->expects($this->once()) - ->method('getIsRequired') + /** @var AbstractAttribute | MockObject $attribute */ + $attribute = $this->createAttribute(); + $attribute->method('getIsRequired') ->willReturn(true); - $attributeMock->expects($this->once()) - ->method('getFrontendClass') + $attribute->method('getFrontendClass') ->willReturn(''); - $attributeMock->expects($this->exactly(3)) + $attribute->expects($this->exactly(3)) ->method('getValidateRules') ->willReturn([ - 'input_validation' => 'alphanumeric', + 'input_validation' => $validationRule, 'min_text_length' => 1, 'max_text_length' => 2, ]); - $this->model->setAttribute($attributeMock); + $this->model->setAttribute($attribute); $result = $this->model->getClass(); - $this->assertContains('validate-alphanum', $result); - $this->assertContains('minimum-length-1', $result); - $this->assertContains('maximum-length-2', $result); - $this->assertContains('validate-length', $result); + self::assertContains($expectedClass, $result); + self::assertContains('minimum-length-1', $result); + self::assertContains('maximum-length-2', $result); + self::assertContains('validate-length', $result); + } + + /** + * Provides possible validation types. + * + * @return array + */ + public function validationRulesDataProvider(): array + { + return [ + ['alphanumeric', 'validate-alphanum'], + ['alphanum-with-spaces', 'validate-alphanum-with-spaces'], + ['alpha', 'validate-alpha'], + ['numeric', 'validate-digits'], + ['url', 'validate-url'], + ['email', 'validate-email'], + ['length', 'validate-length'] + ]; } public function testGetClassLength() { - $attributeMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute\AbstractAttribute::class) - ->disableOriginalConstructor() - ->setMethods([ - 'getIsRequired', - 'getFrontendClass', - 'getValidateRules', - ]) - ->getMock(); - $attributeMock->expects($this->once()) - ->method('getIsRequired') + $attribute = $this->createAttribute(); + $attribute->method('getIsRequired') ->willReturn(true); - $attributeMock->expects($this->once()) - ->method('getFrontendClass') + $attribute->method('getFrontendClass') ->willReturn(''); - $attributeMock->expects($this->exactly(3)) + $attribute->expects($this->exactly(3)) ->method('getValidateRules') ->willReturn([ 'input_validation' => 'length', @@ -180,12 +183,31 @@ public function testGetClassLength() 'max_text_length' => 2, ]); - $this->model->setAttribute($attributeMock); + $this->model->setAttribute($attribute); $result = $this->model->getClass(); - $this->assertContains('minimum-length-1', $result); - $this->assertContains('maximum-length-2', $result); - $this->assertContains('validate-length', $result); + self::assertContains('minimum-length-1', $result); + self::assertContains('maximum-length-2', $result); + self::assertContains('validate-length', $result); + } + + /** + * Entity attribute factory. + * + * @return AbstractAttribute | MockObject + */ + private function createAttribute() + { + return $this->getMockBuilder(AbstractAttribute::class) + ->disableOriginalConstructor() + ->setMethods([ + 'getIsRequired', + 'getFrontendClass', + 'getValidateRules', + 'getAttributeCode', + 'getSource' + ]) + ->getMockForAbstractClass(); } public function testGetSelectOptions() @@ -196,33 +218,25 @@ public function testGetSelectOptions() $options = ['option1', 'option2']; $serializedOptions = "{['option1', 'option2']}"; - $this->storeManagerMock->expects($this->once()) - ->method('getStore') - ->willReturn($this->storeMock); - $this->storeMock->expects($this->once()) - ->method('getId') + $this->storeManager->method('getStore') + ->willReturn($this->store); + $this->store->method('getId') ->willReturn($storeId); - $this->attributeMock->expects($this->once()) - ->method('getAttributeCode') + $this->attribute->method('getAttributeCode') ->willReturn($attributeCode); - $this->cacheMock->expects($this->once()) - ->method('load') + $this->cache->method('load') ->with($cacheKey) ->willReturn(false); - $this->attributeMock->expects($this->once()) - ->method('getSource') - ->willReturn($this->sourceMock); - $this->sourceMock->expects($this->once()) - ->method('getAllOptions') + $this->attribute->method('getSource') + ->willReturn($this->source); + $this->source->method('getAllOptions') ->willReturn($options); - $this->serializerMock->expects($this->once()) - ->method('serialize') + $this->serializer->method('serialize') ->with($options) ->willReturn($serializedOptions); - $this->cacheMock->expects($this->once()) - ->method('save') + $this->cache->method('save') ->with($serializedOptions, $cacheKey, $this->cacheTags); - $this->assertSame($options, $this->model->getSelectOptions()); + self::assertSame($options, $this->model->getSelectOptions()); } } diff --git a/app/code/Magento/Eav/etc/di.xml b/app/code/Magento/Eav/etc/di.xml index 8e897b979d2..a4c89dcfab2 100644 --- a/app/code/Magento/Eav/etc/di.xml +++ b/app/code/Magento/Eav/etc/di.xml @@ -8,6 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> <preference for="Magento\Eav\Model\Entity\Setup\PropertyMapperInterface" type="Magento\Eav\Model\Entity\Setup\PropertyMapper\Composite" /> <preference for="Magento\Eav\Model\Entity\AttributeLoaderInterface" type="Magento\Eav\Model\Entity\AttributeLoader" /> + <preference for="Magento\Eav\Model\Entity\Attribute\UniqueValidationInterface" type="Magento\Eav\Model\Entity\Attribute\UniqueValidator" /> <preference for="Magento\Eav\Api\Data\AttributeInterface" type="Magento\Eav\Model\Entity\Attribute" /> <preference for="Magento\Eav\Api\AttributeRepositoryInterface" type="Magento\Eav\Model\AttributeRepository" /> <preference for="Magento\Eav\Api\Data\AttributeGroupInterface" type="Magento\Eav\Model\Entity\Attribute\Group" /> diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/ProductDataMapper.php b/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/ProductDataMapper.php index e4f5de46c4c..270ca37e2d4 100644 --- a/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/ProductDataMapper.php +++ b/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/ProductDataMapper.php @@ -12,12 +12,18 @@ use Magento\Elasticsearch\Model\Adapter\BatchDataMapperInterface; use Magento\Elasticsearch\Model\Adapter\FieldType\Date as DateFieldType; use Magento\AdvancedSearch\Model\Adapter\DataMapper\AdditionalFieldsProviderInterface; +use Magento\Eav\Api\Data\AttributeOptionInterface; /** * Map product index data to search engine metadata */ class ProductDataMapper implements BatchDataMapperInterface { + /** + * @var AttributeOptionInterface[] + */ + private $attributeOptionsCache; + /** * @var Builder */ @@ -95,6 +101,7 @@ public function __construct( $this->excludedAttributes = array_merge($this->defaultExcludedAttributes, $excludedAttributes); $this->additionalFieldsProvider = $additionalFieldsProvider; $this->dataProvider = $dataProvider; + $this->attributeOptionsCache = []; } /** @@ -272,7 +279,13 @@ private function isAttributeDate(Attribute $attribute): bool private function getValuesLabels(Attribute $attribute, array $attributeValues): array { $attributeLabels = []; - foreach ($attribute->getOptions() as $option) { + + $options = $this->getAttributeOptions($attribute); + if (empty($options)) { + return $attributeLabels; + } + + foreach ($options as $option) { if (\in_array($option->getValue(), $attributeValues)) { $attributeLabels[] = $option->getLabel(); } @@ -281,6 +294,22 @@ private function getValuesLabels(Attribute $attribute, array $attributeValues): return $attributeLabels; } + /** + * Retrieve options for attribute + * + * @param Attribute $attribute + * @return array + */ + private function getAttributeOptions(Attribute $attribute): array + { + if (!isset($this->attributeOptionsCache[$attribute->getId()])) { + $options = $attribute->getOptions() ?? []; + $this->attributeOptionsCache[$attribute->getId()] = $options; + } + + return $this->attributeOptionsCache[$attribute->getId()]; + } + /** * Retrieve value for field. If field have only one value this method return it. * Otherwise will be returned array of these values. diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/DynamicField.php b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/DynamicField.php index c7e2a4beabb..9e2659a7579 100644 --- a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/DynamicField.php +++ b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/DynamicField.php @@ -126,7 +126,7 @@ public function getFields(array $context = []): array foreach ($groups as $group) { $groupPriceKey = $this->fieldNameResolver->getFieldName( $priceAttribute, - ['customerGroupId' => $group->getId(), 'websiteId' => $context['websiteId']] + array_merge($context, ['customerGroupId' => $group->getId()]) ); $allAttributes[$groupPriceKey] = [ 'type' => $this->fieldTypeConverter->convert(FieldTypeConverterInterface::INTERNAL_DATA_TYPE_FLOAT), diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Aggregation/Builder/Term.php b/app/code/Magento/Elasticsearch/SearchAdapter/Aggregation/Builder/Term.php index bcfb7f5565b..eeb48f805bc 100644 --- a/app/code/Magento/Elasticsearch/SearchAdapter/Aggregation/Builder/Term.php +++ b/app/code/Magento/Elasticsearch/SearchAdapter/Aggregation/Builder/Term.php @@ -8,10 +8,13 @@ use Magento\Framework\Search\Request\BucketInterface as RequestBucketInterface; use Magento\Framework\Search\Dynamic\DataProviderInterface; +/** + * Builder for term buckets. + */ class Term implements BucketBuilderInterface { /** - * {@inheritdoc} + * @inheritdoc */ public function build( RequestBucketInterface $bucket, @@ -19,13 +22,15 @@ public function build( array $queryResult, DataProviderInterface $dataProvider ) { + $buckets = $queryResult['aggregations'][$bucket->getName()]['buckets'] ?? []; $values = []; - foreach ($queryResult['aggregations'][$bucket->getName()]['buckets'] as $resultBucket) { + foreach ($buckets as $resultBucket) { $values[$resultBucket['key']] = [ 'value' => $resultBucket['key'], 'count' => $resultBucket['doc_count'], ]; } + return $values; } } diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Dynamic/DataProvider.php b/app/code/Magento/Elasticsearch/SearchAdapter/Dynamic/DataProvider.php index cfdab246331..496a77e4c5a 100644 --- a/app/code/Magento/Elasticsearch/SearchAdapter/Dynamic/DataProvider.php +++ b/app/code/Magento/Elasticsearch/SearchAdapter/Dynamic/DataProvider.php @@ -212,6 +212,7 @@ public function getAggregation( 'histogram' => [ 'field' => $fieldName, 'interval' => (float)$range, + 'min_doc_count' => 1, ], ], ]; diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Match.php b/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Match.php index f1c3451482b..e83c49941bc 100644 --- a/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Match.php +++ b/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Match.php @@ -5,11 +5,16 @@ */ namespace Magento\Elasticsearch\SearchAdapter\Query\Builder; +use Magento\Elasticsearch\SearchAdapter\Query\ValueTransformerPool; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Search\Request\Query\BoolExpression; use Magento\Framework\Search\Request\QueryInterface as RequestQueryInterface; use Magento\Elasticsearch\Model\Adapter\FieldMapperInterface; use Magento\Framework\Search\Adapter\Preprocessor\PreprocessorInterface; +/** + * Builder for match query. + */ class Match implements QueryInterface { /** @@ -23,24 +28,35 @@ class Match implements QueryInterface private $fieldMapper; /** + * @deprecated + * @see \Magento\Elasticsearch\SearchAdapter\Query\ValueTransformer\TextTransformer * @var PreprocessorInterface[] */ protected $preprocessorContainer; + /** + * @var ValueTransformerPool + */ + private $valueTransformerPool; + /** * @param FieldMapperInterface $fieldMapper * @param PreprocessorInterface[] $preprocessorContainer + * @param ValueTransformerPool|null $valueTransformerPool */ public function __construct( FieldMapperInterface $fieldMapper, - array $preprocessorContainer + array $preprocessorContainer, + ValueTransformerPool $valueTransformerPool = null ) { $this->fieldMapper = $fieldMapper; $this->preprocessorContainer = $preprocessorContainer; + $this->valueTransformerPool = $valueTransformerPool ?? ObjectManager::getInstance() + ->get(ValueTransformerPool::class); } /** - * {@inheritdoc} + * @inheritdoc */ public function build(array $selectQuery, RequestQueryInterface $requestQuery, $conditionType) { @@ -61,16 +77,14 @@ public function build(array $selectQuery, RequestQueryInterface $requestQuery, $ } /** + * Prepare query. + * * @param string $queryValue * @param string $conditionType * @return array */ protected function prepareQuery($queryValue, $conditionType) { - $queryValue = $this->escape($queryValue); - foreach ($this->preprocessorContainer as $preprocessor) { - $queryValue = $preprocessor->process($queryValue); - } $condition = $conditionType === BoolExpression::QUERY_CONDITION_NOT ? self::QUERY_CONDITION_MUST_NOT : $conditionType; return [ @@ -99,21 +113,34 @@ protected function buildQueries(array $matches, array $queryValue) // Checking for quoted phrase \"phrase test\", trim escaped surrounding quotes if found $count = 0; - $value = preg_replace('#^\\\\"(.*)\\\\"$#m', '$1', $queryValue['value'], -1, $count); + $value = preg_replace('#^"(.*)"$#m', '$1', $queryValue['value'], -1, $count); $condition = ($count) ? 'match_phrase' : 'match'; + $attributesTypes = $this->fieldMapper->getAllAttributesTypes(); + $transformedTypes = []; foreach ($matches as $match) { $resolvedField = $this->fieldMapper->getFieldName( $match['field'], ['type' => FieldMapperInterface::TYPE_QUERY] ); + $valueTransformer = $this->valueTransformerPool->get($attributesTypes[$resolvedField]['type'] ?? 'text'); + $valueTransformerHash = \spl_object_hash($valueTransformer); + if (!isset($transformedTypes[$valueTransformerHash])) { + $transformedTypes[$valueTransformerHash] = $valueTransformer->transform($value); + } + $transformedValue = $transformedTypes[$valueTransformerHash]; + if (null === $transformedValue) { + //Value is incompatible with this field type. + continue; + } + $conditions[] = [ 'condition' => $queryValue['condition'], 'body' => [ $condition => [ $resolvedField => [ - 'query' => $value, - 'boost' => isset($match['boost']) ? $match['boost'] : 1, + 'query' => $transformedValue, + 'boost' => $match['boost'] ?? 1, ], ], ], @@ -124,18 +151,15 @@ protected function buildQueries(array $matches, array $queryValue) } /** - * Cut trailing plus or minus sign, and @ symbol, using of which causes InnoDB to report a syntax error. - * @link https://dev.mysql.com/doc/refman/5.7/en/fulltext-boolean.html Fulltext-boolean search docs. - * * Escape a value for special query characters such as ':', '(', ')', '*', '?', etc. * + * @deprecated + * @see \Magento\Elasticsearch\SearchAdapter\Query\ValueTransformer\TextTransformer * @param string $value * @return string */ protected function escape($value) { - $value = preg_replace('/@+|[@+-]+$/', '', $value); - $pattern = '/(\+|-|&&|\|\||!|\(|\)|\{|}|\[|]|\^|"|~|\*|\?|:|\\\)/'; $replace = '\\\$1'; diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformer/DateTransformer.php b/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformer/DateTransformer.php new file mode 100644 index 00000000000..49eca6e9d82 --- /dev/null +++ b/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformer/DateTransformer.php @@ -0,0 +1,44 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Elasticsearch\SearchAdapter\Query\ValueTransformer; + +use Magento\Elasticsearch\Model\Adapter\FieldType\Date; +use Magento\Elasticsearch\SearchAdapter\Query\ValueTransformerInterface; + +/** + * Value transformer for date type fields. + */ +class DateTransformer implements ValueTransformerInterface +{ + /** + * @var Date + */ + private $dateFieldType; + + /** + * @param Date $dateFieldType + */ + public function __construct(Date $dateFieldType) + { + $this->dateFieldType = $dateFieldType; + } + + /** + * @inheritdoc + */ + public function transform(string $value): ?string + { + try { + $formattedDate = $this->dateFieldType->formatDate(null, $value); + } catch (\Exception $e) { + $formattedDate = null; + } + + return $formattedDate; + } +} diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformer/FloatTransformer.php b/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformer/FloatTransformer.php new file mode 100644 index 00000000000..5e330076d3d --- /dev/null +++ b/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformer/FloatTransformer.php @@ -0,0 +1,24 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Elasticsearch\SearchAdapter\Query\ValueTransformer; + +use Magento\Elasticsearch\SearchAdapter\Query\ValueTransformerInterface; + +/** + * Value transformer for float type fields. + */ +class FloatTransformer implements ValueTransformerInterface +{ + /** + * @inheritdoc + */ + public function transform(string $value): ?float + { + return \is_numeric($value) ? (float) $value : null; + } +} diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformer/IntegerTransformer.php b/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformer/IntegerTransformer.php new file mode 100644 index 00000000000..0846ff3a9bd --- /dev/null +++ b/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformer/IntegerTransformer.php @@ -0,0 +1,24 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Elasticsearch\SearchAdapter\Query\ValueTransformer; + +use Magento\Elasticsearch\SearchAdapter\Query\ValueTransformerInterface; + +/** + * Value transformer for integer type fields. + */ +class IntegerTransformer implements ValueTransformerInterface +{ + /** + * @inheritdoc + */ + public function transform(string $value): ?int + { + return \is_numeric($value) ? (int) $value : null; + } +} diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformer/TextTransformer.php b/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformer/TextTransformer.php new file mode 100644 index 00000000000..68bec2580f6 --- /dev/null +++ b/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformer/TextTransformer.php @@ -0,0 +1,65 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Elasticsearch\SearchAdapter\Query\ValueTransformer; + +use Magento\Elasticsearch\SearchAdapter\Query\ValueTransformerInterface; +use Magento\Framework\Search\Adapter\Preprocessor\PreprocessorInterface; + +/** + * Value transformer for fields with text types. + */ +class TextTransformer implements ValueTransformerInterface +{ + /** + * @var PreprocessorInterface[] + */ + private $preprocessors; + + /** + * @param PreprocessorInterface[] $preprocessors + */ + public function __construct(array $preprocessors = []) + { + foreach ($preprocessors as $preprocessor) { + if (!$preprocessor instanceof PreprocessorInterface) { + throw new \InvalidArgumentException( + \sprintf('"%s" is not a instance of ValueTransformerInterface.', get_class($preprocessor)) + ); + } + } + + $this->preprocessors = $preprocessors; + } + + /** + * @inheritdoc + */ + public function transform(string $value): string + { + $value = $this->escape($value); + foreach ($this->preprocessors as $preprocessor) { + $value = $preprocessor->process($value); + } + + return $value; + } + + /** + * Escape a value for special query characters such as ':', '(', ')', '*', '?', etc. + * + * @param string $value + * @return string + */ + private function escape(string $value): string + { + $pattern = '/(\+|-|&&|\|\||!|\(|\)|\{|}|\[|]|\^|"|~|\*|\?|:|\\\)/'; + $replace = '\\\$1'; + + return preg_replace($pattern, $replace, $value); + } +} diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformerInterface.php b/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformerInterface.php new file mode 100644 index 00000000000..c84ddc69cc7 --- /dev/null +++ b/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformerInterface.php @@ -0,0 +1,22 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Elasticsearch\SearchAdapter\Query; + +/** + * Value transformer of search term for matching with ES field types. + */ +interface ValueTransformerInterface +{ + /** + * Transform value according to field type. + * + * @param string $value + * @return mixed + */ + public function transform(string $value); +} diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformerPool.php b/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformerPool.php new file mode 100644 index 00000000000..11a35d79ce1 --- /dev/null +++ b/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformerPool.php @@ -0,0 +1,46 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Elasticsearch\SearchAdapter\Query; + +/** + * Pool of value transformers. + */ +class ValueTransformerPool +{ + /** + * @var ValueTransformerInterface[] + */ + private $transformers; + + /** + * @param ValueTransformerInterface[] $valueTransformers + */ + public function __construct(array $valueTransformers = []) + { + foreach ($valueTransformers as $valueTransformer) { + if (!$valueTransformer instanceof ValueTransformerInterface) { + throw new \InvalidArgumentException( + \sprintf('"%s" is not a instance of ValueTransformerInterface.', get_class($valueTransformer)) + ); + } + } + + $this->transformers = $valueTransformers; + } + + /** + * Get value transformer related to field type. + * + * @param string $fieldType + * @return ValueTransformerInterface + */ + public function get(string $fieldType): ValueTransformerInterface + { + return $this->transformers[$fieldType] ?? $this->transformers['default']; + } +} diff --git a/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Dynamic/DataProviderTest.php b/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Dynamic/DataProviderTest.php index 9c717ea240a..6258a4a20d6 100644 --- a/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Dynamic/DataProviderTest.php +++ b/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Dynamic/DataProviderTest.php @@ -321,8 +321,15 @@ public function testGetAggregation() $this->clientMock->expects($this->once()) ->method('query') ->with($this->callback(function ($query) { + $histogramParams = $query['body']['aggregations']['prices']['histogram']; // Assert the interval is queried as a float. See MAGETWO-95471 - return $query['body']['aggregations']['prices']['histogram']['interval'] === 10.0; + if ($histogramParams['interval'] !== 10.0) { + return false; + } + if (!isset($histogramParams['min_doc_count']) || $histogramParams['min_doc_count'] !== 1) { + return false; + } + return true; })) ->willReturn([ 'aggregations' => [ diff --git a/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Query/Builder/MatchTest.php b/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Query/Builder/MatchTest.php index 8114feb09d3..c8aa3db39bd 100644 --- a/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Query/Builder/MatchTest.php +++ b/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Query/Builder/MatchTest.php @@ -7,6 +7,8 @@ use Magento\Elasticsearch\Model\Adapter\FieldMapperInterface; use Magento\Elasticsearch\SearchAdapter\Query\Builder\Match as MatchQueryBuilder; +use Magento\Elasticsearch\SearchAdapter\Query\ValueTransformerInterface; +use Magento\Elasticsearch\SearchAdapter\Query\ValueTransformerPool; use Magento\Framework\Search\Request\Query\Match as MatchRequestQuery; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use PHPUnit_Framework_MockObject_MockObject as MockObject; @@ -23,46 +25,48 @@ class MatchTest extends \PHPUnit\Framework\TestCase */ protected function setUp() { + $valueTransformerPoolMock = $this->createMock(ValueTransformerPool::class); + $valueTransformerMock = $this->createMock(ValueTransformerInterface::class); + $valueTransformerPoolMock->method('get') + ->willReturn($valueTransformerMock); + $valueTransformerMock->method('transform') + ->willReturnArgument(0); + $this->matchQueryBuilder = (new ObjectManager($this))->getObject( MatchQueryBuilder::class, [ 'fieldMapper' => $this->getFieldMapper(), 'preprocessorContainer' => [], + 'valueTransformerPool' => $valueTransformerPoolMock, ] ); } /** * Tests that method constructs a correct select query. - * @see MatchQueryBuilder::build * - * @dataProvider queryValuesInvariantsProvider - * - * @param string $rawQueryValue - * @param string $errorMessage + * @see MatchQueryBuilder::build */ - public function testBuild($rawQueryValue, $errorMessage) + public function testBuild() { - $this->assertSelectQuery( - $this->matchQueryBuilder->build([], $this->getMatchRequestQuery($rawQueryValue), 'not'), - $errorMessage - ); - } + $rawQueryValue = 'query_value'; + $selectQuery = $this->matchQueryBuilder->build([], $this->getMatchRequestQuery($rawQueryValue), 'not'); - /** - * @link https://dev.mysql.com/doc/refman/5.7/en/fulltext-boolean.html Fulltext-boolean search docs. - * - * @return array - */ - public function queryValuesInvariantsProvider() - { - return [ - ['query_value', 'Select query field must match simple raw query value.'], - ['query_value+', 'Specifying a trailing plus sign causes InnoDB to report a syntax error.'], - ['query_value-', 'Specifying a trailing minus sign causes InnoDB to report a syntax error.'], - ['query_@value', 'The @ symbol is reserved for use by the @distance proximity search operator.'], - ['query_value+@', 'The @ symbol is reserved for use by the @distance proximity search operator.'], + $expectedSelectQuery = [ + 'bool' => [ + 'must_not' => [ + [ + 'match' => [ + 'some_field' => [ + 'query' => $rawQueryValue, + 'boost' => 43, + ], + ], + ], + ], + ], ]; + $this->assertEquals($expectedSelectQuery, $selectQuery); } /** @@ -111,30 +115,6 @@ public function matchProvider() ]; } - /** - * @param array $selectQuery - * @param string $errorMessage - */ - private function assertSelectQuery($selectQuery, $errorMessage) - { - $expectedSelectQuery = [ - 'bool' => [ - 'must_not' => [ - [ - 'match' => [ - 'some_field' => [ - 'query' => 'query_value', - 'boost' => 43, - ], - ], - ], - ], - ], - ]; - - $this->assertEquals($expectedSelectQuery, $selectQuery, $errorMessage); - } - /** * Gets fieldMapper mock object. * diff --git a/app/code/Magento/Elasticsearch/etc/di.xml b/app/code/Magento/Elasticsearch/etc/di.xml index 6a42e4b3c9f..05a67605ba0 100644 --- a/app/code/Magento/Elasticsearch/etc/di.xml +++ b/app/code/Magento/Elasticsearch/etc/di.xml @@ -68,7 +68,7 @@ </argument> </arguments> </type> - <type name="\Magento\Elasticsearch\Elasticsearch5\Model\Adapter\FieldMapper\ProductFieldMapperProxy"> + <type name="Magento\Elasticsearch\Elasticsearch5\Model\Adapter\FieldMapper\ProductFieldMapperProxy"> <arguments> <argument name="productFieldMappers" xsi:type="array"> <item name="elasticsearch" xsi:type="object">Magento\Elasticsearch\Model\Adapter\FieldMapper\ProductFieldMapper</item> @@ -287,7 +287,7 @@ <argument name="fieldNameResolver" xsi:type="object">elasticsearch5FieldNameResolver</argument> </arguments> </type> - <type name="\Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver\CompositeResolver"> + <type name="Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver\CompositeResolver"> <arguments> <argument name="items" xsi:type="array"> <item name="notEav" xsi:type="object">\Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver\NotEavAttribute</item> @@ -317,7 +317,7 @@ <argument name="fieldTypeConverter" xsi:type="object">Magento\Elasticsearch\Elasticsearch5\Model\Adapter\FieldMapper\Product\FieldProvider\FieldType\Converter</argument> </arguments> </virtualType> - <type name="\Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldType\Resolver\CompositeResolver"> + <type name="Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldType\Resolver\CompositeResolver"> <arguments> <argument name="items" xsi:type="array"> <item name="integer" xsi:type="object">\Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldType\Resolver\IntegerType</item> @@ -327,7 +327,7 @@ </argument> </arguments> </type> - <type name="\Magento\Elasticsearch\Elasticsearch5\Model\Adapter\FieldMapper\Product\FieldProvider\FieldType\Resolver\CompositeResolver"> + <type name="Magento\Elasticsearch\Elasticsearch5\Model\Adapter\FieldMapper\Product\FieldProvider\FieldType\Resolver\CompositeResolver"> <arguments> <argument name="items" xsi:type="array"> <item name="keyword" xsi:type="object">\Magento\Elasticsearch\Elasticsearch5\Model\Adapter\FieldMapper\Product\FieldProvider\FieldType\Resolver\KeywordType</item> @@ -368,12 +368,12 @@ <argument name="indexTypeConverter" xsi:type="object">Magento\Elasticsearch\Elasticsearch5\Model\Adapter\FieldMapper\Product\FieldProvider\FieldIndex\Converter</argument> </arguments> </virtualType> - <type name="\Magento\Elasticsearch\Elasticsearch5\Model\Adapter\FieldMapper\Product\FieldProvider\FieldType\Resolver\KeywordType"> + <type name="Magento\Elasticsearch\Elasticsearch5\Model\Adapter\FieldMapper\Product\FieldProvider\FieldType\Resolver\KeywordType"> <arguments> <argument name="fieldTypeConverter" xsi:type="object">Magento\Elasticsearch\Elasticsearch5\Model\Adapter\FieldMapper\Product\FieldProvider\FieldType\Converter</argument> </arguments> </type> - <type name="\Magento\Elasticsearch\Elasticsearch5\Model\Adapter\FieldMapper\Product\FieldProvider\FieldType\Resolver\IntegerType"> + <type name="Magento\Elasticsearch\Elasticsearch5\Model\Adapter\FieldMapper\Product\FieldProvider\FieldType\Resolver\IntegerType"> <arguments> <argument name="fieldTypeConverter" xsi:type="object">Magento\Elasticsearch\Elasticsearch5\Model\Adapter\FieldMapper\Product\FieldProvider\FieldType\Converter</argument> </arguments> @@ -393,13 +393,13 @@ <argument name="fieldTypeConverter" xsi:type="object">Magento\Elasticsearch\Elasticsearch5\Model\Adapter\FieldMapper\Product\FieldProvider\FieldType\Converter</argument> </arguments> </virtualType> - <type name="\Magento\Elasticsearch\Elasticsearch5\Model\Adapter\FieldMapper\ProductFieldMapper"> + <type name="Magento\Elasticsearch\Elasticsearch5\Model\Adapter\FieldMapper\ProductFieldMapper"> <arguments> <argument name="fieldProvider" xsi:type="object">elasticsearch5FieldProvider</argument> <argument name="fieldNameResolver" xsi:type="object">elasticsearch5FieldNameResolver</argument> </arguments> </type> - <type name="\Magento\Elasticsearch\Model\Adapter\FieldMapper\ProductFieldMapper"> + <type name="Magento\Elasticsearch\Model\Adapter\FieldMapper\ProductFieldMapper"> <arguments> <argument name="attributeAdapterProvider" xsi:type="object">Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeProvider</argument> <argument name="fieldProvider" xsi:type="object">Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProviderInterface</argument> @@ -421,4 +421,22 @@ </argument> </arguments> </type> + <type name="Magento\Elasticsearch\SearchAdapter\Query\ValueTransformerPool"> + <arguments> + <argument name="valueTransformers" xsi:type="array"> + <item name="default" xsi:type="object">Magento\Elasticsearch\SearchAdapter\Query\ValueTransformer\TextTransformer</item> + <item name="date" xsi:type="object">Magento\Elasticsearch\SearchAdapter\Query\ValueTransformer\DateTransformer</item> + <item name="float" xsi:type="object">Magento\Elasticsearch\SearchAdapter\Query\ValueTransformer\FloatTransformer</item> + <item name="integer" xsi:type="object">Magento\Elasticsearch\SearchAdapter\Query\ValueTransformer\IntegerTransformer</item> + </argument> + </arguments> + </type> + <type name="Magento\Elasticsearch\SearchAdapter\Query\ValueTransformer\TextTransformer"> + <arguments> + <argument name="preprocessors" xsi:type="array"> + <item name="stopwordsPreprocessor" xsi:type="object">Magento\Elasticsearch\SearchAdapter\Query\Preprocessor\Stopwords</item> + <item name="synonymsPreprocessor" xsi:type="object">Magento\Search\Adapter\Query\Preprocessor\Synonyms</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Email/Model/Transport.php b/app/code/Magento/Email/Model/Transport.php index 28d2d171eec..90a4e6571c9 100644 --- a/app/code/Magento/Email/Model/Transport.php +++ b/app/code/Magento/Email/Model/Transport.php @@ -87,7 +87,7 @@ public function __construct( public function sendMessage() { try { - $zendMessage = Message::fromString($this->message->getRawMessage()); + $zendMessage = Message::fromString($this->message->getRawMessage())->setEncoding('utf-8'); if (2 === $this->isSetReturnPath && $this->returnPathValue) { $zendMessage->setSender($this->returnPathValue); } elseif (1 === $this->isSetReturnPath && $zendMessage->getFrom()->count()) { diff --git a/app/code/Magento/Email/Test/Mftf/Test/TransactionalEmailsLogoUploadTest.xml b/app/code/Magento/Email/Test/Mftf/Test/TransactionalEmailsLogoUploadTest.xml new file mode 100644 index 00000000000..c3870417fa5 --- /dev/null +++ b/app/code/Magento/Email/Test/Mftf/Test/TransactionalEmailsLogoUploadTest.xml @@ -0,0 +1,40 @@ +<?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="TransactionalEmailsLogoUploadTest"> + <annotations> + <features value="Email"/> + <stories value="Email"/> + <title value="MC-13908: Uploading a Transactional Emails logo"/> + <description value="Transactional Emails Logo should be able to be uploaded in the admin and previewed"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-13908"/> + <group value="LogoUpload"/> + </annotations> + <!--Login to Admin Area--> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginToAdminArea"/> + </before> + <!--Logout from Admin Area--> + <after> + <actionGroup ref="logout" stepKey="logoutOfAdmin"/> + </after> + <!--Navigate to content->Design->Config page--> + <amOnPage url="{{DesignConfigPage.url}}" stepKey="navigateToDesignConfigPage" /> + <waitForPageLoad stepKey="waitForPageloadToViewDesignConfigPage"/> + <click selector="{{AdminDesignConfigSection.scopeRow('3')}}" stepKey="editStoreView"/> + <waitForPageLoad stepKey="waitForPageLoadToOpenStoreViewEditPage"/> + <!--Click Upload logo in Transactional Emails and upload the image and preview it--> + <click selector="{{AdminDesignConfigSection.logoWrapperOpen}}" stepKey="openTab" /> + <attachFile selector="{{AdminDesignConfigSection.logoUpload}}" userInput="{{MagentoLogo.file}}" stepKey="attachLogo"/> + <wait time="5" stepKey="waitingForLogoToUpload" /> + <seeElement selector="{{AdminDesignConfigSection.logoPreview}}" stepKey="LogoPreviewIsVisible"/> + </test> +</tests> diff --git a/app/code/Magento/Email/view/adminhtml/ui_component/design_config_form.xml b/app/code/Magento/Email/view/adminhtml/ui_component/design_config_form.xml index 91c38c92dc7..76a914d10b2 100644 --- a/app/code/Magento/Email/view/adminhtml/ui_component/design_config_form.xml +++ b/app/code/Magento/Email/view/adminhtml/ui_component/design_config_form.xml @@ -13,7 +13,7 @@ <collapsible>true</collapsible> <label translate="true">Transactional Emails</label> </settings> - <field name="email_logo" formElement="fileUploader"> + <field name="email_logo" formElement="imageUploader"> <settings> <notice translate="true">To optimize logo for high-resolution displays, upload an image that is 3x normal size and then specify 1x dimensions in the width/height fields below.</notice> <label translate="true">Logo Image</label> diff --git a/app/code/Magento/GraphQl/Controller/GraphQl.php b/app/code/Magento/GraphQl/Controller/GraphQl.php index c4a0b55de9b..c04bb7f5775 100644 --- a/app/code/Magento/GraphQl/Controller/GraphQl.php +++ b/app/code/Magento/GraphQl/Controller/GraphQl.php @@ -111,10 +111,10 @@ public function dispatch(RequestInterface $request) : ResponseInterface $data = $this->jsonSerializer->unserialize($request->getContent()); $query = isset($data['query']) ? $data['query'] : ''; - + $variables = isset($data['variables']) ? $data['variables'] : null; // We have to extract queried field names to avoid instantiation of non necessary fields in webonyx schema // Temporal coupling is required for performance optimization - $this->queryFields->setQuery($query); + $this->queryFields->setQuery($query, $variables); $schema = $this->schemaGenerator->generate(); $result = $this->queryProcessor->process( diff --git a/app/code/Magento/GraphQl/etc/di.xml b/app/code/Magento/GraphQl/etc/di.xml index b2083ea758e..6acb78f9c7f 100644 --- a/app/code/Magento/GraphQl/etc/di.xml +++ b/app/code/Magento/GraphQl/etc/di.xml @@ -27,7 +27,7 @@ <argument name="factoryMapByConfigElementType" xsi:type="array"> <item name="graphql_interface" xsi:type="object">Magento\Framework\GraphQl\Config\Element\InterfaceFactory</item> <item name="graphql_type" xsi:type="object">Magento\Framework\GraphQl\Config\Element\TypeFactory</item> - <item name="graphql_input" xsi:type="object">Magento\Framework\GraphQl\Config\Element\TypeFactory</item> + <item name="graphql_input" xsi:type="object">Magento\Framework\GraphQl\Config\Element\InputFactory</item> <item name="graphql_enum" xsi:type="object">Magento\Framework\GraphQl\Config\Element\EnumFactory</item> </argument> </arguments> @@ -55,24 +55,16 @@ </argument> </arguments> </virtualType> - <type name="Magento\Framework\GraphQl\Schema\Type\Output\OutputFactory"> + <type name="Magento\Framework\GraphQl\Schema\Type\TypeRegistry"> <arguments> - <argument name="prototypes" xsi:type="array"> + <argument name="configToTypeMap" xsi:type="array"> <item name="Magento\Framework\GraphQl\Config\Element\Type" xsi:type="string">Magento\Framework\GraphQl\Schema\Type\Output\OutputTypeObject</item> + <item name="Magento\Framework\GraphQl\Config\Element\Input" xsi:type="string">Magento\Framework\GraphQl\Schema\Type\Input\InputObjectType</item> <item name="Magento\Framework\GraphQl\Config\Element\InterfaceType" xsi:type="string">Magento\Framework\GraphQl\Schema\Type\Output\OutputInterfaceObject</item> <item name="Magento\Framework\GraphQl\Config\Element\Enum" xsi:type="string">Magento\Framework\GraphQl\Schema\Type\Enum\Enum</item> </argument> </arguments> </type> - <type name="Magento\Framework\GraphQl\Schema\Type\Input\InputFactory"> - <arguments> - <argument name="prototypes" xsi:type="array"> - <item name="Magento\Framework\GraphQl\Config\Element\Type" xsi:type="string">Magento\Framework\GraphQl\Schema\Type\Input\InputObjectType</item> - <item name="Magento\Framework\GraphQl\Config\Element\InterfaceType" xsi:type="string">Magento\Framework\GraphQl\Schema\Type\Input\InputObjectType</item> - <item name="Magento\Framework\GraphQl\Config\Element\Enum" xsi:type="string">Magento\Framework\GraphQl\Schema\Type\Enum\Enum</item> - </argument> - </arguments> - </type> <type name="Magento\Framework\GraphQl\Schema\Type\Output\ElementMapper"> <arguments> <argument name="formatter" xsi:type="object">Magento\Framework\GraphQl\Schema\Type\Output\ElementMapper\FormatterComposite</argument> diff --git a/app/code/Magento/GraphQl/etc/schema.graphqls b/app/code/Magento/GraphQl/etc/schema.graphqls index 2281495d059..7ea715097cd 100644 --- a/app/code/Magento/GraphQl/etc/schema.graphqls +++ b/app/code/Magento/GraphQl/etc/schema.graphqls @@ -5,7 +5,6 @@ type Query { } type Mutation { - placeholderMutation: String @doc(description: "Mutation type cannot be declared without fields. The placeholder will be removed when at least one mutation field is declared.") } input FilterTypeInput @doc(description: "FilterTypeInput specifies which action will be performed in a query ") { diff --git a/app/code/Magento/GroupedImportExport/etc/di.xml b/app/code/Magento/GroupedImportExport/etc/di.xml index 38030b3ec94..25fd3b56975 100644 --- a/app/code/Magento/GroupedImportExport/etc/di.xml +++ b/app/code/Magento/GroupedImportExport/etc/di.xml @@ -9,7 +9,7 @@ <type name="Magento\CatalogImportExport\Model\Export\RowCustomizer\Composite"> <arguments> <argument name="customizers" xsi:type="array"> - <item name="gropedProduct" xsi:type="string">Magento\GroupedImportExport\Model\Export\RowCustomizer</item> + <item name="groupedProduct" xsi:type="string">Magento\GroupedImportExport\Model\Export\RowCustomizer</item> </argument> </arguments> </type> diff --git a/app/code/Magento/GroupedProduct/Test/Mftf/Data/GroupedProductData.xml b/app/code/Magento/GroupedProduct/Test/Mftf/Data/GroupedProductData.xml index 4d979953a93..cb268b51f08 100644 --- a/app/code/Magento/GroupedProduct/Test/Mftf/Data/GroupedProductData.xml +++ b/app/code/Magento/GroupedProduct/Test/Mftf/Data/GroupedProductData.xml @@ -28,4 +28,16 @@ <requiredEntity type="custom_attribute_array">ApiProductDescription</requiredEntity> <requiredEntity type="custom_attribute_array">ApiProductShortDescription</requiredEntity> </entity> + <entity name="ApiGroupedProduct2" type="product3"> + <data key="sku" unique="suffix">api-grouped-product</data> + <data key="type_id">grouped</data> + <data key="attribute_set_id">4</data> + <data key="name" unique="suffix">Api Grouped Product</data> + <data key="status">1</data> + <data key="urlKey" unique="suffix">api-grouped-product</data> + <requiredEntity type="custom_attribute_array">CustomAttributeCategoryIds</requiredEntity> + <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> + <requiredEntity type="custom_attribute_array">ApiProductDescription</requiredEntity> + <requiredEntity type="custom_attribute_array">ApiProductShortDescription</requiredEntity> + </entity> </entities> diff --git a/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminAddDefaultVideoGroupedProductTest.xml b/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminAddDefaultVideoGroupedProductTest.xml index 8634fc3f6f9..c3a95bbef3a 100644 --- a/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminAddDefaultVideoGroupedProductTest.xml +++ b/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminAddDefaultVideoGroupedProductTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminAddDefaultVideoGroupedProductTest" extends="AdminAddDefaultVideoSimpleProductTest"> <annotations> <features value="GroupedProduct"/> diff --git a/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminDeleteGroupedProductTest.xml b/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminDeleteGroupedProductTest.xml new file mode 100644 index 00000000000..966e2485139 --- /dev/null +++ b/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminDeleteGroupedProductTest.xml @@ -0,0 +1,57 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminDeleteGroupedProductTest"> + <annotations> + <features value="GroupedProduct"/> + <title value="Delete Grouped Product"/> + <description value="Admin should be able to delete a grouped product"/> + <testCaseId value="MC-11019"/> + <severity value="CRITICAL"/> + <group value="mtf_migrated"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <createData entity="ApiProductWithDescription" stepKey="createSimpleProduct"/> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="ApiGroupedProduct2" stepKey="createGroupedProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="OneSimpleProductLink" stepKey="addProductOne"> + <requiredEntity createDataKey="createGroupedProduct"/> + <requiredEntity createDataKey="createSimpleProduct"/> + </createData> + </before> + <after> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="deleteProductUsingProductGrid" stepKey="deleteGroupedProductFilteredBySkuAndName"> + <argument name="product" value="$$createGroupedProduct$$"/> + </actionGroup> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> + <see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="A total of 1 record(s) have been deleted." stepKey="deleteMessage"/> + <!--Verify product on Product Page --> + <amOnPage url="{{StorefrontProductPage.url($$createGroupedProduct.name$$)}}" stepKey="amOnGroupedProductPage"/> + <see selector="{{StorefrontProductInfoMainSection.productName}}" userInput="Whoops, our bad..." stepKey="seeWhoops"/> + <!--Search for the product by sku--> + <fillField selector="{{StorefrontQuickSearchSection.searchPhrase}}" userInput="$$createGroupedProduct.sku$$" stepKey="fillSearchBarByProductSku"/> + <waitForPageLoad stepKey="waitForSearchButton"/> + <click selector="{{StorefrontQuickSearchSection.searchButton}}" stepKey="clickSearchButton"/> + <waitForPageLoad stepKey="waitForSearchResults"/> + <!-- Should not see any search results --> + <dontSee userInput="$$createGroupedProduct.sku$$" selector="{{StorefrontCatalogSearchMainSection.searchResults}}" stepKey="dontSeeProduct"/> + <see selector="{{StorefrontCatalogSearchMainSection.message}}" userInput="Your search returned no results." stepKey="seeCantFindProductOneMessage"/> + <!-- Go to the category page that we created in the before block --> + <amOnPage url="{{StorefrontCategoryPage.url($$createCategory.name$$)}}" stepKey="onCategoryPage"/> + <!-- Should not see the product --> + <dontSee userInput="$$createGroupedProduct.name$$" selector="{{StorefrontCategoryMainSection.productsList}}" stepKey="dontSeeProductInCategory"/> + <see selector="{{StorefrontCategoryMainSection.emptyProductMessage}}" userInput="We can't find products matching the selection." stepKey="seeEmptyProductMessage"/> + </test> +</tests> diff --git a/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminRemoveDefaultVideoGroupedProductTest.xml b/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminRemoveDefaultVideoGroupedProductTest.xml index 25c45abdfe0..e322d4a1eb0 100644 --- a/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminRemoveDefaultVideoGroupedProductTest.xml +++ b/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdminRemoveDefaultVideoGroupedProductTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminRemoveDefaultVideoGroupedProductTest" extends="AdminRemoveDefaultVideoSimpleProductTest"> <annotations> <features value="GroupedProduct"/> diff --git a/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdvanceCatalogSearchGroupedProductTest.xml b/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdvanceCatalogSearchGroupedProductTest.xml index 0fd52ac4a65..2a600d38250 100644 --- a/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdvanceCatalogSearchGroupedProductTest.xml +++ b/app/code/Magento/GroupedProduct/Test/Mftf/Test/AdvanceCatalogSearchGroupedProductTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdvanceCatalogSearchGroupedProductByNameTest" extends="AdvanceCatalogSearchSimpleProductByNameTest"> <annotations> <features value="GroupedProduct"/> diff --git a/app/code/Magento/GroupedProduct/Test/Unit/Model/Product/Type/Grouped/PriceTest.php b/app/code/Magento/GroupedProduct/Test/Unit/Model/Product/Type/Grouped/PriceTest.php index f02849c244c..176c29add48 100644 --- a/app/code/Magento/GroupedProduct/Test/Unit/Model/Product/Type/Grouped/PriceTest.php +++ b/app/code/Magento/GroupedProduct/Test/Unit/Model/Product/Type/Grouped/PriceTest.php @@ -64,7 +64,7 @@ public function testGetFinalPrice( $expectedFinalPrice ) { $rawFinalPrice = 10; - $rawPriceCheckStep = 6; + $rawPriceCheckStep = 5; $this->productMock->expects( $this->any() @@ -155,7 +155,7 @@ public function getFinalPriceDataProvider() 'custom_option_null' => [ 'associatedProducts' => [], 'options' => [[], []], - 'expectedPriceCall' => 6, /* product call number to check final price formed correctly */ + 'expectedPriceCall' => 5, /* product call number to check final price formed correctly */ 'expectedFinalPrice' => 10, /* 10(product price) + 2(options count) * 5(qty) * 5(option price) */ ], 'custom_option_exist' => [ @@ -165,7 +165,7 @@ public function getFinalPriceDataProvider() ['associated_product_2', $optionMock], ['associated_product_3', $optionMock], ], - 'expectedPriceCall' => 16, /* product call number to check final price formed correctly */ + 'expectedPriceCall' => 15, /* product call number to check final price formed correctly */ 'expectedFinalPrice' => 35, /* 10(product price) + 2(options count) * 5(qty) * 5(option price) */ ] ]; diff --git a/app/code/Magento/GroupedProductGraphQl/Model/GroupedProductTypeResolver.php b/app/code/Magento/GroupedProductGraphQl/Model/GroupedProductTypeResolver.php index 087cf10c8d6..8818766692f 100644 --- a/app/code/Magento/GroupedProductGraphQl/Model/GroupedProductTypeResolver.php +++ b/app/code/Magento/GroupedProductGraphQl/Model/GroupedProductTypeResolver.php @@ -8,19 +8,21 @@ namespace Magento\GroupedProductGraphQl\Model; use Magento\Framework\GraphQl\Query\Resolver\TypeResolverInterface; +use Magento\GroupedProduct\Model\Product\Type\Grouped as Type; /** - * {@inheritdoc} + * @inheritdoc */ class GroupedProductTypeResolver implements TypeResolverInterface { + const GROUPED_PRODUCT = 'GroupedProduct'; /** - * {@inheritdoc} + * @inheritdoc */ public function resolveType(array $data) : string { - if (isset($data['type_id']) && $data['type_id'] == 'grouped') { - return 'GroupedProduct'; + if (isset($data['type_id']) && $data['type_id'] == Type::TYPE_CODE) { + return self::GROUPED_PRODUCT; } return ''; } diff --git a/app/code/Magento/ImportExport/Block/Adminhtml/Export/Filter.php b/app/code/Magento/ImportExport/Block/Adminhtml/Export/Filter.php index 8721dd05a0f..d032f2f7621 100644 --- a/app/code/Magento/ImportExport/Block/Adminhtml/Export/Filter.php +++ b/app/code/Magento/ImportExport/Block/Adminhtml/Export/Filter.php @@ -237,8 +237,8 @@ protected function _getSelectHtmlWithValue(Attribute $attribute, $value) if ($attribute->getFilterOptions()) { $options = []; - foreach ($attribute->getFilterOptions() as $value => $label) { - $options[] = ['value' => $value, 'label' => $label]; + foreach ($attribute->getFilterOptions() as $optionValue => $label) { + $options[] = ['value' => $optionValue, 'label' => $label]; } } else { $options = $attribute->getSource()->getAllOptions(false); diff --git a/app/code/Magento/ImportExport/Model/Import.php b/app/code/Magento/ImportExport/Model/Import.php index 1372322ee58..1cc8dc224ac 100644 --- a/app/code/Magento/ImportExport/Model/Import.php +++ b/app/code/Magento/ImportExport/Model/Import.php @@ -7,10 +7,12 @@ namespace Magento\ImportExport\Model; use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\App\ObjectManager; use Magento\Framework\HTTP\Adapter\FileTransferFactory; use Magento\Framework\Stdlib\DateTime\DateTime; use Magento\ImportExport\Model\Import\ErrorProcessing\ProcessingError; use Magento\ImportExport\Model\Import\ErrorProcessing\ProcessingErrorAggregatorInterface; +use Magento\Framework\Message\ManagerInterface; /** * Import model @@ -179,6 +181,11 @@ class Import extends \Magento\ImportExport\Model\AbstractModel */ private $localeDate; + /** + * @var ManagerInterface + */ + private $messageManager; + /** * @param \Psr\Log\LoggerInterface $logger * @param \Magento\Framework\Filesystem $filesystem @@ -195,6 +202,7 @@ class Import extends \Magento\ImportExport\Model\AbstractModel * @param History $importHistoryModel * @param DateTime $localeDate * @param array $data + * @param ManagerInterface|null $messageManager * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -212,7 +220,8 @@ public function __construct( \Magento\Framework\Indexer\IndexerRegistry $indexerRegistry, \Magento\ImportExport\Model\History $importHistoryModel, DateTime $localeDate, - array $data = [] + array $data = [], + ManagerInterface $messageManager = null ) { $this->_importExportData = $importExportData; $this->_coreConfig = $coreConfig; @@ -227,6 +236,7 @@ public function __construct( $this->_filesystem = $filesystem; $this->importHistoryModel = $importHistoryModel; $this->localeDate = $localeDate; + $this->messageManager = $messageManager ?: ObjectManager::getInstance()->get(ManagerInterface::class); parent::__construct($logger, $filesystem, $data); } @@ -620,9 +630,13 @@ public function validateSource(\Magento\ImportExport\Model\Import\AbstractSource $messages = $this->getOperationResultMessages($errorAggregator); $this->addLogComment($messages); - $result = !$errorAggregator->getErrorsCount(); + $errorsCount = $errorAggregator->getErrorsCount(); + $result = !$errorsCount; $validationStrategy = $this->getData(self::FIELD_NAME_VALIDATION_STRATEGY); - if ($validationStrategy === ProcessingErrorAggregatorInterface::VALIDATION_STRATEGY_SKIP_ERRORS) { + if ($errorsCount + && $validationStrategy === ProcessingErrorAggregatorInterface::VALIDATION_STRATEGY_SKIP_ERRORS + ) { + $this->messageManager->addWarningMessage(__('Skipped errors: %1', $errorsCount)); $result = true; } diff --git a/app/code/Magento/ImportExport/Test/Mftf/Page/AdminExportIndexPage.xml b/app/code/Magento/ImportExport/Test/Mftf/Page/AdminExportIndexPage.xml new file mode 100644 index 00000000000..55ed3edd9bc --- /dev/null +++ b/app/code/Magento/ImportExport/Test/Mftf/Page/AdminExportIndexPage.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="AdminExportIndexPage" url="admin/export/" area="admin" module="Magento_ImportExport"> + <section name="AdminExportMainSection"/> + </page> +</pages> diff --git a/app/code/Magento/ImportExport/Test/Mftf/Page/AdminImportIndexPage.xml b/app/code/Magento/ImportExport/Test/Mftf/Page/AdminImportIndexPage.xml new file mode 100644 index 00000000000..87807eb9b0e --- /dev/null +++ b/app/code/Magento/ImportExport/Test/Mftf/Page/AdminImportIndexPage.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="AdminImportIndexPage" url="admin/import/" area="admin" module="Magento_ImportExport"> + <section name="AdminImportHeaderSection"/> + <section name="AdminImportMainSection"/> + </page> +</pages> diff --git a/app/code/Magento/ImportExport/Test/Mftf/Section/AdminExportAttributeSection.xml b/app/code/Magento/ImportExport/Test/Mftf/Section/AdminExportAttributeSection.xml new file mode 100644 index 00000000000..ad9e7672ce1 --- /dev/null +++ b/app/code/Magento/ImportExport/Test/Mftf/Section/AdminExportAttributeSection.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="AdminExportAttributeSection"> + <element name="filterByAttributeCode" type="input" selector="#export_filter_grid_filter_attribute_code"/> + <element name="resetFilter" type="button" selector="button[data-action='grid-filter-reset']" timeout="30"/> + <element name="search" type="button" selector="button[data-action=grid-filter-apply]" timeout="30"/> + </section> +</sections> diff --git a/app/code/Magento/ImportExport/Test/Mftf/Section/AdminExportMainSection.xml b/app/code/Magento/ImportExport/Test/Mftf/Section/AdminExportMainSection.xml new file mode 100644 index 00000000000..da1d928607e --- /dev/null +++ b/app/code/Magento/ImportExport/Test/Mftf/Section/AdminExportMainSection.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="AdminExportMainSection"> + <element name="entityType" type="select" selector="#entity"/> + <element name="entityAttributes" type="select" selector="#export_filter_form"/> + </section> +</sections> diff --git a/app/code/Magento/ImportExport/Test/Mftf/Section/AdminImportHeaderSection.xml b/app/code/Magento/ImportExport/Test/Mftf/Section/AdminImportHeaderSection.xml new file mode 100644 index 00000000000..748580be094 --- /dev/null +++ b/app/code/Magento/ImportExport/Test/Mftf/Section/AdminImportHeaderSection.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminImportHeaderSection"> + <element name="checkDataButton" type="button" selector="#upload_button" timeout="30"/> + </section> +</sections> diff --git a/app/code/Magento/ImportExport/Test/Mftf/Section/AdminImportMainSection.xml b/app/code/Magento/ImportExport/Test/Mftf/Section/AdminImportMainSection.xml new file mode 100644 index 00000000000..2ce6b1e3577 --- /dev/null +++ b/app/code/Magento/ImportExport/Test/Mftf/Section/AdminImportMainSection.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminImportMainSection"> + <element name="entityType" type="select" selector="#entity"/> + <element name="importBehavior" type="select" selector="#basic_behavior"/> + <element name="selectFileToImport" type="input" selector="#import_file"/> + <element name="importButton" type="button" selector="#import_validation_container button" timeout="30"/> + </section> +</sections> diff --git a/app/code/Magento/ImportExport/Test/Mftf/Test/AdminImportProductsWithDeleteBehaviorTest.xml b/app/code/Magento/ImportExport/Test/Mftf/Test/AdminImportProductsWithDeleteBehaviorTest.xml new file mode 100644 index 00000000000..4cbb0603d90 --- /dev/null +++ b/app/code/Magento/ImportExport/Test/Mftf/Test/AdminImportProductsWithDeleteBehaviorTest.xml @@ -0,0 +1,65 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminImportProductsWithDeleteBehaviorTest"> + <annotations> + <description value="Verify Magento native import products with delete behavior."/> + <stories value="Verify Magento native import products with delete behavior."/> + <features value="Import/Export"/> + <title value="Verify Magento native import products with delete behavior."/> + <severity value="CRITICAL"/> + <testCaseId value="MAGETWO-30587"/> + <group value="importExport"/> + </annotations> + <before> + <!--Create Simple product--> + <createData entity="SimpleProduct2" stepKey="createSimpleProduct"> + <field key="name">Simple Product for Test</field> + <field key="sku">Simple Product for Test</field> + </createData> + <!--Create Virtual product--> + <createData entity="VirtualProduct" stepKey="createVirtualProduct"> + <field key="name">Virtual Product for Test</field> + <field key="sku">Virtual Product for Test</field> + </createData> + <!-- Create Downloadable product --> + <createData entity="ApiDownloadableProduct" stepKey="createDownloadableProduct"> + <field key="name">Api Downloadable Product for Test</field> + <field key="sku">Api Downloadable Product for Test</field> + </createData> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <actionGroup ref="AdminClearFiltersActionGroup" stepKey="clearProductFilters"/> + <actionGroup ref="logout" stepKey="logoutFromAdmin"/> + </after> + <amOnPage url="{{AdminImportIndexPage.url}}" stepKey="goToImportIndexPage"/> + <selectOption selector="{{AdminImportMainSection.entityType}}" userInput="Products" stepKey="selectProductsOption"/> + <waitForElementVisible selector="{{AdminImportMainSection.importBehavior}}" stepKey="waitForImportBehaviorElementVisible"/> + <selectOption selector="{{AdminImportMainSection.importBehavior}}" userInput="Delete" stepKey="selectDeleteOption"/> + <attachFile selector="{{AdminImportMainSection.selectFileToImport}}" userInput="catalog_products.csv" stepKey="attachFileForImport"/> + <click selector="{{AdminImportHeaderSection.checkDataButton}}" stepKey="clickCheckDataButton"/> + <click selector="{{AdminImportMainSection.importButton}}" stepKey="clickImportButton"/> + <see selector="{{AdminMessagesSection.successMessage}}" userInput="Import successfully done" stepKey="assertSuccessMessage"/> + <see selector="{{AdminMessagesSection.notice}}" userInput="Created: 0, Updated: 0, Deleted: 3" stepKey="assertNotice"/> + <actionGroup ref="SearchForProductOnBackendActionGroup" stepKey="searchSimpleProductOnBackend"> + <argument name="product" value="$$createSimpleProduct$$"/> + </actionGroup> + <see selector="{{AdminDataGridTableSection.dataGridEmpty}}" userInput="We couldn't find any records." stepKey="assertDataGridEmptyMessage"/> + <actionGroup ref="SearchForProductOnBackendActionGroup" stepKey="searchVirtualProductOnBackend"> + <argument name="product" value="$$createVirtualProduct$$"/> + </actionGroup> + <see selector="{{AdminDataGridTableSection.dataGridEmpty}}" userInput="We couldn't find any records." stepKey="assertDataGridEmptyMessage1"/> + <actionGroup ref="SearchForProductOnBackendActionGroup" stepKey="searchDownloadableProductOnBackend"> + <argument name="product" value="$$createDownloadableProduct$$"/> + </actionGroup> + <see selector="{{AdminDataGridTableSection.dataGridEmpty}}" userInput="We couldn't find any records." stepKey="assertDataGridEmptyMessage2"/> + </test> +</tests> diff --git a/app/code/Magento/ImportExport/etc/adminhtml/di.xml b/app/code/Magento/ImportExport/etc/adminhtml/di.xml index 03c24c7b2bf..8f7955e679c 100644 --- a/app/code/Magento/ImportExport/etc/adminhtml/di.xml +++ b/app/code/Magento/ImportExport/etc/adminhtml/di.xml @@ -6,7 +6,7 @@ */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> - <type name="\Magento\ImportExport\Controller\Adminhtml\Import\Start"> + <type name="Magento\ImportExport\Controller\Adminhtml\Import\Start"> <arguments> <argument name="exceptionMessageFactory" xsi:type="object">Magento\Framework\Message\ExceptionMessageLookupFactory</argument> </arguments> diff --git a/app/code/Magento/Indexer/Model/Message/Invalid.php b/app/code/Magento/Indexer/Model/Message/Invalid.php index 5a3f879b0ad..79f9fcef964 100644 --- a/app/code/Magento/Indexer/Model/Message/Invalid.php +++ b/app/code/Magento/Indexer/Model/Message/Invalid.php @@ -6,6 +6,9 @@ namespace Magento\Indexer\Model\Message; +/** + * Message about invalid indexers. + */ class Invalid implements \Magento\Framework\Notification\MessageInterface { /** @@ -71,7 +74,7 @@ public function getText() return __( 'One or more <a href="%1">indexers are invalid</a>. Make sure your <a href="%2" target="_blank">Magento cron job</a> is running.', $url, - 'http://devdocs.magento.com/guides/v2.2/config-guide/cli/config-cli-subcommands-cron.html#create-or-remove-the-magento-crontab' + 'https://devdocs.magento.com/guides/v2.2/config-guide/cli/config-cli-subcommands-cron.html#create-or-remove-the-magento-crontab' ); //@codingStandardsIgnoreEnd } diff --git a/app/code/Magento/Indexer/Test/Mftf/Page/AdminIndexManagementPage.xml b/app/code/Magento/Indexer/Test/Mftf/Page/AdminIndexManagementPage.xml new file mode 100644 index 00000000000..ed9a3dbb8c7 --- /dev/null +++ b/app/code/Magento/Indexer/Test/Mftf/Page/AdminIndexManagementPage.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="AdminIndexManagementPage" url="indexer/indexer/list/" area="admin" module="Indexer"> + <section name="AdminIndexManagementSection"/> + </page> +</pages> diff --git a/app/code/Magento/Indexer/Test/Mftf/Section/AdminIndexManagementSection.xml b/app/code/Magento/Indexer/Test/Mftf/Section/AdminIndexManagementSection.xml index db98116c224..860b600de2b 100644 --- a/app/code/Magento/Indexer/Test/Mftf/Section/AdminIndexManagementSection.xml +++ b/app/code/Magento/Indexer/Test/Mftf/Section/AdminIndexManagementSection.xml @@ -9,9 +9,12 @@ <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminIndexManagementSection"> - <!--<element name="catalogSearchCheckbox" type="checkbox" selector="input[value='catalogsearch_fulltext']"/>--> + <element name="catalogSearchCheckbox" type="checkbox" selector="input[value='catalogsearch_fulltext']"/> <element name="indexerCheckbox" type="checkbox" selector="input[value='{{var1}}']" parameterized="true"/> <element name="massActionSelect" type="select" selector="#gridIndexer_massaction-select"/> <element name="massActionSubmit" type="button" selector="#gridIndexer_massaction-form button"/> + <element name="indexerSelect" type="select" selector="//select[contains(@class,'action-select-multiselect')]"/> + <element name="indexerStatus" type="text" selector="//tr[descendant::td[contains(., '{{status}}')]]//*[contains(@class, 'col-indexer_status')]/span" parameterized="true"/> + <element name="successMessage" type="text" selector="//*[@data-ui-id='messages-message-success']"/> </section> </sections> diff --git a/app/code/Magento/Indexer/etc/di.xml b/app/code/Magento/Indexer/etc/di.xml index c7603191e86..76e7e7a4622 100644 --- a/app/code/Magento/Indexer/etc/di.xml +++ b/app/code/Magento/Indexer/etc/di.xml @@ -42,7 +42,7 @@ <plugin name="page-cache-indexer-reindex-clean-cache" type="Magento\Indexer\Model\Processor\CleanCache" sortOrder="10"/> </type> - <type name="\Magento\Indexer\Model\ProcessManager"> + <type name="Magento\Indexer\Model\ProcessManager"> <arguments> <argument name="threadsCount" xsi:type="init_parameter">Magento\Indexer\Model\ProcessManager::THREADS_COUNT</argument> </arguments> diff --git a/app/code/Magento/InstantPurchase/README.md b/app/code/Magento/InstantPurchase/README.md index 534696bf353..9b618eaca99 100644 --- a/app/code/Magento/InstantPurchase/README.md +++ b/app/code/Magento/InstantPurchase/README.md @@ -10,7 +10,7 @@ Prerequisites to display the Instant Purchase button: ## Structure -In addition to [a typical file structure for a Magento 2 module](http://devdocs.magento.com/guides/v2.2/extension-dev-guide/build/module-file-structure.html) `PaymentMethodsIntegration` directory contains interfaces and basic implementation of integration vault payment method to the instant purchase. +In addition to [a typical file structure for a Magento 2 module](https://devdocs.magento.com/guides/v2.2/extension-dev-guide/build/module-file-structure.html) `PaymentMethodsIntegration` directory contains interfaces and basic implementation of integration vault payment method to the instant purchase. ## Extensibility @@ -22,7 +22,7 @@ All payments created for instant purchase also have `'instant-purchase' => true` ### Payment method integration -Instant purchase support may be implemented for any payment method with [vault support](http://devdocs.magento.com/guides/v2.1/payments-integrations/vault/vault-intro.html). +Instant purchase support may be implemented for any payment method with [vault support](https://devdocs.magento.com/guides/v2.1/payments-integrations/vault/vault-intro.html). Basic implementation provided in `Magento\InstantPurchase\PaymentMethodIntegration` should be enough in most cases. It is not enabled by default to avoid issues on production sites and authors of vault payment method should verify correct work for instant purchase manually. To enable basic implementation just add single option to configuration of payemnt method in `config.xml`: @@ -52,7 +52,7 @@ Basic implementation is a good start point but it's recommended to provide own i The `Magento_InstantPurchase` module does not introduce backward incompatible changes. -You can track [backward incompatible changes in patch releases](http://devdocs.magento.com/guides/v2.2/release-notes/changes/ce_changes.html). +You can track [backward incompatible changes in patch releases](https://devdocs.magento.com/guides/v2.2/release-notes/changes/ce_changes.html). *** diff --git a/app/code/Magento/LayeredNavigation/Test/Mftf/Section/LayeredNavigationSection.xml b/app/code/Magento/LayeredNavigation/Test/Mftf/Section/LayeredNavigationSection.xml index ad94d44d636..b44ee9ddbd7 100644 --- a/app/code/Magento/LayeredNavigation/Test/Mftf/Section/LayeredNavigationSection.xml +++ b/app/code/Magento/LayeredNavigation/Test/Mftf/Section/LayeredNavigationSection.xml @@ -16,4 +16,8 @@ <element name="PriceNavigationStep" type="button" selector="#catalog_layered_navigation_price_range_step"/> <element name="PriceNavigationStepSystemValue" type="button" selector="#catalog_layered_navigation_price_range_step_inherit"/> </section> + + <section name="StorefrontLayeredNavigationSection"> + <element name="shoppingOptionsByName" type="button" selector="//*[text()='Shopping Options']/..//*[contains(text(),'{{arg}}')]" parameterized="true"/> + </section> </sections> diff --git a/app/code/Magento/LayeredNavigation/etc/adminhtml/system.xml b/app/code/Magento/LayeredNavigation/etc/adminhtml/system.xml index de463784745..8d3f70c2806 100644 --- a/app/code/Magento/LayeredNavigation/etc/adminhtml/system.xml +++ b/app/code/Magento/LayeredNavigation/etc/adminhtml/system.xml @@ -20,7 +20,7 @@ </field> <field id="price_range_step" translate="label" type="text" sortOrder="15" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Default Price Navigation Step</label> - <validate>validate-number validate-number-range number-range-0.01-1000000000</validate> + <validate>validate-number validate-number-range number-range-0.01-9999999999999999</validate> <depends> <field id="price_range_calculation">manual</field> </depends> diff --git a/app/code/Magento/Msrp/view/base/web/js/msrp.js b/app/code/Magento/Msrp/view/base/web/js/msrp.js index deeadd9b55b..72dd1d8bbec 100644 --- a/app/code/Magento/Msrp/view/base/web/js/msrp.js +++ b/app/code/Magento/Msrp/view/base/web/js/msrp.js @@ -73,6 +73,7 @@ define([ this.initTierPopup(); } $(this.options.cartButtonId).on('click', this._addToCartSubmit.bind(this)); + $(this.options.cartForm).on('submit', this._onSubmitForm.bind(this)); }, /** @@ -249,8 +250,10 @@ define([ /** * Handler for addToCart action + * + * @param {Object} e */ - _addToCartSubmit: function () { + _addToCartSubmit: function (e) { this.element.trigger('addToCart', this.element); if (this.element.data('stop-processing')) { @@ -266,8 +269,20 @@ define([ if (this.options.addToCartUrl) { $('.mage-dropdown-dialog > .ui-dialog-content').dropdownDialog('close'); } + + e.preventDefault(); $(this.options.cartForm).submit(); + }, + /** + * Handler for submit form + * + * @private + */ + _onSubmitForm: function () { + if ($(this.options.cartForm).valid()) { + $(this.options.cartButtonId).prop('disabled', true); + } } }); diff --git a/app/code/Magento/Multishipping/Model/Checkout/Type/Multishipping.php b/app/code/Magento/Multishipping/Model/Checkout/Type/Multishipping.php index d1df064f571..42f5289d210 100644 --- a/app/code/Magento/Multishipping/Model/Checkout/Type/Multishipping.php +++ b/app/code/Magento/Multishipping/Model/Checkout/Type/Multishipping.php @@ -1182,7 +1182,7 @@ private function removePlacedItemsFromQuote(array $shippingAddresses, array $pla { foreach ($shippingAddresses as $address) { foreach ($address->getAllItems() as $addressItem) { - if (in_array($addressItem->getId(), $placedAddressItems)) { + if (in_array($addressItem->getQuoteItemId(), $placedAddressItems)) { if ($addressItem->getProduct()->getIsVirtual()) { $addressItem->isDeleted(true); } else { @@ -1232,7 +1232,7 @@ private function searchQuoteAddressId(OrderInterface $order, array $addresses): $item = array_pop($items); foreach ($addresses as $address) { foreach ($address->getAllItems() as $addressItem) { - if ($addressItem->getId() == $item->getQuoteItemId()) { + if ($addressItem->getQuoteItemId() == $item->getQuoteItemId()) { return (int)$address->getId(); } } diff --git a/app/code/Magento/Multishipping/view/frontend/layout/multishipping_checkout_customer_address.xml b/app/code/Magento/Multishipping/view/frontend/layout/multishipping_checkout_customer_address.xml index c6bcdeb7b04..fee3cb790a5 100644 --- a/app/code/Magento/Multishipping/view/frontend/layout/multishipping_checkout_customer_address.xml +++ b/app/code/Magento/Multishipping/view/frontend/layout/multishipping_checkout_customer_address.xml @@ -12,6 +12,7 @@ <block class="Magento\Customer\Block\Address\Edit" name="customer_address_edit" template="Magento_Customer::address/edit.phtml" cacheable="false"> <arguments> <argument name="attribute_data" xsi:type="object">Magento\Customer\Block\DataProviders\AddressAttributeData</argument> + <argument name="post_code_config" xsi:type="object">Magento\Customer\Block\DataProviders\PostCodesPatternsAttributeData</argument> </arguments> </block> </referenceContainer> diff --git a/app/code/Magento/NewRelicReporting/etc/db_schema.xml b/app/code/Magento/NewRelicReporting/etc/db_schema.xml index b5db533f90c..c6e61b88f4b 100644 --- a/app/code/Magento/NewRelicReporting/etc/db_schema.xml +++ b/app/code/Magento/NewRelicReporting/etc/db_schema.xml @@ -38,8 +38,8 @@ comment="Entity ID"/> <column xsi:type="int" name="customer_id" padding="10" unsigned="true" nullable="true" identity="false" comment="Customer ID"/> - <column xsi:type="decimal" name="total" scale="0" precision="10" unsigned="true" nullable="true"/> - <column xsi:type="decimal" name="total_base" scale="0" precision="10" unsigned="true" nullable="true"/> + <column xsi:type="decimal" name="total" scale="4" precision="20" unsigned="true" nullable="true"/> + <column xsi:type="decimal" name="total_base" scale="4" precision="20" unsigned="true" nullable="true"/> <column xsi:type="int" name="item_count" padding="10" unsigned="true" nullable="false" identity="false" comment="Line Item Count"/> <column xsi:type="timestamp" name="updated_at" on_update="false" nullable="false" default="CURRENT_TIMESTAMP" diff --git a/app/code/Magento/Newsletter/Controller/Manage/Save.php b/app/code/Magento/Newsletter/Controller/Manage/Save.php index 419cbac10ff..698c2d19aae 100644 --- a/app/code/Magento/Newsletter/Controller/Manage/Save.php +++ b/app/code/Magento/Newsletter/Controller/Manage/Save.php @@ -1,6 +1,5 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ @@ -8,9 +7,15 @@ namespace Magento\Newsletter\Controller\Manage; use Magento\Customer\Api\CustomerRepositoryInterface as CustomerRepository; +use Magento\Customer\Model\Customer; +use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Newsletter\Model\Subscriber; -class Save extends \Magento\Newsletter\Controller\Manage +/** + * Customers newsletter subscription save controller + */ +class Save extends \Magento\Newsletter\Controller\Manage implements HttpPostActionInterface, HttpGetActionInterface { /** * @var \Magento\Framework\Data\Form\FormKey\Validator @@ -81,6 +86,8 @@ public function execute() $isSubscribedParam = (boolean)$this->getRequest() ->getParam('is_subscribed', false); if ($isSubscribedParam !== $isSubscribedState) { + // No need to validate customer and customer address while saving subscription preferences + $this->setIgnoreValidationFlag($customer); $this->customerRepository->save($customer); if ($isSubscribedParam) { $subscribeModel = $this->subscriberFactory->create() @@ -105,4 +112,15 @@ public function execute() } $this->_redirect('customer/account/'); } + + /** + * Set ignore_validation_flag to skip unnecessary address and customer validation + * + * @param Customer $customer + * @return void + */ + private function setIgnoreValidationFlag($customer) + { + $customer->setData('ignore_validation_flag', true); + } } diff --git a/app/code/Magento/Newsletter/Observer/PredispatchNewsletterObserver.php b/app/code/Magento/Newsletter/Observer/PredispatchNewsletterObserver.php new file mode 100644 index 00000000000..9860798b2b9 --- /dev/null +++ b/app/code/Magento/Newsletter/Observer/PredispatchNewsletterObserver.php @@ -0,0 +1,70 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types = 1); + +namespace Magento\Newsletter\Observer; + +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\Event\Observer; +use Magento\Framework\Event\ObserverInterface; +use Magento\Framework\UrlInterface; +use Magento\Store\Model\ScopeInterface; + +/** + * Class PredispatchNewsletterObserver + */ +class PredispatchNewsletterObserver implements ObserverInterface +{ + /** + * Configuration path to newsletter active setting + */ + const XML_PATH_NEWSLETTER_ACTIVE = 'newsletter/general/active'; + + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + + /** + * @var UrlInterface + */ + private $url; + + /** + * PredispatchNewsletterObserver constructor. + * + * @param ScopeConfigInterface $scopeConfig + * @param UrlInterface $url + */ + public function __construct(ScopeConfigInterface $scopeConfig, UrlInterface $url) + { + $this->scopeConfig = $scopeConfig; + $this->url = $url; + } + + /** + * Redirect newsletter routes to 404 when newsletter module is disabled. + * + * @param Observer $observer + */ + public function execute(Observer $observer) : void + { + if (!$this->scopeConfig->getValue( + self::XML_PATH_NEWSLETTER_ACTIVE, + ScopeInterface::SCOPE_STORE + ) + ) { + $defaultNoRouteUrl = $this->scopeConfig->getValue( + 'web/default/no_route', + ScopeInterface::SCOPE_STORE + ); + $redirectUrl = $this->url->getUrl($defaultNoRouteUrl); + $observer->getControllerAction() + ->getResponse() + ->setRedirect($redirectUrl); + } + } +} diff --git a/app/code/Magento/Newsletter/Test/Mftf/ActionGroup/VerifySubscribedNewsletterDisplayedActionGroup.xml b/app/code/Magento/Newsletter/Test/Mftf/ActionGroup/VerifySubscribedNewsletterDisplayedActionGroup.xml index 92d17e9d71e..059f157c407 100644 --- a/app/code/Magento/Newsletter/Test/Mftf/ActionGroup/VerifySubscribedNewsletterDisplayedActionGroup.xml +++ b/app/code/Magento/Newsletter/Test/Mftf/ActionGroup/VerifySubscribedNewsletterDisplayedActionGroup.xml @@ -6,13 +6,10 @@ */ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <!--Create an Account. Check Sign Up for Newsletter checkbox --> <actionGroup name="StorefrontCreateNewAccountNewsletterChecked" extends="SignUpNewUserFromStorefrontActionGroup"> - <arguments> - <argument name="Customer"/> - </arguments> <click selector="{{StorefrontCustomerCreateFormSection.signUpForNewsletter}}" stepKey="selectSignUpForNewsletterCheckbox" after="fillLastName"/> <see stepKey="seeDescriptionNewsletter" userInput='You are subscribed to "General Subscription".' selector="{{CustomerMyAccountPage.DescriptionNewsletter}}" /> </actionGroup> @@ -28,4 +25,9 @@ <see stepKey="seeThankYouMessage" userInput="Thank you for registering with NewStore."/> </actionGroup> + <!--Check Subscribed Newsletter via StoreFront--> + <actionGroup name="CheckSubscribedNewsletterActionGroup"> + <amOnPage url="{{StorefrontNewsletterManagePage.url}}" stepKey="goToNewsletterManage"/> + <seeCheckboxIsChecked selector="{{StorefrontNewsletterManageSection.subscriptionCheckbox}}" stepKey="checkSubscribedNewsletter"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/Newsletter/Test/Mftf/Page/StorefrontNewsletterManagePage.xml b/app/code/Magento/Newsletter/Test/Mftf/Page/StorefrontNewsletterManagePage.xml new file mode 100644 index 00000000000..81fd3eb7c39 --- /dev/null +++ b/app/code/Magento/Newsletter/Test/Mftf/Page/StorefrontNewsletterManagePage.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="StorefrontNewsletterManagePage" url="newsletter/manage/" area="storefront" module="Magento_Newsletter"> + <section name="StorefrontNewsletterManageSection"/> + </page> +</pages> diff --git a/app/code/Magento/Newsletter/Test/Mftf/Section/StorefrontNewsletterManageSection.xml b/app/code/Magento/Newsletter/Test/Mftf/Section/StorefrontNewsletterManageSection.xml new file mode 100644 index 00000000000..96a944a4952 --- /dev/null +++ b/app/code/Magento/Newsletter/Test/Mftf/Section/StorefrontNewsletterManageSection.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="StorefrontNewsletterManageSection"> + <element name="subscriptionCheckbox" type="checkbox" selector="#subscription" /> + </section> +</sections> diff --git a/app/code/Magento/Newsletter/Test/Mftf/Section/VerifySubscribedNewsLetterDisplayedSection.xml b/app/code/Magento/Newsletter/Test/Mftf/Section/VerifySubscribedNewsLetterDisplayedSection.xml index 06f76290043..bb651784d4d 100644 --- a/app/code/Magento/Newsletter/Test/Mftf/Section/VerifySubscribedNewsLetterDisplayedSection.xml +++ b/app/code/Magento/Newsletter/Test/Mftf/Section/VerifySubscribedNewsLetterDisplayedSection.xml @@ -7,8 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> - + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontCustomerCreateFormSection"> <element name="signUpForNewsletter" type="checkbox" selector="//span[contains(text(), 'Sign Up for Newsletter')]"/> </section> @@ -16,5 +15,4 @@ <section name="CustomerMyAccountPage"> <element name="DescriptionNewsletter" type="text" selector=".box-newsletter p"/> </section> - -</sections> \ No newline at end of file +</sections> diff --git a/app/code/Magento/Newsletter/Test/Mftf/Test/VerifySubscribedNewsletterDisplayedTest.xml b/app/code/Magento/Newsletter/Test/Mftf/Test/VerifySubscribedNewsletterDisplayedTest.xml index faed8b1af95..22ca214c94a 100644 --- a/app/code/Magento/Newsletter/Test/Mftf/Test/VerifySubscribedNewsletterDisplayedTest.xml +++ b/app/code/Magento/Newsletter/Test/Mftf/Test/VerifySubscribedNewsletterDisplayedTest.xml @@ -5,8 +5,9 @@ * See COPYING.txt for license details. */ --> + <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="VerifySubscribedNewsletterDisplayedTest"> <annotations> <features value="Newsletter"/> @@ -46,6 +47,8 @@ <actionGroup ref="AdminDeleteWebsiteActionGroup" stepKey="deleteWebsite"> <argument name="websiteName" value="Second"/> </actionGroup> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> <amOnPage url="admin/admin/auth/logout/" stepKey="amOnLogoutPage"/> </after> @@ -63,4 +66,3 @@ </actionGroup> </test> </tests> - diff --git a/app/code/Magento/Newsletter/Test/Unit/Observer/PredispatchNewsletterObserverTest.php b/app/code/Magento/Newsletter/Test/Unit/Observer/PredispatchNewsletterObserverTest.php new file mode 100644 index 00000000000..38d69e5128a --- /dev/null +++ b/app/code/Magento/Newsletter/Test/Unit/Observer/PredispatchNewsletterObserverTest.php @@ -0,0 +1,147 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types = 1); + +namespace Magento\Newsletter\Test\Unit\Observer; + +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\Response\RedirectInterface; +use Magento\Framework\App\ResponseInterface; +use Magento\Framework\Event\Observer; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Framework\UrlInterface; +use Magento\Newsletter\Observer\PredispatchNewsletterObserver; +use Magento\Store\Model\ScopeInterface; +use PHPUnit\Framework\TestCase; + +/** + * Test class for \Magento\Newsletter\Observer\PredispatchNewsletterObserver + */ +class PredispatchNewsletterObserverTest extends TestCase +{ + /** + * @var Observer|\PHPUnit_Framework_MockObject_MockObject + */ + private $mockObject; + + /** + * @var ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $configMock; + + /** + * @var UrlInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $urlMock; + + /** + * @var \Magento\Framework\App\Response\RedirectInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $redirectMock; + + /** + * @var ResponseInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $responseMock; + + /** + * @var ObjectManager + */ + private $objectManager; + + /** + * @inheritdoc + */ + protected function setUp() : void + { + $this->configMock = $this->getMockBuilder(ScopeConfigInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->urlMock = $this->getMockBuilder(UrlInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->responseMock = $this->getMockBuilder(ResponseInterface::class) + ->disableOriginalConstructor() + ->setMethods(['setRedirect']) + ->getMockForAbstractClass(); + $this->redirectMock = $this->getMockBuilder(RedirectInterface::class) + ->getMock(); + $this->objectManager = new ObjectManager($this); + $this->mockObject = $this->objectManager->getObject( + PredispatchNewsletterObserver::class, + [ + 'scopeConfig' => $this->configMock, + 'url' => $this->urlMock + ] + ); + } + + /** + * Test with enabled newsletter active config. + */ + public function testNewsletterEnabled() : void + { + $observerMock = $this->getMockBuilder(Observer::class) + ->disableOriginalConstructor() + ->setMethods(['getResponse', 'getData', 'setRedirect']) + ->getMockForAbstractClass(); + + $this->configMock->method('getValue') + ->with(PredispatchNewsletterObserver::XML_PATH_NEWSLETTER_ACTIVE, ScopeInterface::SCOPE_STORE) + ->willReturn(true); + $observerMock->expects($this->never()) + ->method('getData') + ->with('controller_action') + ->willReturnSelf(); + + $observerMock->expects($this->never()) + ->method('getResponse') + ->willReturnSelf(); + + $this->assertNull($this->mockObject->execute($observerMock)); + } + + /** + * Test with disabled newsletter active config. + */ + public function testNewsletterDisabled() : void + { + $observerMock = $this->getMockBuilder(Observer::class) + ->disableOriginalConstructor() + ->setMethods(['getControllerAction', 'getResponse']) + ->getMockForAbstractClass(); + + $this->configMock->expects($this->at(0)) + ->method('getValue') + ->with(PredispatchNewsletterObserver::XML_PATH_NEWSLETTER_ACTIVE, ScopeInterface::SCOPE_STORE) + ->willReturn(false); + + $expectedRedirectUrl = 'https://test.com/index'; + + $this->configMock->expects($this->at(1)) + ->method('getValue') + ->with('web/default/no_route', ScopeInterface::SCOPE_STORE) + ->willReturn($expectedRedirectUrl); + + $this->urlMock->expects($this->once()) + ->method('getUrl') + ->willReturn($expectedRedirectUrl); + + $observerMock->expects($this->once()) + ->method('getControllerAction') + ->willReturnSelf(); + + $observerMock->expects($this->once()) + ->method('getResponse') + ->willReturn($this->responseMock); + + $this->responseMock->expects($this->once()) + ->method('setRedirect') + ->with($expectedRedirectUrl); + + $this->assertNull($this->mockObject->execute($observerMock)); + } +} diff --git a/app/code/Magento/Newsletter/etc/adminhtml/system.xml b/app/code/Magento/Newsletter/etc/adminhtml/system.xml index 277005240ea..16af7b2158d 100644 --- a/app/code/Magento/Newsletter/etc/adminhtml/system.xml +++ b/app/code/Magento/Newsletter/etc/adminhtml/system.xml @@ -11,6 +11,13 @@ <label>Newsletter</label> <tab>customer</tab> <resource>Magento_Newsletter::newsletter</resource> + <group id="general" translate="label" type="text" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="1"> + <label>General Options</label> + <field id="active" translate="label" type="select" sortOrder="1" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> + <label>Enabled</label> + <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> + </field> + </group> <group id="subscription" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Subscription Options</label> <field id="allow_guest_subscribe" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> diff --git a/app/code/Magento/Newsletter/etc/config.xml b/app/code/Magento/Newsletter/etc/config.xml index f976ece8d71..4c5e385105c 100644 --- a/app/code/Magento/Newsletter/etc/config.xml +++ b/app/code/Magento/Newsletter/etc/config.xml @@ -8,6 +8,9 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Store:etc/config.xsd"> <default> <newsletter> + <general> + <active>1</active> + </general> <subscription> <allow_guest_subscribe>1</allow_guest_subscribe> <confirm>0</confirm> diff --git a/app/code/Magento/Newsletter/etc/frontend/events.xml b/app/code/Magento/Newsletter/etc/frontend/events.xml new file mode 100644 index 00000000000..6c46d562f51 --- /dev/null +++ b/app/code/Magento/Newsletter/etc/frontend/events.xml @@ -0,0 +1,12 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd"> + <event name="controller_action_predispatch_newsletter"> + <observer name="newsletter_enabled" instance="Magento\Newsletter\Observer\PredispatchNewsletterObserver" /> + </event> +</config> diff --git a/app/code/Magento/Newsletter/view/frontend/layout/customer_account.xml b/app/code/Magento/Newsletter/view/frontend/layout/customer_account.xml index 99190bb35fc..fd55fce8ee0 100644 --- a/app/code/Magento/Newsletter/view/frontend/layout/customer_account.xml +++ b/app/code/Magento/Newsletter/view/frontend/layout/customer_account.xml @@ -8,7 +8,7 @@ <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> <referenceBlock name="customer_account_navigation"> - <block class="Magento\Customer\Block\Account\SortLinkInterface" name="customer-account-navigation-newsletter-subscriptions-link"> + <block class="Magento\Customer\Block\Account\SortLinkInterface" ifconfig="newsletter/general/active" name="customer-account-navigation-newsletter-subscriptions-link"> <arguments> <argument name="path" xsi:type="string">newsletter/manage</argument> <argument name="label" xsi:type="string" translate="true">Newsletter Subscriptions</argument> diff --git a/app/code/Magento/Newsletter/view/frontend/layout/default.xml b/app/code/Magento/Newsletter/view/frontend/layout/default.xml index d84f4894a9d..32a08359333 100644 --- a/app/code/Magento/Newsletter/view/frontend/layout/default.xml +++ b/app/code/Magento/Newsletter/view/frontend/layout/default.xml @@ -8,10 +8,10 @@ <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> <referenceBlock name="head.components"> - <block class="Magento\Framework\View\Element\Js\Components" name="newsletter_head_components" template="Magento_Newsletter::js/components.phtml"/> + <block class="Magento\Framework\View\Element\Js\Components" name="newsletter_head_components" template="Magento_Newsletter::js/components.phtml" ifconfig="newsletter/general/active"/> </referenceBlock> <referenceContainer name="footer"> - <block class="Magento\Newsletter\Block\Subscribe" name="form.subscribe" as="subscribe" before="-" template="Magento_Newsletter::subscribe.phtml"/> + <block class="Magento\Newsletter\Block\Subscribe" name="form.subscribe" as="subscribe" before="-" template="Magento_Newsletter::subscribe.phtml" ifconfig="newsletter/general/active"/> </referenceContainer> </body> </page> diff --git a/app/code/Magento/OfflineShipping/Model/Carrier/Freeshipping.php b/app/code/Magento/OfflineShipping/Model/Carrier/Freeshipping.php index 26f32746889..c2e6d0e9223 100644 --- a/app/code/Magento/OfflineShipping/Model/Carrier/Freeshipping.php +++ b/app/code/Magento/OfflineShipping/Model/Carrier/Freeshipping.php @@ -80,7 +80,7 @@ public function collectRates(RateRequest $request) $this->_updateFreeMethodQuote($request); - if ($request->getFreeShipping() || $request->getBaseSubtotalInclTax() >= $this->getConfigData( + if ($request->getFreeShipping() || $request->getPackageValueWithDiscount() >= $this->getConfigData( 'free_shipping_subtotal' ) ) { diff --git a/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php b/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php index bb81f9ebb47..373d64afc8c 100644 --- a/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php +++ b/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php @@ -227,12 +227,12 @@ public function getCode($type, $code = '') $codes = [ 'condition_name' => [ 'package_weight' => __('Weight vs. Destination'), - 'package_value' => __('Price vs. Destination'), + 'package_value_with_discount' => __('Price vs. Destination'), 'package_qty' => __('# of Items vs. Destination'), ], 'condition_name_short' => [ 'package_weight' => __('Weight (and above)'), - 'package_value' => __('Order Subtotal (and above)'), + 'package_value_with_discount' => __('Order Subtotal (and above)'), 'package_qty' => __('# of Items (and above)'), ], ]; diff --git a/app/code/Magento/OfflineShipping/Setup/Patch/Data/UpdateShippingTablerate.php b/app/code/Magento/OfflineShipping/Setup/Patch/Data/UpdateShippingTablerate.php new file mode 100644 index 00000000000..070105846fd --- /dev/null +++ b/app/code/Magento/OfflineShipping/Setup/Patch/Data/UpdateShippingTablerate.php @@ -0,0 +1,73 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\OfflineShipping\Setup\Patch\Data; + +use Magento\Framework\Setup\Patch\DataPatchInterface; +use Magento\OfflineShipping\Model\Carrier\Tablerate; + +/** + * Update for shipping_tablerate table for using price with discount in condition. + */ +class UpdateShippingTablerate implements DataPatchInterface +{ + /** + * @var \Magento\Framework\Setup\ModuleDataSetupInterface + */ + private $moduleDataSetup; + + /** + * PatchInitial constructor. + * @param \Magento\Framework\Setup\ModuleDataSetupInterface $moduleDataSetup + */ + public function __construct( + \Magento\Framework\Setup\ModuleDataSetupInterface $moduleDataSetup + ) { + $this->moduleDataSetup = $moduleDataSetup; + } + + /** + * @inheritdoc + */ + public function apply() + { + $this->moduleDataSetup->getConnection()->startSetup(); + $connection = $this->moduleDataSetup->getConnection(); + $connection->update( + $this->moduleDataSetup->getTable('shipping_tablerate'), + ['condition_name' => 'package_value_with_discount'], + [new \Zend_Db_Expr('condition_name = \'package_value\'')] + ); + $connection->update( + $this->moduleDataSetup->getTable('core_config_data'), + ['value' => 'package_value_with_discount'], + [ + new \Zend_Db_Expr('value = \'package_value\''), + new \Zend_Db_Expr('path = \'carriers/tablerate/condition_name\'') + ] + ); + $this->moduleDataSetup->getConnection()->endSetup(); + + $connection->endSetup(); + } + + /** + * @inheritdoc + */ + public static function getDependencies() + { + return []; + } + + /** + * @inheritdoc + */ + public function getAliases() + { + return []; + } +} diff --git a/app/code/Magento/OfflineShipping/etc/db_schema.xml b/app/code/Magento/OfflineShipping/etc/db_schema.xml index 0510ce9b9b8..5129e8a29b2 100644 --- a/app/code/Magento/OfflineShipping/etc/db_schema.xml +++ b/app/code/Magento/OfflineShipping/etc/db_schema.xml @@ -18,7 +18,7 @@ default="0" comment="Destination Region Id"/> <column xsi:type="varchar" name="dest_zip" nullable="false" length="10" default="*" comment="Destination Post Code (Zip)"/> - <column xsi:type="varchar" name="condition_name" nullable="false" length="20" comment="Rate Condition name"/> + <column xsi:type="varchar" name="condition_name" nullable="false" length="30" comment="Rate Condition name"/> <column xsi:type="decimal" name="condition_value" scale="4" precision="12" unsigned="false" nullable="false" default="0" comment="Rate condition value"/> <column xsi:type="decimal" name="price" scale="4" precision="12" unsigned="false" nullable="false" default="0" diff --git a/app/code/Magento/Payment/Api/Data/PaymentAdditionalInfoInterface.php b/app/code/Magento/Payment/Api/Data/PaymentAdditionalInfoInterface.php new file mode 100644 index 00000000000..8afa064efd3 --- /dev/null +++ b/app/code/Magento/Payment/Api/Data/PaymentAdditionalInfoInterface.php @@ -0,0 +1,17 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Payment\Api\Data; + +use Magento\Framework\DataObject\KeyValueObjectInterface; + +/** + * Payment additional info interface. + */ +interface PaymentAdditionalInfoInterface extends KeyValueObjectInterface +{ +} diff --git a/app/code/Magento/Payment/Gateway/Validator/AbstractValidator.php b/app/code/Magento/Payment/Gateway/Validator/AbstractValidator.php index f1a89505141..110fe10ee5c 100644 --- a/app/code/Magento/Payment/Gateway/Validator/AbstractValidator.php +++ b/app/code/Magento/Payment/Gateway/Validator/AbstractValidator.php @@ -3,11 +3,14 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + +declare(strict_types=1); + namespace Magento\Payment\Gateway\Validator; /** - * Class AbstractValidator - * @package Magento\Payment\Gateway\Validator + * Represents a basic validator shell that can create a result + * * @api * @since 100.0.2 */ @@ -33,7 +36,7 @@ public function __construct( * @param bool $isValid * @param array $fails * @param array $errorCodes - * @return void + * @return \Magento\Payment\Gateway\Validator\ResultInterface */ protected function createResult($isValid, array $fails = [], array $errorCodes = []) { diff --git a/app/code/Magento/Payment/Gateway/Validator/ValidatorComposite.php b/app/code/Magento/Payment/Gateway/Validator/ValidatorComposite.php index b7f1368ddab..8ea97d31ed4 100644 --- a/app/code/Magento/Payment/Gateway/Validator/ValidatorComposite.php +++ b/app/code/Magento/Payment/Gateway/Validator/ValidatorComposite.php @@ -10,10 +10,9 @@ use Magento\Payment\Gateway\Validator\ResultInterfaceFactory; /** - * Class ValidatorComposite - * @package Magento\Payment\Gateway\Validator + * Compiles a result using the results of multiple validators + * * @api - * @since 100.0.2 */ class ValidatorComposite extends AbstractValidator { @@ -22,15 +21,22 @@ class ValidatorComposite extends AbstractValidator */ private $validators; + /** + * @var array + */ + private $chainBreakingValidators; + /** * @param ResultInterfaceFactory $resultFactory * @param TMapFactory $tmapFactory * @param array $validators + * @param array $chainBreakingValidators */ public function __construct( ResultInterfaceFactory $resultFactory, TMapFactory $tmapFactory, - array $validators = [] + array $validators = [], + array $chainBreakingValidators = [] ) { $this->validators = $tmapFactory->create( [ @@ -38,6 +44,7 @@ public function __construct( 'type' => ValidatorInterface::class ] ); + $this->chainBreakingValidators = $chainBreakingValidators; parent::__construct($resultFactory); } @@ -51,7 +58,8 @@ public function validate(array $validationSubject) { $isValid = true; $failsDescriptionAggregate = []; - foreach ($this->validators as $validator) { + $errorCodesAggregate = []; + foreach ($this->validators as $key => $validator) { $result = $validator->validate($validationSubject); if (!$result->isValid()) { $isValid = false; @@ -59,9 +67,16 @@ public function validate(array $validationSubject) $failsDescriptionAggregate, $result->getFailsDescription() ); + $errorCodesAggregate = array_merge( + $errorCodesAggregate, + $result->getErrorCodes() + ); + if (!empty($this->chainBreakingValidators[$key])) { + break; + } } } - return $this->createResult($isValid, $failsDescriptionAggregate); + return $this->createResult($isValid, $failsDescriptionAggregate, $errorCodesAggregate); } } diff --git a/app/code/Magento/Payment/Helper/Data.php b/app/code/Magento/Payment/Helper/Data.php index 0a4990313fa..9bea19700d4 100644 --- a/app/code/Magento/Payment/Helper/Data.php +++ b/app/code/Magento/Payment/Helper/Data.php @@ -84,6 +84,8 @@ public function __construct( } /** + * Get config name of method model + * * @param string $code * @return string */ @@ -259,10 +261,13 @@ public function getPaymentMethodList($sorted = true, $asLabelValue = false, $wit $groupRelations = []; foreach ($this->getPaymentMethods() as $code => $data) { - if (isset($data['title'])) { - $methods[$code] = $data['title']; - } else { - $methods[$code] = $this->getMethodInstance($code)->getConfigData('title', $store); + if (!empty($data['active'])) { + $storedTitle = $this->getMethodInstance($code)->getConfigData('title', $store); + if (isset($storedTitle)) { + $methods[$code] = $storedTitle; + } elseif (isset($data['title'])) { + $methods[$code] = $data['title']; + } } if ($asLabelValue && $withGroups && isset($data['group'])) { $groupRelations[$code] = $data['group']; diff --git a/app/code/Magento/Payment/Model/Method/AbstractMethod.php b/app/code/Magento/Payment/Model/Method/AbstractMethod.php index 33200014c7e..c0ccf887b18 100644 --- a/app/code/Magento/Payment/Model/Method/AbstractMethod.php +++ b/app/code/Magento/Payment/Model/Method/AbstractMethod.php @@ -24,7 +24,7 @@ * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @deprecated 100.0.6 * @see \Magento\Payment\Model\Method\Adapter - * @see http://devdocs.magento.com/guides/v2.1/payments-integrations/payment-gateway/payment-gateway-intro.html + * @see https://devdocs.magento.com/guides/v2.1/payments-integrations/payment-gateway/payment-gateway-intro.html * @since 100.0.2 */ abstract class AbstractMethod extends \Magento\Framework\Model\AbstractExtensibleModel implements @@ -258,7 +258,7 @@ protected function initializeData($data = []) } /** - * {inheritdoc} + * @inheritdoc * @deprecated 100.2.0 */ public function setStore($storeId) @@ -267,7 +267,7 @@ public function setStore($storeId) } /** - * {inheritdoc} + * @inheritdoc * @deprecated 100.2.0 */ public function getStore() @@ -360,7 +360,8 @@ public function canRefundPartialPerInvoice() } /** - * Check void availability + * Check void availability. + * * @return bool * @internal param \Magento\Framework\DataObject $payment * @api @@ -372,8 +373,9 @@ public function canVoid() } /** - * Using internal pages for input payment data - * Can be used in admin + * Using internal pages for input payment data. + * + * Can be used in admin. * * @return bool * @deprecated 100.2.0 @@ -715,7 +717,8 @@ public function void(\Magento\Payment\Model\InfoInterface $payment) } /** - * Whether this method can accept or deny payment + * Whether this method can accept or deny payment. + * * @return bool * @api * @deprecated 100.2.0 @@ -867,8 +870,7 @@ public function isActive($storeId = null) } /** - * Method that will be executed instead of authorize or capture - * if flag isInitializeNeeded set to true + * Method that will be executed instead of authorize or capture if flag isInitializeNeeded set to true. * * @param string $paymentAction * @param object $stateObject @@ -884,8 +886,9 @@ public function initialize($paymentAction, $stateObject) } /** - * Get config payment action url - * Used to universalize payment actions when processing payment place + * Get config payment action url. + * + * Used to universalize payment actions when processing payment place. * * @return string * @api diff --git a/app/code/Magento/Payment/Model/PaymentAdditionalInfo.php b/app/code/Magento/Payment/Model/PaymentAdditionalInfo.php new file mode 100644 index 00000000000..c4f135d5e00 --- /dev/null +++ b/app/code/Magento/Payment/Model/PaymentAdditionalInfo.php @@ -0,0 +1,60 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Payment\Model; + +use Magento\Payment\Api\Data\PaymentAdditionalInfoInterface; + +/** + * Payment additional info class. + */ +class PaymentAdditionalInfo implements PaymentAdditionalInfoInterface +{ + /** + * @var string + */ + private $key; + + /** + * @var string + */ + private $value; + + /** + * @inheritdoc + */ + public function getKey() + { + return $this->key; + } + + /** + * @inheritdoc + */ + public function getValue() + { + return $this->value; + } + + /** + * @inheritdoc + */ + public function setKey($key) + { + $this->key = $key; + return $key; + } + + /** + * @inheritdoc + */ + public function setValue($value) + { + $this->value = $value; + return $value; + } +} diff --git a/app/code/Magento/Payment/Test/Unit/Gateway/Validator/ValidatorCompositeTest.php b/app/code/Magento/Payment/Test/Unit/Gateway/Validator/ValidatorCompositeTest.php index 7352cb7a4ac..5dec99e2a4b 100644 --- a/app/code/Magento/Payment/Test/Unit/Gateway/Validator/ValidatorCompositeTest.php +++ b/app/code/Magento/Payment/Test/Unit/Gateway/Validator/ValidatorCompositeTest.php @@ -13,9 +13,9 @@ class ValidatorCompositeTest extends \PHPUnit\Framework\TestCase public function testValidate() { $validationSubject = []; - $validator1 = $this->getMockBuilder(\Magento\Payment\Gateway\Validator\ValidatorInterface::class) + $validator1 = $this->getMockBuilder(ValidatorInterface::class) ->getMockForAbstractClass(); - $validator2 = $this->getMockBuilder(\Magento\Payment\Gateway\Validator\ValidatorInterface::class) + $validator2 = $this->getMockBuilder(ValidatorInterface::class) ->getMockForAbstractClass(); $tMapFactory = $this->getMockBuilder(\Magento\Framework\ObjectManager\TMapFactory::class) ->disableOriginalConstructor() @@ -30,8 +30,8 @@ public function testValidate() ->with( [ 'array' => [ - 'validator1' => \Magento\Payment\Gateway\Validator\ValidatorInterface::class, - 'validator2' => \Magento\Payment\Gateway\Validator\ValidatorInterface::class + 'validator1' => ValidatorInterface::class, + 'validator2' => ValidatorInterface::class ], 'type' => ValidatorInterface::class ] @@ -54,6 +54,9 @@ public function testValidate() $resultFail->expects(static::once()) ->method('getFailsDescription') ->willReturn(['Fail']); + $resultFail->expects(static::once()) + ->method('getErrorCodes') + ->willReturn(['abc123']); $validator1->expects(static::once()) ->method('validate') @@ -76,7 +79,7 @@ public function testValidate() [ 'isValid' => false, 'failsDescription' => ['Fail'], - 'errorCodes' => [] + 'errorCodes' => ['abc123'] ] ) ->willReturn($compositeResult); @@ -85,10 +88,91 @@ public function testValidate() $resultFactory, $tMapFactory, [ - 'validator1' => \Magento\Payment\Gateway\Validator\ValidatorInterface::class, - 'validator2' => \Magento\Payment\Gateway\Validator\ValidatorInterface::class + 'validator1' => ValidatorInterface::class, + 'validator2' => ValidatorInterface::class ] ); static::assertSame($compositeResult, $validatorComposite->validate($validationSubject)); } + + public function testValidateChainBreaksCorrectly() + { + $validationSubject = []; + $validator1 = $this->getMockBuilder(ValidatorInterface::class) + ->getMockForAbstractClass(); + $validator2 = $this->getMockBuilder(ValidatorInterface::class) + ->getMockForAbstractClass(); + $tMapFactory = $this->getMockBuilder(\Magento\Framework\ObjectManager\TMapFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + $tMap = $this->getMockBuilder(\Magento\Framework\ObjectManager\TMap::class) + ->disableOriginalConstructor() + ->getMock(); + + $tMapFactory->expects($this->once()) + ->method('create') + ->with( + [ + 'array' => [ + 'validator1' => ValidatorInterface::class, + 'validator2' => ValidatorInterface::class + ], + 'type' => ValidatorInterface::class + ] + ) + ->willReturn($tMap); + $tMap->expects($this->once()) + ->method('getIterator') + ->willReturn(new \ArrayIterator([$validator1, $validator2])); + + $resultFail = $this->getMockBuilder(\Magento\Payment\Gateway\Validator\ResultInterface::class) + ->getMockForAbstractClass(); + $resultFail->expects($this->once()) + ->method('isValid') + ->willReturn(false); + $resultFail->expects($this->once()) + ->method('getFailsDescription') + ->willReturn(['Fail']); + $resultFail->expects($this->once()) + ->method('getErrorCodes') + ->willReturn(['abc123']); + + $validator1->expects($this->once()) + ->method('validate') + ->with($validationSubject) + ->willReturn($resultFail); + + // Assert this is never called + $validator2->expects($this->never()) + ->method('validate'); + + $compositeResult = $this->getMockBuilder(\Magento\Payment\Gateway\Validator\ResultInterface::class) + ->getMockForAbstractClass(); + $resultFactory = $this->getMockBuilder(\Magento\Payment\Gateway\Validator\ResultInterfaceFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + $resultFactory->expects($this->once()) + ->method('create') + ->with( + [ + 'isValid' => false, + 'failsDescription' => ['Fail'], + 'errorCodes' => ['abc123'] + ] + ) + ->willReturn($compositeResult); + + $validatorComposite = new ValidatorComposite( + $resultFactory, + $tMapFactory, + [ + 'validator1' => ValidatorInterface::class, + 'validator2' => ValidatorInterface::class + ], + ['validator1'] + ); + $this->assertSame($compositeResult, $validatorComposite->validate($validationSubject)); + } } diff --git a/app/code/Magento/Payment/Ui/Component/Listing/Column/Method/Options.php b/app/code/Magento/Payment/Ui/Component/Listing/Column/Method/Options.php index 5afaa9fcf97..fbf80de519f 100644 --- a/app/code/Magento/Payment/Ui/Component/Listing/Column/Method/Options.php +++ b/app/code/Magento/Payment/Ui/Component/Listing/Column/Method/Options.php @@ -25,8 +25,9 @@ class Options implements \Magento\Framework\Data\OptionSourceInterface * * @param \Magento\Payment\Helper\Data $paymentHelper */ - public function __construct(\Magento\Payment\Helper\Data $paymentHelper) - { + public function __construct( + \Magento\Payment\Helper\Data $paymentHelper + ) { $this->paymentHelper = $paymentHelper; } diff --git a/app/code/Magento/Payment/etc/di.xml b/app/code/Magento/Payment/etc/di.xml index 74f553cc640..b7422bb00d5 100644 --- a/app/code/Magento/Payment/etc/di.xml +++ b/app/code/Magento/Payment/etc/di.xml @@ -7,6 +7,7 @@ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> <preference for="Magento\Payment\Api\Data\PaymentMethodInterface" type="Magento\Payment\Model\PaymentMethod"/> + <preference for="Magento\Payment\Api\Data\PaymentAdditionalInfoInterface" type="Magento\Payment\Model\PaymentAdditionalInfo"/> <preference for="Magento\Payment\Api\PaymentMethodListInterface" type="Magento\Payment\Model\PaymentMethodList"/> <preference for="Magento\Payment\Gateway\Validator\ResultInterface" type="Magento\Payment\Gateway\Validator\Result"/> <preference for="Magento\Payment\Gateway\ConfigFactoryInterface" type="Magento\Payment\Gateway\Config\ConfigFactory" /> diff --git a/app/code/Magento/Paypal/Block/Adminhtml/System/Config/Field/Depends/ButtonStylesLabel.php b/app/code/Magento/Paypal/Block/Adminhtml/System/Config/Field/Depends/ButtonStylesLabel.php new file mode 100644 index 00000000000..82e0e556606 --- /dev/null +++ b/app/code/Magento/Paypal/Block/Adminhtml/System/Config/Field/Depends/ButtonStylesLabel.php @@ -0,0 +1,26 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Paypal\Block\Adminhtml\System\Config\Field\Depends; + +use Magento\Paypal\Block\Adminhtml\System\Config\Field\Enable\AbstractEnable; + +/** + * Class ButtonStylesLabel + */ +class ButtonStylesLabel extends AbstractEnable +{ + /** + * Getting the name of a UI attribute + * + * @return string + */ + protected function getDataAttributeName() + { + return 'button-label'; + } +} diff --git a/app/code/Magento/Paypal/Block/Adminhtml/System/Config/Field/Enable/BmlApi.php b/app/code/Magento/Paypal/Block/Adminhtml/System/Config/Field/Enable/BmlApi.php index 1a8a5895c43..88a33f19de2 100644 --- a/app/code/Magento/Paypal/Block/Adminhtml/System/Config/Field/Enable/BmlApi.php +++ b/app/code/Magento/Paypal/Block/Adminhtml/System/Config/Field/Enable/BmlApi.php @@ -7,6 +7,8 @@ /** * Class Bml + * @deprecated + * "Enable PayPal Credit" setting was removed. Please @see "Disable Funding Options" */ class BmlApi extends AbstractEnable { diff --git a/app/code/Magento/Paypal/Block/Adminhtml/System/Config/MultiSelect/DisabledFundingOptions.php b/app/code/Magento/Paypal/Block/Adminhtml/System/Config/MultiSelect/DisabledFundingOptions.php new file mode 100644 index 00000000000..bad4dad4c09 --- /dev/null +++ b/app/code/Magento/Paypal/Block/Adminhtml/System/Config/MultiSelect/DisabledFundingOptions.php @@ -0,0 +1,91 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Paypal\Block\Adminhtml\System\Config\MultiSelect; + +use Magento\Paypal\Block\Adminhtml\System\Config\Field\Enable\AbstractEnable; +use Magento\Paypal\Model\Config\StructurePlugin; +use Magento\Backend\Block\Template\Context; +use Magento\Paypal\Model\Config; +use Magento\Framework\Data\Form\Element\AbstractElement; + +/** + * Class DisabledFundingOptions + */ +class DisabledFundingOptions extends AbstractEnable +{ + /** + * @var Config + */ + private $config; + + /** + * DisabledFundingOptions constructor. + * @param Context $context + * @param Config $config + * @param array $data + */ + public function __construct( + Context $context, + Config $config, + $data = [] + ) { + $this->config = $config; + parent::__construct($context, $data); + } + + /** + * Render country field considering request parameter + * + * @param AbstractElement $element + * @return string + */ + public function render(AbstractElement $element) + { + if (!$this->isSelectedMerchantCountry('US')) { + $fundingOptions = $element->getValues(); + $element->setValues($this->filterValuesForPaypalCredit($fundingOptions)); + } + return parent::render($element); + } + + /** + * Getting the name of a UI attribute + * + * @return string + */ + protected function getDataAttributeName(): string + { + return 'disable-funding-options'; + } + + /** + * Filters array for CREDIT + * + * @param array $options + * @return array + */ + private function filterValuesForPaypalCredit($options): array + { + return array_filter($options, function ($opt) { + return ($opt['value'] !== 'CREDIT'); + }); + } + + /** + * Checks for chosen Merchant country from the config/url + * + * @param string $country + * @return bool + */ + private function isSelectedMerchantCountry(string $country): bool + { + $merchantCountry = $this->getRequest()->getParam(StructurePlugin::REQUEST_PARAM_COUNTRY) + ?: $this->config->getMerchantCountry(); + return $merchantCountry === $country; + } +} diff --git a/app/code/Magento/Paypal/Block/Bml/Shortcut.php b/app/code/Magento/Paypal/Block/Bml/Shortcut.php index 39e5dbd3cef..d2f5ca009a1 100644 --- a/app/code/Magento/Paypal/Block/Bml/Shortcut.php +++ b/app/code/Magento/Paypal/Block/Bml/Shortcut.php @@ -8,7 +8,13 @@ use Magento\Catalog\Block as CatalogBlock; use Magento\Paypal\Helper\Shortcut\ValidatorInterface; +use Magento\Paypal\Model\ConfigFactory; +use Magento\Paypal\Model\Config; +use Magento\Framework\App\ObjectManager; +/** + * Class shortcut + */ class Shortcut extends \Magento\Framework\View\Element\Template implements CatalogBlock\ShortcutInterface { /** @@ -66,6 +72,11 @@ class Shortcut extends \Magento\Framework\View\Element\Template implements Catal */ private $_shortcutValidator; + /** + * @var Config + */ + private $config; + /** * @param \Magento\Framework\View\Element\Template\Context $context * @param \Magento\Payment\Helper\Data $paymentData @@ -77,7 +88,9 @@ class Shortcut extends \Magento\Framework\View\Element\Template implements Catal * @param string $bmlMethodCode * @param string $shortcutTemplate * @param array $data + * @param ConfigFactory|null $config * @SuppressWarnings(PHPMD.ExcessiveParameterList) + * @codingStandardsIgnoreStart */ public function __construct( \Magento\Framework\View\Element\Template\Context $context, @@ -89,28 +102,35 @@ public function __construct( $alias, $bmlMethodCode, $shortcutTemplate, - array $data = [] + array $data = [], + ConfigFactory $config = null ) { $this->_paymentData = $paymentData; $this->_mathRandom = $mathRandom; $this->_shortcutValidator = $shortcutValidator; - $this->_paymentMethodCode = $paymentMethodCode; $this->_startAction = $startAction; $this->_alias = $alias; $this->setTemplate($shortcutTemplate); $this->_bmlMethodCode = $bmlMethodCode; + $this->config = $config + ? $config->create() + : ObjectManager::getInstance()->get(ConfigFactory::class)->create(); + $this->config->setMethod($this->_paymentMethodCode); parent::__construct($context, $data); } + //@codingStandardsIgnoreEnd /** - * @return \Magento\Framework\View\Element\AbstractBlock + * @inheritdoc */ protected function _beforeToHtml() { $result = parent::_beforeToHtml(); $isInCatalog = $this->getIsInCatalogProduct(); - if (!$this->_shortcutValidator->validate($this->_paymentMethodCode, $isInCatalog)) { + if (!$this->_shortcutValidator->validate($this->_paymentMethodCode, $isInCatalog) + || (bool)(int)$this->config->getValue('in_context') + ) { $this->_shouldRender = false; return $result; } diff --git a/app/code/Magento/Paypal/Block/Express/InContext/Minicart/Button.php b/app/code/Magento/Paypal/Block/Express/InContext/Minicart/Button.php index 79142ecb1bf..8d1e04c1397 100644 --- a/app/code/Magento/Paypal/Block/Express/InContext/Minicart/Button.php +++ b/app/code/Magento/Paypal/Block/Express/InContext/Minicart/Button.php @@ -9,7 +9,6 @@ use Magento\Payment\Model\MethodInterface; use Magento\Paypal\Model\Config; use Magento\Paypal\Model\ConfigFactory; -use Magento\Paypal\Block\Express\InContext; use Magento\Framework\View\Element\Template; use Magento\Catalog\Block\ShortcutInterface; use Magento\Framework\Locale\ResolverInterface; @@ -17,6 +16,7 @@ /** * Class Button + * @deprecated @see \Magento\Paypal\Block\Express\InContext\Minicart\SmartButton */ class Button extends Template implements ShortcutInterface { @@ -59,8 +59,8 @@ class Button extends Template implements ShortcutInterface * @param Context $context * @param ResolverInterface $localeResolver * @param ConfigFactory $configFactory - * @param MethodInterface $payment * @param Session $session + * @param MethodInterface $payment * @param array $data */ public function __construct( @@ -101,8 +101,7 @@ private function isVisibleOnCart() } /** - * Check is Paypal In-Context Express Checkout button - * should render in cart/mini-cart + * Check is Paypal In-Context Express Checkout button should render in cart/mini-cart * * @return bool */ @@ -127,6 +126,8 @@ protected function _toHtml() } /** + * Returns container id + * * @return string */ public function getContainerId() @@ -135,6 +136,8 @@ public function getContainerId() } /** + * Returns link action + * * @return string */ public function getLinkAction() @@ -143,6 +146,8 @@ public function getLinkAction() } /** + * Returns add to cart selector + * * @return string */ public function getAddToCartSelector() @@ -151,6 +156,8 @@ public function getAddToCartSelector() } /** + * Returns image url + * * @return string */ public function getImageUrl() @@ -171,6 +178,8 @@ public function getAlias() } /** + * Set information if button renders in the mini cart + * * @param bool $isCatalog * @return $this */ diff --git a/app/code/Magento/Paypal/Block/Express/InContext/Minicart/SmartButton.php b/app/code/Magento/Paypal/Block/Express/InContext/Minicart/SmartButton.php new file mode 100644 index 00000000000..c6a17fa5efb --- /dev/null +++ b/app/code/Magento/Paypal/Block/Express/InContext/Minicart/SmartButton.php @@ -0,0 +1,223 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Paypal\Block\Express\InContext\Minicart; + +use Magento\Checkout\Model\Session; +use Magento\Payment\Model\MethodInterface; +use Magento\Paypal\Model\Config; +use Magento\Paypal\Model\ConfigFactory; +use Magento\Framework\View\Element\Template; +use Magento\Catalog\Block\ShortcutInterface; +use Magento\Framework\View\Element\Template\Context; +use Magento\Framework\Serialize\SerializerInterface; +use Magento\Paypal\Model\SmartButtonConfig; +use Magento\Framework\UrlInterface; +use Magento\Quote\Model\QuoteIdToMaskedQuoteId; +use Magento\Framework\Exception\NoSuchEntityException; + +/** + * Class Button + */ +class SmartButton extends Template implements ShortcutInterface +{ + private const ALIAS_ELEMENT_INDEX = 'alias'; + + /** + * @var Config + */ + private $config; + + /** + * @var MethodInterface + */ + private $payment; + + /** + * @var Session + */ + private $session; + + /** + * @var SerializerInterface + */ + private $serializer; + + /** + * @var SmartButtonConfig + */ + private $smartButtonConfig; + + /** + * @var UrlInterface + */ + private $urlBuilder; + + /** + * @var QuoteIdToMaskedQuoteId + */ + private $quoteIdMask; + + /** + * @param Context $context + * @param ConfigFactory $configFactory + * @param Session $session + * @param MethodInterface $payment + * @param SerializerInterface $serializer + * @param SmartButtonConfig $smartButtonConfig + * @param UrlInterface $urlBuilder + * @param QuoteIdToMaskedQuoteId $quoteIdToMaskedQuoteId + * @param array $data + */ + public function __construct( + Context $context, + ConfigFactory $configFactory, + Session $session, + MethodInterface $payment, + SerializerInterface $serializer, + SmartButtonConfig $smartButtonConfig, + UrlInterface $urlBuilder, + QuoteIdToMaskedQuoteId $quoteIdToMaskedQuoteId, + array $data = [] + ) { + parent::__construct($context, $data); + + $this->config = $configFactory->create(); + $this->config->setMethod(Config::METHOD_EXPRESS); + $this->payment = $payment; + $this->session = $session; + $this->serializer = $serializer; + $this->smartButtonConfig = $smartButtonConfig; + $this->urlBuilder = $urlBuilder; + $this->quoteIdMask = $quoteIdToMaskedQuoteId; + } + + /** + * Check `in_context` config value + * + * @return bool + */ + private function isInContext(): bool + { + return (bool)(int) $this->config->getValue('in_context'); + } + + /** + * Check `visible_on_cart` config value + * + * @return bool + */ + private function isVisibleOnCart(): bool + { + return (bool)(int) $this->config->getValue('visible_on_cart'); + } + + /** + * Check is Paypal In-Context Express Checkout button should render in cart/mini-cart + * + * @return bool + */ + private function shouldRender(): bool + { + return $this->payment->isAvailable($this->session->getQuote()) + && $this->isInContext() + && $this->isVisibleOnCart() + && $this->getQuoteId() + && !$this->getIsInCatalogProduct(); + } + + /** + * @inheritdoc + */ + protected function _toHtml() + { + if (!$this->shouldRender()) { + return ''; + } + + return parent::_toHtml(); + } + + /** + * Get shortcut alias + * + * @return string + */ + public function getAlias() + { + return $this->getData(self::ALIAS_ELEMENT_INDEX); + } + + /** + * Returns string to initialize js component + * + * @return string + */ + public function getJsInitParams(): string + { + $config = []; + $quoteId = $this->getQuoteId(); + if (!empty($quoteId)) { + $clientConfig = [ + 'quoteId' => $quoteId, + 'customerId' => $this->session->getQuote()->getCustomerId(), + 'button' => 1, + 'getTokenUrl' => $this->urlBuilder->getUrl( + 'paypal/express/getTokenData', + ['_secure' => $this->getRequest()->isSecure()] + ), + 'onAuthorizeUrl' => $this->urlBuilder->getUrl( + 'paypal/express/onAuthorization', + ['_secure' => $this->getRequest()->isSecure()] + ), + 'onCancelUrl' => $this->urlBuilder->getUrl( + 'paypal/express/cancel', + ['_secure' => $this->getRequest()->isSecure()] + ) + ]; + $smartButtonsConfig = $this->getIsShoppingCart() + ? $this->smartButtonConfig->getConfig('cart') + : $this->smartButtonConfig->getConfig('mini_cart'); + $clientConfig = array_replace_recursive($clientConfig, $smartButtonsConfig); + $config = [ + 'Magento_Paypal/js/in-context/button' => [ + 'clientConfig' => $clientConfig + ] + ]; + } + $json = $this->serializer->serialize($config); + return $json; + } + + /** + * Returns container id + * + * @return string + */ + public function getContainerId(): string + { + return $this->getData('button_id'); + } + + /** + * Get quote id from session + * + * @return string + */ + private function getQuoteId(): string + { + $quoteId = (int)$this->session->getQuoteId(); + if (!$this->session->getQuote()->getCustomerId()) { + try { + $quoteId = $this->quoteIdMask->execute($quoteId); + } catch (NoSuchEntityException $e) { + $quoteId = ""; + } + } + return (string)$quoteId; + } +} diff --git a/app/code/Magento/Paypal/Block/Express/InContext/SmartButton.php b/app/code/Magento/Paypal/Block/Express/InContext/SmartButton.php new file mode 100644 index 00000000000..6d355038cff --- /dev/null +++ b/app/code/Magento/Paypal/Block/Express/InContext/SmartButton.php @@ -0,0 +1,138 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Paypal\Block\Express\InContext; + +use Magento\Paypal\Model\Config; +use Magento\Paypal\Model\ConfigFactory; +use Magento\Framework\View\Element\Template; +use Magento\Catalog\Block\ShortcutInterface; +use Magento\Framework\View\Element\Template\Context; +use Magento\Framework\Serialize\SerializerInterface; +use Magento\Paypal\Model\SmartButtonConfig; +use Magento\Framework\UrlInterface; + +/** + * Class Button + */ +class SmartButton extends Template implements ShortcutInterface +{ + private const ALIAS_ELEMENT_INDEX = 'alias'; + + /** + * @var Config + */ + private $config; + + /** + * @var SerializerInterface + */ + private $serializer; + + /** + * @var SmartButtonConfig + */ + private $smartButtonConfig; + + /** + * @var UrlInterface + */ + private $urlBuilder; + + /** + * @param Context $context + * @param ConfigFactory $configFactory + * @param SerializerInterface $serializer + * @param SmartButtonConfig $smartButtonConfig + * @param UrlInterface $urlBuilder + * @param array $data + */ + public function __construct( + Context $context, + ConfigFactory $configFactory, + SerializerInterface $serializer, + SmartButtonConfig $smartButtonConfig, + UrlInterface $urlBuilder, + array $data = [] + ) { + parent::__construct($context, $data); + + $this->config = $configFactory->create(); + $this->config->setMethod(Config::METHOD_EXPRESS); + $this->serializer = $serializer; + $this->smartButtonConfig = $smartButtonConfig; + $this->urlBuilder = $urlBuilder; + } + + /** + * Check is Paypal In-Context Express Checkout button should render in cart/mini-cart + * + * @return bool + */ + private function shouldRender(): bool + { + $isInCatalog = $this->getIsInCatalogProduct(); + $isInContext = (bool)(int) $this->config->getValue('in_context'); + + return ($isInContext && $isInCatalog); + } + + /** + * @inheritdoc + */ + protected function _toHtml() + { + if (!$this->shouldRender()) { + return ''; + } + + return parent::_toHtml(); + } + + /** + * Get shortcut alias + * + * @return string + */ + public function getAlias() + { + return $this->getData(self::ALIAS_ELEMENT_INDEX); + } + + /** + * Returns string to initialize js component + * + * @return string + */ + public function getJsInitParams(): string + { + $clientConfig = [ + 'button' => 1, + 'getTokenUrl' => $this->urlBuilder->getUrl( + 'paypal/express/getTokenData', + ['_secure' => $this->getRequest()->isSecure()] + ), + 'onAuthorizeUrl' => $this->urlBuilder->getUrl( + 'paypal/express/onAuthorization', + ['_secure' => $this->getRequest()->isSecure()] + ), + 'onCancelUrl' => $this->urlBuilder->getUrl( + 'paypal/express/cancel', + ['_secure' => $this->getRequest()->isSecure()] + ) + ]; + $smartButtonsConfig = $this->smartButtonConfig->getConfig('product'); + $clientConfig = array_replace_recursive($clientConfig, $smartButtonsConfig); + $config = [ + 'Magento_Paypal/js/in-context/product-express-checkout' => [ + 'clientConfig' => $clientConfig + ] + ]; + + return $this->serializer->serialize($config); + } +} diff --git a/app/code/Magento/Paypal/Block/Express/Shortcut.php b/app/code/Magento/Paypal/Block/Express/Shortcut.php index bdb9279356d..16305238e17 100644 --- a/app/code/Magento/Paypal/Block/Express/Shortcut.php +++ b/app/code/Magento/Paypal/Block/Express/Shortcut.php @@ -137,7 +137,7 @@ public function __construct( } /** - * @return \Magento\Framework\View\Element\AbstractBlock + * @inheritdoc */ protected function _beforeToHtml() { @@ -145,7 +145,9 @@ protected function _beforeToHtml() $isInCatalog = $this->getIsInCatalogProduct(); - if (!$this->_shortcutValidator->validate($this->_paymentMethodCode, $isInCatalog)) { + if (!$this->_shortcutValidator->validate($this->_paymentMethodCode, $isInCatalog) + || (bool)(int)$this->config->getValue('in_context') + ) { $this->_shouldRender = false; return $result; } @@ -186,6 +188,8 @@ protected function _toHtml() } /** + * Check if we should render component + * * @return bool */ protected function shouldRender() diff --git a/app/code/Magento/Paypal/Controller/Express/AbstractExpress.php b/app/code/Magento/Paypal/Controller/Express/AbstractExpress.php index fa131f9591f..7ad8fe658ec 100644 --- a/app/code/Magento/Paypal/Controller/Express/AbstractExpress.php +++ b/app/code/Magento/Paypal/Controller/Express/AbstractExpress.php @@ -9,6 +9,7 @@ use Magento\Framework\App\Action\Action as AppAction; use Magento\Framework\App\Action\HttpGetActionInterface; use Magento\Framework\App\Action\HttpPostActionInterface; +use Magento\Quote\Api\Data\CartInterface; /** * Abstract Express Checkout Controller @@ -132,12 +133,14 @@ public function __construct( /** * Instantiate quote and checkout * + * @param CartInterface|null $quoteObject + * * @return void * @throws \Magento\Framework\Exception\LocalizedException */ - protected function _initCheckout() + protected function _initCheckout(CartInterface $quoteObject = null) { - $quote = $this->_getQuote(); + $quote = $quoteObject ? $quoteObject : $this->_getQuote(); if (!$quote->hasItems() || $quote->getHasError()) { $this->getResponse()->setStatusHeader(403, '1.1', 'Forbidden'); throw new \Magento\Framework\Exception\LocalizedException(__('We can\'t initialize Express Checkout.')); diff --git a/app/code/Magento/Paypal/Controller/Express/GetTokenData.php b/app/code/Magento/Paypal/Controller/Express/GetTokenData.php new file mode 100644 index 00000000000..512dac4cdec --- /dev/null +++ b/app/code/Magento/Paypal/Controller/Express/GetTokenData.php @@ -0,0 +1,206 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Paypal\Controller\Express; + +use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; +use Magento\Framework\Controller\ResultFactory; +use Magento\Framework\Controller\ResultInterface; +use Magento\Framework\Exception\LocalizedException; +use Magento\Paypal\Model\Express\Checkout; +use Magento\Paypal\Model\Config; +use Magento\Framework\App\Action\Context; +use Magento\Customer\Model\Session as CustomerSession; +use Magento\Checkout\Model\Session as CheckoutSession; +use Magento\Sales\Model\OrderFactory; +use Magento\Paypal\Model\Express\Checkout\Factory as CheckoutFactory; +use Magento\Framework\Session\Generic as PayPalSession; +use Magento\Framework\Url\Helper\Data as UrlHelper; +use Magento\Customer\Model\Url as CustomerUrl; +use Magento\Customer\Model\ResourceModel\CustomerRepository; +use Magento\Quote\Api\CartRepositoryInterface; +use Magento\Quote\Api\GuestCartRepositoryInterface; +use Psr\Log\LoggerInterface; + +/** + * Retrieve paypal token + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class GetTokenData extends AbstractExpress implements HttpGetActionInterface +{ + /** + * Config mode type + * + * @var string + */ + protected $_configType = Config::class; + + /** + * Config method type + * + * @var string + */ + protected $_configMethod = Config::METHOD_WPP_EXPRESS; + + /** + * Checkout mode type + * + * @var string + */ + protected $_checkoutType = Checkout::class; + + /** + * @var \Psr\Log\LoggerInterface + */ + private $logger; + + /** + * @var CustomerRepository + */ + private $customerRepository; + + /** + * @var CartRepositoryInterface + */ + private $cartRepository; + + /** + * @var GuestCartRepositoryInterface + */ + private $guestCartRepository; + + /** + * @param Context $context + * @param CustomerSession $customerSession + * @param CheckoutSession $checkoutSession + * @param OrderFactory $orderFactory + * @param CheckoutFactory $checkoutFactory + * @param PayPalSession $paypalSession + * @param UrlHelper $urlHelper + * @param CustomerUrl $customerUrl + * @param LoggerInterface $logger + * @param CustomerRepository $customerRepository + * @param CartRepositoryInterface $cartRepository + * @param GuestCartRepositoryInterface $guestCartRepository + * @SuppressWarnings(PHPMD.ExcessiveParameterList) + */ + public function __construct( + Context $context, + CustomerSession $customerSession, + CheckoutSession $checkoutSession, + OrderFactory $orderFactory, + CheckoutFactory $checkoutFactory, + PayPalSession $paypalSession, + UrlHelper $urlHelper, + CustomerUrl $customerUrl, + LoggerInterface $logger, + CustomerRepository $customerRepository, + CartRepositoryInterface $cartRepository, + GuestCartRepositoryInterface $guestCartRepository + ) { + parent::__construct( + $context, + $customerSession, + $checkoutSession, + $orderFactory, + $checkoutFactory, + $paypalSession, + $urlHelper, + $customerUrl + ); + + $this->logger = $logger; + $this->customerRepository = $customerRepository; + $this->cartRepository = $cartRepository; + $this->guestCartRepository = $guestCartRepository; + } + + /** + * Get token data + * + * @return ResultInterface + */ + public function execute(): ResultInterface + { + $controllerResult = $this->resultFactory->create(ResultFactory::TYPE_JSON); + $responseContent = [ + 'success' => true, + 'error_message' => '', + ]; + + try { + $token = $this->getToken(); + if ($token === null) { + $token = false; + } + $this->_initToken($token); + + $responseContent['token'] = $token; + } catch (LocalizedException $exception) { + $this->logger->critical($exception); + + $responseContent['success'] = false; + $responseContent['error_message'] = $exception->getMessage(); + } catch (\Exception $exception) { + $this->logger->critical($exception); + + $responseContent['success'] = false; + $responseContent['error_message'] = __('Sorry, but something went wrong'); + } + + return $controllerResult->setData($responseContent); + } + + /** + * Get paypal token + * + * @return string|null + * @throws LocalizedException + */ + private function getToken(): ?string + { + $quoteId = $this->getRequest()->getParam('quote_id'); + $customerId = $this->getRequest()->getParam('customer_id') ?: $this->_customerSession->getId(); + $hasButton = (bool)$this->getRequest()->getParam(Checkout::PAYMENT_INFO_BUTTON); + + if ($quoteId) { + $quote = $customerId ? $this->cartRepository->get($quoteId) : $this->guestCartRepository->get($quoteId); + } else { + $quote = $this->_getQuote(); + } + + $this->_initCheckout($quote); + + if ($quote->getIsMultiShipping()) { + $quote->setIsMultiShipping(0); + $quote->removeAllAddresses(); + } + + if ($customerId) { + $customerData = $this->customerRepository->getById((int)$customerId); + + $this->_checkout->setCustomerWithAddressChange( + $customerData, + $quote->getBillingAddress(), + $quote->getShippingAddress() + ); + } + + // giropay urls + $this->_checkout->prepareGiropayUrls( + $this->_url->getUrl('checkout/onepage/success'), + $this->_url->getUrl('paypal/express/cancel'), + $this->_url->getUrl('checkout/onepage/success') + ); + + return $this->_checkout->start( + $this->_url->getUrl('*/*/return'), + $this->_url->getUrl('*/*/cancel'), + $hasButton + ); + } +} diff --git a/app/code/Magento/Paypal/Controller/Express/OnAuthorization.php b/app/code/Magento/Paypal/Controller/Express/OnAuthorization.php new file mode 100644 index 00000000000..62f4c4c4c45 --- /dev/null +++ b/app/code/Magento/Paypal/Controller/Express/OnAuthorization.php @@ -0,0 +1,172 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Paypal\Controller\Express; + +use Magento\Framework\Controller\ResultFactory; +use Magento\Framework\App\Action\HttpPostActionInterface; +use Magento\Framework\Controller\ResultInterface; +use Magento\Paypal\Model\Config as PayPalConfig; +use Magento\Paypal\Model\Express\Checkout as PayPalCheckout; +use Magento\Paypal\Model\Api\ProcessableException as ApiProcessableException; +use Magento\Framework\App\Action\Context; +use Magento\Customer\Model\Session as CustomerSession; +use Magento\Checkout\Model\Session as CheckoutSession; +use Magento\Sales\Model\OrderFactory; +use Magento\Paypal\Model\Express\Checkout\Factory as CheckoutFactory; +use Magento\Framework\Session\Generic as PayPalSession; +use Magento\Framework\Url\Helper\Data as UrlHelper; +use Magento\Customer\Model\Url as CustomerUrl; +use Magento\Quote\Api\CartRepositoryInterface; +use Magento\Framework\UrlInterface; +use Magento\Quote\Api\GuestCartRepositoryInterface; + +/** + * Processes data after returning from PayPal + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class OnAuthorization extends AbstractExpress implements HttpPostActionInterface +{ + /** + * @inheritdoc + */ + protected $_configType = PayPalConfig::class; + + /** + * @inheritdoc + */ + protected $_configMethod = PayPalConfig::METHOD_WPP_EXPRESS; + + /** + * @inheritdoc + */ + protected $_checkoutType = PayPalCheckout::class; + + /** + * @var CartRepositoryInterface + */ + private $cartRepository; + + /** + * Url Builder + * + * @var UrlInterface + */ + private $urlBuilder; + + /** + * @var GuestCartRepositoryInterface + */ + private $guestCartRepository; + + /** + * @param Context $context + * @param CustomerSession $customerSession + * @param CheckoutSession $checkoutSession + * @param OrderFactory $orderFactory + * @param CheckoutFactory $checkoutFactory + * @param PayPalSession $paypalSession + * @param UrlHelper $urlHelper + * @param CustomerUrl $customerUrl + * @param CartRepositoryInterface $cartRepository + * @param UrlInterface $urlBuilder + * @param GuestCartRepositoryInterface $guestCartRepository + * @SuppressWarnings(PHPMD.ExcessiveParameterList) + */ + public function __construct( + Context $context, + CustomerSession $customerSession, + CheckoutSession $checkoutSession, + OrderFactory $orderFactory, + CheckoutFactory $checkoutFactory, + PayPalSession $paypalSession, + UrlHelper $urlHelper, + CustomerUrl $customerUrl, + CartRepositoryInterface $cartRepository, + UrlInterface $urlBuilder, + GuestCartRepositoryInterface $guestCartRepository + ) { + parent::__construct( + $context, + $customerSession, + $checkoutSession, + $orderFactory, + $checkoutFactory, + $paypalSession, + $urlHelper, + $customerUrl + ); + $this->cartRepository = $cartRepository; + $this->urlBuilder = $urlBuilder; + $this->guestCartRepository = $guestCartRepository; + } + + /** + * Place order or redirect on Paypal review page + * + * @return ResultInterface + */ + public function execute(): ResultInterface + { + $controllerResult = $this->resultFactory->create(ResultFactory::TYPE_JSON); + $quoteId = $this->getRequest()->getParam('quoteId'); + $payerId = $this->getRequest()->getParam('payerId'); + $tokenId = $this->getRequest()->getParam('paymentToken'); + $customerId = $this->getRequest()->getParam('customerId') ?: $this->_customerSession->getId(); + + try { + if ($quoteId) { + $quote = $customerId ? $this->cartRepository->get($quoteId) : $this->guestCartRepository->get($quoteId); + } else { + $quote = $this->_getQuote(); + } + + $responseContent = [ + 'success' => true, + 'error_message' => '', + ]; + + /** Populate checkout object with new data */ + $this->_initCheckout($quote); + /** Populate quote with information about billing and shipping addresses*/ + $this->_checkout->returnFromPaypal($tokenId, $payerId); + if ($this->_checkout->canSkipOrderReviewStep()) { + $this->_checkout->place($tokenId); + $order = $this->_checkout->getOrder(); + /** "last successful quote" */ + $this->_getCheckoutSession()->setLastQuoteId($quote->getId())->setLastSuccessQuoteId($quote->getId()); + + $this->_getCheckoutSession()->setLastOrderId($order->getId()) + ->setLastRealOrderId($order->getIncrementId()) + ->setLastOrderStatus($order->getStatus()); + + $this->_eventManager->dispatch( + 'paypal_express_place_order_success', + [ + 'order' => $order, + 'quote' => $quote + ] + ); + $responseContent['redirectUrl'] = $this->urlBuilder->getUrl('checkout/onepage/success/'); + } else { + $responseContent['redirectUrl'] = $this->urlBuilder->getUrl('paypal/express/review'); + $this->_checkoutSession->setQuoteId($quote->getId()); + } + } catch (ApiProcessableException $e) { + $responseContent['success'] = false; + $responseContent['error_message'] = $e->getUserMessage(); + } catch (\Magento\Framework\Exception\LocalizedException $e) { + $responseContent['success'] = false; + $responseContent['error_message'] = $e->getMessage(); + } catch (\Exception $e) { + $responseContent['success'] = false; + $responseContent['error_message'] = __('We can\'t process Express Checkout approval.'); + } + + return $controllerResult->setData($responseContent); + } +} diff --git a/app/code/Magento/Paypal/Controller/Transparent/RequestSecureToken.php b/app/code/Magento/Paypal/Controller/Transparent/RequestSecureToken.php index 85907c9d371..847388eb755 100644 --- a/app/code/Magento/Paypal/Controller/Transparent/RequestSecureToken.php +++ b/app/code/Magento/Paypal/Controller/Transparent/RequestSecureToken.php @@ -83,7 +83,7 @@ public function execute() /** @var Quote $quote */ $quote = $this->sessionManager->getQuote(); - if (!$quote or !$quote instanceof Quote) { + if (!$quote || !$quote instanceof Quote) { return $this->getErrorResponse(); } @@ -107,6 +107,8 @@ public function execute() } /** + * Get error response. + * * @return Json */ private function getErrorResponse() diff --git a/app/code/Magento/Paypal/CustomerData/BillingAgreement.php b/app/code/Magento/Paypal/CustomerData/BillingAgreement.php index 2c4cdf55bff..a6304f32197 100644 --- a/app/code/Magento/Paypal/CustomerData/BillingAgreement.php +++ b/app/code/Magento/Paypal/CustomerData/BillingAgreement.php @@ -79,7 +79,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function getSectionData() { @@ -93,7 +93,7 @@ public function getSectionData() [\Magento\Paypal\Model\Express\Checkout::PAYMENT_INFO_TRANSPORT_BILLING_AGREEMENT => 1] ) ), - 'confirmMessage' => $this->escaper->escapeJs( + 'confirmMessage' => $this->escaper->escapeHtml( __('Would you like to sign a billing agreement to streamline further purchases with PayPal?') ) ]; diff --git a/app/code/Magento/Paypal/Model/AbstractConfig.php b/app/code/Magento/Paypal/Model/AbstractConfig.php index e5beddac3b1..41f122ed9b3 100644 --- a/app/code/Magento/Paypal/Model/AbstractConfig.php +++ b/app/code/Magento/Paypal/Model/AbstractConfig.php @@ -9,7 +9,6 @@ use Magento\Payment\Model\Method\ConfigInterface; use Magento\Payment\Model\MethodInterface; use Magento\Store\Model\ScopeInterface; -use Magento\Paypal\Model\Config; use Magento\Framework\App\ObjectManager; /** @@ -293,11 +292,15 @@ public function isMethodActive($method) break; case Config::METHOD_WPS_BML: case Config::METHOD_WPP_BML: - $isEnabled = $this->_scopeConfig->isSetFlag( - 'payment/' . Config::METHOD_WPS_BML .'/active', + $disabledFunding = $this->_scopeConfig->getValue( + 'payment/paypal_express/disable_funding_options', ScopeInterface::SCOPE_STORE, $this->_storeId - ) + ); + $isExpressCreditEnabled = $disabledFunding + ? strpos($disabledFunding, 'CREDIT') === false + : true; + $isEnabled = $isExpressCreditEnabled || $this->_scopeConfig->isSetFlag( 'payment/' . Config::METHOD_WPP_BML .'/active', ScopeInterface::SCOPE_STORE, diff --git a/app/code/Magento/Paypal/Model/Billing/AbstractAgreement.php b/app/code/Magento/Paypal/Model/Billing/AbstractAgreement.php index 2ebe088d31d..8965684d108 100644 --- a/app/code/Magento/Paypal/Model/Billing/AbstractAgreement.php +++ b/app/code/Magento/Paypal/Model/Billing/AbstractAgreement.php @@ -6,7 +6,7 @@ namespace Magento\Paypal\Model\Billing; /** - * Billing Agreement abstaract class + * Billing Agreement abstract class */ abstract class AbstractAgreement extends \Magento\Framework\Model\AbstractModel { diff --git a/app/code/Magento/Paypal/Model/Config.php b/app/code/Magento/Paypal/Model/Config.php index b058ba129a3..9891f68bf87 100644 --- a/app/code/Magento/Paypal/Model/Config.php +++ b/app/code/Magento/Paypal/Model/Config.php @@ -1635,6 +1635,7 @@ protected function _mapWpukFieldset($fieldName) * * @param string $fieldName * @return string|null + * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ protected function _mapGenericStyleFieldset($fieldName) { @@ -1645,9 +1646,10 @@ protected function _mapGenericStyleFieldset($fieldName) case 'paypal_hdrbackcolor': case 'paypal_hdrbordercolor': case 'paypal_payflowcolor': + case 'disable_funding_options': return "paypal/style/{$fieldName}"; default: - return null; + return $this->mapButtonStyles($fieldName); } } @@ -1697,6 +1699,36 @@ protected function _mapMethodFieldset($fieldName) } } + /** + * Map PayPal button style config fields + * + * @param string $fieldName + * @return null|string + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + */ + private function mapButtonStyles(string $fieldName) + { + $page = substr($fieldName, 0, (int)strpos($fieldName, '_page_button_')); + + if (!$page) { + return null; + } + + switch ($fieldName) { + case "{$page}_page_button_customize": + case "{$page}_page_button_layout": + case "{$page}_page_button_size": + case "{$page}_page_button_color": + case "{$page}_page_button_shape": + case "{$page}_page_button_label": + case "{$page}_page_button_mx_installment_period": + case "{$page}_page_button_br_installment_period": + return "paypal/style/{$fieldName}"; + default: + return null; + } + } + /** * Payment API authentication methods source getter * diff --git a/app/code/Magento/Paypal/Model/Config/Rules/Converter.php b/app/code/Magento/Paypal/Model/Config/Rules/Converter.php index 2bae810a5fd..2baedaa38f5 100644 --- a/app/code/Magento/Paypal/Model/Config/Rules/Converter.php +++ b/app/code/Magento/Paypal/Model/Config/Rules/Converter.php @@ -63,6 +63,7 @@ protected function createEvents(\DOMElement $node) if ($this->hasNodeElement($child)) { $result[$child->getAttribute('name')] = [ 'value' => $child->getAttribute('value'), + 'include' => $child->getAttribute('include'), 'predicate' => $this->createPredicate($child), ]; } diff --git a/app/code/Magento/Paypal/Model/Config/Structure/Element/FieldPlugin.php b/app/code/Magento/Paypal/Model/Config/Structure/Element/FieldPlugin.php index c2056aea08c..5d5db0128b1 100644 --- a/app/code/Magento/Paypal/Model/Config/Structure/Element/FieldPlugin.php +++ b/app/code/Magento/Paypal/Model/Config/Structure/Element/FieldPlugin.php @@ -5,7 +5,6 @@ */ namespace Magento\Paypal\Model\Config\Structure\Element; -use Magento\Framework\App\RequestInterface; use Magento\Config\Model\Config\Structure\Element\Field as FieldConfigStructure; use Magento\Paypal\Model\Config\StructurePlugin as ConfigStructurePlugin; @@ -14,19 +13,6 @@ */ class FieldPlugin { - /** - * @var RequestInterface - */ - private $request; - - /** - * @param RequestInterface $request - */ - public function __construct(RequestInterface $request) - { - $this->request = $request; - } - /** * Get original configPath (not changed by PayPal configuration inheritance) * @@ -36,7 +22,7 @@ public function __construct(RequestInterface $request) */ public function afterGetConfigPath(FieldConfigStructure $subject, $result) { - if (!$result && $this->request->getParam('section') == 'payment') { + if (!$result && strpos($subject->getPath(), 'payment_') === 0) { $result = preg_replace( '@^(' . implode('|', ConfigStructurePlugin::getPaypalConfigCountries(true)) . ')/@', 'payment/', diff --git a/app/code/Magento/Paypal/Model/Express.php b/app/code/Magento/Paypal/Model/Express.php index 33fe5ec3300..e52a85da3e8 100644 --- a/app/code/Magento/Paypal/Model/Express.php +++ b/app/code/Magento/Paypal/Model/Express.php @@ -44,7 +44,7 @@ class Express extends \Magento\Payment\Model\Method\AbstractMethod * * @var bool */ - protected $_isGateway = false; + protected $_isGateway = true; /** * Availability option diff --git a/app/code/Magento/Paypal/Model/Express/Checkout.php b/app/code/Magento/Paypal/Model/Express/Checkout.php index 856e01f7353..38ba0983514 100644 --- a/app/code/Magento/Paypal/Model/Express/Checkout.php +++ b/app/code/Magento/Paypal/Model/Express/Checkout.php @@ -21,6 +21,7 @@ * @SuppressWarnings(PHPMD.TooManyFields) * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) */ class Checkout { @@ -606,10 +607,12 @@ public function canSkipOrderReviewStep() * export shipping address in case address absence * * @param string $token + * @param string|null $payerIdentifier * @return void * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.NPathComplexity) */ - public function returnFromPaypal($token) + public function returnFromPaypal($token, string $payerIdentifier = null) { $this->_getApi() ->setToken($token) @@ -685,7 +688,8 @@ public function returnFromPaypal($token) $payment = $quote->getPayment(); $payment->setMethod($this->_methodType); $this->_paypalInfo->importToPayment($this->_getApi(), $payment); - $payment->setAdditionalInformation(self::PAYMENT_INFO_TRANSPORT_PAYER_ID, $this->_getApi()->getPayerId()) + $payerId = $payerIdentifier ? : $this->_getApi()->getPayerId(); + $payment->setAdditionalInformation(self::PAYMENT_INFO_TRANSPORT_PAYER_ID, $payerId) ->setAdditionalInformation(self::PAYMENT_INFO_TRANSPORT_TOKEN, $token); $quote->collectTotals(); $this->quoteRepository->save($quote); diff --git a/app/code/Magento/Paypal/Model/ExpressConfigProvider.php b/app/code/Magento/Paypal/Model/ExpressConfigProvider.php index 518e8b12bfc..c8adb137299 100644 --- a/app/code/Magento/Paypal/Model/ExpressConfigProvider.php +++ b/app/code/Magento/Paypal/Model/ExpressConfigProvider.php @@ -66,14 +66,20 @@ class ExpressConfigProvider implements ConfigProviderInterface protected $urlBuilder; /** - * Constructor - * + * @var SmartButtonConfig + */ + private $smartButtonConfig; + + /** + * ExpressConfigProvider constructor. * @param ConfigFactory $configFactory * @param ResolverInterface $localeResolver * @param CurrentCustomer $currentCustomer * @param PaypalHelper $paypalHelper * @param PaymentHelper $paymentHelper * @param UrlInterface $urlBuilder + * @param SmartButtonConfig|null $smartButtonConfig + * @throws \Magento\Framework\Exception\LocalizedException */ public function __construct( ConfigFactory $configFactory, @@ -81,7 +87,8 @@ public function __construct( CurrentCustomer $currentCustomer, PaypalHelper $paypalHelper, PaymentHelper $paymentHelper, - UrlInterface $urlBuilder + UrlInterface $urlBuilder, + SmartButtonConfig $smartButtonConfig ) { $this->localeResolver = $localeResolver; $this->config = $configFactory->create(); @@ -89,6 +96,7 @@ public function __construct( $this->paypalHelper = $paypalHelper; $this->paymentHelper = $paymentHelper; $this->urlBuilder = $urlBuilder; + $this->smartButtonConfig = $smartButtonConfig; foreach ($this->methodCodes as $code) { $this->methods[$code] = $this->paymentHelper->getMethodInstance($code); @@ -96,7 +104,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function getConfig() { @@ -123,15 +131,17 @@ public function getConfig() $config['payment']['paypalExpress']['inContextConfig'] = [ 'inContextId' => self::IN_CONTEXT_BUTTON_ID, 'merchantId' => $this->config->getValue('merchant_id'), - 'path' => $this->urlBuilder->getUrl('paypal/express/gettoken', ['_secure' => true]), - 'clientConfig' => [ - 'environment' => ((int) $this->config->getValue('sandbox_flag') ? 'sandbox' : 'production'), - 'locale' => $locale, - 'button' => [ - self::IN_CONTEXT_BUTTON_ID - ] + ]; + $clientConfig = [ + 'button' => [ + self::IN_CONTEXT_BUTTON_ID ], + 'getTokenUrl' => $this->urlBuilder->getUrl('paypal/express/getTokenData'), + 'onAuthorizeUrl' => $this->urlBuilder->getUrl('paypal/express/onAuthorization'), + 'onCancelUrl' => $this->urlBuilder->getUrl('paypal/express/cancel') ]; + $clientConfig = array_replace_recursive($clientConfig, $this->smartButtonConfig->getConfig('checkout')); + $config['payment']['paypalExpress']['inContextConfig']['clientConfig'] = $clientConfig; } foreach ($this->methodCodes as $code) { @@ -146,6 +156,8 @@ public function getConfig() } /** + * Return setting value for in context checkout + * * @return bool */ protected function isInContextCheckout() diff --git a/app/code/Magento/Paypal/Model/Payflowpro.php b/app/code/Magento/Paypal/Model/Payflowpro.php index aeea4c75933..2ba72c4b26b 100644 --- a/app/code/Magento/Paypal/Model/Payflowpro.php +++ b/app/code/Magento/Paypal/Model/Payflowpro.php @@ -894,7 +894,7 @@ public function addRequestOrderInfo(DataObject $request, Order $order) $orderIncrementId = $order->getIncrementId(); $request->setCustref($orderIncrementId) ->setInvnum($orderIncrementId) - ->setComment1($orderIncrementId); + ->setData('comment1', $orderIncrementId); } /** diff --git a/app/code/Magento/Paypal/Model/SmartButtonConfig.php b/app/code/Magento/Paypal/Model/SmartButtonConfig.php new file mode 100644 index 00000000000..80a0d477216 --- /dev/null +++ b/app/code/Magento/Paypal/Model/SmartButtonConfig.php @@ -0,0 +1,154 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Paypal\Model; + +use Magento\Framework\Locale\ResolverInterface; + +/** + * Smart button config + */ +class SmartButtonConfig +{ + /** + * @var \Magento\Framework\Locale\ResolverInterface + */ + private $localeResolver; + + /** + * @var ConfigFactory + */ + private $config; + + /** + * @var array + */ + private $defaultStyles; + + /** + * @var array + */ + private $allowedFunding; + + /** + * @param ResolverInterface $localeResolver + * @param ConfigFactory $configFactory + * @param array $defaultStyles + * @param array $allowedFunding + */ + public function __construct( + ResolverInterface $localeResolver, + ConfigFactory $configFactory, + $defaultStyles = [], + $allowedFunding = [] + ) { + $this->localeResolver = $localeResolver; + $this->config = $configFactory->create(); + $this->config->setMethod(Config::METHOD_EXPRESS); + $this->defaultStyles = $defaultStyles; + $this->allowedFunding = $allowedFunding; + } + + /** + * Get smart button config + * + * @param string $page + * @return array + */ + public function getConfig(string $page): array + { + return [ + 'merchantId' => $this->config->getValue('merchant_id'), + 'environment' => ((int)$this->config->getValue('sandbox_flag') ? 'sandbox' : 'production'), + 'locale' => $this->localeResolver->getLocale(), + 'allowedFunding' => $this->getAllowedFunding($page), + 'disallowedFunding' => $this->getDisallowedFunding(), + 'styles' => $this->getButtonStyles($page) + ]; + } + + /** + * Returns disallowed funding from configuration + * + * @return array + */ + private function getDisallowedFunding(): array + { + $disallowedFunding = $this->config->getValue('disable_funding_options'); + return $disallowedFunding ? explode(',', $disallowedFunding) : []; + } + + /** + * Returns allowed funding + * + * @param string $page + * @return array + */ + private function getAllowedFunding(string $page): array + { + return array_values(array_diff($this->allowedFunding[$page], $this->getDisallowedFunding())); + } + + /** + * Returns button styles based on configuration + * + * @param string $page + * @return array + */ + private function getButtonStyles(string $page): array + { + $styles = $this->defaultStyles[$page]; + if ((boolean)$this->config->getValue("{$page}_page_button_customize")) { + $styles['layout'] = $this->config->getValue("{$page}_page_button_layout"); + $styles['size'] = $this->config->getValue("{$page}_page_button_size"); + $styles['color'] = $this->config->getValue("{$page}_page_button_color"); + $styles['shape'] = $this->config->getValue("{$page}_page_button_shape"); + $styles['label'] = $this->config->getValue("{$page}_page_button_label"); + + $styles = $this->updateStyles($styles, $page); + } + return $styles; + } + + /** + * Update styles based on locale and labels + * + * @param array $styles + * @param string $page + * @return array + */ + private function updateStyles(array $styles, string $page): array + { + $locale = $this->localeResolver->getLocale(); + + $installmentPeriodLocale = [ + 'en_MX' => 'mx', + 'es_MX' => 'mx', + 'en_BR' => 'br', + 'pt_BR' => 'br' + ]; + + // Credit label cannot be used with any custom color option or vertical layout. + if ($styles['label'] === 'credit') { + $styles['color'] = 'darkblue'; + $styles['layout'] = 'horizontal'; + } + + // Installment label is only available for specific locales + if ($styles['label'] === 'installment') { + if (array_key_exists($locale, $installmentPeriodLocale)) { + $styles['installmentperiod'] = (int)$this->config->getValue( + $page .'_page_button_' . $installmentPeriodLocale[$locale] . '_installment_period' + ); + } else { + $styles['label'] = 'paypal'; + } + } + + return $styles; + } +} diff --git a/app/code/Magento/Paypal/Model/System/Config/Source/ButtonStyles.php b/app/code/Magento/Paypal/Model/System/Config/Source/ButtonStyles.php new file mode 100644 index 00000000000..8ad55d045ff --- /dev/null +++ b/app/code/Magento/Paypal/Model/System/Config/Source/ButtonStyles.php @@ -0,0 +1,110 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Paypal\Model\System\Config\Source; + +/** + * Get button style options + */ +class ButtonStyles +{ + /** + * Button color source getter + * + * @return array + */ + public function getColor(): array + { + return [ + 'gold' => __('Gold'), + 'blue' => __('Blue'), + 'silver' => __('Silver'), + 'black' => __('Black') + ]; + } + + /** + * Button layout source getter + * + * @return array + */ + public function getLayout(): array + { + return [ + 'vertical' => __('Vertical'), + 'horizontal' => __('Horizontal') + ]; + } + + /** + * Button shape source getter + * + * @return array + */ + public function getShape(): array + { + return [ + 'pill' => __('Pill'), + 'rect' => __('Rectangle') + ]; + } + + /** + * Button size source getter + * + * @return array + */ + public function getSize(): array + { + return [ + 'medium' => __('Medium'), + 'large' => __('Large'), + 'responsive' => __('Responsive') + ]; + } + + /** + * Button label source getter + * + * @return array + */ + public function getLabel(): array + { + return [ + 'checkout' => __('Checkout'), + 'pay' => __('Pay'), + 'buynow' => __('Buy Now'), + 'paypal' => __('PayPal'), + 'installment' => __('Installment'), + 'credit' => __('Credit') + ]; + } + + /** + * Brazil button installment period source getter + * + * @return array + */ + public function getBrInstallmentPeriod(): array + { + $numbers = range(2, 12); + + return array_combine($numbers, $numbers); + } + + /** + * Mexico button installment period source getter + * + * @return array + */ + public function getMxInstallmentPeriod(): array + { + $numbers = range(3, 12, 3); + + return array_combine($numbers, $numbers); + } +} diff --git a/app/code/Magento/Paypal/Model/System/Config/Source/DisableFundingOptions.php b/app/code/Magento/Paypal/Model/System/Config/Source/DisableFundingOptions.php new file mode 100644 index 00000000000..1a9cfe0998f --- /dev/null +++ b/app/code/Magento/Paypal/Model/System/Config/Source/DisableFundingOptions.php @@ -0,0 +1,35 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Paypal\Model\System\Config\Source; + +/** + * Get disable funding options + */ +class DisableFundingOptions +{ + /** + * @inheritdoc + */ + public function toOptionArray(): array + { + return [ + [ + 'value' => 'CREDIT', + 'label' => __('PayPal Credit') + ], + [ + 'value' => 'CARD', + 'label' => __('PayPal Guest Checkout Credit Card Icons') + ], + [ + 'value' => 'ELV', + 'label' => __('Elektronisches Lastschriftverfahren - German ELV') + ] + ]; + } +} diff --git a/app/code/Magento/Paypal/Observer/AddPaypalShortcutsObserver.php b/app/code/Magento/Paypal/Observer/AddPaypalShortcutsObserver.php index 58edf68f347..861ca740606 100644 --- a/app/code/Magento/Paypal/Observer/AddPaypalShortcutsObserver.php +++ b/app/code/Magento/Paypal/Observer/AddPaypalShortcutsObserver.php @@ -9,6 +9,8 @@ use Magento\Framework\Event\ObserverInterface; use Magento\Paypal\Model\Config as PaypalConfig; use Magento\Framework\Event\Observer as EventObserver; +use Magento\Paypal\Block\Express\InContext\Minicart\SmartButton as MinicartSmartButton; +use Magento\Paypal\Block\Express\InContext\SmartButton as SmartButton; /** * PayPal module observer @@ -50,8 +52,9 @@ public function execute(EventObserver $observer) /** @var \Magento\Catalog\Block\ShortcutButtons $shortcutButtons */ $shortcutButtons = $observer->getEvent()->getContainer(); $blocks = [ - \Magento\Paypal\Block\Express\InContext\Minicart\Button::class => + MinicartSmartButton::class => PaypalConfig::METHOD_WPS_EXPRESS, + SmartButton::class => PaypalConfig::METHOD_WPS_EXPRESS, \Magento\Paypal\Block\Express\Shortcut::class => PaypalConfig::METHOD_WPP_EXPRESS, \Magento\Paypal\Block\Bml\Shortcut::class => PaypalConfig::METHOD_WPP_EXPRESS, \Magento\Paypal\Block\WpsExpress\Shortcut::class => PaypalConfig::METHOD_WPS_EXPRESS, @@ -77,11 +80,9 @@ public function execute(EventObserver $observer) '', $params ); - $shortcut->setIsInCatalogProduct( - $observer->getEvent()->getIsCatalogProduct() - )->setShowOrPosition( - $observer->getEvent()->getOrPosition() - ); + $shortcut->setIsInCatalogProduct($observer->getEvent()->getIsCatalogProduct()) + ->setShowOrPosition($observer->getEvent()->getOrPosition()) + ->setIsShoppingCart((bool) $observer->getEvent()->getIsShoppingCart()); $shortcutButtons->addShortcut($shortcut); } } diff --git a/app/code/Magento/Paypal/Setup/Patch/Data/UpdatePaypalCreditOption.php b/app/code/Magento/Paypal/Setup/Patch/Data/UpdatePaypalCreditOption.php new file mode 100644 index 00000000000..6c4362d83e2 --- /dev/null +++ b/app/code/Magento/Paypal/Setup/Patch/Data/UpdatePaypalCreditOption.php @@ -0,0 +1,84 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Paypal\Setup\Patch\Data; + +use Magento\Framework\Setup\ModuleDataSetupInterface; +use Magento\Framework\Setup\Patch\DataPatchInterface; +use Magento\Framework\Setup\Patch\PatchVersionInterface; + +/** + * Class AddPaypalOrderStates + */ +class UpdatePaypalCreditOption implements DataPatchInterface, PatchVersionInterface +{ + /** + * @var ModuleDataSetupInterface + */ + private $moduleDataSetup; + + /** + * PrepareInitialConfig constructor. + * @param ModuleDataSetupInterface $moduleDataSetup + */ + public function __construct( + ModuleDataSetupInterface $moduleDataSetup + ) { + $this->moduleDataSetup = $moduleDataSetup; + } + + /** + * @inheritdoc + */ + public function apply() + { + $this->moduleDataSetup->getConnection()->startSetup(); + $connection = $this->moduleDataSetup->getConnection(); + $select = $connection->select() + ->from($this->moduleDataSetup->getTable('core_config_data'), ['scope', 'scope_id', 'value']) + ->where('path = ?', 'payment/paypal_express_bml/active'); + foreach ($connection->fetchAll($select) as $pair) { + if (!$pair['value']) { + $this->moduleDataSetup->getConnection() + ->insertOnDuplicate( + $this->moduleDataSetup->getTable('core_config_data'), + [ + 'scope' => $pair['scope'], + 'scope_id' => $pair['scope_id'], + 'path' => 'paypal/style/disable_funding_options', + 'value' => 'CREDIT' + ] + ); + } + } + $this->moduleDataSetup->getConnection()->endSetup(); + } + + /** + * @inheritdoc + */ + public static function getDependencies() + { + return []; + } + + /** + * @inheritdoc + */ + public static function getVersion() + { + return '2.3.1'; + } + + /** + * @inheritdoc + */ + public function getAliases() + { + return []; + } +} diff --git a/app/code/Magento/Paypal/Test/Mftf/ActionGroup/OpenPayPalButtonCheckoutPageActionGroup.xml b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/OpenPayPalButtonCheckoutPageActionGroup.xml new file mode 100644 index 00000000000..97c7fbc471e --- /dev/null +++ b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/OpenPayPalButtonCheckoutPageActionGroup.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="OpenPayPalButtonCheckoutPage"> + <click selector="{{PayPalExpressCheckoutConfigSection.configureBtn}}" stepKey="clickPayPalConfigureBtn"/> + <waitForElementVisible selector="{{PayPalAdvancedSettingConfigSection.advancedSettingTab}}" stepKey="waitForAdvancedSettingTab"/> + <click selector="{{PayPalAdvancedSettingConfigSection.advancedSettingTab}}" stepKey="openAdvancedSettingTab"/> + <waitForElementVisible selector="{{PayPalAdvancedSettingConfigSection.frontendExperienceSettingsTab}}" stepKey="waitForFrontendExperienceSettingsTab"/> + <click selector="{{PayPalAdvancedSettingConfigSection.frontendExperienceSettingsTab}}" stepKey="openFrontendExperienceSettingsTab"/> + <waitForElementVisible selector="{{PayPalAdvancedSettingConfigSection.checkoutPageTab}}" stepKey="waitForCheckoutPageTab"/> + <click selector="{{PayPalAdvancedSettingConfigSection.checkoutPageTab}}" stepKey="openCheckoutPageTab"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Paypal/Test/Mftf/ActionGroup/PayPalExpressCheckoutConfigurationActionGroup.xml b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/PayPalExpressCheckoutConfigurationActionGroup.xml new file mode 100644 index 00000000000..7bf26aceb31 --- /dev/null +++ b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/PayPalExpressCheckoutConfigurationActionGroup.xml @@ -0,0 +1,69 @@ +<?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="ConfigPayPalExpressCheckout"> + <amOnPage url="{{AdminConfigPaymentMethodsPage.url}}" stepKey="navigateToPaymentConfigurationPage"/> + <waitForPageLoad stepKey="waitForPageLoad1"/> + <click selector="{{PayPalExpressCheckoutConfigSection.configureBtn}}" stepKey="clickPayPalConfigureBtn"/> + <waitForElementVisible selector="{{PayPalAdvancedSettingConfigSection.advancedSettingTab}}" stepKey="waitForAdvancedSettingTab"/> + <fillField selector ="{{PayPalExpressCheckoutConfigSection.email}}" userInput="{{_CREDS.paypal_express_email}}" stepKey="inputEmailAssociatedWithPayPalMerchantAccount"/> + <selectOption selector ="{{PayPalExpressCheckoutConfigSection.apiMethod}}" userInput="API Signature" stepKey="inputAPIAuthenticationMethods"/> + <fillField selector ="{{PayPalExpressCheckoutConfigSection.username}}" userInput="{{_CREDS.paypal_express_api_username}}" stepKey="inputAPIUsername"/> + <fillField selector ="{{PayPalExpressCheckoutConfigSection.password}}" userInput="{{_CREDS.paypal_express_api_password}}" stepKey="inputAPIPassword"/> + <fillField selector ="{{PayPalExpressCheckoutConfigSection.signature}}" userInput="{{_CREDS.paypal_express_api_signature}}" stepKey="inputAPISignature"/> + <selectOption selector ="{{PayPalExpressCheckoutConfigSection.sandboxMode}}" userInput="Yes" stepKey="enableSandboxMode"/> + <selectOption selector="{{PayPalExpressCheckoutConfigSection.enableSolution}}" userInput="Yes" stepKey="enableSolution"/> + <fillField selector ="{{PayPalExpressCheckoutConfigSection.merchantID}}" userInput="{{_CREDS.paypal_express_merchantID}}" stepKey="inputMerchantID"/> + <!--Save configuration--> + <click selector="{{AdminConfigSection.saveButton}}" stepKey="saveConfig"/> + </actionGroup> + <actionGroup name="CreatePayPalOrderWithSelectedPaymentMethodActionGroup" extends="CreateOrderToPrintPageActionGroup"> + <waitForElement selector="{{CheckoutPaymentSection.paymentSectionTitle}}" stepKey="waitForPlaceOrderButton"/> + <click selector="{{CheckoutPaymentSection.PayPalPaymentRadio}}" stepKey="clickPlaceOrder"/> + <!--set ID for iframe of PayPal group button--> + <executeJS function="jQuery('.zoid-component-frame.zoid-visible').attr('id', 'myIframe')" stepKey="clickOrderLink"/> + <!--switch to iframe of PayPal group button--> + <comment userInput="switch to iframe of PayPal group button" stepKey="commentSwitchToIframe"/> + <switchToIFrame userInput="myIframe" stepKey="clickPrintOrderLink"/> + <waitForElementVisible selector="{{CheckoutPaymentSection.PayPalBtn}}" stepKey="waitForPayPalBtn"/> + <click selector="{{CheckoutPaymentSection.PayPalBtn}}" stepKey="clickPayPalBtn"/> + <switchToIFrame stepKey="switchBack1"/> + <!--Check in-context--> + <comment userInput="Check in-context" stepKey="commentVerifyInContext"/> + <switchToNextTab stepKey="switchToInContentTab"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <seeCurrentUrlMatches regex="~\//www.sandbox.paypal.com/~" stepKey="seeCurrentUrlMatchesConfigPath1"/> + <waitForElement selector="{{PayPalPaymentSection.email}}" stepKey="waitForLoginForm" /> + <fillField selector="{{PayPalPaymentSection.email}}" userInput="{{Payer.buyerEmail}}" stepKey="fillEmail"/> + <fillField selector="{{PayPalPaymentSection.password}}" userInput="{{Payer.buyerPassword}}" stepKey="fillPassword"/> + <click selector="{{PayPalPaymentSection.loginBtn}}" stepKey="login"/> + <waitForPageLoad stepKey="wait"/> + <seeElement selector="{{PayPalPaymentSection.reviewUserInfo}}" stepKey="seePayerName"/> + </actionGroup> + <actionGroup name="addProductToCheckoutPage"> + <arguments> + <argument name="Category"/> + </arguments> + <amOnPage url="{{StorefrontCategoryPage.url(Category.name)}}" stepKey="onCategoryPage"/> + <waitForPageLoad stepKey="waitForPageLoad1"/> + <moveMouseOver selector="{{StorefrontCategoryMainSection.ProductItemInfo}}" stepKey="hoverProduct"/> + <click selector="{{StorefrontCategoryMainSection.AddToCartBtn}}" stepKey="addToCart"/> + <waitForElementVisible selector="{{StorefrontCategoryMainSection.SuccessMsg}}" time="30" stepKey="waitForProductAdded"/> + <click selector="{{StorefrontMinicartSection.showCart}}" stepKey="clickCart"/> + <click selector="{{StorefrontMinicartSection.goToCheckout}}" stepKey="goToCheckout"/> + <waitForPageLoad stepKey="waitForPageLoad2"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMask"/> + <click selector="{{CheckoutShippingMethodsSection.firstShippingMethod}}" stepKey="selectFirstShippingMethod"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMask2"/> + <waitForElement selector="{{CheckoutShippingMethodsSection.next}}" time="30" stepKey="waitForNextButton"/> + <click selector="{{CheckoutShippingMethodsSection.next}}" stepKey="clickNext"/> + <waitForElement selector="{{CheckoutPaymentSection.paymentSectionTitle}}" stepKey="waitForPlaceOrderButton"/> + <click selector="{{CheckoutPaymentSection.PayPalPaymentRadio}}" stepKey="clickPayPalCheckbox"/> + </actionGroup> +</actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Paypal/Test/Mftf/Data/PaypalData.xml b/app/code/Magento/Paypal/Test/Mftf/Data/PaypalData.xml index 6d5f80e30dc..d97e60043cc 100644 --- a/app/code/Magento/Paypal/Test/Mftf/Data/PaypalData.xml +++ b/app/code/Magento/Paypal/Test/Mftf/Data/PaypalData.xml @@ -58,4 +58,35 @@ <entity name="DefaultApiSignature" type="api_signature"> <data key="value"/> </entity> + <entity name="Payer" type="paypal_buyer"> + <data key="buyerEmail">buyer.mpi@gmail.com</data> + <data key="buyerPassword">12345678</data> + </entity> + <entity name="PayPalLabel" type="paypal"> + <data key="checkout">checkout</data> + <data key="credit">credit</data> + <data key="pay">pay</data> + <data key="buynow">buy now</data> + <data key="paypal">pay pal</data> + <data key="installment">installment</data> + </entity> + <entity name="PayPalLayout" type="paypal"> + <data key="horizontal">horizontal</data> + <data key="vertical">vertical</data> + </entity> + <entity name="PayPalSize" type="paypal"> + <data key="medium">medium</data> + <data key="large">large</data> + <data key="responsive">responsive</data> + </entity> + <entity name="PayPalShape" type="paypal"> + <data key="pill">pill</data> + <data key="rectangle">rectangle</data> + </entity> + <entity name="PayPalColor" type="paypal"> + <data key="gold">gold</data> + <data key="blue">blue</data> + <data key="silver">silver</data> + <data key="black">black</data> + </entity> </entities> diff --git a/app/code/Magento/Paypal/Test/Mftf/Section/PayPalExpressCheckoutConfigSection.xml b/app/code/Magento/Paypal/Test/Mftf/Section/PayPalExpressCheckoutConfigSection.xml new file mode 100644 index 00000000000..3ac0bb27075 --- /dev/null +++ b/app/code/Magento/Paypal/Test/Mftf/Section/PayPalExpressCheckoutConfigSection.xml @@ -0,0 +1,58 @@ +<?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="PayPalExpressCheckoutConfigSection"> + <element name="configureBtn" type="button" selector="#payment_us_paypal_alternative_payment_methods_express_checkout_us-head"/> + <element name="email" type="input" selector="#payment_us_paypal_alternative_payment_methods_express_checkout_us_express_checkout_required_express_checkout_required_express_checkout_business_account" /> + <element name="apiMethod" type="input" selector="#payment_us_paypal_alternative_payment_methods_express_checkout_us_express_checkout_required_express_checkout_required_express_checkout_api_authentication"/> + <element name="username" type="input" selector="#payment_us_paypal_alternative_payment_methods_express_checkout_us_express_checkout_required_express_checkout_required_express_checkout_api_username"/> + <element name="password" type="input" selector="#payment_us_paypal_alternative_payment_methods_express_checkout_us_express_checkout_required_express_checkout_required_express_checkout_api_password"/> + <element name="signature" type="input" selector="#payment_us_paypal_alternative_payment_methods_express_checkout_us_express_checkout_required_express_checkout_required_express_checkout_api_signature"/> + <element name="sandboxMode" type="input" selector="#payment_us_paypal_alternative_payment_methods_express_checkout_us_express_checkout_required_express_checkout_required_express_checkout_sandbox_flag"/> + <element name="enableSolution" type="input" selector="#payment_us_paypal_alternative_payment_methods_express_checkout_us_express_checkout_required_enable_express_checkout"/> + <element name="merchantID" type="input" selector="#payment_us_paypal_alternative_payment_methods_express_checkout_us_express_checkout_required_merchant_id"/> + </section> + <section name="PayPalAdvancedSettingConfigSection"> + <element name="advancedSettingTab" type="button" selector="#payment_us_paypal_alternative_payment_methods_express_checkout_us_settings_ec_settings_ec_advanced-head"/> + <element name="frontendExperienceSettingsTab" type="button" selector="#payment_us_paypal_alternative_payment_methods_express_checkout_us_settings_ec_settings_ec_advanced_express_checkout_frontend-head"/> + <element name="checkoutPageTab" type="button" selector="#payment_us_paypal_alternative_payment_methods_express_checkout_us_settings_ec_settings_ec_advanced_express_checkout_frontend_checkout_page_button-head"/> + </section> + <section name="ButtonCustomization"> + <element name="customizeDrpDown" type="button" selector="//tr[@id='row_payment_us_paypal_alternative_payment_methods_express_checkout_us_settings_ec_settings_ec_advanced_express_checkout_frontend_checkout_page_button_checkout_page_button_customize']//select[contains(@data-ui-id, 'button-customize')]"/> + <element name="customizeNo" type="text" selector="//tr[@id='row_payment_us_paypal_alternative_payment_methods_express_checkout_us_settings_ec_settings_ec_advanced_express_checkout_frontend_checkout_page_button_checkout_page_button_customize']//select[contains(@data-ui-id, 'button-customize')]/option[@value='0' and @selected='selected']"/> + <element name="label" type="input" selector="//tr[@id='row_payment_us_paypal_alternative_payment_methods_express_checkout_us_settings_ec_settings_ec_advanced_express_checkout_frontend_checkout_page_button']//select[contains(@id, 'button_label')]"/> + <element name="layout" type="input" selector="//tr[@id='row_payment_us_paypal_alternative_payment_methods_express_checkout_us_settings_ec_settings_ec_advanced_express_checkout_frontend_checkout_page_button']//select[contains(@id, 'button_layout')]"/> + <element name="size" type="input" selector="//tr[@id='row_payment_us_paypal_alternative_payment_methods_express_checkout_us_settings_ec_settings_ec_advanced_express_checkout_frontend_checkout_page_button']//select[contains(@id, 'button_size')]"/> + <element name="shape" type="input" selector="//tr[@id='row_payment_us_paypal_alternative_payment_methods_express_checkout_us_settings_ec_settings_ec_advanced_express_checkout_frontend_checkout_page_button']//select[contains(@id, 'button_shape')]"/> + <element name="color" type="input" selector="//tr[@id='row_payment_us_paypal_alternative_payment_methods_express_checkout_us_settings_ec_settings_ec_advanced_express_checkout_frontend_checkout_page_button']//select[contains(@id, 'button_color')]"/> + </section> + <section name="PayPalButtonOnStorefront"> + <element name="label" type="text" selector="[aria-label='{{label}}']" parameterized="true"/> + <element name="layout" type="text" selector="[data-layout='{{layout}}']" parameterized="true"/> + <element name="size" type="text" selector="[data-size='{{size}}']" parameterized="true"/> + <element name="shape" type="text" selector=".paypal-button-shape-{{shape}}" parameterized="true"/> + <element name="color" type="text" selector=".paypal-button-color-{{color}}" parameterized="true"/> + </section> + <section name="CheckoutPaymentSection"> + <element name="PayPalPaymentRadio" type="radio" selector="input#paypal_express.radio" timeout="30"/> + <element name="PayPalBtn" type="radio" selector=".paypal-button.paypal-button-number-0" timeout="30"/> + </section> + <section name="PayPalPaymentSection"> + <element name="guestCheckout" type="input" selector="#guest"/> + <element name="loginSection" type="input" selector=" #main>#login"/> + <element name="email" type="input" selector="//input[contains(@name, 'email') and not(contains(@style, 'display:none'))]"/> + <element name="password" type="input" selector="//input[contains(@name, 'password') and not(contains(@style, 'display:none'))]"/> + <element name="loginBtn" type="input" selector="button#btnLogin"/> + <element name="reviewUserInfo" type="text" selector="//p[@id='reviewUserInfo' and contains(text(),'Hi, MPI!')]"/> + <element name="cartIcon" type="text" selector="#transactionCart"/> + <element name="itemName" type="text" selector="//span[@title='{{productName}}']" parameterized="true"/> + <element name="PayPalSubmitBtn" type="text" selector="//input[@type='submit']"/> + </section> +</sections> \ No newline at end of file diff --git a/app/code/Magento/Paypal/Test/Mftf/Suite/suite.xml b/app/code/Magento/Paypal/Test/Mftf/Suite/suite.xml new file mode 100644 index 00000000000..621f2e6a676 --- /dev/null +++ b/app/code/Magento/Paypal/Test/Mftf/Suite/suite.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<suites xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Suite/etc/suiteSchema.xsd"> + <suite name="PaypalTestSuite"> + <include> + <test name="CheckDefaultValueOfPayPalCustomizeButtonTest"/> + <test name="PayPalSmartButtonInCheckoutPage"/> + <test name="CheckCreditButtonConfiguration"/> + </include> + </suite> +</suites> \ No newline at end of file diff --git a/app/code/Magento/Paypal/Test/Mftf/Test/AdminConfigPaymentsSectionState.xml b/app/code/Magento/Paypal/Test/Mftf/Test/AdminConfigPaymentsSectionState.xml index ac752e8412f..934449dfd13 100644 --- a/app/code/Magento/Paypal/Test/Mftf/Test/AdminConfigPaymentsSectionState.xml +++ b/app/code/Magento/Paypal/Test/Mftf/Test/AdminConfigPaymentsSectionState.xml @@ -18,7 +18,7 @@ <testCaseId value="MAGETWO-92043"/> </annotations> <after> - <amOnPage url="admin/admin/auth/logout/" stepKey="amOnLogoutPage"/> + <actionGroup ref="logout" stepKey="amOnLogoutPage"/> </after> <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin1"/> diff --git a/app/code/Magento/Paypal/Test/Mftf/Test/PayPalSmartButtonInCheckoutPage.xml b/app/code/Magento/Paypal/Test/Mftf/Test/PayPalSmartButtonInCheckoutPage.xml new file mode 100644 index 00000000000..1858ee130a3 --- /dev/null +++ b/app/code/Magento/Paypal/Test/Mftf/Test/PayPalSmartButtonInCheckoutPage.xml @@ -0,0 +1,170 @@ +<?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="CheckDefaultValueOfPayPalCustomizeButtonTest"> + <annotations> + <features value="PayPal"/> + <stories value="Button Configuration"/> + <title value="Check Default Value Of PayPal Customize Button"/> + <description value="Default value of PayPal Customize Button should be NO"/> + <severity value="AVERAGE"/> + <testCaseId value="MC-10904"/> + <skip> + <issueId value="DEVOPS-3311"/> + </skip> + </annotations> + <before> + <actionGroup ref="LoginActionGroup" stepKey="login"/> + <actionGroup ref="ConfigPayPalExpressCheckout" stepKey="ConfigPayPalExpressCheckout"/> + </before> + <after> + <actionGroup ref="logout" stepKey="logoutFromAdmin"/> + </after> + <amOnPage url="{{AdminConfigPaymentMethodsPage.url}}" stepKey="navigateToPaymentConfigurationPage"/> + <waitForPageLoad stepKey="waitForPageLoad1"/> + <actionGroup ref="OpenPayPalButtonCheckoutPage" stepKey="openPayPalButtonCheckoutPage"/> + <seeElement selector="{{ButtonCustomization.customizeDrpDown}}" stepKey="seeCustomizeDropDown"/> + <seeOptionIsSelected selector="{{ButtonCustomization.customizeDrpDown}}" userInput="No" stepKey="seeNoIsDefaultValue"/> + <selectOption selector="{{ButtonCustomization.customizeDrpDown}}" userInput="Yes" stepKey="enableButtonCustomization"/> + <!--Verify default value--> + <comment userInput="Verify default value" stepKey="commentVerifyDefaultValue1"/> + <seeElement selector="{{ButtonCustomization.label}}" stepKey="seeLabel"/> + <seeElement selector="{{ButtonCustomization.layout}}" stepKey="seeLayout"/> + <seeElement selector="{{ButtonCustomization.size}}" stepKey="seeSize1"/> + <seeElement selector="{{ButtonCustomization.shape}}" stepKey="seeShape1"/> + <seeElement selector="{{ButtonCustomization.color}}" stepKey="seeColor"/> + </test> + <test name="CheckCreditButtonConfiguration"> + <annotations> + <features value="PayPal"/> + <stories value="Button Configuration"/> + <title value="Check Credit Button Configuration"/> + <description value="Admin is able to customize Credit button"/> + <severity value="AVERAGE"/> + <testCaseId value="MC-10900"/> + <skip> + <issueId value="DEVOPS-3311"/> + </skip> + </annotations> + <before> + <createData entity="_defaultCategory" stepKey="createPreReqCategory"/> + <createData entity="_defaultProduct" stepKey="createPreReqProduct"> + <requiredEntity createDataKey="createPreReqCategory"/> + </createData> + <!-- Create Customer --> + <createData entity="Simple_US_Customer" stepKey="createCustomer"/> + <actionGroup ref="LoginActionGroup" stepKey="login"/> + <!--Config PayPal Express Checkout--> + <comment userInput="config PayPal Express Checkout" stepKey="commemtConfigPayPalExpressCheckout"/> + <actionGroup ref="ConfigPayPalExpressCheckout" stepKey="ConfigPayPalExpressCheckout"/> + </before> + <after> + <deleteData stepKey="deleteCategory" createDataKey="createPreReqCategory"/> + <deleteData stepKey="deleteProduct" createDataKey="createPreReqProduct"/> + <deleteData stepKey="deleteCustomer" createDataKey="createCustomer"/> + <actionGroup ref="logout" stepKey="logoutFromAdmin"/> + </after> + <amOnPage url="{{AdminConfigPaymentMethodsPage.url}}" stepKey="navigateToPaymentConfigurationPage"/> + <waitForPageLoad stepKey="waitForPageLoad1"/> + <!--Navigate to button configuration setting--> + <comment userInput="Navigate to button configuration setting in Admin site" stepKey="commentNavigateToButtonConfigurationInAdmin"/> + <actionGroup ref="OpenPayPalButtonCheckoutPage" stepKey="openPayPalButtonCheckoutPage"/> + <waitForElement selector="{{ButtonCustomization.customizeDrpDown}}" stepKey="seeCustomizeDropDown"/> + <selectOption selector="{{ButtonCustomization.customizeDrpDown}}" userInput="Yes" stepKey="enableButtonCustomization"/> + <!--Verify Credit Button value--> + <comment userInput="Verify Credit Button value" stepKey="commentVerifyDefaultValue2"/> + <selectOption selector="{{ButtonCustomization.label}}" userInput="{{PayPalLabel.credit}}" stepKey="selectCreditAsLabel"/> + <seeElement selector="{{ButtonCustomization.size}}" stepKey="seeSize2"/> + <seeElement selector="{{ButtonCustomization.shape}}" stepKey="seeShape2"/> + <dontSeeElement selector="{{ButtonCustomization.layout}}" stepKey="dontSeeLayout"/> + <dontSeeElement selector="{{ButtonCustomization.color}}" stepKey="dontSeeColor"/> + <!--Customize Credit Button--> + <selectOption selector="{{ButtonCustomization.size}}" userInput="{{PayPalSize.medium}}" stepKey="selectSize"/> + <selectOption selector="{{ButtonCustomization.shape}}" userInput="{{PayPalShape.pill}}" stepKey="selectShape"/> + <!--Save configuration--> + <click selector="{{AdminConfigSection.saveButton}}" stepKey="saveConfig"/> + <waitForPageLoad stepKey="waitForConfigSave"/> + <openNewTab stepKey="openNewTab"/> + <amOnPage url="/" stepKey="openStorefront"/> + <!--Login to storefront as previously created customer--> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginAsCustomer"> + <argument name="Customer" value="$$createCustomer$$"/> + </actionGroup> + <actionGroup ref="addProductToCheckoutPage" stepKey="addProductToCheckoutPage"> + <argument name="Category" value="$$createPreReqCategory$$"/> + </actionGroup> + <!--set ID for iframe of PayPal group button--> + <executeJS function="jQuery('.zoid-component-frame.zoid-visible').attr('id', 'myIframe')" stepKey="clickOrderLink"/> + <!--switch to iframe of PayPal group button--> + <comment userInput="switch to iframe of PayPal group button" stepKey="commentSwitchToIframe"/> + <switchToIframe userInput="myIframe" stepKey="clickPrintOrderLink"/> + <waitForElementVisible selector="{{CheckoutPaymentSection.PayPalBtn}}" stepKey="waitForPayPalBtn"/> + <seeElement selector="{{PayPalButtonOnStorefront.label(PayPalLabel.credit)}}{{PayPalButtonOnStorefront.size(PayPalSize.medium)}}" stepKey="seeButtonInMediumSize"/> + <seeElement selector="{{PayPalButtonOnStorefront.label(PayPalLabel.credit)}}{{PayPalButtonOnStorefront.shape(PayPalShape.pill)}}" stepKey="seeButtonInPillShape"/> + </test> + <test name="PayPalSmartButtonInCheckoutPage"> + <annotations> + <features value="PayPal"/> + <stories value="Generic checkout skeleton flow"/> + <title value="Mainflow of PayPal Smart Button"/> + <description value="Users are able to place order using PayPal Smart Button"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-13690"/> + <skip> + <issueId value="DEVOPS-3311"/> + </skip> + </annotations> + <before> + <createData entity="_defaultCategory" stepKey="createPreReqCategory"/> + <createData entity="_defaultProduct" stepKey="createPreReqProduct"> + <requiredEntity createDataKey="createPreReqCategory"/> + </createData> + <!-- Create Customer --> + <createData entity="Simple_US_Customer" stepKey="createCustomer"/> + <actionGroup ref="LoginActionGroup" stepKey="login"/> + <!--Config PayPal Express Checkout--> + <comment userInput="config PayPal Express Checkout" stepKey="commemtConfigPayPalExpressCheckout"/> + <actionGroup ref="ConfigPayPalExpressCheckout" stepKey="ConfigPayPalExpressCheckout"/> + <magentoCLI command="config:set payment/paypal_express/in_context 1" stepKey="disableInContextPayPal"/> + </before> + <after> + <deleteData stepKey="deleteCategory" createDataKey="createPreReqCategory"/> + <deleteData stepKey="deleteProduct" createDataKey="createPreReqProduct"/> + <deleteData stepKey="deleteCustomer" createDataKey="createCustomer"/> + <magentoCLI command="config:set payment/paypal_express/in_context 0" stepKey="enableInContextPayPal"/> + <actionGroup ref="logout" stepKey="logoutFromAdmin"/> + </after> + <magentoCLI command="config:set payment/paypal_express/payment_action Authorization" stepKey="inputPaymentAction"/> + <magentoCLI command="config:set payment/paypal_express/solution_type Sole" stepKey="enablePayPalGuestCheckout"/> + <magentoCLI command="config:set payment/paypal_express/line_items_enabled 1" stepKey="enableTransferCartLine"/> + <magentoCLI command="config:set payment/paypal_express/skip_order_review_step 1" stepKey="enableSkipOrderReview"/> + <!--Login to storefront as previously created customer--> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginAsCustomer"> + <argument name="Customer" value="$$createCustomer$$"/> + </actionGroup> + <!--Place an order using PayPal method--> + <comment userInput="Place an order using PayPal method" stepKey="commentPayPalPlaceOrder"/> + <actionGroup ref="CreatePayPalOrderWithSelectedPaymentMethodActionGroup" stepKey="createPayPalOrder"> + <argument name="Category" value="$$createPreReqCategory$$"/> + </actionGroup> + <!--Open Cart on PayPal--> + <comment userInput="Open Cart on PayPal" stepKey="commentOpenCart"/> + <click selector="{{PayPalPaymentSection.cartIcon}}" stepKey="openCart"/> + <seeElement selector="{{PayPalPaymentSection.itemName($$createPreReqProduct.name$$)}}" stepKey="seeProductname"/> + <click selector="{{PayPalPaymentSection.PayPalSubmitBtn}}" stepKey="clickPayPalSubmitBtn"/> + <switchToPreviousTab stepKey="switchToPreviousTab"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <!--I see order successful Page instead of Order Review Page--> + <comment userInput="I see order successful Page instead of Order Review Page" stepKey="commentVerifyOrderReviewPage"/> + <waitForElement selector="{{CheckoutSuccessMainSection.successTitle}}" stepKey="waitForLoadSuccessPageTitle"/> + <waitForElement selector="{{CheckoutSuccessMainSection.success}}" time="30" stepKey="waitForLoadSuccessPage"/> + <seeElement selector="{{CheckoutSuccessMainSection.orderLink}}" stepKey="seeOrderLink"/> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Paypal/Test/Unit/Block/Adminhtml/System/Config/Multiselect/DisabledFundingOptionsTest.php b/app/code/Magento/Paypal/Test/Unit/Block/Adminhtml/System/Config/Multiselect/DisabledFundingOptionsTest.php new file mode 100644 index 00000000000..2c9a33ce438 --- /dev/null +++ b/app/code/Magento/Paypal/Test/Unit/Block/Adminhtml/System/Config/Multiselect/DisabledFundingOptionsTest.php @@ -0,0 +1,142 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Paypal\Test\Unit\Block\Adminhtml\System\Config\Multiselect; + +use \Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use \Magento\Framework\Data\Form\Element\AbstractElement; +use \Magento\Framework\App\RequestInterface; +use \Magento\Framework\View\Helper\Js; +use \Magento\Paypal\Model\Config; +use \Magento\Paypal\Block\Adminhtml\System\Config\MultiSelect\DisabledFundingOptions; +use \Magento\Paypal\Model\Config\StructurePlugin; +use \PHPUnit\Framework\TestCase; + +/** + * Class DisabledFundingOptionsTest + */ +class DisabledFundingOptionsTest extends TestCase +{ + /** + * @var \Magento\Paypal\Block\Adminhtml\System\Config\Multiselect\DisabledFundingOptions + */ + private $model; + + /** + * @var \Magento\Framework\Data\Form\Element\AbstractElement + */ + private $element; + + /** + * @var \Magento\Framework\App\RequestInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $request; + + /** + * @var \Magento\Framework\View\Helper\Js|\PHPUnit_Framework_MockObject_MockObject + */ + private $jsHelper; + + /** + * @var \Magento\Paypal\Model\Config + */ + private $config; + + protected function setUp() + { + $helper = new ObjectManager($this); + $this->element = $this->getMockForAbstractClass( + AbstractElement::class, + [], + '', + false, + true, + true, + ['getHtmlId', 'getElementHtml', 'getName'] + ); + $this->request = $this->getMockForAbstractClass(RequestInterface::class); + $this->jsHelper = $this->createMock(Js::class); + $this->config = $this->createMock(Config::class); + $this->element->setValues($this->getDefaultFundingOptions()); + $this->model = $helper->getObject( + DisabledFundingOptions::class, + ['request' => $this->request, 'jsHelper' => $this->jsHelper, 'config' => $this->config] + ); + } + + /** + * @param null|string $requestCountry + * @param null|string $merchantCountry + * @param bool $shouldContainPaypalCredit + * @dataProvider isPaypalCreditAvailableDataProvider + */ + public function testIsPaypalCreditAvailable( + ?string $requestCountry, + ?string $merchantCountry, + bool $shouldContainPaypalCredit + ) { + $this->request->expects($this->any()) + ->method('getParam') + ->will($this->returnCallback(function ($param) use ($requestCountry) { + if ($param == StructurePlugin::REQUEST_PARAM_COUNTRY) { + return $requestCountry; + } + return $param; + })); + $this->config->expects($this->any()) + ->method('getMerchantCountry') + ->will($this->returnCallback(function () use ($merchantCountry) { + return $merchantCountry; + })); + $this->model->render($this->element); + $payPalCreditOption = [ + 'value' => 'CREDIT', + 'label' => __('PayPal Credit') + ]; + $elementValues = $this->element->getValues(); + if ($shouldContainPaypalCredit) { + $this->assertContains($payPalCreditOption, $elementValues); + } else { + $this->assertNotContains($payPalCreditOption, $elementValues); + } + } + + /** + * @return array + */ + public function isPaypalCreditAvailableDataProvider(): array + { + return [ + [null, 'US', true], + ['US', 'US', true], + ['US', 'GB', true], + ['GB', 'GB', false], + ['GB', 'US', false], + ['GB', null, false], + ]; + } + + /** + * @inheritdoc + */ + private function getDefaultFundingOptions(): array + { + return [ + [ + 'value' => 'CREDIT', + 'label' => __('PayPal Credit') + ], + [ + 'value' => 'CARD', + 'label' => __('PayPal Guest Checkout Credit Card Icons') + ], + [ + 'value' => 'ELV', + 'label' => __('Elektronisches Lastschriftverfahren - German ELV') + ] + ]; + } +} diff --git a/app/code/Magento/Paypal/Test/Unit/Block/Bml/ShortcutTest.php b/app/code/Magento/Paypal/Test/Unit/Block/Bml/ShortcutTest.php index b8558cdc084..3fce5dab9dd 100644 --- a/app/code/Magento/Paypal/Test/Unit/Block/Bml/ShortcutTest.php +++ b/app/code/Magento/Paypal/Test/Unit/Block/Bml/ShortcutTest.php @@ -8,6 +8,8 @@ use Magento\Catalog\Block as CatalogBlock; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +use Magento\Paypal\Model\ConfigFactory; +use Magento\Paypal\Model\Config; class ShortcutTest extends \PHPUnit\Framework\TestCase { @@ -33,12 +35,24 @@ protected function setUp() $this->paypalShortcutHelperMock = $this->createMock(\Magento\Paypal\Helper\Shortcut\ValidatorInterface::class); $this->objectManagerHelper = new ObjectManagerHelper($this); + $configFactoryMock = $this->getMockBuilder(ConfigFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + + $configMock = $this->getMockBuilder(Config::class) + ->disableOriginalConstructor() + ->setMethods(['setMethod']) + ->getMock(); + $configFactoryMock->expects($this->any())->method('create')->willReturn($configMock); + $this->shortcut = $this->objectManagerHelper->getObject( \Magento\Paypal\Block\Bml\Shortcut::class, [ 'paymentData' => $this->paymentHelperMock, 'mathRandom' => $this->randomMock, 'shortcutValidator' => $this->paypalShortcutHelperMock, + 'config' => $configFactoryMock ] ); } diff --git a/app/code/Magento/Paypal/Test/Unit/CustomerData/BillingAgreementTest.php b/app/code/Magento/Paypal/Test/Unit/CustomerData/BillingAgreementTest.php index 010c3f8f71d..82e94462445 100644 --- a/app/code/Magento/Paypal/Test/Unit/CustomerData/BillingAgreementTest.php +++ b/app/code/Magento/Paypal/Test/Unit/CustomerData/BillingAgreementTest.php @@ -11,6 +11,7 @@ use Magento\Paypal\Model\Config; use Magento\Paypal\Model\ConfigFactory; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Framework\Escaper; class BillingAgreementTest extends \PHPUnit\Framework\TestCase { @@ -35,9 +36,16 @@ class BillingAgreementTest extends \PHPUnit\Framework\TestCase */ private $billingAgreement; + /** + * @var Escaper + */ + private $escaperMock; + protected function setUp() { + $helper = new ObjectManager($this); $this->paypalConfig = $this->createMock(Config::class); + $this->escaperMock = $helper->getObject(Escaper::class); $this->paypalConfig ->expects($this->once()) ->method('setMethod') @@ -59,14 +67,13 @@ protected function setUp() ->willReturn($customerId); $this->paypalData = $this->createMock(Data::class); - - $helper = new ObjectManager($this); $this->billingAgreement = $helper->getObject( BillingAgreement::class, [ 'paypalConfigFactory' => $paypalConfigFactory, 'paypalData' => $this->paypalData, - 'currentCustomer' => $this->currentCustomer + 'currentCustomer' => $this->currentCustomer, + 'escaper' => $this->escaperMock ] ); } @@ -83,6 +90,10 @@ public function testGetSectionData() $this->assertArrayHasKey('askToCreate', $result); $this->assertArrayHasKey('confirmUrl', $result); $this->assertArrayHasKey('confirmMessage', $result); + $this->assertEquals( + 'Would you like to sign a billing agreement to streamline further purchases with PayPal?', + $result['confirmMessage'] + ); $this->assertTrue($result['askToCreate']); } diff --git a/app/code/Magento/Paypal/Test/Unit/Model/AbstractConfigTest.php b/app/code/Magento/Paypal/Test/Unit/Model/AbstractConfigTest.php index 78bd269403b..6bb2173e06f 100644 --- a/app/code/Magento/Paypal/Test/Unit/Model/AbstractConfigTest.php +++ b/app/code/Magento/Paypal/Test/Unit/Model/AbstractConfigTest.php @@ -293,6 +293,48 @@ public function testIsMethodActive() $this->config->isMethodActive('method'); } + /** + * Check bill me later active setting uses disable funding options + * + * @param string|null $disableFundingOptions + * @param int $expectedFlag + * @param bool $expectedValue + * + * @dataProvider isMethodActiveBmlDataProvider + */ + public function testIsMethodActiveBml($disableFundingOptions, $expectedFlag, $expectedValue) + { + $this->scopeConfigMock->method('getValue') + ->with( + self::equalTo('payment/paypal_express/disable_funding_options'), + self::equalTo('store') + ) + ->willReturn($disableFundingOptions); + + $this->scopeConfigMock->method('isSetFlag') + ->with('payment/paypal_express_bml/active') + ->willReturn($expectedFlag); + + self::assertEquals($expectedValue, $this->config->isMethodActive('paypal_express_bml')); + } + + /** + * @return array + */ + public function isMethodActiveBmlDataProvider() + { + return [ + ['CREDIT,CARD,ELV', 0, false], + ['CREDIT,CARD,ELV', 1, true], + ['CREDIT', 0, false], + ['CREDIT', 1, true], + ['CARD', 0, true], + ['CARD', 1, true], + [null, 0, true], + [null, 1, true] + ]; + } + /** * Checks a case, when notation code based on Magento edition. */ diff --git a/app/code/Magento/Paypal/Test/Unit/Model/Config/Rules/ConverterTest.php b/app/code/Magento/Paypal/Test/Unit/Model/Config/Rules/ConverterTest.php index c1a3a5d5bd9..e7e723f1f3a 100644 --- a/app/code/Magento/Paypal/Test/Unit/Model/Config/Rules/ConverterTest.php +++ b/app/code/Magento/Paypal/Test/Unit/Model/Config/Rules/ConverterTest.php @@ -60,6 +60,7 @@ public function dataProviderExpectedData() 'value' => '0', 'predicate' => [ ], + 'include' => '', ], 'event1' => [ 'value' => '1', @@ -72,6 +73,7 @@ public function dataProviderExpectedData() 'argument2' => 'argument2', ], ], + 'include' => '', ], ], ], @@ -109,6 +111,7 @@ public function dataProviderExpectedData() 'event0' => [ 'value' => '0', 'predicate' => [], + 'include' => '', ], 'event1' => [ 'value' => '1', @@ -121,6 +124,7 @@ public function dataProviderExpectedData() 'argument2' => 'argument2', ], ], + 'include' => '', ], ], ], diff --git a/app/code/Magento/Paypal/Test/Unit/Model/Config/Structure/Element/FieldPluginTest.php b/app/code/Magento/Paypal/Test/Unit/Model/Config/Structure/Element/FieldPluginTest.php index 8615b91383a..f0dda20b71c 100644 --- a/app/code/Magento/Paypal/Test/Unit/Model/Config/Structure/Element/FieldPluginTest.php +++ b/app/code/Magento/Paypal/Test/Unit/Model/Config/Structure/Element/FieldPluginTest.php @@ -7,7 +7,6 @@ use Magento\Paypal\Model\Config\Structure\Element\FieldPlugin as FieldConfigStructurePlugin; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; -use Magento\Framework\App\RequestInterface; use Magento\Config\Model\Config\Structure\Element\Field as FieldConfigStructureMock; class FieldPluginTest extends \PHPUnit\Framework\TestCase @@ -22,11 +21,6 @@ class FieldPluginTest extends \PHPUnit\Framework\TestCase */ private $objectManagerHelper; - /** - * @var RequestInterface|\PHPUnit_Framework_MockObject_MockObject - */ - private $requestMock; - /** * @var FieldConfigStructureMock|\PHPUnit_Framework_MockObject_MockObject */ @@ -34,16 +28,13 @@ class FieldPluginTest extends \PHPUnit\Framework\TestCase protected function setUp() { - $this->requestMock = $this->getMockBuilder(RequestInterface::class) - ->getMockForAbstractClass(); $this->subjectMock = $this->getMockBuilder(FieldConfigStructureMock::class) ->disableOriginalConstructor() ->getMock(); $this->objectManagerHelper = new ObjectManagerHelper($this); $this->plugin = $this->objectManagerHelper->getObject( - FieldConfigStructurePlugin::class, - ['request' => $this->requestMock] + FieldConfigStructurePlugin::class ); } @@ -56,10 +47,9 @@ public function testAroundGetConfigPathHasResult() public function testAroundGetConfigPathNonPaymentSection() { - $this->requestMock->expects(static::once()) - ->method('getParam') - ->with('section') - ->willReturn('non-payment'); + $this->subjectMock->expects($this->once()) + ->method('getPath') + ->willReturn('non-payment/group/field'); $this->assertNull($this->plugin->afterGetConfigPath($this->subjectMock, null)); } @@ -72,11 +62,7 @@ public function testAroundGetConfigPathNonPaymentSection() */ public function testAroundGetConfigPath($subjectPath, $expectedConfigPath) { - $this->requestMock->expects(static::once()) - ->method('getParam') - ->with('section') - ->willReturn('payment'); - $this->subjectMock->expects(static::once()) + $this->subjectMock->expects($this->exactly(2)) ->method('getPath') ->willReturn($subjectPath); diff --git a/app/code/Magento/Paypal/Test/Unit/Model/ConfigTest.php b/app/code/Magento/Paypal/Test/Unit/Model/ConfigTest.php index 113aa5766ed..dd3cf11b87e 100644 --- a/app/code/Magento/Paypal/Test/Unit/Model/ConfigTest.php +++ b/app/code/Magento/Paypal/Test/Unit/Model/ConfigTest.php @@ -7,6 +7,7 @@ use Magento\Paypal\Model\Config; use Magento\Store\Model\ScopeInterface; +use Magento\Framework\App\Config\ScopeConfigInterface; class ConfigTest extends \PHPUnit\Framework\TestCase { @@ -16,7 +17,7 @@ class ConfigTest extends \PHPUnit\Framework\TestCase private $model; /** - * @var \Magento\Framework\App\Config\ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject + * @var ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject */ private $scopeConfig; @@ -117,14 +118,29 @@ public function testIsMethodAvailableWPPPE() */ public function testIsMethodAvailableForIsMethodActive($methodName, $expected) { - $this->scopeConfig->expects($this->any()) - ->method('getValue') - ->with('paypal/general/merchant_country') - ->will($this->returnValue('US')); - $this->scopeConfig->expects($this->exactly(2)) - ->method('isSetFlag') - ->withAnyParameters() - ->will($this->returnValue(true)); + if ($methodName == Config::METHOD_WPP_BML) { + $valueMap = [ + ['paypal/general/merchant_country', ScopeConfigInterface::SCOPE_TYPE_DEFAULT, null, 'US'], + ['paypal/general/merchant_country', ScopeInterface::SCOPE_STORE, null, 'US'], + ['payment/paypal_express/disable_funding_options', ScopeConfigInterface::SCOPE_TYPE_DEFAULT, null, []], + ]; + $this->scopeConfig + ->method('getValue') + ->willReturnMap($valueMap); + $this->scopeConfig->expects($this->exactly(1)) + ->method('isSetFlag') + ->withAnyParameters() + ->willReturn(true); + } else { + $this->scopeConfig + ->method('getValue') + ->with('paypal/general/merchant_country') + ->willReturn('US'); + $this->scopeConfig->expects($this->exactly(2)) + ->method('isSetFlag') + ->withAnyParameters() + ->willReturn(true); + } $this->model->setMethod($methodName); $this->assertEquals($expected, $this->model->isMethodAvailable($methodName)); @@ -219,6 +235,34 @@ public function testGetSpecificConfigPathPayflowAdvancedLink() $this->assertEquals('Authorization', $this->model->getValue('payment_action')); } + /** + * @param string $name + * @param string $expectedValue + * @param string|null $expectedResult + * + * @dataProvider payPalStylesDataProvider + */ + public function testGetSpecificConfigPathPayPalStyles($name, $expectedValue, $expectedResult) + { + // _mapGenericStyleFieldset + $this->scopeConfig->method('getValue') + ->with('paypal/style/' . $name) + ->willReturn($expectedValue); + + $this->assertEquals($expectedResult, $this->model->getValue($name)); + } + + /** + * @return array + */ + public function payPalStylesDataProvider(): array + { + return [ + ['checkout_page_button_customize', 'value', 'value'], + ['test', 'value', null], + ]; + } + /** * @dataProvider skipOrderReviewStepDataProvider */ diff --git a/app/code/Magento/Paypal/Test/Unit/Model/ExpressConfigProviderTest.php b/app/code/Magento/Paypal/Test/Unit/Model/ExpressConfigProviderTest.php index 935b4484a8d..b316f92c0ce 100644 --- a/app/code/Magento/Paypal/Test/Unit/Model/ExpressConfigProviderTest.php +++ b/app/code/Magento/Paypal/Test/Unit/Model/ExpressConfigProviderTest.php @@ -5,7 +5,10 @@ */ namespace Magento\Paypal\Test\Unit\Model; +use Magento\Framework\UrlInterface; use Magento\Paypal\Model\ExpressConfigProvider; +use Magento\Paypal\Model\SmartButtonConfig; +use PHPUnit\Framework\MockObject\MockObject; class ExpressConfigProviderTest extends \PHPUnit\Framework\TestCase { @@ -40,16 +43,19 @@ public function testGetConfig() $payment->expects($this->atLeastOnce())->method('getCheckoutRedirectUrl')->willReturn('http://redirect.url'); $paymentHelper->expects($this->atLeastOnce())->method('getMethodInstance')->willReturn($payment); - /** @var \Magento\Framework\UrlInterface|\PHPUnit_Framework_MockObject_MockObject $urlBuilderMock */ + /** @var UrlInterface|MockObject $urlBuilderMock */ $urlBuilderMock = $this->createMock(\Magento\Framework\UrlInterface::class); + $smartButtonConfigMock = $this->createMock(SmartButtonConfig::class); + $configProvider = new ExpressConfigProvider( $configFactory, $localeResolver, $currentCustomer, $paypalHelper, $paymentHelper, - $urlBuilderMock + $urlBuilderMock, + $smartButtonConfigMock ); $configProvider->getConfig(); } diff --git a/app/code/Magento/Paypal/Test/Unit/Model/PayflowlinkTest.php b/app/code/Magento/Paypal/Test/Unit/Model/PayflowlinkTest.php index 80c8194e076..5ac436bcf0a 100644 --- a/app/code/Magento/Paypal/Test/Unit/Model/PayflowlinkTest.php +++ b/app/code/Magento/Paypal/Test/Unit/Model/PayflowlinkTest.php @@ -131,7 +131,7 @@ public function testInitialize() ->method('postRequest') ->willReturn($response); - $this->payflowRequest->expects($this->exactly(3)) + $this->payflowRequest->expects($this->exactly(4)) ->method('setData') ->willReturnMap( [ diff --git a/app/code/Magento/Paypal/Test/Unit/Model/PayflowproTest.php b/app/code/Magento/Paypal/Test/Unit/Model/PayflowproTest.php index 3e8af6b2ee7..7c352fc497a 100644 --- a/app/code/Magento/Paypal/Test/Unit/Model/PayflowproTest.php +++ b/app/code/Magento/Paypal/Test/Unit/Model/PayflowproTest.php @@ -580,6 +580,40 @@ public function testPostRequestException() $this->payflowpro->postRequest($request, $config); } + /** + * @covers \Magento\Paypal\Model\Payflowpro::addRequestOrderInfo + */ + public function testAddRequestOrderInfo() + { + $orderData = [ + 'id' => 1, + 'increment_id' => '0000001' + ]; + $data = [ + 'ponum' => $orderData['id'], + 'custref' => $orderData['increment_id'], + 'invnum' => $orderData['increment_id'], + 'comment1' => $orderData['increment_id'] + ]; + $expectedData = new DataObject($data); + $actualData = new DataObject(); + + $orderMock = $this->getMockBuilder(\Magento\Sales\Model\Order::class) + ->disableOriginalConstructor() + ->setMethods(['getIncrementId', 'getId']) + ->getMock(); + $orderMock->expects(static::once()) + ->method('getId') + ->willReturn($orderData['id']); + $orderMock->expects(static::atLeastOnce()) + ->method('getIncrementId') + ->willReturn($orderData['increment_id']); + + $this->payflowpro->addRequestOrderInfo($actualData, $orderMock); + + $this->assertEquals($expectedData, $actualData); + } + /** * @covers \Magento\Paypal\Model\Payflowpro::assignData */ diff --git a/app/code/Magento/Paypal/Test/Unit/Model/SmartButtonConfigTest.php b/app/code/Magento/Paypal/Test/Unit/Model/SmartButtonConfigTest.php new file mode 100644 index 00000000000..ed62efe36c4 --- /dev/null +++ b/app/code/Magento/Paypal/Test/Unit/Model/SmartButtonConfigTest.php @@ -0,0 +1,117 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Paypal\Test\Unit\Model; + +use Magento\Paypal\Model\SmartButtonConfig; +use Magento\Framework\Locale\ResolverInterface; +use Magento\Paypal\Model\ConfigFactory; + +class SmartButtonConfigTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\Paypal\Model\SmartButtonConfig + */ + private $model; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $localeResolverMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $configMock; + + protected function setUp() + { + $this->localeResolverMock = $this->getMockForAbstractClass(ResolverInterface::class); + $this->configMock = $this->getMockBuilder(\Magento\Paypal\Model\Config::class) + ->disableOriginalConstructor() + ->getMock(); + /** @var \PHPUnit_Framework_MockObject_MockObject $configFactoryMock */ + $configFactoryMock = $this->getMockBuilder(ConfigFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + $configFactoryMock->expects($this->once())->method('create')->willReturn($this->configMock); + $this->model = new SmartButtonConfig( + $this->localeResolverMock, + $configFactoryMock, + $this->getDefaultStyles(), + $this->getAllowedFundings() + ); + } + + /** + * @param string $page + * @param string $locale + * @param string $disallowedFundings + * @param string $layout + * @param string $size + * @param string $shape + * @param string $label + * @param string $color + * @param string $installmentPeriodLabel + * @param string $installmentPeriodLocale + * @param array $expected + * @dataProvider getConfigDataProvider + * @SuppressWarnings(PHPMD.ExcessiveParameterList) + */ + public function testGetConfig( + string $page, + string $locale, + bool $isCustomize, + ?string $disallowedFundings, + string $layout, + string $size, + string $shape, + string $label, + string $color, + string $installmentPeriodLabel, + string $installmentPeriodLocale, + array $expected = [] + ) { + $this->localeResolverMock->expects($this->any())->method('getLocale')->willReturn($locale); + $this->configMock->expects($this->any())->method('getValue')->will($this->returnValueMap([ + ['merchant_id', null, 'merchant'], + ['sandbox_flag', null, true], + ['disable_funding_options', null, $disallowedFundings], + ["{$page}_page_button_customize", null, $isCustomize], + ["{$page}_page_button_layout", null, $layout], + ["{$page}_page_button_size", null, $size], + ["{$page}_page_button_color", null, $color], + ["{$page}_page_button_shape", null, $shape], + ["{$page}_page_button_label", null, $label], + [$page . '_page_button_' . $installmentPeriodLocale . '_installment_period', null, $installmentPeriodLabel] + ])); + + self::assertEquals($expected, $this->model->getConfig($page)); + } + + public function getConfigDataProvider() + { + return include __DIR__ . '/_files/expected_config.php'; + } + + /** + * @return array + */ + private function getDefaultStyles() + { + return include __DIR__ . '/_files/default_styles.php'; + } + + /** + * @return array + */ + private function getAllowedFundings() + { + return include __DIR__ . '/_files/allowed_fundings.php'; + } +} diff --git a/app/code/Magento/Paypal/Test/Unit/Model/_files/allowed_fundings.php b/app/code/Magento/Paypal/Test/Unit/Model/_files/allowed_fundings.php new file mode 100644 index 00000000000..6b6f8ccb87e --- /dev/null +++ b/app/code/Magento/Paypal/Test/Unit/Model/_files/allowed_fundings.php @@ -0,0 +1,24 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +return [ + 'checkout' => [ + 'CREDIT', + 'ELV' + ], + 'cart' => [ + 'CREDIT', + 'ELV' + ], + 'mini_cart' => [ + 'CREDIT', + 'ELV' + ], + 'product' => [ + 'CREDIT' + ] +]; diff --git a/app/code/Magento/Paypal/Test/Unit/Model/_files/default_styles.php b/app/code/Magento/Paypal/Test/Unit/Model/_files/default_styles.php new file mode 100644 index 00000000000..87da99ed2e1 --- /dev/null +++ b/app/code/Magento/Paypal/Test/Unit/Model/_files/default_styles.php @@ -0,0 +1,37 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +return [ + 'checkout' => [ + 'layout' => 'vertical', + 'size' => 'responsive', + 'color' => 'gold', + 'shape' => 'rect', + 'label' =>'paypal' + ], + 'cart' => [ + 'layout' => 'vertical', + 'size' => 'responsive', + 'color' => 'gold', + 'shape' => 'rect', + 'label' =>'paypal' + ], + 'mini_cart' => [ + 'layout' => 'vertical', + 'size' => 'responsive', + 'color' => 'gold', + 'shape' => 'rect', + 'label' =>'paypal' + ], + 'product' => [ + 'layout' => 'horizontal', + 'size' => 'responsive', + 'color' => 'gold', + 'shape' => 'pill', + 'label' =>'buynow' + ] +]; diff --git a/app/code/Magento/Paypal/Test/Unit/Model/_files/expected_config.php b/app/code/Magento/Paypal/Test/Unit/Model/_files/expected_config.php new file mode 100644 index 00000000000..3a76d11e513 --- /dev/null +++ b/app/code/Magento/Paypal/Test/Unit/Model/_files/expected_config.php @@ -0,0 +1,146 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +return [ + 'cart' => [ + 'cart', + 'es_MX', + true, + 'CREDIT', + 'horizontal', + 'small', + 'pillow', + 'installment', + 'blue', + 'my_label', + 'mx', + [ + 'merchantId' => 'merchant', + 'environment' => 'sandbox', + 'locale' => 'es_MX', + 'allowedFunding' => ['ELV'], + 'disallowedFunding' => ['CREDIT'], + 'styles' => [ + 'layout' => 'horizontal', + 'size' => 'small', + 'color' => 'blue', + 'shape' => 'pillow', + 'label' => 'installment', + 'installmentperiod' => 0 + ] + ] + ], + 'checkout' => [ + 'cart', + 'en_BR', + true, + null, + 'horizontal', + 'small', + 'pillow', + 'installment', + 'blue', + 'my_label', + 'br', + [ + 'merchantId' => 'merchant', + 'environment' => 'sandbox', + 'locale' => 'en_BR', + 'allowedFunding' => ['CREDIT', 'ELV'], + 'disallowedFunding' => [], + 'styles' => [ + 'layout' => 'horizontal', + 'size' => 'small', + 'color' => 'blue', + 'shape' => 'pillow', + 'label' => 'installment', + 'installmentperiod' => 0 + ] + ] + ], + 'mini_cart' => [ + 'cart', + 'en', + false, + null, + 'horizontal', + 'small', + 'pillow', + 'installment', + 'blue', + 'my_label', + 'br', + [ + 'merchantId' => 'merchant', + 'environment' => 'sandbox', + 'locale' => 'en', + 'allowedFunding' => ['CREDIT', 'ELV'], + 'disallowedFunding' => [], + 'styles' => [ + 'layout' => 'vertical', + 'size' => 'responsive', + 'color' => 'gold', + 'shape' => 'rect', + 'label' => 'paypal' + ] + ] + ], + 'mini_cart' => [ + 'cart', + 'en', + false, + null, + 'horizontal', + 'small', + 'pillow', + 'installment', + 'blue', + 'my_label', + 'br', + [ + 'merchantId' => 'merchant', + 'environment' => 'sandbox', + 'locale' => 'en', + 'allowedFunding' => ['CREDIT', 'ELV'], + 'disallowedFunding' => [], + 'styles' => [ + 'layout' => 'vertical', + 'size' => 'responsive', + 'color' => 'gold', + 'shape' => 'rect', + 'label' => 'paypal' + ] + ] + ], + 'product' => [ + 'cart', + 'en', + false, + 'CREDIT', + 'horizontal', + 'small', + 'pillow', + 'installment', + 'blue', + 'my_label', + 'br', + [ + 'merchantId' => 'merchant', + 'environment' => 'sandbox', + 'locale' => 'en', + 'allowedFunding' => ['ELV'], + 'disallowedFunding' => ['CREDIT'], + 'styles' => [ + 'layout' => 'vertical', + 'size' => 'responsive', + 'color' => 'gold', + 'shape' => 'rect', + 'label' => 'paypal', + ] + ] + ] +]; diff --git a/app/code/Magento/Paypal/Test/Unit/Observer/AddPaypalShortcutsObserverTest.php b/app/code/Magento/Paypal/Test/Unit/Observer/AddPaypalShortcutsObserverTest.php index 7cb521073e3..542b327475d 100644 --- a/app/code/Magento/Paypal/Test/Unit/Observer/AddPaypalShortcutsObserverTest.php +++ b/app/code/Magento/Paypal/Test/Unit/Observer/AddPaypalShortcutsObserverTest.php @@ -9,9 +9,9 @@ use Magento\Catalog\Block\ShortcutInterface; use Magento\Framework\DataObject; use Magento\Framework\Event\Observer; -use Magento\Framework\View\Element\Template; use Magento\Framework\View\Layout; -use Magento\Paypal\Block\Express\InContext\Minicart\Button; +use Magento\Paypal\Block\Express\InContext\Minicart\SmartButton as MinicartButton; +use Magento\Paypal\Block\Express\InContext\SmartButton as Button; use Magento\Paypal\Helper\Shortcut\Factory; use Magento\Paypal\Model\Config; use Magento\Paypal\Observer\AddPaypalShortcutsObserver; @@ -119,7 +119,7 @@ public function testAddShortcutsButtons(array $blocks) ++$callIndexSession; } - $blockMock = $this->getMockBuilder(Button::class) + $blockMock = $this->getMockBuilder(MinicartButton::class) ->setMethods(['setIsInCatalogProduct', 'setShowOrPosition']) ->disableOriginalConstructor() ->getMockForAbstractClass(); @@ -159,7 +159,12 @@ public function dataProviderShortcutsButtons() return [ [ 'blocks1' => [ - \Magento\Paypal\Block\Express\InContext\Minicart\Button::class => [ + MinicartButton::class => [ + self::PAYMENT_CODE => Config::METHOD_WPS_EXPRESS, + self::PAYMENT_AVAILABLE => true, + self::PAYMENT_IS_BML => false, + ], + Button::class => [ self::PAYMENT_CODE => Config::METHOD_WPS_EXPRESS, self::PAYMENT_AVAILABLE => true, self::PAYMENT_IS_BML => false, @@ -198,11 +203,16 @@ public function dataProviderShortcutsButtons() ], [ 'blocks2' => [ - \Magento\Paypal\Block\Express\InContext\Minicart\Button::class => [ + MinicartButton::class => [ self::PAYMENT_CODE => Config::METHOD_WPS_EXPRESS, self::PAYMENT_AVAILABLE => false, self::PAYMENT_IS_BML => false, ], + Button::class => [ + self::PAYMENT_CODE => Config::METHOD_WPS_EXPRESS, + self::PAYMENT_AVAILABLE => true, + self::PAYMENT_IS_BML => false, + ], \Magento\Paypal\Block\Express\Shortcut::class => [ self::PAYMENT_CODE => Config::METHOD_WPP_EXPRESS, self::PAYMENT_AVAILABLE => false, diff --git a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_au.xml b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_au.xml index 99a6668c015..cbb95e376c9 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_au.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_au.xml @@ -132,6 +132,7 @@ <rule type="conflict" event=":load"> <argument name="wps_other">wps_other</argument> </rule> + <rule type="removeCreditOptionConditional" event=":load"/> </relation> </payment> </rules> diff --git a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_ca.xml b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_ca.xml index a8aac92fccd..51297a96438 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_ca.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_ca.xml @@ -207,6 +207,7 @@ <rule type="paypalExpressLockConfigurationConditional" event=":load"> <argument name="payflow_link_ca">payflow_link_ca</argument> </rule> + <rule type="removeCreditOptionConditional" event=":load"/> </relation> </payment> </rules> diff --git a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_de.xml b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_de.xml index fd570c9822f..46c61b52b75 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_de.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_de.xml @@ -26,6 +26,7 @@ <rule type="inContextActivate" event="activate-in-context-api"/> <rule type="inContextDeactivate" event="deactivate-in-context-api"/> <rule type="inContextDisableConditional" event=":load"/> + <rule type="removeCreditOptionConditional" event=":load"/> </relation> </payment> </rules> diff --git a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_es.xml b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_es.xml index fd2bcb26676..28cc075e0c6 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_es.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_es.xml @@ -95,6 +95,7 @@ <rule type="conflict" event=":load"> <argument name="wps_other">wps_other</argument> </rule> + <rule type="removeCreditOptionConditional" event=":load"/> </relation> </payment> </rules> diff --git a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_fr.xml b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_fr.xml index da0c1bd6353..7f1fcc08334 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_fr.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_fr.xml @@ -95,6 +95,7 @@ <rule type="conflict" event=":load"> <argument name="wps_other">wps_other</argument> </rule> + <rule type="removeCreditOptionConditional" event=":load"/> </relation> </payment> </rules> diff --git a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_gb.xml b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_gb.xml index a809817fe9b..56596251888 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_gb.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_gb.xml @@ -95,6 +95,7 @@ <rule type="conflict" event=":load"> <argument name="wps_express">wps_express</argument> </rule> + <rule type="removeCreditOptionConditional" event=":load"/> </relation> </payment> </rules> diff --git a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_hk.xml b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_hk.xml index 1c5dbaf22f9..50ce14e66ee 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_hk.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_hk.xml @@ -95,6 +95,7 @@ <rule type="conflict" event=":load"> <argument name="wps_other">wps_other</argument> </rule> + <rule type="removeCreditOptionConditional" event=":load"/> </relation> </payment> </rules> diff --git a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_it.xml b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_it.xml index 2fe4ad78d4b..de059dcc59c 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_it.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_it.xml @@ -95,6 +95,7 @@ <rule type="conflict" event=":load"> <argument name="wps_other">wps_other</argument> </rule> + <rule type="removeCreditOptionConditional" event=":load"/> </relation> </payment> </rules> diff --git a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_jp.xml b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_jp.xml index e6fe55aa904..d9fc7ef3f20 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_jp.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_jp.xml @@ -95,6 +95,7 @@ <rule type="conflict" event=":load"> <argument name="wps_other">wps_other</argument> </rule> + <rule type="removeCreditOptionConditional" event=":load"/> </relation> </payment> </rules> diff --git a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_nz.xml b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_nz.xml index 79309bcee70..c5b8b09c3a2 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_nz.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_nz.xml @@ -95,6 +95,7 @@ <rule type="conflict" event=":load"> <argument name="wps_other">wps_other</argument> </rule> + <rule type="removeCreditOptionConditional" event=":load"/> </relation> </payment> </rules> diff --git a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_other.xml b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_other.xml index a4118cc964f..972cc45505e 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_other.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_other.xml @@ -64,6 +64,7 @@ <rule type="conflict" event=":load"> <argument name="wps_other">wps_other</argument> </rule> + <rule type="removeCreditOptionConditional" event=":load"/> </relation> </payment> </rules> diff --git a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_us.xml b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_us.xml index 02cb608c07c..b7924e770aa 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_us.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_us.xml @@ -396,6 +396,10 @@ <event value="0" name="deactivate-in-context-api"/> <event value="1" name="activate-in-context-api"/> </events> + <events selector="[data-enable='disable-funding-options']"> + <event value="CREDIT" include="true" name="remove-option"/> + <event value="CREDIT" include="false" name="add-option"/> + </events> <relation target="wps_express"> <rule type="disable" event="activate-rule"/> </relation> @@ -433,6 +437,9 @@ <argument name="paypal_payflowpro_with_express_checkout">paypal_payflowpro_with_express_checkout</argument> <argument name="payflow_link_us">payflow_link_us</argument> </rule> + <rule type="removeCreditOption" event="remove-option"/> + <rule type="addCreditOption" event="add-option"/> + <rule type="removeCreditOptionConditional" event=":load"/> </relation> </payment> </rules> diff --git a/app/code/Magento/Paypal/etc/adminhtml/system.xml b/app/code/Magento/Paypal/etc/adminhtml/system.xml index 26ec9b3152e..ea48aa65132 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/system.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/system.xml @@ -106,12 +106,23 @@ <field id="enable_express_checkout"> <config_path>payment/wps_express/active</config_path> </field> - <field id="enable_express_checkout_bml"> + <field id="enable_express_checkout_bml" showInDefault="1" showInWebsite="1"> <config_path>payment/wps_express_bml/active</config_path> </field> + <field id="express_checkout_bml_sort_order" showInDefault="1" showInWebsite="1"/> </group> <group id="settings_ec" translate="label"> <label>Basic Settings - PayPal Website Payments Standard</label> + <group id="settings_ec_advanced"> + <group id="express_checkout_frontend"> + <field id="checkout_display" showInDefault="0" showInWebsite="0" showInStore="0"/> + <group id="checkout_page_button" showInDefault="0" showInWebsite="0" showInStore="0"/> + <group id="product_page_button" showInDefault="0" showInWebsite="0" showInStore="0"/> + <group id="cart_page_button" showInDefault="0" showInWebsite="0" showInStore="0"/> + <group id="mini_cart_page_button" showInDefault="0" showInWebsite="0" showInStore="0"/> + <group id="features" showInDefault="0" showInWebsite="0" showInStore="0"/> + </group> + </group> </group> </group> </group> diff --git a/app/code/Magento/Paypal/etc/adminhtml/system/express_checkout.xml b/app/code/Magento/Paypal/etc/adminhtml/system/express_checkout.xml index bff076aad9c..7abefbe1a67 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/system/express_checkout.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/system/express_checkout.xml @@ -158,7 +158,7 @@ </depends> <validate>required-entry</validate> </field> - <field id="enable_express_checkout_bml" translate="label comment" type="select" sortOrder="23" showInDefault="1" showInWebsite="1"> + <field id="enable_express_checkout_bml" translate="label comment" type="select" sortOrder="23" showInDefault="0" showInWebsite="0"> <label>Enable PayPal Credit</label> <comment><![CDATA[PayPal Express Checkout lets you give customers access to financing through PayPal Credit® - at no additional cost to you. You get paid up front, even though customers have more time to pay. A pre-integrated payment button lets customers pay quickly with PayPal Credit®. @@ -171,7 +171,7 @@ <field id="enable_express_checkout"/> </requires> </field> - <field id="express_checkout_bml_sort_order" translate="label" type="text" sortOrder="25" showInDefault="1" showInWebsite="1" showInStore="1"> + <field id="express_checkout_bml_sort_order" translate="label" type="text" sortOrder="25" showInDefault="0" showInWebsite="0" showInStore="0"> <label>Sort Order PayPal Credit</label> <config_path>payment/paypal_express_bml/sort_order</config_path> <frontend_class>validate-number</frontend_class> @@ -645,6 +645,262 @@ </tooltip> <attribute type="shared">1</attribute> </field> + <field id="checkout_display" translate="label" sortOrder="80" showInDefault="1" showInWebsite="1" showInStore="1"> + <label>Customize Smart Buttons</label> + <frontend_model>Magento\Config\Block\System\Config\Form\Field\Heading</frontend_model> + <attribute type="shared">1</attribute> + </field> + <group id="checkout_page_button" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="90"> + <label>Checkout Page</label> + <field id="checkout_page_button_customize" translate="label" type="select" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="10"> + <label>Customize Button</label> + <config_path>paypal/style/checkout_page_button_customize</config_path> + <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> + <attribute type="shared">1</attribute> + </field> + <field id="checkout_page_button_label" translate="label comment" type="select" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="20"> + <label>Label</label> + <comment><![CDATA[The installment feature is available only in these locales: en_MX, es_MX, en_BR, pt_BR.]]></comment> + <config_path>paypal/style/checkout_page_button_label</config_path> + <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Field\Depends\ButtonStylesLabel</frontend_model> + <source_model>Magento\Paypal\Model\System\Config\Source\ButtonStyles::getLabel</source_model> + <depends> + <field id="checkout_page_button_customize">1</field> + </depends> + <attribute type="shared">1</attribute> + </field> + <field id="checkout_page_button_mx_installment_period" translate="label" type="select" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="20"> + <label>Mexico Installment Period</label> + <config_path>paypal/style/checkout_page_button_mx_installment_period</config_path> + <source_model>Magento\Paypal\Model\System\Config\Source\ButtonStyles::getMxInstallmentPeriod</source_model> + <depends> + <field id="checkout_page_button_customize">1</field> + <field id="checkout_page_button_label">installment</field> + </depends> + <attribute type="shared">1</attribute> + </field> + <field id="checkout_page_button_br_installment_period" translate="label" type="select" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="20"> + <label>Brazil Installment Period</label> + <config_path>paypal/style/checkout_page_button_br_installment_period</config_path> + <source_model>Magento\Paypal\Model\System\Config\Source\ButtonStyles::getBrInstallmentPeriod</source_model> + <depends> + <field id="checkout_page_button_customize">1</field> + <field id="checkout_page_button_label">installment</field> + </depends> + <attribute type="shared">1</attribute> + </field> + <field id="checkout_page_button_layout" translate="label" type="select" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="30"> + <label>Layout</label> + <config_path>paypal/style/checkout_page_button_layout</config_path> + <source_model>Magento\Paypal\Model\System\Config\Source\ButtonStyles::getLayout</source_model> + <depends> + <field id="checkout_page_button_customize">1</field> + <field id="checkout_page_button_label" negative="1">credit</field> + </depends> + <attribute type="shared">1</attribute> + </field> + <field id="checkout_page_button_size" translate="label tooltip" type="select" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="40"> + <label>Size</label> + <config_path>paypal/style/checkout_page_button_size</config_path> + <source_model>Magento\Paypal\Model\System\Config\Source\ButtonStyles::getSize</source_model> + <tooltip>Select Responsive to ensure the PayPal button renders correctly on mobile devices.</tooltip> + <depends> + <field id="checkout_page_button_customize">1</field> + </depends> + <attribute type="shared">1</attribute> + </field> + <field id="checkout_page_button_shape" translate="label" type="select" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="50"> + <label>Shape</label> + <config_path>paypal/style/checkout_page_button_shape</config_path> + <source_model>Magento\Paypal\Model\System\Config\Source\ButtonStyles::getShape</source_model> + <depends> + <field id="checkout_page_button_customize">1</field> + </depends> + <attribute type="shared">1</attribute> + </field> + <field id="checkout_page_button_color" translate="label" type="select" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="60"> + <label>Color</label> + <config_path>paypal/style/checkout_page_button_color</config_path> + <source_model>Magento\Paypal\Model\System\Config\Source\ButtonStyles::getColor</source_model> + <depends> + <field id="checkout_page_button_customize">1</field> + <field id="checkout_page_button_label" negative="1">credit</field> + </depends> + <attribute type="shared">1</attribute> + </field> + </group> + <group id="product_page_button" translate="label comment" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="100"> + <label>Product Pages</label> + <field id="product_page_button_customize" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_customize"> + <config_path>paypal/style/product_page_button_customize</config_path> + </field> + <field id="product_page_button_label" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_label"> + <config_path>paypal/style/product_page_button_label</config_path> + <depends> + <field id="product_page_button_customize">1</field> + </depends> + </field> + <field id="product_page_button_mx_installment_period" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_mx_installment_period"> + <config_path>paypal/style/product_page_button_mx_installment_period</config_path> + <depends> + <field id="product_page_button_customize">1</field> + <field id="product_page_button_label">installment</field> + </depends> + </field> + <field id="product_page_button_br_installment_period" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_br_installment_period"> + <config_path>paypal/style/product_page_button_br_installment_period</config_path> + <depends> + <field id="product_page_button_customize">1</field> + <field id="product_page_button_label">installment</field> + </depends> + </field> + <field id="product_page_button_layout" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_layout"> + <config_path>paypal/style/product_page_button_layout</config_path> + <depends> + <field id="product_page_button_customize">1</field> + <field id="product_page_button_label" negative="1">credit</field> + </depends> + </field> + <field id="product_page_button_size" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_size"> + <config_path>paypal/style/product_page_button_size</config_path> + <depends> + <field id="product_page_button_customize">1</field> + </depends> + </field> + <field id="product_page_button_shape" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_shape"> + <config_path>paypal/style/product_page_button_shape</config_path> + <depends> + <field id="product_page_button_customize">1</field> + </depends> + </field> + <field id="product_page_button_color" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_color"> + <config_path>paypal/style/product_page_button_color</config_path> + <depends> + <field id="product_page_button_customize">1</field> + <field id="product_page_button_label" negative="1">credit</field> + </depends> + </field> + </group> + <group id="cart_page_button" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="110"> + <label>Cart Page</label> + <field id="cart_page_button_customize" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_customize"> + <config_path>paypal/style/cart_page_button_customize</config_path> + </field> + <field id="cart_page_button_label" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_label"> + <config_path>paypal/style/cart_page_button_label</config_path> + <depends> + <field id="cart_page_button_customize">1</field> + </depends> + </field> + <field id="cart_page_button_mx_installment_period" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_mx_installment_period"> + <config_path>paypal/style/cart_page_button_mx_installment_period</config_path> + <depends> + <field id="cart_page_button_customize">1</field> + <field id="cart_page_button_label">installment</field> + </depends> + </field> + <field id="cart_page_button_br_installment_period" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_br_installment_period"> + <config_path>paypal/style/cart_page_button_br_installment_period</config_path> + <depends> + <field id="cart_page_button_customize">1</field> + <field id="cart_page_button_label">installment</field> + </depends> + </field> + <field id="cart_page_button_layout" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_layout"> + <config_path>paypal/style/cart_page_button_layout</config_path> + <depends> + <field id="cart_page_button_customize">1</field> + <field id="cart_page_button_label" negative="1">credit</field> + </depends> + </field> + <field id="cart_page_button_size" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_size"> + <config_path>paypal/style/cart_page_button_size</config_path> + <depends> + <field id="cart_page_button_customize">1</field> + </depends> + </field> + <field id="cart_page_button_shape" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_shape"> + <config_path>paypal/style/cart_page_button_shape</config_path> + <depends> + <field id="cart_page_button_customize">1</field> + </depends> + </field> + <field id="cart_page_button_color" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_color"> + <config_path>paypal/style/cart_page_button_color</config_path> + <depends> + <field id="cart_page_button_customize">1</field> + <field id="cart_page_button_label" negative="1">credit</field> + </depends> + </field> + </group> + <group id="mini_cart_page_button" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="120"> + <label>Mini Cart</label> + <field id="mini_cart_page_button_customize" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_customize"> + <config_path>paypal/style/mini_cart_page_button_customize</config_path> + </field> + <field id="mini_cart_page_button_label" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_label"> + <config_path>paypal/style/mini_cart_page_button_label</config_path> + <depends> + <field id="mini_cart_page_button_customize">1</field> + </depends> + </field> + <field id="mini_cart_page_button_mx_installment_period" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_mx_installment_period"> + <config_path>paypal/style/mini_cart_page_button_mx_installment_period</config_path> + <depends> + <field id="mini_cart_page_button_customize">1</field> + <field id="mini_cart_page_button_label">installment</field> + </depends> + </field> + <field id="mini_cart_page_button_br_installment_period" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_br_installment_period"> + <config_path>paypal/style/mini_cart_page_button_br_installment_period</config_path> + <depends> + <field id="mini_cart_page_button_customize">1</field> + <field id="mini_cart_page_button_label">installment</field> + </depends> + </field> + <field id="mini_cart_page_button_layout" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_layout"> + <config_path>paypal/style/mini_cart_page_button_layout</config_path> + <depends> + <field id="mini_cart_page_button_customize">1</field> + <field id="mini_cart_page_button_label" negative="1">credit</field> + </depends> + </field> + <field id="mini_cart_page_button_size" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_size"> + <config_path>paypal/style/mini_cart_page_button_size</config_path> + <depends> + <field id="mini_cart_page_button_customize">1</field> + </depends> + </field> + <field id="mini_cart_page_button_shape" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_shape"> + <config_path>paypal/style/mini_cart_page_button_shape</config_path> + <depends> + <field id="mini_cart_page_button_customize">1</field> + </depends> + </field> + <field id="mini_cart_page_button_color" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_color"> + <config_path>paypal/style/mini_cart_page_button_color</config_path> + <depends> + <field id="mini_cart_page_button_customize">1</field> + <field id="mini_cart_page_button_label" negative="1">credit</field> + </depends> + </field> + </group> + <group id="features" translate="label comment" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="130"> + <label>Features</label> + <field id="disable_funding_options" translate="label comment" type="multiselect" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1"> + <label>Disable Funding Options</label> + <comment> + <![CDATA[PayPal will automatically display each enabled funding option to eligible buyers. + For example, PayPal Credit is only shown to buyers in countries where PayPal Credit is + offered and the currency offered by the merchant is USD.]]> + </comment> + <config_path>paypal/style/disable_funding_options</config_path> + <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\MultiSelect\DisabledFundingOptions</frontend_model> + <source_model>Magento\Paypal\Model\System\Config\Source\DisableFundingOptions</source_model> + <attribute type="shared">1</attribute> + <can_be_empty>1</can_be_empty> + </field> + </group> </group> </group> </group> diff --git a/app/code/Magento/Paypal/etc/adminhtml/system/payflow_advanced.xml b/app/code/Magento/Paypal/etc/adminhtml/system/payflow_advanced.xml index 5eb596c9c4f..e7de9c0d641 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/system/payflow_advanced.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/system/payflow_advanced.xml @@ -97,7 +97,7 @@ <field id="enable_payflow_advanced"/> </requires> </field> - <field id="enable_express_checkout_bml" sortOrder="42" extends="payment_all_paypal/express_checkout/express_checkout_required/enable_express_checkout_bml"> + <field id="enable_express_checkout_bml" sortOrder="42" extends="payment_all_paypal/express_checkout/express_checkout_required/enable_express_checkout_bml" showInDefault="1" showInWebsite="1"> <comment><![CDATA[PayPal Express Checkout Payflow Edition lets you give customers access to financing through PayPal Credit® - at no additional cost to you. You get paid up front, even though customers have more time to pay. A pre-integrated payment button lets customers pay quickly with PayPal Credit®. <a href="https://www.paypal.com/webapps/mpp/promotional-financing" target="_blank">Learn More</a>]]> @@ -108,7 +108,7 @@ <field id="enable_payflow_advanced"/> </requires> </field> - <field id="express_checkout_bml_sort_order" sortOrder="50" extends="payment_all_paypal/express_checkout/express_checkout_required/express_checkout_bml_sort_order"> + <field id="express_checkout_bml_sort_order" sortOrder="50" extends="payment_all_paypal/express_checkout/express_checkout_required/express_checkout_bml_sort_order" showInDefault="1" showInWebsite="1"> <config_path>payment/payflow_express_bml/sort_order</config_path> <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Field\Depends\BmlSortOrder</frontend_model> <depends> diff --git a/app/code/Magento/Paypal/etc/adminhtml/system/payflow_link.xml b/app/code/Magento/Paypal/etc/adminhtml/system/payflow_link.xml index d27dde02c57..647bc7a6097 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/system/payflow_link.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/system/payflow_link.xml @@ -106,7 +106,7 @@ <field id="enable_payflow_link"/> </requires> </field> - <field id="enable_express_checkout_bml" extends="payment_all_paypal/express_checkout/express_checkout_required/enable_express_checkout_bml" sortOrder="41"> + <field id="enable_express_checkout_bml" extends="payment_all_paypal/express_checkout/express_checkout_required/enable_express_checkout_bml" sortOrder="41" showInDefault="1" showInWebsite="1"> <comment><![CDATA[Payflow Link lets you give customers access to financing through PayPal Credit® - at no additional cost to you. You get paid up front, even though customers have more time to pay. A pre-integrated payment button lets customers pay quickly with PayPal Credit®. <a href="https://www.paypal.com/webapps/mpp/promotional-financing" target="_blank">Learn More</a>]]> @@ -117,7 +117,7 @@ <field id="enable_express_checkout"/> </requires> </field> - <field id="express_checkout_bml_sort_order" sortOrder="50" extends="payment_all_paypal/express_checkout/express_checkout_required/express_checkout_bml_sort_order"> + <field id="express_checkout_bml_sort_order" sortOrder="50" extends="payment_all_paypal/express_checkout/express_checkout_required/express_checkout_bml_sort_order" showInDefault="1" showInWebsite="1"> <config_path>payment/payflow_express_bml/sort_order</config_path> <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Field\Depends\BmlSortOrder</frontend_model> <depends> diff --git a/app/code/Magento/Paypal/etc/adminhtml/system/payments_pro_hosted_solution.xml b/app/code/Magento/Paypal/etc/adminhtml/system/payments_pro_hosted_solution.xml index 77acff48c24..35cd8442048 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/system/payments_pro_hosted_solution.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/system/payments_pro_hosted_solution.xml @@ -46,7 +46,7 @@ <frontend_class>paypal-enabler paypal-ec-separate</frontend_class> </field> - <field id="enable_express_checkout_bml" extends="payment_all_paypal/express_checkout/express_checkout_required/enable_express_checkout_bml" sortOrder="21"> + <field id="enable_express_checkout_bml" extends="payment_all_paypal/express_checkout/express_checkout_required/enable_express_checkout_bml" sortOrder="21" showInDefault="1" showInWebsite="1"> <comment><![CDATA[Payments Pro Hosted Solution lets you give customers access to financing through PayPal Credit® - at no additional cost to you. You get paid up front, even though customers have more time to pay. A pre-integrated payment button lets customers pay quickly with PayPal Credit®. <a href="https://www.paypal.com/webapps/mpp/promotional-financing" target="_blank">Learn More</a>]]> diff --git a/app/code/Magento/Paypal/etc/adminhtml/system/paypal_payflowpro_with_express_checkout.xml b/app/code/Magento/Paypal/etc/adminhtml/system/paypal_payflowpro_with_express_checkout.xml index 6090025024d..425e4cffb66 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/system/paypal_payflowpro_with_express_checkout.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/system/paypal_payflowpro_with_express_checkout.xml @@ -43,7 +43,7 @@ <field id="enable_paypal_payflow"/> </requires> </field> - <field id="express_checkout_bml_sort_order" sortOrder="30" extends="payment_all_paypal/express_checkout/express_checkout_required/express_checkout_bml_sort_order"> + <field id="express_checkout_bml_sort_order" sortOrder="30" extends="payment_all_paypal/express_checkout/express_checkout_required/express_checkout_bml_sort_order" showInDefault="1" showInWebsite="1"> <config_path>payment/payflow_express_bml/sort_order</config_path> <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Field\Depends\BmlSortOrder</frontend_model> <depends> diff --git a/app/code/Magento/Paypal/etc/config.xml b/app/code/Magento/Paypal/etc/config.xml index f0df648af90..1880417af1b 100644 --- a/app/code/Magento/Paypal/etc/config.xml +++ b/app/code/Magento/Paypal/etc/config.xml @@ -10,6 +10,30 @@ <paypal> <style> <logo></logo> + <checkout_page_button_customize>0</checkout_page_button_customize> + <checkout_page_button_label>paypal</checkout_page_button_label> + <checkout_page_button_layout>vertical</checkout_page_button_layout> + <checkout_page_button_size>responsive</checkout_page_button_size> + <checkout_page_button_shape>rect</checkout_page_button_shape> + <checkout_page_button_color>gold</checkout_page_button_color> + <product_page_button_customize>0</product_page_button_customize> + <product_page_button_label>buynow</product_page_button_label> + <product_page_button_layout>horizontal</product_page_button_layout> + <product_page_button_size>responsive</product_page_button_size> + <product_page_button_shape>pill</product_page_button_shape> + <product_page_button_color>gold</product_page_button_color> + <cart_page_button_customize>0</cart_page_button_customize> + <cart_page_button_label>paypal</cart_page_button_label> + <cart_page_button_layout>vertical</cart_page_button_layout> + <cart_page_button_size>responsive</cart_page_button_size> + <cart_page_button_shape>rect</cart_page_button_shape> + <cart_page_button_color>gold</cart_page_button_color> + <mini_cart_page_button_customize>0</mini_cart_page_button_customize> + <mini_cart_page_button_label>paypal</mini_cart_page_button_label> + <mini_cart_page_button_layout>vertical</mini_cart_page_button_layout> + <mini_cart_page_button_size>responsive</mini_cart_page_button_size> + <mini_cart_page_button_shape>rect</mini_cart_page_button_shape> + <mini_cart_page_button_color>gold</mini_cart_page_button_color> </style> <wpp> <api_password backend_model="Magento\Config\Model\Config\Backend\Encrypted" /> @@ -100,6 +124,7 @@ <instant_purchase> <tokenFormat>\Magento\Paypal\Model\InstantPurchase\Payflow\Pro\TokenFormatter</tokenFormat> </instant_purchase> + <group>paypal</group> </payflowpro_cc_vault> <paypal_billing_agreement> <active>1</active> diff --git a/app/code/Magento/Paypal/etc/frontend/di.xml b/app/code/Magento/Paypal/etc/frontend/di.xml index 407c251fc42..8c29ae1e268 100644 --- a/app/code/Magento/Paypal/etc/frontend/di.xml +++ b/app/code/Magento/Paypal/etc/frontend/di.xml @@ -86,7 +86,7 @@ </argument> </arguments> </type> - <type name="Magento\Paypal\Block\Express\InContext\Minicart\Button"> + <type name="Magento\Paypal\Block\Express\InContext\Minicart\SmartButton"> <arguments> <argument name="data" xsi:type="array"> <item name="template" xsi:type="string">Magento_Paypal::express/in-context/shortcut/button.phtml</item> @@ -97,6 +97,14 @@ <argument name="payment" xsi:type="object">Magento\Paypal\Model\Express</argument> </arguments> </type> + <type name="Magento\Paypal\Block\Express\InContext\SmartButton"> + <arguments> + <argument name="data" xsi:type="array"> + <item name="alias" xsi:type="string">product.info.addtocart.paypalexpress</item> + <item name="template" xsi:type="string">express/shortcut_button.phtml</item> + </argument> + </arguments> + </type> <type name="Magento\Vault\Model\Ui\TokensConfigProvider"> @@ -116,4 +124,56 @@ <type name="Magento\Quote\Model\QuoteRepository\SaveHandler"> <plugin name="paypal-cartitem" type="Magento\Paypal\Model\Express\QuotePlugin"/> </type> + + <type name="Magento\Paypal\Model\SmartButtonConfig"> + <arguments> + <argument name="defaultStyles" xsi:type="array"> + <item name="checkout" xsi:type="array"> + <item name="layout" xsi:type="string">vertical</item> + <item name="size" xsi:type="string">responsive</item> + <item name="color" xsi:type="string">gold</item> + <item name="shape" xsi:type="string">rect</item> + <item name="label" xsi:type="string">paypal</item> + </item> + <item name="cart" xsi:type="array"> + <item name="layout" xsi:type="string">vertical</item> + <item name="size" xsi:type="string">responsive</item> + <item name="color" xsi:type="string">gold</item> + <item name="shape" xsi:type="string">rect</item> + <item name="label" xsi:type="string">paypal</item> + </item> + <item name="mini_cart" xsi:type="array"> + <item name="layout" xsi:type="string">vertical</item> + <item name="size" xsi:type="string">responsive</item> + <item name="color" xsi:type="string">gold</item> + <item name="shape" xsi:type="string">rect</item> + <item name="label" xsi:type="string">paypal</item> + </item> + <item name="product" xsi:type="array"> + <item name="layout" xsi:type="string">horizontal</item> + <item name="size" xsi:type="string">responsive</item> + <item name="color" xsi:type="string">gold</item> + <item name="shape" xsi:type="string">pill</item> + <item name="label" xsi:type="string">buynow</item> + </item> + </argument> + <argument name="allowedFunding" xsi:type="array"> + <item name="checkout" xsi:type="array"> + <item name="0" xsi:type="string">CREDIT</item> + <item name="1" xsi:type="string">ELV</item> + </item> + <item name="cart" xsi:type="array"> + <item name="0" xsi:type="string">CREDIT</item> + <item name="1" xsi:type="string">ELV</item> + </item> + <item name="mini_cart" xsi:type="array"> + <item name="0" xsi:type="string">CREDIT</item> + <item name="1" xsi:type="string">ELV</item> + </item> + <item name="product" xsi:type="array"> + <item name="0" xsi:type="string">CREDIT</item> + </item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Paypal/etc/frontend/sections.xml b/app/code/Magento/Paypal/etc/frontend/sections.xml index 466b930fb9b..ca32d57cd9d 100644 --- a/app/code/Magento/Paypal/etc/frontend/sections.xml +++ b/app/code/Magento/Paypal/etc/frontend/sections.xml @@ -15,4 +15,8 @@ <section name="cart"/> <section name="checkout-data"/> </action> + <action name="paypal/express/onAuthorization"> + <section name="cart"/> + <section name="checkout-data"/> + </action> </config> diff --git a/app/code/Magento/Paypal/etc/rules.xsd b/app/code/Magento/Paypal/etc/rules.xsd index 9a274a2dbd1..c4385396cc0 100644 --- a/app/code/Magento/Paypal/etc/rules.xsd +++ b/app/code/Magento/Paypal/etc/rules.xsd @@ -31,6 +31,7 @@ </xs:sequence> <xs:attribute type="xs:string" name="value" use="required"/> <xs:attribute type="xs:string" name="name" use="required"/> + <xs:attribute type="xs:boolean" name="include" use="optional"/> </xs:complexType> <xs:complexType name="predicate"> <xs:sequence minOccurs="0" maxOccurs="unbounded"> diff --git a/app/code/Magento/Paypal/i18n/en_US.csv b/app/code/Magento/Paypal/i18n/en_US.csv index c3f3e38fa03..ad07d642de1 100644 --- a/app/code/Magento/Paypal/i18n/en_US.csv +++ b/app/code/Magento/Paypal/i18n/en_US.csv @@ -697,3 +697,38 @@ User,User The PayPal Advertising Program has been shown to generate additional purchases as well as increase consumer's average purchase sizes by 15% or more. <a href=""https://financing.paypal.com/ppfinportal/content/forrester"" target=""_blank"">See Details</a>. " +"Customize Smart Buttons","Customize Smart Buttons" +"Checkout Page","Checkout Page" +"Label","Label" +"The installment feature is available only in these locales: en_MX, es_MX, en_BR, pt_BR.","The installment feature is available only in these locales: en_MX, es_MX, en_BR, pt_BR." +"Checkout","Checkout" +"Credit","Credit" +"Pay","Pay" +"Buy Now","Buy Now" +"PayPal","PayPal" +"Installment","Installment" +"Mexico Installment Period","Mexico Installment Period" +"Brazil Installment Period","Brazil Installment Period" +"Layout","Layout" +"Vertical","Vertical" +"Horizontal","Horizontal" +"Size","Size" +"Medium","Medium" +"Large","Large" +"Responsive","Responsive" +"Shape","Shape" +"Pill","Pill" +"Rectangle","Rectangle" +"Color","Color" +"Gold","Gold" +"Blue","Blue" +"Silver","Silver" +"Black","Black" +"Product Pages","Product Pages" +"Cart Page","Cart Page" +"Mini Cart","Mini Cart" +"Features","Features" +"PayPal will automatically display each enabled funding option to eligible buyers. For example, PayPal Credit is only shown to buyers in countries where PayPal Credit is offered and the currency offered by the merchant is USD.","PayPal will automatically display each enabled funding option to eligible buyers. For example, PayPal Credit is only shown to buyers in countries where PayPal Credit is offered and the currency offered by the merchant is USD." +"PayPal Credit","PayPal Credit" +"PayPal Guest Checkout Credit Card Icons","PayPal Guest Checkout Credit Card Icons" +"Elektronisches Lastschriftverfahren - German ELV","Elektronisches Lastschriftverfahren - German ELV" \ No newline at end of file diff --git a/app/code/Magento/Paypal/view/adminhtml/web/js/rules.js b/app/code/Magento/Paypal/view/adminhtml/web/js/rules.js index 4a5248cb875..555d2a80a86 100644 --- a/app/code/Magento/Paypal/view/adminhtml/web/js/rules.js +++ b/app/code/Magento/Paypal/view/adminhtml/web/js/rules.js @@ -585,6 +585,41 @@ define([ 'Please re-enable the previously enabled payment solutions.' }); } + }, + + /** + * @param {*} $target + * @param {*} $owner + * @param {Object} data + */ + removeCreditOption: function ($target, $owner, data) { + if ($target.find(data.dependsButtonLabel + ' option[value="credit"]').length > 0) { + $target.find(data.dependsButtonLabel + ' option[value="credit"]').remove(); + } + }, + + /** + * @param {*} $target + * @param {*} $owner + * @param {Object} data + */ + addCreditOption: function ($target, $owner, data) { + if ($target.find(data.dependsButtonLabel + ' option[value="credit"]').length === 0) { + $target.find(data.dependsButtonLabel).append('<option value="credit">Credit</option>'); + } + }, + + /** + * @param {*} $target + * @param {*} $owner + * @param {Object} data + */ + removeCreditOptionConditional: function ($target, $owner, data) { + if ($target.find(data.dependsDisableFundingOptions + ' option[value="CREDIT"]').length === 0 || + $target.find(data.dependsDisableFundingOptions + ' option[value="CREDIT"]:selected').length > 0 + ) { + this.removeCreditOption($target, $owner, data); + } } }); }); diff --git a/app/code/Magento/Paypal/view/adminhtml/web/js/solution.js b/app/code/Magento/Paypal/view/adminhtml/web/js/solution.js index 3d832db09aa..3e4a1ab0ccc 100644 --- a/app/code/Magento/Paypal/view/adminhtml/web/js/solution.js +++ b/app/code/Magento/Paypal/view/adminhtml/web/js/solution.js @@ -65,6 +65,18 @@ define([ */ dependsBmlApiSortOrder: '[data-enable="bml-api-sort-order"]', + /** + * An attribute of the element responsible for the visibility of the + * button Label credit option (data attribute) + */ + dependsButtonLabel: '[data-enable="button-label"]', + + /** + * An attribute of the element responsible for the visibility of the + * button Label credit option on load (data attribute) + */ + dependsDisableFundingOptions: '[data-enable="disable-funding-options"]', + /** * Templates element selectors */ @@ -119,7 +131,9 @@ define([ } }; - if (solution.getValue($(this)) === elementEvent.value) { + if (solution.getValue($(this)) === elementEvent.value || + $(this).prop('multiple') && solution.checkMultiselectValue($(this), elementEvent) + ) { if (predicate.name) { require([ 'Magento_Paypal/js/predicate/' + predicate.name @@ -147,6 +161,23 @@ define([ return $element.val(); }, + /** + * Check multiselect value based on include value + * + * @param {Object} $element + * @param {Object} elementEvent + * @returns {Boolean} + */ + checkMultiselectValue: function ($element, elementEvent) { + var isValueSelected = $.inArray(elementEvent.value, $element.val()) >= 0; + + if (elementEvent.include) { + isValueSelected = (isValueSelected ? 'true' : 'false') === elementEvent.include; + } + + return isValueSelected; + }, + /** * Adding event listeners * @@ -175,6 +206,8 @@ define([ dependsMerchantId: this.dependsMerchantId, dependsBmlSortOrder: this.dependsBmlSortOrder, dependsBmlApiSortOrder: this.dependsBmlApiSortOrder, + dependsButtonLabel: this.dependsButtonLabel, + dependsDisableFundingOptions: this.dependsDisableFundingOptions, solutionsElements: this.solutionsElements, argument: instance.argument } diff --git a/app/code/Magento/Paypal/view/frontend/layout/checkout_index_index.xml b/app/code/Magento/Paypal/view/frontend/layout/checkout_index_index.xml index 73c44faff5a..ebf38dd2d99 100644 --- a/app/code/Magento/Paypal/view/frontend/layout/checkout_index_index.xml +++ b/app/code/Magento/Paypal/view/frontend/layout/checkout_index_index.xml @@ -47,9 +47,6 @@ <item name="paypal_express" xsi:type="array"> <item name="isBillingAddressRequired" xsi:type="boolean">false</item> </item> - <item name="paypal_express_bml" xsi:type="array"> - <item name="isBillingAddressRequired" xsi:type="boolean">false</item> - </item> <item name="payflow_express_bml" xsi:type="array"> <item name="isBillingAddressRequired" xsi:type="boolean">false</item> </item> diff --git a/app/code/Magento/Paypal/view/frontend/templates/express/in-context/shortcut/button.phtml b/app/code/Magento/Paypal/view/frontend/templates/express/in-context/shortcut/button.phtml index 3725f51f0b8..66dddfb0bda 100644 --- a/app/code/Magento/Paypal/view/frontend/templates/express/in-context/shortcut/button.phtml +++ b/app/code/Magento/Paypal/view/frontend/templates/express/in-context/shortcut/button.phtml @@ -4,26 +4,10 @@ * See COPYING.txt for license details. */ -use Magento\Paypal\Block\Express\InContext\Minicart\Button; - /** - * @var \Magento\Paypal\Block\Express\InContext\Minicart\Button $block + * @var \Magento\Paypal\Block\Express\InContext\Minicart\SmartButton $block */ -$config = [ - 'Magento_Paypal/js/in-context/button' => [ - 'id' => $block->escapeHtml($block->getContainerId()), - 'linkDataAction' => $block->escapeHtml($block->getLinkAction()), - 'paypalButton' => $block->escapeHtml(Button::PAYPAL_BUTTON_ID), - 'addToCartSelector' => $block->escapeHtml($block->getAddToCartSelector()) - ] -]; - ?> -<div data-mage-init='<?= /* @noEscape */ json_encode($config) ?>' +<div data-mage-init='<?= /* @noEscape */ $block->getJsInitParams() ?>' class="paypal checkout paypal-logo <?= $block->escapeHtml($block->getContainerId()) ?>-container"> - <a data-action="<?= $block->escapeHtml($block->getLinkAction()) ?>" href="#"> - <img class="paypal-button-hidden" - src="<?= $block->escapeHtml($block->getImageUrl()) ?>" - alt="Check out with PayPal" /> - </a> -</div> +</div> \ No newline at end of file diff --git a/app/code/Magento/Paypal/view/frontend/templates/express/shortcut_button.phtml b/app/code/Magento/Paypal/view/frontend/templates/express/shortcut_button.phtml new file mode 100644 index 00000000000..ac0eda99ee9 --- /dev/null +++ b/app/code/Magento/Paypal/view/frontend/templates/express/shortcut_button.phtml @@ -0,0 +1,13 @@ + +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +// @codingStandardsIgnoreFile +/** + * @var \Magento\Paypal\Block\Express\Shortcut $block + */ +?> +<div data-mage-init='<?= /* @noEscape */ $block->getJsInitParams() ?>'></div> \ No newline at end of file diff --git a/app/code/Magento/Paypal/view/frontend/web/js/in-context/button.js b/app/code/Magento/Paypal/view/frontend/web/js/in-context/button.js index 8b4855cff68..012a1f18f9a 100644 --- a/app/code/Magento/Paypal/view/frontend/web/js/in-context/button.js +++ b/app/code/Magento/Paypal/view/frontend/web/js/in-context/button.js @@ -2,50 +2,52 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -define( - [ - 'uiComponent', - 'jquery', - 'domReady!' - ], - function ( - Component, - $ - ) { - 'use strict'; - - return Component.extend({ - - defaults: {}, - - /** - * @returns {Object} - */ - initialize: function () { - this._super(); - - return this.initEvents(); - }, - - /** - * @returns {Object} - */ - initEvents: function () { - $('a[data-action="' + this.linkDataAction + '"]').off('click.' + this.id) - .on('click.' + this.id, this.click.bind(this)); - - return this; - }, - - /** - * @param {Object} event - * @returns void - */ - click: function (event) { - event.preventDefault(); - - $('#' + this.paypalButton).click(); +define([ + 'uiComponent', + 'jquery', + 'Magento_Paypal/js/in-context/express-checkout-wrapper', + 'Magento_Customer/js/customer-data' +], function (Component, $, Wrapper, customerData) { + 'use strict'; + + return Component.extend(Wrapper).extend({ + defaults: { + declinePayment: false + }, + + /** @inheritdoc */ + initialize: function (config, element) { + var cart = customerData.get('cart'), + customer = customerData.get('customer'); + + this._super(); + this.renderPayPalButtons(element); + this.declinePayment = !customer().firstname && !cart().isGuestCheckoutAllowed; + + return this; + }, + + /** @inheritdoc */ + beforePayment: function (resolve, reject) { + var promise = $.Deferred(); + + if (this.declinePayment) { + this.addError(this.signInMessage, 'warning'); + + reject(); + } else { + promise.resolve(); } - }); - } -); + + return promise; + }, + + /** @inheritdoc */ + prepareClientConfig: function () { + this._super(); + this.clientConfig.commit = false; + + return this.clientConfig; + } + }); +}); diff --git a/app/code/Magento/Paypal/view/frontend/web/js/in-context/express-checkout-smart-buttons.js b/app/code/Magento/Paypal/view/frontend/web/js/in-context/express-checkout-smart-buttons.js new file mode 100644 index 00000000000..ad7e86f2e99 --- /dev/null +++ b/app/code/Magento/Paypal/view/frontend/web/js/in-context/express-checkout-smart-buttons.js @@ -0,0 +1,123 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +define([ + 'underscore', + 'paypalInContextExpressCheckout' +], function (_, paypal) { + 'use strict'; + + /** + * Returns array of allowed funding + * + * @param {Object} config + * @return {Array} + */ + function getFunding(config) { + return _.map(config, function (name) { + return paypal.FUNDING[name]; + }); + } + + return function (clientConfig, element) { + paypal.Button.render({ + env: clientConfig.environment, + client: clientConfig.client, + locale: clientConfig.locale, + funding: { + allowed: getFunding(clientConfig.allowedFunding), + disallowed: getFunding(clientConfig.disallowedFunding) + }, + style: clientConfig.styles, + + // Enable Pay Now checkout flow (optional) + commit: clientConfig.commit, + + /** + * Validate payment method + * + * @param {Object} actions + */ + validate: function (actions) { + clientConfig.rendererComponent.validate(actions); + }, + + /** + * Execute logic on Paypal button click + */ + onClick: function () { + clientConfig.rendererComponent.onClick(); + }, + + /** + * Set up a payment + * + * @return {*} + */ + payment: function () { + var params = { + 'quote_id': clientConfig.quoteId, + 'customer_id': clientConfig.customerId || '', + 'form_key': clientConfig.formKey, + button: clientConfig.button + }; + + return new paypal.Promise(function (resolve, reject) { + clientConfig.rendererComponent.beforePayment(resolve, reject).then(function () { + paypal.request.post(clientConfig.getTokenUrl, params).then(function (res) { + return clientConfig.rendererComponent.afterPayment(res, resolve, reject); + }).catch(function (err) { + return clientConfig.rendererComponent.catchPayment(err, resolve, reject); + }); + }); + }); + }, + + /** + * Execute the payment + * + * @param {Object} data + * @param {Object} actions + * @return {*} + */ + onAuthorize: function (data, actions) { + var params = { + paymentToken: data.paymentToken, + payerId: data.payerID, + quoteId: clientConfig.quoteId || '', + customerId: clientConfig.customerId || '', + 'form_key': clientConfig.formKey + }; + + return new paypal.Promise(function (resolve, reject) { + clientConfig.rendererComponent.beforeOnAuthorize(resolve, reject, actions).then(function () { + paypal.request.post(clientConfig.onAuthorizeUrl, params).then(function (res) { + clientConfig.rendererComponent.afterOnAuthorize(res, resolve, reject, actions); + }).catch(function (err) { + return clientConfig.rendererComponent.catchOnAuthorize(err, resolve, reject); + }); + }); + }); + + }, + + /** + * Process cancel action + * + * @param {Object} data + * @param {Object} actions + */ + onCancel: function (data, actions) { + clientConfig.rendererComponent.onCancel(data, actions); + }, + + /** + * Process errors + */ + onError: function (err) { + clientConfig.rendererComponent.onError(err); + } + }, element); + }; +}); diff --git a/app/code/Magento/Paypal/view/frontend/web/js/in-context/express-checkout-wrapper.js b/app/code/Magento/Paypal/view/frontend/web/js/in-context/express-checkout-wrapper.js new file mode 100644 index 00000000000..905f860fe26 --- /dev/null +++ b/app/code/Magento/Paypal/view/frontend/web/js/in-context/express-checkout-wrapper.js @@ -0,0 +1,187 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +define([ + 'jquery', + 'mage/translate', + 'Magento_Customer/js/customer-data', + 'Magento_Paypal/js/in-context/express-checkout-smart-buttons', + 'mage/cookies' +], function ($, $t, customerData, checkoutSmartButtons) { + 'use strict'; + + return { + defaults: { + paymentActionError: $t('Something went wrong with your request. Please try again later.'), + signInMessage: $t('To check out, please sign in with your email address.') + }, + + /** + * Render PayPal buttons using checkout.js + */ + renderPayPalButtons: function (element) { + checkoutSmartButtons(this.prepareClientConfig(), element); + }, + + /** + * Validate payment method + * + * @param {Object} actions + */ + validate: function (actions) { + this.actions = actions || this.actions; + }, + + /** + * Execute logic on Paypal button click + */ + onClick: function () {}, + + /** + * Before payment execute + * + * @param {Function} resolve + * @param {Function} reject + * @return {*} + */ + beforePayment: function (resolve, reject) { //eslint-disable-line no-unused-vars + return $.Deferred().resolve(); + }, + + /** + * After payment execute + * + * @param {Object} res + * @param {Function} resolve + * @param {Function} reject + * + * @return {*} + */ + afterPayment: function (res, resolve, reject) { + if (res.success) { + return resolve(res.token); + } + + this.addError(res['error_message']); + + return reject(new Error(res['error_message'])); + }, + + /** + * Catch payment + * + * @param {Error} err + * @param {Function} resolve + * @param {Function} reject + */ + catchPayment: function (err, resolve, reject) { + this.addError(this.paymentActionError); + reject(err); + }, + + /** + * Before onAuthorize execute + * + * @param {Function} resolve + * @param {Function} reject + * @param {Object} actions + * + * @return {jQuery.Deferred} + */ + beforeOnAuthorize: function (resolve, reject, actions) { //eslint-disable-line no-unused-vars + return $.Deferred().resolve(); + }, + + /** + * After onAuthorize execute + * + * @param {Object} res + * @param {Function} resolve + * @param {Function} reject + * @param {Object} actions + * + * @return {*} + */ + afterOnAuthorize: function (res, resolve, reject, actions) { + if (res.success) { + resolve(); + + return actions.redirect(window, res.redirectUrl); + } + + this.addError(res['error_message']); + + return reject(new Error(res['error_message'])); + }, + + /** + * Catch payment + * + * @param {Error} err + * @param {Function} resolve + * @param {Function} reject + */ + catchOnAuthorize: function (err, resolve, reject) { + this.addError(this.paymentActionError); + reject(err); + }, + + /** + * Process cancel action + * + * @param {Object} data + * @param {Object} actions + */ + onCancel: function (data, actions) { + actions.redirect(window, this.clientConfig.onCancelUrl); + }, + + /** + * Process errors + * + * @param {Error} err + */ + onError: function (err) { //eslint-disable-line no-unused-vars + // Uncaught error isn't displayed in the console + }, + + /** + * Adds error message + * + * @param {String} message + * @param {String} [type] + */ + addError: function (message, type) { + type = type || 'error'; + customerData.set('messages', { + messages: [{ + type: type, + text: message + }], + 'data_id': Math.floor(Date.now() / 1000) + }); + }, + + /** + * @returns {String} + */ + getButtonId: function () { + return this.inContextId; + }, + + /** + * Populate client config with all required data + * + * @return {Object} + */ + prepareClientConfig: function () { + this.clientConfig.client = {}; + this.clientConfig.client[this.clientConfig.environment] = this.clientConfig.merchantId; + this.clientConfig.rendererComponent = this; + this.clientConfig.formKey = $.mage.cookies.get('form_key'); + + return this.clientConfig; + } + }; +}); diff --git a/app/code/Magento/Paypal/view/frontend/web/js/in-context/product-express-checkout.js b/app/code/Magento/Paypal/view/frontend/web/js/in-context/product-express-checkout.js new file mode 100644 index 00000000000..413820cc731 --- /dev/null +++ b/app/code/Magento/Paypal/view/frontend/web/js/in-context/product-express-checkout.js @@ -0,0 +1,76 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +define([ + 'underscore', + 'jquery', + 'uiComponent', + 'Magento_Paypal/js/in-context/express-checkout-wrapper', + 'Magento_Customer/js/customer-data' +], function (_, $, Component, Wrapper, customerData) { + 'use strict'; + + return Component.extend(Wrapper).extend({ + defaults: { + productFormSelector: '#product_addtocart_form', + declinePayment: false, + formInvalid: false + }, + + /** @inheritdoc */ + initialize: function (config, element) { + var cart = customerData.get('cart'), + customer = customerData.get('customer'); + + this._super(); + this.renderPayPalButtons(element); + this.declinePayment = !customer().firstname && !cart().isGuestCheckoutAllowed; + + return this; + }, + + /** @inheritdoc */ + onClick: function () { + var $form = $(this.productFormSelector); + + if (!this.declinePayment) { + $form.submit(); + this.formInvalid = !$form.validation('isValid'); + } + }, + + /** @inheritdoc */ + beforePayment: function (resolve, reject) { + var promise = $.Deferred(); + + if (this.declinePayment) { + this.addError(this.signInMessage, 'warning'); + reject(); + } else if (this.formInvalid) { + reject(); + } else { + $(document).on('ajax:addToCart', function (e, data) { + if (_.isEmpty(data.response)) { + return promise.resolve(); + } + + return reject(); + }); + $(document).on('ajax:addToCart:error', reject); + } + + return promise; + }, + + /** @inheritdoc */ + prepareClientConfig: function () { + this._super(); + this.clientConfig.quoteId = ''; + this.clientConfig.customerId = ''; + this.clientConfig.commit = false; + + return this.clientConfig; + } + }); +}); diff --git a/app/code/Magento/Paypal/view/frontend/web/js/view/payment/method-renderer/iframe-methods.js b/app/code/Magento/Paypal/view/frontend/web/js/view/payment/method-renderer/iframe-methods.js index 3315a7c402d..7fb94a7e234 100644 --- a/app/code/Magento/Paypal/view/frontend/web/js/view/payment/method-renderer/iframe-methods.js +++ b/app/code/Magento/Paypal/view/frontend/web/js/view/payment/method-renderer/iframe-methods.js @@ -4,10 +4,9 @@ */ define([ 'Magento_Checkout/js/view/payment/default', - 'ko', 'Magento_Paypal/js/model/iframe', 'Magento_Checkout/js/model/full-screen-loader' -], function (Component, ko, iframe, fullScreenLoader) { +], function (Component, iframe, fullScreenLoader) { 'use strict'; return Component.extend({ diff --git a/app/code/Magento/Paypal/view/frontend/web/js/view/payment/method-renderer/in-context/checkout-express.js b/app/code/Magento/Paypal/view/frontend/web/js/view/payment/method-renderer/in-context/checkout-express.js index c56f21bc718..5c509238fe5 100644 --- a/app/code/Magento/Paypal/view/frontend/web/js/view/payment/method-renderer/in-context/checkout-express.js +++ b/app/code/Magento/Paypal/view/frontend/web/js/view/payment/method-renderer/in-context/checkout-express.js @@ -2,134 +2,103 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -define( - [ - 'underscore', - 'jquery', - 'Magento_Paypal/js/view/payment/method-renderer/paypal-express-abstract', - 'Magento_Paypal/js/action/set-payment-method', - 'Magento_Checkout/js/model/payment/additional-validators', - 'Magento_Ui/js/lib/view/utils/dom-observer', - 'paypalInContextExpressCheckout', - 'Magento_Customer/js/customer-data', - 'Magento_Ui/js/model/messageList' - ], - function ( - _, - $, - Component, - setPaymentMethodAction, - additionalValidators, - domObserver, - paypalExpressCheckout, - customerData, - messageList - ) { - 'use strict'; - - // State of PayPal module initialization - var clientInit = false; - - return Component.extend({ - - defaults: { - template: 'Magento_Paypal/payment/paypal-express-in-context', - clientConfig: { - /** - * @param {Object} event - */ - click: function (event) { - event.preventDefault(); - - if (additionalValidators.validate()) { - paypalExpressCheckout.checkout.initXO(); - - this.selectPaymentMethod(); - setPaymentMethodAction(this.messageContainer).done(function () { - $('body').trigger('processStart'); - - $.getJSON(this.path, { - button: 0 - }).done(function (response) { - var message = response && response.message; - - if (message) { - if (message.type === 'error') { - messageList.addErrorMessage({ - message: message.text - }); - } else { - messageList.addSuccessMessage({ - message: message.text - }); - } - } - - if (response && response.url) { - paypalExpressCheckout.checkout.startFlow(response.url); - - return; - } - - paypalExpressCheckout.checkout.closeFlow(); - }).fail(function () { - paypalExpressCheckout.checkout.closeFlow(); - }).always(function () { - $('body').trigger('processStop'); - customerData.invalidate(['cart']); - }); - }.bind(this)).fail(function () { - paypalExpressCheckout.checkout.closeFlow(); - }); - } - } - } - }, - - /** - * @returns {Object} - */ - initialize: function () { - this._super(); - this.initClient(); - - return this; - }, +define([ + 'jquery', + 'Magento_Paypal/js/view/payment/method-renderer/paypal-express-abstract', + 'Magento_Paypal/js/in-context/express-checkout-wrapper', + 'Magento_Paypal/js/action/set-payment-method', + 'Magento_Checkout/js/model/payment/additional-validators', + 'Magento_Ui/js/model/messageList', + 'Magento_Ui/js/lib/view/utils/async' +], function ($, Component, Wrapper, setPaymentMethod, additionalValidators, messageList) { + 'use strict'; + + return Component.extend(Wrapper).extend({ + defaults: { + template: 'Magento_Paypal/payment/paypal-express-in-context', + validationElements: 'input' + }, + + /** + * Listens element on change and validate it. + * + * @param {HTMLElement} context + */ + initListeners: function (context) { + $.async(this.validationElements, context, function (element) { + $(element).on('change', function () { + this.validate(); + }.bind(this)); + }.bind(this)); + }, + + /** + * Validates Smart Buttons + */ + validate: function () { + this._super(); + + if (this.actions) { + additionalValidators.validate(true) ? this.actions.enable() : this.actions.disable(); + } + }, - /** - * @returns {Object} - */ - initClient: function () { - var selector = '#' + this.getButtonId(); + /** @inheritdoc */ + beforePayment: function (resolve, reject) { + var promise = $.Deferred(); - _.each(this.clientConfig, function (fn, name) { - if (typeof fn === 'function') { - this.clientConfig[name] = fn.bind(this); - } - }, this); + setPaymentMethod(this.messageContainer).done(function () { + return promise.resolve(); + }).fail(function (response) { + var error; - if (!clientInit) { - domObserver.get(selector, function () { - paypalExpressCheckout.checkout.setup(this.merchantId, this.clientConfig); - clientInit = true; - domObserver.off(selector); - }.bind(this)); - } else { - domObserver.get(selector, function () { - $(selector).on('click', this.clientConfig.click); - domObserver.off(selector); - }.bind(this)); + try { + error = JSON.parse(response.responseText); + } catch (exception) { + error = this.paymentActionError; } - return this; - }, - - /** - * @returns {String} - */ - getButtonId: function () { - return this.inContextId; - } - }); - } -); + this.addError(error); + + return reject(new Error(error)); + }.bind(this)); + + return promise; + }, + + /** + * Populate client config with all required data + * + * @return {Object} + */ + prepareClientConfig: function () { + this._super(); + this.clientConfig.quoteId = window.checkoutConfig.quoteData['entity_id']; + this.clientConfig.customerId = window.customerData.id; + this.clientConfig.merchantId = this.merchantId; + this.clientConfig.button = 0; + this.clientConfig.commit = true; + + return this.clientConfig; + }, + + /** + * Adding logic to be triggered onClick action for smart buttons component + */ + onClick: function () { + additionalValidators.validate(); + this.selectPaymentMethod(); + }, + + /** + * Adds error message + * + * @param {String} message + */ + addError: function (message) { + messageList.addErrorMessage({ + message: message + }); + } + }); +}); diff --git a/app/code/Magento/Paypal/view/frontend/web/js/view/payment/method-renderer/payflowpro/vault.js b/app/code/Magento/Paypal/view/frontend/web/js/view/payment/method-renderer/payflowpro/vault.js index d0d72bf7dcd..a0f3d3867fe 100644 --- a/app/code/Magento/Paypal/view/frontend/web/js/view/payment/method-renderer/payflowpro/vault.js +++ b/app/code/Magento/Paypal/view/frontend/web/js/view/payment/method-renderer/payflowpro/vault.js @@ -2,12 +2,10 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -/*browser:true*/ -/*global define*/ + define([ - 'jquery', 'Magento_Vault/js/view/payment/method-renderer/vault' -], function ($, VaultComponent) { +], function (VaultComponent) { 'use strict'; return VaultComponent.extend({ diff --git a/app/code/Magento/Paypal/view/frontend/web/js/view/payment/method-renderer/paypal-express-abstract.js b/app/code/Magento/Paypal/view/frontend/web/js/view/payment/method-renderer/paypal-express-abstract.js index d038f08c348..b01d0454b55 100644 --- a/app/code/Magento/Paypal/view/frontend/web/js/view/payment/method-renderer/paypal-express-abstract.js +++ b/app/code/Magento/Paypal/view/frontend/web/js/view/payment/method-renderer/paypal-express-abstract.js @@ -15,7 +15,7 @@ define([ return Component.extend({ defaults: { - template: 'Magento_Paypal/payment/paypal-express-bml', + template: 'Magento_Paypal/payment/payflow-express-bml', billingAgreement: '' }, diff --git a/app/code/Magento/Paypal/view/frontend/web/js/view/payment/method-renderer/paypal-express-bml.js b/app/code/Magento/Paypal/view/frontend/web/js/view/payment/method-renderer/paypal-express-bml.js deleted file mode 100644 index 561b3c0e971..00000000000 --- a/app/code/Magento/Paypal/view/frontend/web/js/view/payment/method-renderer/paypal-express-bml.js +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -define([ - 'Magento_Paypal/js/view/payment/method-renderer/paypal-express-abstract' -], function (Component) { - 'use strict'; - - return Component.extend({ - defaults: { - template: 'Magento_Paypal/payment/paypal-express-bml' - } - }); -}); diff --git a/app/code/Magento/Paypal/view/frontend/web/js/view/payment/paypal-payments.js b/app/code/Magento/Paypal/view/frontend/web/js/view/payment/paypal-payments.js index 9eae0dfb45e..1628bbed8f1 100644 --- a/app/code/Magento/Paypal/view/frontend/web/js/view/payment/paypal-payments.js +++ b/app/code/Magento/Paypal/view/frontend/web/js/view/payment/paypal-payments.js @@ -19,10 +19,6 @@ define([ component: paypalExpress, config: window.checkoutConfig.payment.paypalExpress.inContextConfig }, - { - type: 'paypal_express_bml', - component: 'Magento_Paypal/js/view/payment/method-renderer/paypal-express-bml' - }, { type: 'payflow_express', component: 'Magento_Paypal/js/view/payment/method-renderer/payflow-express' diff --git a/app/code/Magento/Paypal/view/frontend/web/js/view/review/actions/iframe.js b/app/code/Magento/Paypal/view/frontend/web/js/view/review/actions/iframe.js index e181faf56e3..09dffc73baa 100644 --- a/app/code/Magento/Paypal/view/frontend/web/js/view/review/actions/iframe.js +++ b/app/code/Magento/Paypal/view/frontend/web/js/view/review/actions/iframe.js @@ -8,9 +8,8 @@ */ define([ 'uiComponent', - 'ko', 'Magento_Paypal/js/model/iframe' -], function (Component, ko, iframe) { +], function (Component, iframe) { 'use strict'; return Component.extend({ diff --git a/app/code/Magento/Paypal/view/frontend/web/template/payment/paypal-express-bml.html b/app/code/Magento/Paypal/view/frontend/web/template/payment/paypal-express-bml.html deleted file mode 100644 index 0f042824fe8..00000000000 --- a/app/code/Magento/Paypal/view/frontend/web/template/payment/paypal-express-bml.html +++ /dev/null @@ -1,52 +0,0 @@ -<!-- -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> -<div class="payment-method" data-bind="css: {'_active': (getCode() == isChecked())}"> - <div class="payment-method-title field choice"> - <input type="radio" - name="payment[method]" - class="radio" - data-bind="attr: {'id': getCode()}, value: getCode(), checked: isChecked, click: selectPaymentMethod, visible: isRadioButtonVisible()" /> - <label data-bind="attr: {'for': getCode()}" class="label"> - <!-- PayPal Logo --> - <img src="https://www.paypalobjects.com/webstatic/en_US/i/buttons/ppc-acceptance-medium.png" - data-bind="attr: {alt: $t('Acceptance Mark')}" - class="payment-icon"/> - <!-- PayPal Logo --> - <span data-bind="text: getTitle()"></span> - <a href="https://www.securecheckout.billmelater.com/paycapture-content/fetch?hash=AU826TU8&content=/bmlweb/ppwpsiw.html" - data-bind="click: showAcceptanceWindow" - class="action action-help"> - <!-- ko i18n: 'See terms' --><!-- /ko --> - </a> - </label> - </div> - <div class="payment-method-content"> - <!-- ko foreach: getRegion('messages') --> - <!-- ko template: getTemplate() --><!-- /ko --> - <!--/ko--> - <fieldset class="fieldset" data-bind='attr: {id: "payment_form_" + getCode()}'> - <div class="payment-method-note"> - <!-- ko i18n: 'You will be redirected to the PayPal website when you place an order.' --><!-- /ko --> - </div> - </fieldset> - <div class="checkout-agreements-block"> - <!-- ko foreach: $parent.getRegion('before-place-order') --> - <!-- ko template: getTemplate() --><!-- /ko --> - <!--/ko--> - </div> - <div class="actions-toolbar"> - <div class="primary"> - <button class="action primary checkout" - type="submit" - data-bind="click: continueToPayPal, enable: (getCode() == isChecked())" - disabled> - <span data-bind="i18n: 'Continue to PayPal'"></span> - </button> - </div> - </div> - </div> -</div> diff --git a/app/code/Magento/Paypal/view/frontend/web/template/payment/paypal-express-in-context.html b/app/code/Magento/Paypal/view/frontend/web/template/payment/paypal-express-in-context.html index 562243decaa..5f321832523 100644 --- a/app/code/Magento/Paypal/view/frontend/web/template/payment/paypal-express-in-context.html +++ b/app/code/Magento/Paypal/view/frontend/web/template/payment/paypal-express-in-context.html @@ -4,41 +4,32 @@ * See COPYING.txt for license details. */ --> -<div class="payment-method" data-bind="css: {'_active': (getCode() == isChecked())}"> +<div class="payment-method" css="_active: getCode() == isChecked()" afterRender="initListeners"> <div class="payment-method-title field choice"> <input type="radio" name="payment[method]" class="radio" - data-bind="attr: {'id': getCode()}, value: getCode(), checked: isChecked, click: selectPaymentMethod, visible: isRadioButtonVisible()" /> - <label data-bind="attr: {'for': getCode()}" class="label"> + attr="id: getCode()" + ko-value="getCode()" + ko-checked="isChecked" + click="selectPaymentMethod" + visible="isRadioButtonVisible()"/> + <label attr="for: getCode()" class="label"> <!-- PayPal Logo --> - <img data-bind="attr: {src: getPaymentAcceptanceMarkSrc(), alt: $t('Acceptance Mark')}" class="payment-icon"/> + <img attr="src: getPaymentAcceptanceMarkSrc(), alt: $t('Acceptance Mark')" class="payment-icon"/> <!-- PayPal Logo --> - <span data-bind="text: getTitle()"></span> - <a data-bind="attr: {href: getPaymentAcceptanceMarkHref()}, click: showAcceptanceWindow" - class="action action-help"> - <!-- ko i18n: 'What is PayPal?' --><!-- /ko --> - </a> + <span text="getTitle()"/> + <a class="action action-help" + attr="href: getPaymentAcceptanceMarkHref()" + click="showAcceptanceWindow" + translate="'What is PayPal?'"/> </label> </div> <div class="payment-method-content"> - <!-- ko foreach: getRegion('messages') --> - <!-- ko template: getTemplate() --><!-- /ko --> - <!--/ko--> + <each args="getRegion('messages')" render=""/> <div class="checkout-agreements-block"> - <!-- ko foreach: $parent.getRegion('before-place-order') --> - <!-- ko template: getTemplate() --><!-- /ko --> - <!--/ko--> - </div> - <div class="actions-toolbar"> - <div class="primary"> - <button class="action primary checkout" - type="submit" - data-bind="enable: (getCode() == isChecked()), attr: {id: getButtonId()}" - disabled> - <span data-bind="i18n: 'Continue to PayPal'"></span> - </button> - </div> + <each args="$parent.getRegion('before-place-order')" render=""/> </div> + <div class="actions-toolbar" attr="id: getButtonId()" afterRender="renderPayPalButtons"/> </div> </div> diff --git a/app/code/Magento/Persistent/Block/Header/Additional.php b/app/code/Magento/Persistent/Block/Header/Additional.php index c740f5a3469..dfde2adf1e6 100644 --- a/app/code/Magento/Persistent/Block/Header/Additional.php +++ b/app/code/Magento/Persistent/Block/Header/Additional.php @@ -5,6 +5,10 @@ */ namespace Magento\Persistent\Block\Header; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Serialize\Serializer\Json; +use Magento\Persistent\Helper\Data; + /** * Remember Me block * @@ -30,20 +34,37 @@ class Additional extends \Magento\Framework\View\Element\Html\Link protected $customerRepository; /** - * Constructor - * + * @var string + */ + protected $_template = 'Magento_Persistent::additional.phtml'; + + /** + * @var Json + */ + private $jsonSerializer; + + /** + * @var Data + */ + private $persistentHelper; + + /** * @param \Magento\Framework\View\Element\Template\Context $context * @param \Magento\Customer\Helper\View $customerViewHelper * @param \Magento\Persistent\Helper\Session $persistentSessionHelper * @param \Magento\Customer\Api\CustomerRepositoryInterface $customerRepository * @param array $data + * @param Json|null $jsonSerializer + * @param Data|null $persistentHelper */ public function __construct( \Magento\Framework\View\Element\Template\Context $context, \Magento\Customer\Helper\View $customerViewHelper, \Magento\Persistent\Helper\Session $persistentSessionHelper, \Magento\Customer\Api\CustomerRepositoryInterface $customerRepository, - array $data = [] + array $data = [], + Json $jsonSerializer = null, + Data $persistentHelper = null ) { $this->isScopePrivate = true; $this->_customerViewHelper = $customerViewHelper; @@ -51,6 +72,8 @@ public function __construct( $this->customerRepository = $customerRepository; parent::__construct($context, $data); $this->_isScopePrivate = true; + $this->jsonSerializer = $jsonSerializer ?: ObjectManager::getInstance()->get(Json::class); + $this->persistentHelper = $persistentHelper ?: ObjectManager::getInstance()->get(Data::class); } /** @@ -64,17 +87,26 @@ public function getHref() } /** - * Render additional header html + * Get customer id. * - * @return string + * @return int */ - protected function _toHtml() + public function getCustomerId(): int { - if ($this->_persistentSessionHelper->getSession()->getCustomerId()) { - return '<span><a ' . $this->getLinkAttributes() . ' >' . __('Not you?') - . '</a></span>'; - } + return $this->_persistentSessionHelper->getSession()->getCustomerId(); + } - return ''; + /** + * Get persistent config. + * + * @return string + */ + public function getConfig(): string + { + return $this->jsonSerializer->serialize( + [ + 'expirationLifetime' => $this->persistentHelper->getLifeTime(), + ] + ); } } diff --git a/app/code/Magento/Persistent/CustomerData/Persistent.php b/app/code/Magento/Persistent/CustomerData/Persistent.php new file mode 100644 index 00000000000..5800e4e7aee --- /dev/null +++ b/app/code/Magento/Persistent/CustomerData/Persistent.php @@ -0,0 +1,72 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Persistent\CustomerData; + +use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Customer\CustomerData\SectionSourceInterface; +use Magento\Customer\Helper\View; +use Magento\Persistent\Helper\Session; + +/** + * Customer persistent section + */ +class Persistent implements SectionSourceInterface +{ + /** + * @var Session + */ + private $persistentSession; + + /** + * @var View + */ + private $customerViewHelper; + + /** + * @var CustomerRepositoryInterface + */ + private $customerRepository; + + /** + * @param Session $persistentSession + * @param View $customerViewHelper + * @param CustomerRepositoryInterface $customerRepository + */ + public function __construct( + Session $persistentSession, + View $customerViewHelper, + CustomerRepositoryInterface $customerRepository + ) { + $this->persistentSession = $persistentSession; + $this->customerViewHelper = $customerViewHelper; + $this->customerRepository = $customerRepository; + } + + /** + * Get data. + * + * @return array + */ + public function getSectionData(): array + { + if (!$this->persistentSession->isPersistent()) { + return []; + } + + $customerId = $this->persistentSession->getSession()->getCustomerId(); + if (!$customerId) { + return []; + } + + $customer = $this->customerRepository->getById($customerId); + + return [ + 'fullname' => $this->customerViewHelper->getCustomerName($customer), + ]; + } +} diff --git a/app/code/Magento/Persistent/Model/Observer.php b/app/code/Magento/Persistent/Model/Observer.php index 53fe5f95531..81c2870071a 100644 --- a/app/code/Magento/Persistent/Model/Observer.php +++ b/app/code/Magento/Persistent/Model/Observer.php @@ -86,13 +86,8 @@ public function __construct( */ public function emulateWelcomeBlock($block) { - $customerName = $this->_customerViewHelper->getCustomerName( - $this->customerRepository->getById($this->_persistentSession->getSession()->getCustomerId()) - ); + $block->setWelcome(' '); - $this->_applyAccountLinksPersistentData(); - $welcomeMessage = __('Welcome, %1!', $customerName); - $block->setWelcome($welcomeMessage); return $this; } diff --git a/app/code/Magento/Persistent/Model/Plugin/PersistentCustomerContext.php b/app/code/Magento/Persistent/Model/Plugin/PersistentCustomerContext.php new file mode 100644 index 00000000000..be8998bc9be --- /dev/null +++ b/app/code/Magento/Persistent/Model/Plugin/PersistentCustomerContext.php @@ -0,0 +1,43 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Persistent\Model\Plugin; + +/** + * Plugin for Magento\Framework\App\Http\Context to create new page cache variation for persistent session. + */ +class PersistentCustomerContext +{ + /** + * Persistent session. + * + * @var \Magento\Persistent\Helper\Session + */ + private $persistentSession; + + /** + * @param \Magento\Persistent\Helper\Session $persistentSession + */ + public function __construct( + \Magento\Persistent\Helper\Session $persistentSession + ) { + $this->persistentSession = $persistentSession; + } + + /** + * Sets appropriate header if customer session is persistent. + * + * @param \Magento\Framework\App\Http\Context $subject + * @return mixed + */ + public function beforeGetVaryString(\Magento\Framework\App\Http\Context $subject) + { + if ($this->persistentSession->isPersistent()) { + $subject->setValue('PERSISTENT', 1, 0); + } + } +} diff --git a/app/code/Magento/Persistent/Test/Mftf/ActionGroup/StorefrontCustomerActionGroup.xml b/app/code/Magento/Persistent/Test/Mftf/ActionGroup/StorefrontCustomerActionGroup.xml new file mode 100644 index 00000000000..293fa04d804 --- /dev/null +++ b/app/code/Magento/Persistent/Test/Mftf/ActionGroup/StorefrontCustomerActionGroup.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="CustomerLoginOnStorefrontWithRememberMeChecked" extends="LoginToStorefrontActionGroup"> + <checkOption selector="{{StorefrontCustomerSignInFormSection.rememberMe}}" + before="clickSignInAccountButton" + stepKey="checkRememberMe"/> + </actionGroup> + + <actionGroup name="CustomerLoginOnStorefrontWithRememberMeUnChecked" extends="LoginToStorefrontActionGroup"> + <uncheckOption selector="{{StorefrontCustomerSignInFormSection.rememberMe}}" + before="clickSignInAccountButton" + stepKey="unCheckRememberMe"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Persistent/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml b/app/code/Magento/Persistent/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml new file mode 100644 index 00000000000..c2220c33a60 --- /dev/null +++ b/app/code/Magento/Persistent/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="StorefrontCustomerSignInFormSection"> + <element name="rememberMe" type="checkbox" selector="[name='persistent_remember_me']"/> + </section> +</sections> diff --git a/app/code/Magento/Persistent/Test/Mftf/Test/StorefrontCorrectWelcomeMessageAfterCustomerIsLoggedOutTest.xml b/app/code/Magento/Persistent/Test/Mftf/Test/StorefrontCorrectWelcomeMessageAfterCustomerIsLoggedOutTest.xml new file mode 100644 index 00000000000..2b58e5c7bf6 --- /dev/null +++ b/app/code/Magento/Persistent/Test/Mftf/Test/StorefrontCorrectWelcomeMessageAfterCustomerIsLoggedOutTest.xml @@ -0,0 +1,79 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontCorrectWelcomeMessageAfterCustomerIsLoggedOutTest"> + <annotations> + <features value="Persistent"/> + <stories value="MAGETWO-97278 - Incorrect use of cookies for customer"/> + <title value="Checking welcome message for persistent customer after logout"/> + <description value="Checking welcome message for persistent customer after logout"/> + <severity value="MAJOR"/> + <testCaseId value="MC-10800"/> + <group value="persistent"/> + <group value="customer"/> + </annotations> + <before> + <!--Enable Persistence--> + <createData entity="PersistentConfigEnabled" stepKey="enablePersistent"/> + <createData entity="PersistentLogoutClearDisable" stepKey="persistentLogoutClearDisable"/> + + <!--Create customers--> + <createData entity="Simple_US_Customer" stepKey="createCustomer"/> + <createData entity="Simple_US_Customer" stepKey="createCustomerForPersistent"> + <field key="firstname">John1</field> + <field key="lastname">Doe1</field> + </createData> + </before> + <after> + <!--Roll back configuration--> + <createData entity="PersistentConfigDefault" stepKey="setDefaultPersistentState"/> + <createData entity="PersistentLogoutClearEnabled" stepKey="persistentLogoutClearEnabled"/> + + <!-- Logout customer on Storefront--> + <actionGroup ref="StorefrontCustomerLogoutActionGroup" stepKey="customerLogoutStorefront"/> + <!--Delete customers--> + <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> + <deleteData createDataKey="createCustomerForPersistent" stepKey="deleteCustomerForPersistent"/> + </after> + <!--Login as a Customer with remember me unchecked--> + <actionGroup ref="CustomerLoginOnStorefrontWithRememberMeUnChecked" stepKey="loginToStorefrontAccountWithRememberMeUnchecked"> + <argument name="Customer" value="$$createCustomer$$"/> + </actionGroup> + + <!--Check customer name and last name in welcome message--> + <seeInCurrentUrl url="{{StorefrontCustomerDashboardPage.url}}" stepKey="seeCustomerAccountPageUrl"/> + <see userInput="Welcome, $$createCustomer.firstname$$ $$createCustomer.lastname$$!" + selector="{{StorefrontHeaderSection.welcomeMessage}}" + stepKey="seeLoggedInCustomerWelcomeMessage"/> + <!--Logout and check default welcome message--> + <actionGroup ref="CustomerLogoutStorefrontByMenuItemsActionGroup" stepKey="storefrontCustomerLogout"/> + <seeInCurrentUrl url="{{StorefrontCustomerLogoutSuccessPage.url}}" wait="5" stepKey="seeCustomerSignOutPageUrl"/> + <see userInput="Default welcome msg!" + selector="{{StorefrontHeaderSection.welcomeMessage}}" + stepKey="seeDefaultWelcomeMessage"/> + + <!--Login as a Customer with remember me checked--> + <actionGroup ref="CustomerLoginOnStorefrontWithRememberMeChecked" stepKey="loginToStorefrontAccountWithRememberMeChecked"> + <argument name="Customer" value="$$createCustomerForPersistent$$"/> + </actionGroup> + <!--Check customer name and last name in welcome message--> + <seeInCurrentUrl url="{{StorefrontCustomerDashboardPage.url}}" stepKey="seeCustomerAccountPageUrl1"/> + <see userInput="Welcome, $$createCustomerForPersistent.firstname$$ $$createCustomerForPersistent.lastname$$!" + selector="{{StorefrontHeaderSection.welcomeMessage}}" + stepKey="seeLoggedInCustomerWelcomeMessage1"/> + + <!--Logout and check persistent customer welcome message--> + <actionGroup ref="CustomerLogoutStorefrontByMenuItemsActionGroup" stepKey="storefrontCustomerLogout1"/> + <seeInCurrentUrl url="{{StorefrontCustomerLogoutSuccessPage.url}}" wait="5" stepKey="seeCustomerSignOutPageUrl1"/> + <see userInput="Welcome, $$createCustomerForPersistent.firstname$$ $$createCustomerForPersistent.lastname$$! Not you?" + selector="{{StorefrontHeaderSection.welcomeMessage}}" + stepKey="seePersistentWelcomeMessage"/> + </test> +</tests> diff --git a/app/code/Magento/Persistent/Test/Unit/Block/Header/AdditionalTest.php b/app/code/Magento/Persistent/Test/Unit/Block/Header/AdditionalTest.php index b88b02ab4cf..407dad05c3b 100644 --- a/app/code/Magento/Persistent/Test/Unit/Block/Header/AdditionalTest.php +++ b/app/code/Magento/Persistent/Test/Unit/Block/Header/AdditionalTest.php @@ -34,44 +34,14 @@ class AdditionalTest extends \PHPUnit\Framework\TestCase protected $contextMock; /** - * @var \Magento\Framework\Event\ManagerInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Framework\Serialize\Serializer\Json|\PHPUnit_Framework_MockObject_MockObject */ - protected $eventManagerMock; + private $jsonSerializerMock; /** - * @var \Magento\Framework\App\Config\ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Persistent\Helper\Data|\PHPUnit_Framework_MockObject_MockObject */ - protected $scopeConfigMock; - - /** - * @var \Magento\Framework\App\Cache\StateInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $cacheStateMock; - - /** - * @var \Magento\Framework\App\CacheInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $cacheMock; - - /** - * @var \Magento\Framework\Session\SidResolverInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $sidResolverMock; - - /** - * @var \Magento\Framework\Session\SessionManagerInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $sessionMock; - - /** - * @var \Magento\Framework\Escaper|\PHPUnit_Framework_MockObject_MockObject - */ - protected $escaperMock; - - /** - * @var \Magento\Framework\UrlInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $urlBuilderMock; + private $persistentHelperMock; /** * @var \Magento\Persistent\Block\Header\Additional @@ -93,17 +63,7 @@ protected function setUp() { $this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - $this->contextMock = $this->createPartialMock(\Magento\Framework\View\Element\Template\Context::class, [ - 'getEventManager', - 'getScopeConfig', - 'getCacheState', - 'getCache', - 'getInlineTranslation', - 'getSidResolver', - 'getSession', - 'getEscaper', - 'getUrlBuilder' - ]); + $this->contextMock = $this->createPartialMock(\Magento\Framework\View\Element\Template\Context::class, []); $this->customerViewHelperMock = $this->createMock(\Magento\Customer\Helper\View::class); $this->persistentSessionHelperMock = $this->createPartialMock( \Magento\Persistent\Helper\Session::class, @@ -119,103 +79,14 @@ protected function setUp() ['getById'] ); - $this->eventManagerMock = $this->getMockForAbstractClass( - \Magento\Framework\Event\ManagerInterface::class, - [], - '', - false, - true, - true, - ['dispatch'] - ); - $this->scopeConfigMock = $this->getMockForAbstractClass( - \Magento\Framework\App\Config\ScopeConfigInterface::class, - [], - '', - false, - true, - true, - ['getValue'] - ); - $this->cacheStateMock = $this->getMockForAbstractClass( - \Magento\Framework\App\Cache\StateInterface::class, - [], - '', - false, - true, - true, - ['isEnabled'] + $this->jsonSerializerMock = $this->createPartialMock( + \Magento\Framework\Serialize\Serializer\Json::class, + ['serialize'] ); - $this->cacheMock = $this->getMockForAbstractClass( - \Magento\Framework\App\CacheInterface::class, - [], - '', - false, - true, - true, - ['load'] + $this->persistentHelperMock = $this->createPartialMock( + \Magento\Persistent\Helper\Data::class, + ['getLifeTime'] ); - $this->sidResolverMock = $this->getMockForAbstractClass( - \Magento\Framework\Session\SidResolverInterface::class, - [], - '', - false, - true, - true, - ['getSessionIdQueryParam'] - ); - $this->sessionMock = $this->getMockForAbstractClass( - \Magento\Framework\Session\SessionManagerInterface::class, - [], - '', - false, - true, - true, - ['getSessionId'] - ); - $this->escaperMock = $this->getMockForAbstractClass( - \Magento\Framework\Escaper::class, - [], - '', - false, - true, - true, - ['escapeHtml'] - ); - $this->urlBuilderMock = $this->getMockForAbstractClass( - \Magento\Framework\UrlInterface::class, - [], - '', - false, - true, - true, - ['getUrl'] - ); - - $this->contextMock->expects($this->once()) - ->method('getEventManager') - ->willReturn($this->eventManagerMock); - $this->contextMock->expects($this->once()) - ->method('getScopeConfig') - ->willReturn($this->scopeConfigMock); - $this->contextMock->expects($this->once()) - ->method('getCacheState') - ->willReturn($this->cacheStateMock); - $this->contextMock->expects($this->once()) - ->method('getCache') - ->willReturn($this->cacheMock); - $this->contextMock->expects($this->once()) - ->method('getSidResolver') - ->willReturn($this->sidResolverMock); - $this->contextMock->expects($this->once()) - ->method('getSession') - ->willReturn($this->sessionMock); - $this->contextMock->expects($this->once()) - ->method('getEscaper') - ->willReturn($this->escaperMock); - $this->contextMock->expects($this->once()) - ->method('getUrlBuilder') - ->willReturn($this->urlBuilderMock); $this->additional = $this->objectManager->getObject( \Magento\Persistent\Block\Header\Additional::class, @@ -224,91 +95,48 @@ protected function setUp() 'customerViewHelper' => $this->customerViewHelperMock, 'persistentSessionHelper' => $this->persistentSessionHelperMock, 'customerRepository' => $this->customerRepositoryMock, - 'data' => [] + 'data' => [], + 'jsonSerializer' => $this->jsonSerializerMock, + 'persistentHelper' => $this->persistentHelperMock, ] ); } /** - * Run test toHtml method - * - * @param bool $customerId * @return void - * - * @dataProvider dataProviderToHtml */ - public function testToHtml($customerId) + public function testGetCustomerId(): void { - $cacheData = false; - $idQueryParam = 'id-query-param'; - $sessionId = 'session-id'; - - $this->additional->setData('cache_lifetime', 789); - $this->additional->setData('cache_key', 'cache-key'); - - $this->eventManagerMock->expects($this->at(0)) - ->method('dispatch') - ->with('view_block_abstract_to_html_before', ['block' => $this->additional]); - $this->eventManagerMock->expects($this->at(1)) - ->method('dispatch') - ->with('view_block_abstract_to_html_after'); - $this->scopeConfigMock->expects($this->once()) - ->method('getValue') - ->with( - 'advanced/modules_disable_output/Magento_Persistent', - \Magento\Store\Model\ScopeInterface::SCOPE_STORE - )->willReturn(false); - - // get cache - $this->cacheStateMock->expects($this->at(0)) - ->method('isEnabled') - ->with(\Magento\Persistent\Block\Header\Additional::CACHE_GROUP) - ->willReturn(true); - // save cache - $this->cacheStateMock->expects($this->at(1)) - ->method('isEnabled') - ->with(\Magento\Persistent\Block\Header\Additional::CACHE_GROUP) - ->willReturn(false); - - $this->cacheMock->expects($this->once()) - ->method('load') - ->willReturn($cacheData); - $this->sidResolverMock->expects($this->never()) - ->method('getSessionIdQueryParam') - ->with($this->sessionMock) - ->willReturn($idQueryParam); - $this->sessionMock->expects($this->never()) - ->method('getSessionId') - ->willReturn($sessionId); - - // call protected _toHtml method + $customerId = 1; + /** @var \Magento\Persistent\Model\Session|\PHPUnit_Framework_MockObject_MockObject $sessionMock */ $sessionMock = $this->createPartialMock(\Magento\Persistent\Model\Session::class, ['getCustomerId']); - - $this->persistentSessionHelperMock->expects($this->atLeastOnce()) - ->method('getSession') - ->willReturn($sessionMock); - - $sessionMock->expects($this->atLeastOnce()) + $sessionMock->expects($this->once()) ->method('getCustomerId') ->willReturn($customerId); + $this->persistentSessionHelperMock->expects($this->once()) + ->method('getSession') + ->willReturn($sessionMock); - if ($customerId) { - $this->assertEquals('<span><a >Not you?</a></span>', $this->additional->toHtml()); - } else { - $this->assertEquals('', $this->additional->toHtml()); - } + $this->assertEquals($customerId, $this->additional->getCustomerId()); } /** - * Data provider for dataProviderToHtml method - * - * @return array + * @return void */ - public function dataProviderToHtml() + public function testGetConfig(): void { - return [ - ['customerId' => 2], - ['customerId' => null], - ]; + $lifeTime = 500; + $arrayToSerialize = ['expirationLifetime' => $lifeTime]; + $serializedArray = '{"expirationLifetime":' . $lifeTime . '}'; + + $this->persistentHelperMock->expects($this->once()) + ->method('getLifeTime') + ->willReturn($lifeTime); + $this->jsonSerializerMock->expects($this->once()) + ->method('serialize') + ->with($arrayToSerialize) + ->willReturn($serializedArray); + + $this->assertEquals($serializedArray, $this->additional->getConfig()); } } diff --git a/app/code/Magento/Persistent/Test/Unit/Model/ObserverTest.php b/app/code/Magento/Persistent/Test/Unit/Model/ObserverTest.php index 7008a9eb25e..6d4db70adc6 100644 --- a/app/code/Magento/Persistent/Test/Unit/Model/ObserverTest.php +++ b/app/code/Magento/Persistent/Test/Unit/Model/ObserverTest.php @@ -80,31 +80,18 @@ protected function setUp() ); } - public function testEmulateWelcomeBlock() + /** + * @return void + */ + public function testEmulateWelcomeBlock(): void { - $customerId = 1; - $customerName = 'Test Customer Name'; - $welcomeMessage = __('Welcome, %1!', $customerName); - $customerMock = $this->getMockForAbstractClass(\Magento\Customer\Api\Data\CustomerInterface::class); + $welcomeMessage = __(' '); $block = $this->getMockBuilder(\Magento\Framework\View\Element\AbstractBlock::class) ->disableOriginalConstructor() ->setMethods(['setWelcome']) ->getMock(); - $headerAdditionalBlock = $this->getMockBuilder(\Magento\Framework\View\Element\AbstractBlock::class) - ->disableOriginalConstructor() - ->getMock(); - $this->persistentSessionMock->expects($this->once())->method('getSession')->willReturn($this->sessionMock); - $this->sessionMock->expects($this->once())->method('getCustomerId')->willReturn($customerId); - $this->customerRepositoryMock - ->expects($this->once()) - ->method('getById') - ->with($customerId)->willReturn($customerMock); - $this->customerViewHelperMock->expects($this->once())->method('getCustomerName')->willReturn($customerName); - $this->layoutMock->expects($this->once()) - ->method('getBlock') - ->with('header.additional') - ->willReturn($headerAdditionalBlock); $block->expects($this->once())->method('setWelcome')->with($welcomeMessage); + $this->observer->emulateWelcomeBlock($block); } } diff --git a/app/code/Magento/Persistent/etc/di.xml b/app/code/Magento/Persistent/etc/di.xml index f49d4361acb..c28426b4f25 100644 --- a/app/code/Magento/Persistent/etc/di.xml +++ b/app/code/Magento/Persistent/etc/di.xml @@ -12,4 +12,7 @@ <type name="Magento\Customer\CustomerData\Customer"> <plugin name="section_data" type="Magento\Persistent\Model\Plugin\CustomerData" /> </type> + <type name="Magento\Framework\App\Http\Context"> + <plugin name="persistent_page_cache_variation" type="Magento\Persistent\Model\Plugin\PersistentCustomerContext" /> + </type> </config> diff --git a/app/code/Magento/Persistent/etc/frontend/di.xml b/app/code/Magento/Persistent/etc/frontend/di.xml index f976f4de79c..3c33f8a51c4 100644 --- a/app/code/Magento/Persistent/etc/frontend/di.xml +++ b/app/code/Magento/Persistent/etc/frontend/di.xml @@ -35,4 +35,18 @@ <type name="Magento\Checkout\Model\GuestPaymentInformationManagement"> <plugin name="inject_guest_address_for_nologin" type="Magento\Persistent\Model\Checkout\GuestPaymentInformationManagementPlugin" /> </type> + <type name="Magento\Customer\CustomerData\SectionPoolInterface"> + <arguments> + <argument name="sectionSourceMap" xsi:type="array"> + <item name="persistent" xsi:type="string">Magento\Persistent\CustomerData\Persistent</item> + </argument> + </arguments> + </type> + <type name="Magento\Customer\Block\CustomerData"> + <arguments> + <argument name="expirableSectionNames" xsi:type="array"> + <item name="persistent" xsi:type="string">persistent</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Persistent/etc/frontend/sections.xml b/app/code/Magento/Persistent/etc/frontend/sections.xml new file mode 100644 index 00000000000..16b44c502fc --- /dev/null +++ b/app/code/Magento/Persistent/etc/frontend/sections.xml @@ -0,0 +1,13 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Customer:etc/sections.xsd"> + <action name="persistent/index/unsetCookie"> + <section name="persistent"/> + </action> +</config> diff --git a/app/code/Magento/Persistent/view/frontend/requirejs-config.js b/app/code/Magento/Persistent/view/frontend/requirejs-config.js new file mode 100644 index 00000000000..e30e07c454b --- /dev/null +++ b/app/code/Magento/Persistent/view/frontend/requirejs-config.js @@ -0,0 +1,14 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +var config = { + config: { + mixins: { + 'Magento_Customer/js/customer-data': { + 'Magento_Persistent/js/view/customer-data-mixin': true + } + } + } +}; diff --git a/app/code/Magento/Persistent/view/frontend/templates/additional.phtml b/app/code/Magento/Persistent/view/frontend/templates/additional.phtml new file mode 100644 index 00000000000..28dce5dc23c --- /dev/null +++ b/app/code/Magento/Persistent/view/frontend/templates/additional.phtml @@ -0,0 +1,21 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +?> +<?php if ($block->getCustomerId()) :?> + <span> + <a <?= /* @escapeNotVerified */ $block->getLinkAttributes()?>><?= $block->escapeHtml(__('Not you?'));?></a> + </span> +<?php endif;?> +<script type="application/javascript"> + window.persistent = <?= /* @noEscape */ $block->getConfig(); ?>; +</script> +<script type="text/x-magento-init"> + { + "li.greet.welcome > span.not-logged-in": { + "Magento_Persistent/js/view/additional-welcome": {} + } + } +</script> diff --git a/app/code/Magento/Persistent/view/frontend/web/js/view/additional-welcome.js b/app/code/Magento/Persistent/view/frontend/web/js/view/additional-welcome.js new file mode 100644 index 00000000000..7ace6e60d1c --- /dev/null +++ b/app/code/Magento/Persistent/view/frontend/web/js/view/additional-welcome.js @@ -0,0 +1,55 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'jquery', + 'mage/translate', + 'Magento_Customer/js/customer-data' +], function ($, $t, customerData) { + 'use strict'; + + return { + /** + * Init. + */ + init: function () { + var persistent = customerData.get('persistent'); + + if (persistent().fullname === undefined) { + customerData.get('persistent').subscribe(this.replacePersistentWelcome); + } else { + this.replacePersistentWelcome(); + } + }, + + /** + * Replace welcome message for customer with persistent cookie. + */ + replacePersistentWelcome: function () { + var persistent = customerData.get('persistent'), + welcomeElems; + + if (persistent().fullname !== undefined) { + welcomeElems = $('li.greet.welcome > span.not-logged-in'); + + if (welcomeElems.length) { + $(welcomeElems).each(function () { + var html = $t('Welcome, %1!').replace('%1', persistent().fullname); + + $(this).attr('data-bind', html); + $(this).html(html); + }); + } + } + }, + + /** + * @constructor + */ + 'Magento_Persistent/js/view/additional-welcome': function () { + this.init(); + } + }; +}); diff --git a/app/code/Magento/Persistent/view/frontend/web/js/view/customer-data-mixin.js b/app/code/Magento/Persistent/view/frontend/web/js/view/customer-data-mixin.js new file mode 100644 index 00000000000..855404c6f6f --- /dev/null +++ b/app/code/Magento/Persistent/view/frontend/web/js/view/customer-data-mixin.js @@ -0,0 +1,51 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +define([ + 'jquery', + 'mage/utils/wrapper' +], function ($, wrapper) { + 'use strict'; + + var mixin = { + + /** + * Check if persistent section is expired due to lifetime. + * + * @param {Function} originFn - Original method. + * @return {Array} + */ + getExpiredSectionNames: function (originFn) { + var expiredSections = originFn(), + storage = $.initNamespaceStorage('mage-cache-storage').localStorage, + currentTimestamp = Math.floor(Date.now() / 1000), + persistentIndex = expiredSections.indexOf('persistent'), + persistentLifeTime = 0, + sectionData; + + if (window.persistent !== undefined && window.persistent.expirationLifetime !== undefined) { + persistentLifeTime = window.persistent.expirationLifetime; + } + + if (persistentIndex !== -1) { + sectionData = storage.get('persistent'); + + if (typeof sectionData === 'object' && + sectionData['data_id'] + persistentLifeTime >= currentTimestamp + ) { + expiredSections.splice(persistentIndex, 1); + } + } + + return expiredSections; + } + }; + + /** + * Override default customer-data.getExpiredSectionNames(). + */ + return function (target) { + return wrapper.extend(target, mixin); + }; +}); diff --git a/app/code/Magento/ProductAlert/Model/Email.php b/app/code/Magento/ProductAlert/Model/Email.php index 7aee4ca0124..3351166aa6a 100644 --- a/app/code/Magento/ProductAlert/Model/Email.php +++ b/app/code/Magento/ProductAlert/Model/Email.php @@ -39,6 +39,8 @@ * * @api * @since 100.0.2 + * @method int getStoreId() + * @method $this setStoreId() */ class Email extends AbstractModel { @@ -136,11 +138,6 @@ class Email extends AbstractModel */ protected $_customerHelper; - /** - * @var int - */ - private $storeId = null; - /** * @param Context $context * @param Registry $registry @@ -215,18 +212,6 @@ public function setWebsite(\Magento\Store\Model\Website $website) return $this; } - /** - * Set store id from product alert. - * - * @param int $storeId - * @return $this - */ - public function setStoreId(int $storeId) - { - $this->storeId = $storeId; - return $this; - } - /** * Set website id * @@ -357,7 +342,7 @@ public function send() return false; } - $storeId = $this->storeId ?: (int) $this->_customer->getStoreId(); + $storeId = $this->getStoreId() ?: (int) $this->_customer->getStoreId(); $store = $this->getStore($storeId); $this->_appEmulation->startEnvironmentEmulation($storeId); diff --git a/app/code/Magento/ProductAlert/Model/ResourceModel/AbstractResource.php b/app/code/Magento/ProductAlert/Model/ResourceModel/AbstractResource.php index 710ede8ecef..c7b3d59138e 100644 --- a/app/code/Magento/ProductAlert/Model/ResourceModel/AbstractResource.php +++ b/app/code/Magento/ProductAlert/Model/ResourceModel/AbstractResource.php @@ -5,6 +5,8 @@ */ namespace Magento\ProductAlert\Model\ResourceModel; +use Magento\Framework\Model\AbstractModel; + /** * Product alert for back in abstract resource model * @@ -15,13 +17,13 @@ abstract class AbstractResource extends \Magento\Framework\Model\ResourceModel\D /** * Retrieve alert row by object parameters * - * @param \Magento\Framework\Model\AbstractModel $object + * @param AbstractModel $object * @return array|false */ - protected function _getAlertRow(\Magento\Framework\Model\AbstractModel $object) + protected function _getAlertRow(AbstractModel $object) { $connection = $this->getConnection(); - if ($object->getCustomerId() && $object->getProductId() && $object->getWebsiteId()) { + if ($this->isExistAllBindIds($object)) { $select = $connection->select()->from( $this->getMainTable() )->where( @@ -30,24 +32,41 @@ protected function _getAlertRow(\Magento\Framework\Model\AbstractModel $object) 'product_id = :product_id' )->where( 'website_id = :website_id' + )->where( + 'store_id = :store_id' ); $bind = [ ':customer_id' => $object->getCustomerId(), ':product_id' => $object->getProductId(), ':website_id' => $object->getWebsiteId(), + ':store_id' => $object->getStoreId() ]; return $connection->fetchRow($select, $bind); } return false; } + /** + * Is exists all bind ids. + * + * @param AbstractModel $object + * @return bool + */ + private function isExistAllBindIds(AbstractModel $object): bool + { + return ($object->getCustomerId() + && $object->getProductId() + && $object->getWebsiteId() + && $object->getStoreId()); + } + /** * Load object data by parameters * - * @param \Magento\Framework\Model\AbstractModel $object + * @param AbstractModel $object * @return $this */ - public function loadByParam(\Magento\Framework\Model\AbstractModel $object) + public function loadByParam(AbstractModel $object) { $row = $this->_getAlertRow($object); if ($row) { @@ -59,13 +78,13 @@ public function loadByParam(\Magento\Framework\Model\AbstractModel $object) /** * Delete all customer alerts on website * - * @param \Magento\Framework\Model\AbstractModel $object + * @param AbstractModel $object * @param int $customerId * @param int $websiteId * @return $this * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public function deleteCustomer(\Magento\Framework\Model\AbstractModel $object, $customerId, $websiteId = null) + public function deleteCustomer(AbstractModel $object, $customerId, $websiteId = null) { $connection = $this->getConnection(); $where = []; diff --git a/app/code/Magento/ProductVideo/Model/Plugin/Catalog/Product/Gallery/CreateHandler.php b/app/code/Magento/ProductVideo/Model/Plugin/Catalog/Product/Gallery/CreateHandler.php index ce1493b349a..42407ca6be0 100644 --- a/app/code/Magento/ProductVideo/Model/Plugin/Catalog/Product/Gallery/CreateHandler.php +++ b/app/code/Magento/ProductVideo/Model/Plugin/Catalog/Product/Gallery/CreateHandler.php @@ -19,6 +19,8 @@ class CreateHandler extends AbstractHandler const ADDITIONAL_STORE_DATA_KEY = 'additional_store_data'; /** + * Execute before Plugin + * * @param \Magento\Catalog\Model\Product\Gallery\CreateHandler $mediaGalleryCreateHandler * @param \Magento\Catalog\Model\Product $product * @param array $arguments @@ -44,6 +46,8 @@ public function beforeExecute( } /** + * Execute plugin + * * @param \Magento\Catalog\Model\Product\Gallery\CreateHandler $mediaGalleryCreateHandler * @param \Magento\Catalog\Model\Product $product * @return \Magento\Catalog\Model\Product @@ -58,6 +62,9 @@ public function afterExecute( ); if (!empty($mediaCollection)) { + if ($product->getIsDuplicate() === true) { + $mediaCollection = $this->makeAllNewVideos($product->getId(), $mediaCollection); + } $newVideoCollection = $this->collectNewVideos($mediaCollection); $this->saveVideoData($newVideoCollection, 0); @@ -70,6 +77,8 @@ public function afterExecute( } /** + * Saves video data + * * @param array $videoDataCollection * @param int $storeId * @return void @@ -83,6 +92,8 @@ protected function saveVideoData(array $videoDataCollection, $storeId) } /** + * Saves additioanal video data + * * @param array $videoDataCollection * @return void */ @@ -99,6 +110,8 @@ protected function saveAdditionalStoreData(array $videoDataCollection) } /** + * Saves video data + * * @param array $item * @return void */ @@ -111,6 +124,8 @@ protected function saveVideoValuesItem(array $item) } /** + * Excludes current store data + * * @param array $mediaCollection * @param int $currentStoreId * @return array @@ -126,6 +141,8 @@ function ($item) use ($currentStoreId) { } /** + * Prepare video data for saving + * * @param array $rowData * @return array */ @@ -143,6 +160,8 @@ protected function prepareVideoRowDataForSave(array $rowData) } /** + * Loads video data + * * @param array $mediaCollection * @param int $excludedStore * @return array @@ -165,6 +184,8 @@ protected function loadStoreViewVideoData(array $mediaCollection, $excludedStore } /** + * Collect video data + * * @param array $mediaCollection * @return array */ @@ -182,6 +203,8 @@ protected function collectVideoData(array $mediaCollection) } /** + * Extract video data + * * @param array $rowData * @return array */ @@ -194,6 +217,8 @@ protected function extractVideoDataFromRowData(array $rowData) } /** + * Collect items for additional data adding + * * @param array $mediaCollection * @return array */ @@ -209,6 +234,8 @@ protected function collectVideoEntriesIdsToAdditionalLoad(array $mediaCollection } /** + * Add additional data + * * @param array $mediaCollection * @param array $data * @return array @@ -229,6 +256,8 @@ protected function addAdditionalStoreData(array $mediaCollection, array $data): } /** + * Creates additional video data + * * @param array $storeData * @param int $valueId * @return array @@ -247,6 +276,8 @@ protected function createAdditionalStoreDataCollection(array $storeData, $valueI } /** + * Collect new videos + * * @param array $mediaCollection * @return array */ @@ -262,6 +293,8 @@ private function collectNewVideos(array $mediaCollection): array } /** + * Checks if gallery item is video + * * @param array $item * @return bool */ @@ -273,6 +306,8 @@ private function isVideoItem(array $item): bool } /** + * Checks if video is new + * * @param array $item * @return bool */ @@ -282,4 +317,23 @@ private function isNewVideo(array $item): bool || empty($item['video_url_default']) || empty($item['video_title_default']); } + + /** + * Mark all videos as new + * + * @param int $entityId + * @param array $mediaCollection + * @return array + */ + private function makeAllNewVideos($entityId, array $mediaCollection): array + { + foreach ($mediaCollection as $key => $video) { + if ($this->isVideoItem($video)) { + unset($video['video_url_default'], $video['video_title_default']); + $video['entity_id'] = $entityId; + $mediaCollection[$key] = $video; + } + } + return $mediaCollection; + } } diff --git a/app/code/Magento/ProductVideo/Test/Mftf/Test/AdminAddDefaultVideoSimpleProductTest.xml b/app/code/Magento/ProductVideo/Test/Mftf/Test/AdminAddDefaultVideoSimpleProductTest.xml index bd7cc0cdf5b..2b5f87f78d5 100644 --- a/app/code/Magento/ProductVideo/Test/Mftf/Test/AdminAddDefaultVideoSimpleProductTest.xml +++ b/app/code/Magento/ProductVideo/Test/Mftf/Test/AdminAddDefaultVideoSimpleProductTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminAddDefaultVideoSimpleProductTest"> <annotations> <group value="ProductVideo"/> diff --git a/app/code/Magento/ProductVideo/Test/Mftf/Test/AdminRemoveDefaultVideoSimpleProductTest.xml b/app/code/Magento/ProductVideo/Test/Mftf/Test/AdminRemoveDefaultVideoSimpleProductTest.xml index f5a7886fed4..d4da0ffa544 100644 --- a/app/code/Magento/ProductVideo/Test/Mftf/Test/AdminRemoveDefaultVideoSimpleProductTest.xml +++ b/app/code/Magento/ProductVideo/Test/Mftf/Test/AdminRemoveDefaultVideoSimpleProductTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminRemoveDefaultVideoSimpleProductTest"> <annotations> <group value="ProductVideo"/> diff --git a/app/code/Magento/ProductVideo/view/frontend/web/js/fotorama-add-video-events.js b/app/code/Magento/ProductVideo/view/frontend/web/js/fotorama-add-video-events.js index 20deba5b9b4..cd0f3b3d630 100644 --- a/app/code/Magento/ProductVideo/view/frontend/web/js/fotorama-add-video-events.js +++ b/app/code/Magento/ProductVideo/view/frontend/web/js/fotorama-add-video-events.js @@ -177,12 +177,14 @@ define([ * @private */ clearEvents: function () { - this.fotoramaItem.off( - 'fotorama:show.' + this.PV + - ' fotorama:showend.' + this.PV + - ' fotorama:fullscreenenter.' + this.PV + - ' fotorama:fullscreenexit.' + this.PV - ); + if (this.fotoramaItem !== undefined) { + this.fotoramaItem.off( + 'fotorama:show.' + this.PV + + ' fotorama:showend.' + this.PV + + ' fotorama:fullscreenenter.' + this.PV + + ' fotorama:fullscreenexit.' + this.PV + ); + } }, /** diff --git a/app/code/Magento/Quote/Api/CartRepositoryInterface.php b/app/code/Magento/Quote/Api/CartRepositoryInterface.php index f507c1e83f1..ee122d1b02f 100644 --- a/app/code/Magento/Quote/Api/CartRepositoryInterface.php +++ b/app/code/Magento/Quote/Api/CartRepositoryInterface.php @@ -25,10 +25,9 @@ public function get($cartId); * Enables administrative users to list carts that match specified search criteria. * * This call returns an array of objects, but detailed information about each object’s attributes might not be - * included. See http://devdocs.magento.com/codelinks/attributes.html#CartRepositoryInterface to determine + * included. See https://devdocs.magento.com/codelinks/attributes.html#CartRepositoryInterface to determine * which call to use to get detailed information about all attributes for an object. * - * * @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria * @return \Magento\Quote\Api\Data\CartSearchResultsInterface */ diff --git a/app/code/Magento/Quote/Api/GuestPaymentMethodManagementInterface.php b/app/code/Magento/Quote/Api/GuestPaymentMethodManagementInterface.php index a9d1772684b..f1ee8bd83fe 100644 --- a/app/code/Magento/Quote/Api/GuestPaymentMethodManagementInterface.php +++ b/app/code/Magento/Quote/Api/GuestPaymentMethodManagementInterface.php @@ -37,7 +37,7 @@ public function get($cartId); * List available payment methods for a specified shopping cart. * * This call returns an array of objects, but detailed information about each object’s attributes might not be - * included. See http://devdocs.magento.com/codelinks/attributes.html#GuestPaymentMethodManagementInterface to + * included. See https://devdocs.magento.com/codelinks/attributes.html#GuestPaymentMethodManagementInterface to * determine which call to use to get detailed information about all attributes for an object. * * @param string $cartId The cart ID. diff --git a/app/code/Magento/Quote/Api/PaymentMethodManagementInterface.php b/app/code/Magento/Quote/Api/PaymentMethodManagementInterface.php index 50fac772ed3..b00a6617bea 100644 --- a/app/code/Magento/Quote/Api/PaymentMethodManagementInterface.php +++ b/app/code/Magento/Quote/Api/PaymentMethodManagementInterface.php @@ -37,7 +37,7 @@ public function get($cartId); * Lists available payment methods for a specified shopping cart. * * This call returns an array of objects, but detailed information about each object’s attributes might not be - * included. See http://devdocs.magento.com/codelinks/attributes.html#PaymentMethodManagementInterface to + * included. See https://devdocs.magento.com/codelinks/attributes.html#PaymentMethodManagementInterface to * determine which call to use to get detailed information about all attributes for an object. * * @param int $cartId The cart ID. diff --git a/app/code/Magento/Quote/Model/Cart/CartTotalRepository.php b/app/code/Magento/Quote/Model/Cart/CartTotalRepository.php index e18ab8587fc..60e5ad9f4ca 100644 --- a/app/code/Magento/Quote/Model/Cart/CartTotalRepository.php +++ b/app/code/Magento/Quote/Model/Cart/CartTotalRepository.php @@ -79,7 +79,7 @@ public function __construct( } /** - * {@inheritDoc} + * @inheritdoc * * @param int $cartId The cart ID. * @return Totals Quote totals data. diff --git a/app/code/Magento/Quote/Model/Quote.php b/app/code/Magento/Quote/Model/Quote.php index 3f045197136..b1f68d0411c 100644 --- a/app/code/Magento/Quote/Model/Quote.php +++ b/app/code/Magento/Quote/Model/Quote.php @@ -1375,14 +1375,13 @@ public function addShippingAddress(\Magento\Quote\Api\Data\AddressInterface $add * * @param bool $useCache * @return \Magento\Eav\Model\Entity\Collection\AbstractCollection - * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function getItemsCollection($useCache = true) { - if ($this->hasItemsCollection()) { + if ($this->hasItemsCollection() && $useCache) { return $this->getData('items_collection'); } - if (null === $this->_items) { + if (null === $this->_items || !$useCache) { $this->_items = $this->_quoteItemCollectionFactory->create(); $this->extensionAttributesJoinProcessor->process($this->_items); $this->_items->setQuote($this); @@ -1399,7 +1398,7 @@ public function getAllItems() { $items = []; foreach ($this->getItemsCollection() as $item) { - /** @var \Magento\Quote\Model\ResourceModel\Quote\Item $item */ + /** @var \Magento\Quote\Model\Quote\Item $item */ if (!$item->isDeleted()) { $items[] = $item; } @@ -2246,6 +2245,11 @@ public function validateMinimumAmount($multishipping = false) if (!$minOrderActive) { return true; } + $includeDiscount = $this->_scopeConfig->getValue( + 'sales/minimum_order/include_discount_amount', + \Magento\Store\Model\ScopeInterface::SCOPE_STORE, + $storeId + ); $minOrderMulti = $this->_scopeConfig->isSetFlag( 'sales/minimum_order/multi_address', \Magento\Store\Model\ScopeInterface::SCOPE_STORE, @@ -2279,7 +2283,10 @@ public function validateMinimumAmount($multishipping = false) $taxes = ($taxInclude) ? $address->getBaseTaxAmount() : 0; foreach ($address->getQuote()->getItemsCollection() as $item) { /** @var \Magento\Quote\Model\Quote\Item $item */ - $amount = $item->getBaseRowTotal() - $item->getBaseDiscountAmount() + $taxes; + $amount = $includeDiscount ? + $item->getBaseRowTotal() - $item->getBaseDiscountAmount() + $taxes : + $item->getBaseRowTotal() + $taxes; + if ($amount < $minAmount) { return false; } @@ -2289,7 +2296,9 @@ public function validateMinimumAmount($multishipping = false) $baseTotal = 0; foreach ($addresses as $address) { $taxes = ($taxInclude) ? $address->getBaseTaxAmount() : 0; - $baseTotal += $address->getBaseSubtotalWithDiscount() + $taxes; + $baseTotal += $includeDiscount ? + $address->getBaseSubtotalWithDiscount() + $taxes : + $address->getBaseSubtotal() + $taxes; } if ($baseTotal < $minAmount) { return false; diff --git a/app/code/Magento/Quote/Model/Quote/Address.php b/app/code/Magento/Quote/Model/Quote/Address.php index bafd6634a94..3ecbc69b807 100644 --- a/app/code/Magento/Quote/Model/Quote/Address.php +++ b/app/code/Magento/Quote/Model/Quote/Address.php @@ -1149,6 +1149,11 @@ public function validateMinimumAmount() return true; } + $includeDiscount = $this->_scopeConfig->getValue( + 'sales/minimum_order/include_discount_amount', + \Magento\Store\Model\ScopeInterface::SCOPE_STORE, + $storeId + ); $amount = $this->_scopeConfig->getValue( 'sales/minimum_order/amount', \Magento\Store\Model\ScopeInterface::SCOPE_STORE, @@ -1159,9 +1164,12 @@ public function validateMinimumAmount() \Magento\Store\Model\ScopeInterface::SCOPE_STORE, $storeId ); + $taxes = $taxInclude ? $this->getBaseTaxAmount() : 0; - return ($this->getBaseSubtotalWithDiscount() + $taxes >= $amount); + return $includeDiscount ? + ($this->getBaseSubtotalWithDiscount() + $taxes >= $amount) : + ($this->getBaseSubtotal() + $taxes >= $amount); } /** diff --git a/app/code/Magento/Quote/Model/Quote/Address/Item.php b/app/code/Magento/Quote/Model/Quote/Address/Item.php index d7014403f78..ade4f9270b6 100644 --- a/app/code/Magento/Quote/Model/Quote/Address/Item.php +++ b/app/code/Magento/Quote/Model/Quote/Address/Item.php @@ -8,6 +8,8 @@ use Magento\Quote\Model\Quote; /** + * Quote item model. + * * @api * @method int getParentItemId() * @method \Magento\Quote\Model\Quote\Address\Item setParentItemId(int $value) @@ -46,6 +48,8 @@ * @method \Magento\Quote\Model\Quote\Address\Item setSuperProductId(int $value) * @method int getParentProductId() * @method \Magento\Quote\Model\Quote\Address\Item setParentProductId(int $value) + * @method int getStoreId() + * @method \Magento\Quote\Model\Quote\Address\Item setStoreId(int $value) * @method string getSku() * @method \Magento\Quote\Model\Quote\Address\Item setSku(string $value) * @method string getImage() @@ -101,7 +105,7 @@ class Item extends \Magento\Quote\Model\Quote\Item\AbstractItem protected $_quote; /** - * @return void + * @inheritdoc */ protected function _construct() { @@ -109,7 +113,7 @@ protected function _construct() } /** - * @return $this|\Magento\Quote\Model\Quote\Item\AbstractItem + * @inheritdoc */ public function beforeSave() { @@ -154,6 +158,8 @@ public function getQuote() } /** + * Import quote item. + * * @param \Magento\Quote\Model\Quote\Item $quoteItem * @return $this */ @@ -168,6 +174,8 @@ public function importQuoteItem(\Magento\Quote\Model\Quote\Item $quoteItem) $quoteItem->getProductId() )->setProduct( $quoteItem->getProduct() + )->setStoreId( + $quoteItem->getStoreId() )->setSku( $quoteItem->getSku() )->setName( @@ -190,10 +198,9 @@ public function importQuoteItem(\Magento\Quote\Model\Quote\Item $quoteItem) } /** - * @param string $code - * @return \Magento\Catalog\Model\Product\Configuration\Item\Option\OptionInterface|null + * @inheritdoc */ - public function getOptionBycode($code) + public function getOptionByCode($code) { if ($this->getQuoteItem()) { return $this->getQuoteItem()->getOptionBycode($code); diff --git a/app/code/Magento/Quote/Model/Quote/Address/Total.php b/app/code/Magento/Quote/Model/Quote/Address/Total.php index 42224c970ed..00060c15c10 100644 --- a/app/code/Magento/Quote/Model/Quote/Address/Total.php +++ b/app/code/Magento/Quote/Model/Quote/Address/Total.php @@ -6,6 +6,8 @@ namespace Magento\Quote\Model\Quote\Address; /** + * Class Total + * * @method string getCode() * * @api @@ -54,6 +56,8 @@ public function __construct( */ public function setTotalAmount($code, $amount) { + $amount = is_float($amount) ? round($amount, 4) : $amount; + $this->totalAmounts[$code] = $amount; if ($code != 'subtotal') { $code = $code . '_amount'; @@ -72,6 +76,8 @@ public function setTotalAmount($code, $amount) */ public function setBaseTotalAmount($code, $amount) { + $amount = is_float($amount) ? round($amount, 4) : $amount; + $this->baseTotalAmounts[$code] = $amount; if ($code != 'subtotal') { $code = $code . '_amount'; @@ -167,6 +173,7 @@ public function getAllBaseTotalAmounts() /** * Set the full info, which is used to capture tax related information. + * * If a string is used, it is assumed to be serialized. * * @param array|string $info diff --git a/app/code/Magento/Quote/Model/Quote/Item/ToOrderItem.php b/app/code/Magento/Quote/Model/Quote/Item/ToOrderItem.php index 32687499274..6192d3471cc 100644 --- a/app/code/Magento/Quote/Model/Quote/Item/ToOrderItem.php +++ b/app/code/Magento/Quote/Model/Quote/Item/ToOrderItem.php @@ -48,6 +48,8 @@ public function __construct( } /** + * Convert quote item(quote address item) into order item. + * * @param Item|AddressItem $item * @param array $data * @return OrderItemInterface @@ -63,6 +65,16 @@ public function convert($item, $data = []) 'to_order_item', $item ); + if ($item instanceof \Magento\Quote\Model\Quote\Address\Item) { + $orderItemData = array_merge( + $orderItemData, + $this->objectCopyService->getDataFromFieldset( + 'quote_convert_address_item', + 'to_order_item', + $item + ) + ); + } if (!$item->getNoDiscount()) { $data = array_merge( $data, diff --git a/app/code/Magento/Quote/Model/QuoteValidator.php b/app/code/Magento/Quote/Model/QuoteValidator.php index 062cf76bcaa..e67a0f13562 100644 --- a/app/code/Magento/Quote/Model/QuoteValidator.php +++ b/app/code/Magento/Quote/Model/QuoteValidator.php @@ -25,7 +25,7 @@ class QuoteValidator /** * Maximum available number */ - const MAXIMUM_AVAILABLE_NUMBER = 99999999; + const MAXIMUM_AVAILABLE_NUMBER = 10000000000000000; /** * @var AllowedCountries diff --git a/app/code/Magento/Quote/Plugin/UpdateQuoteItemStore.php b/app/code/Magento/Quote/Plugin/UpdateQuoteItemStore.php new file mode 100644 index 00000000000..19a7e03264d --- /dev/null +++ b/app/code/Magento/Quote/Plugin/UpdateQuoteItemStore.php @@ -0,0 +1,72 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Quote\Plugin; + +use Magento\Checkout\Model\Session; +use Magento\Quote\Model\QuoteRepository; +use Magento\Store\Api\Data\StoreInterface; +use Magento\Store\Model\StoreSwitcherInterface; + +/** + * Updates quote items store id. + * + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) + */ +class UpdateQuoteItemStore +{ + /** + * @var QuoteRepository + */ + private $quoteRepository; + + /** + * @var Session + */ + private $checkoutSession; + + /** + * @param QuoteRepository $quoteRepository + * @param Session $checkoutSession + */ + public function __construct( + QuoteRepository $quoteRepository, + Session $checkoutSession + ) { + $this->quoteRepository = $quoteRepository; + $this->checkoutSession = $checkoutSession; + } + + /** + * Update store id in active quote after store view switching. + * + * @param StoreSwitcherInterface $subject + * @param string $result + * @param StoreInterface $fromStore store where we came from + * @param StoreInterface $targetStore store where to go to + * @param string $redirectUrl original url requested for redirect after switching + * @return string url to be redirected after switching + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterSwitch( + StoreSwitcherInterface $subject, + $result, + StoreInterface $fromStore, + StoreInterface $targetStore, + string $redirectUrl + ): string { + $quote = $this->checkoutSession->getQuote(); + if ($quote->getIsActive()) { + $quote->setStoreId( + $targetStore->getId() + ); + $quote->getItemsCollection(false); + $this->quoteRepository->save($quote); + } + return $result; + } +} diff --git a/app/code/Magento/Quote/Test/Unit/Model/Cart/CartTotalRepositoryTest.php b/app/code/Magento/Quote/Test/Unit/Model/Cart/CartTotalRepositoryTest.php index 1e999cb5e52..804f0863d2d 100644 --- a/app/code/Magento/Quote/Test/Unit/Model/Cart/CartTotalRepositoryTest.php +++ b/app/code/Magento/Quote/Test/Unit/Model/Cart/CartTotalRepositoryTest.php @@ -77,7 +77,8 @@ protected function setUp() 'getAllVisibleItems', 'getBaseCurrencyCode', 'getQuoteCurrencyCode', - 'getItemsQty' + 'getItemsQty', + 'collectTotals' ]); $this->quoteRepositoryMock = $this->createMock(\Magento\Quote\Api\CartRepositoryInterface::class); $this->addressMock = $this->createPartialMock( diff --git a/app/code/Magento/Quote/Test/Unit/Model/Quote/AddressTest.php b/app/code/Magento/Quote/Test/Unit/Model/Quote/AddressTest.php index c1c131260f1..242f81b2225 100644 --- a/app/code/Magento/Quote/Test/Unit/Model/Quote/AddressTest.php +++ b/app/code/Magento/Quote/Test/Unit/Model/Quote/AddressTest.php @@ -216,6 +216,7 @@ public function testValidateMinimumAmountVirtual() $scopeConfigValues = [ ['sales/minimum_order/active', ScopeInterface::SCOPE_STORE, $storeId, true], ['sales/minimum_order/amount', ScopeInterface::SCOPE_STORE, $storeId, 20], + ['sales/minimum_order/include_discount_amount', ScopeInterface::SCOPE_STORE, $storeId, true], ['sales/minimum_order/tax_including', ScopeInterface::SCOPE_STORE, $storeId, true], ]; @@ -240,6 +241,31 @@ public function testValidateMinimumAmount() $scopeConfigValues = [ ['sales/minimum_order/active', ScopeInterface::SCOPE_STORE, $storeId, true], ['sales/minimum_order/amount', ScopeInterface::SCOPE_STORE, $storeId, 20], + ['sales/minimum_order/include_discount_amount', ScopeInterface::SCOPE_STORE, $storeId, true], + ['sales/minimum_order/tax_including', ScopeInterface::SCOPE_STORE, $storeId, true], + ]; + + $this->quote->expects($this->once()) + ->method('getStoreId') + ->willReturn($storeId); + $this->quote->expects($this->once()) + ->method('getIsVirtual') + ->willReturn(false); + + $this->scopeConfig->expects($this->once()) + ->method('isSetFlag') + ->willReturnMap($scopeConfigValues); + + $this->assertTrue($this->address->validateMinimumAmount()); + } + + public function testValidateMiniumumAmountWithoutDiscount() + { + $storeId = 1; + $scopeConfigValues = [ + ['sales/minimum_order/active', ScopeInterface::SCOPE_STORE, $storeId, true], + ['sales/minimum_order/amount', ScopeInterface::SCOPE_STORE, $storeId, 20], + ['sales/minimum_order/include_discount_amount', ScopeInterface::SCOPE_STORE, $storeId, false], ['sales/minimum_order/tax_including', ScopeInterface::SCOPE_STORE, $storeId, true], ]; @@ -263,6 +289,7 @@ public function testValidateMinimumAmountNegative() $scopeConfigValues = [ ['sales/minimum_order/active', ScopeInterface::SCOPE_STORE, $storeId, true], ['sales/minimum_order/amount', ScopeInterface::SCOPE_STORE, $storeId, 20], + ['sales/minimum_order/include_discount_amount', ScopeInterface::SCOPE_STORE, $storeId, true], ['sales/minimum_order/tax_including', ScopeInterface::SCOPE_STORE, $storeId, true], ]; diff --git a/app/code/Magento/Quote/Test/Unit/Model/QuoteTest.php b/app/code/Magento/Quote/Test/Unit/Model/QuoteTest.php index 22785f051dc..07e203f7171 100644 --- a/app/code/Magento/Quote/Test/Unit/Model/QuoteTest.php +++ b/app/code/Magento/Quote/Test/Unit/Model/QuoteTest.php @@ -975,6 +975,7 @@ public function testValidateMinimumAmount() ['sales/minimum_order/active', ScopeInterface::SCOPE_STORE, $storeId, true], ['sales/minimum_order/multi_address', ScopeInterface::SCOPE_STORE, $storeId, true], ['sales/minimum_order/amount', ScopeInterface::SCOPE_STORE, $storeId, 20], + ['sales/minimum_order/include_discount_amount', ScopeInterface::SCOPE_STORE, $storeId, true], ['sales/minimum_order/tax_including', ScopeInterface::SCOPE_STORE, $storeId, true], ]; $this->scopeConfig->expects($this->any()) @@ -1001,6 +1002,7 @@ public function testValidateMinimumAmountNegative() ['sales/minimum_order/active', ScopeInterface::SCOPE_STORE, $storeId, true], ['sales/minimum_order/multi_address', ScopeInterface::SCOPE_STORE, $storeId, true], ['sales/minimum_order/amount', ScopeInterface::SCOPE_STORE, $storeId, 20], + ['sales/minimum_order/include_discount_amount', ScopeInterface::SCOPE_STORE, $storeId, true], ['sales/minimum_order/tax_including', ScopeInterface::SCOPE_STORE, $storeId, true], ]; $this->scopeConfig->expects($this->any()) diff --git a/app/code/Magento/Quote/etc/db_schema.xml b/app/code/Magento/Quote/etc/db_schema.xml index 7dd215b87e8..6f9f81ba6b3 100644 --- a/app/code/Magento/Quote/etc/db_schema.xml +++ b/app/code/Magento/Quote/etc/db_schema.xml @@ -37,9 +37,9 @@ comment="Store Currency Code"/> <column xsi:type="varchar" name="quote_currency_code" nullable="true" length="255" comment="Quote Currency Code"/> - <column xsi:type="decimal" name="grand_total" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="grand_total" scale="4" precision="20" unsigned="false" nullable="true" default="0" comment="Grand Total"/> - <column xsi:type="decimal" name="base_grand_total" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_grand_total" scale="4" precision="20" unsigned="false" nullable="true" default="0" comment="Base Grand Total"/> <column xsi:type="varchar" name="checkout_method" nullable="true" length="255" comment="Checkout Method"/> <column xsi:type="int" name="customer_id" padding="10" unsigned="true" nullable="true" identity="false" @@ -68,19 +68,19 @@ <column xsi:type="varchar" name="coupon_code" nullable="true" length="255" comment="Coupon Code"/> <column xsi:type="varchar" name="global_currency_code" nullable="true" length="255" comment="Global Currency Code"/> - <column xsi:type="decimal" name="base_to_global_rate" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_to_global_rate" scale="4" precision="20" unsigned="false" nullable="true" comment="Base To Global Rate"/> - <column xsi:type="decimal" name="base_to_quote_rate" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_to_quote_rate" scale="4" precision="20" unsigned="false" nullable="true" comment="Base To Quote Rate"/> <column xsi:type="varchar" name="customer_taxvat" nullable="true" length="255" comment="Customer Taxvat"/> <column xsi:type="varchar" name="customer_gender" nullable="true" length="255" comment="Customer Gender"/> - <column xsi:type="decimal" name="subtotal" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="subtotal" scale="4" precision="20" unsigned="false" nullable="true" comment="Subtotal"/> - <column xsi:type="decimal" name="base_subtotal" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_subtotal" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Subtotal"/> - <column xsi:type="decimal" name="subtotal_with_discount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="subtotal_with_discount" scale="4" precision="20" unsigned="false" nullable="true" comment="Subtotal With Discount"/> - <column xsi:type="decimal" name="base_subtotal_with_discount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_subtotal_with_discount" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Subtotal With Discount"/> <column xsi:type="int" name="is_changed" padding="10" unsigned="true" nullable="true" identity="false" comment="Is Changed"/> @@ -143,57 +143,57 @@ comment="Shipping Description"/> <column xsi:type="decimal" name="weight" scale="4" precision="12" unsigned="false" nullable="false" default="0" comment="Weight"/> - <column xsi:type="decimal" name="subtotal" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="subtotal" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Subtotal"/> - <column xsi:type="decimal" name="base_subtotal" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="base_subtotal" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Base Subtotal"/> - <column xsi:type="decimal" name="subtotal_with_discount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="subtotal_with_discount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Subtotal With Discount"/> - <column xsi:type="decimal" name="base_subtotal_with_discount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_subtotal_with_discount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Base Subtotal With Discount"/> - <column xsi:type="decimal" name="tax_amount" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="tax_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Tax Amount"/> - <column xsi:type="decimal" name="base_tax_amount" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="base_tax_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Base Tax Amount"/> - <column xsi:type="decimal" name="shipping_amount" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="shipping_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Shipping Amount"/> - <column xsi:type="decimal" name="base_shipping_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_shipping_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Base Shipping Amount"/> - <column xsi:type="decimal" name="shipping_tax_amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="shipping_tax_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Shipping Tax Amount"/> - <column xsi:type="decimal" name="base_shipping_tax_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_shipping_tax_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Shipping Tax Amount"/> - <column xsi:type="decimal" name="discount_amount" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="discount_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Discount Amount"/> - <column xsi:type="decimal" name="base_discount_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_discount_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Base Discount Amount"/> - <column xsi:type="decimal" name="grand_total" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="grand_total" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Grand Total"/> - <column xsi:type="decimal" name="base_grand_total" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="base_grand_total" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Base Grand Total"/> <column xsi:type="text" name="customer_notes" nullable="true" comment="Customer Notes"/> <column xsi:type="text" name="applied_taxes" nullable="true" comment="Applied Taxes"/> <column xsi:type="varchar" name="discount_description" nullable="true" length="255" comment="Discount Description"/> - <column xsi:type="decimal" name="shipping_discount_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="shipping_discount_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Shipping Discount Amount"/> - <column xsi:type="decimal" name="base_shipping_discount_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_shipping_discount_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Shipping Discount Amount"/> - <column xsi:type="decimal" name="subtotal_incl_tax" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="subtotal_incl_tax" scale="4" precision="20" unsigned="false" nullable="true" comment="Subtotal Incl Tax"/> - <column xsi:type="decimal" name="base_subtotal_total_incl_tax" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_subtotal_total_incl_tax" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Subtotal Total Incl Tax"/> - <column xsi:type="decimal" name="discount_tax_compensation_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="discount_tax_compensation_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Discount Tax Compensation Amount"/> - <column xsi:type="decimal" name="base_discount_tax_compensation_amount" scale="4" precision="12" + <column xsi:type="decimal" name="base_discount_tax_compensation_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Discount Tax Compensation Amount"/> - <column xsi:type="decimal" name="shipping_discount_tax_compensation_amount" scale="4" precision="12" + <column xsi:type="decimal" name="shipping_discount_tax_compensation_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Shipping Discount Tax Compensation Amount"/> - <column xsi:type="decimal" name="base_shipping_discount_tax_compensation_amnt" scale="4" precision="12" + <column xsi:type="decimal" name="base_shipping_discount_tax_compensation_amnt" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Shipping Discount Tax Compensation Amount"/> - <column xsi:type="decimal" name="shipping_incl_tax" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="shipping_incl_tax" scale="4" precision="20" unsigned="false" nullable="true" comment="Shipping Incl Tax"/> - <column xsi:type="decimal" name="base_shipping_incl_tax" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_shipping_incl_tax" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Shipping Incl Tax"/> <column xsi:type="text" name="vat_id" nullable="true" comment="Vat Id"/> <column xsi:type="smallint" name="vat_is_valid" padding="6" unsigned="false" nullable="true" identity="false" @@ -249,45 +249,45 @@ comment="Custom Price"/> <column xsi:type="decimal" name="discount_percent" scale="4" precision="12" unsigned="false" nullable="true" default="0" comment="Discount Percent"/> - <column xsi:type="decimal" name="discount_amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="discount_amount" scale="4" precision="20" unsigned="false" nullable="true" default="0" comment="Discount Amount"/> - <column xsi:type="decimal" name="base_discount_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_discount_amount" scale="4" precision="20" unsigned="false" nullable="true" default="0" comment="Base Discount Amount"/> <column xsi:type="decimal" name="tax_percent" scale="4" precision="12" unsigned="false" nullable="true" default="0" comment="Tax Percent"/> - <column xsi:type="decimal" name="tax_amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="tax_amount" scale="4" precision="20" unsigned="false" nullable="true" default="0" comment="Tax Amount"/> - <column xsi:type="decimal" name="base_tax_amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_tax_amount" scale="4" precision="20" unsigned="false" nullable="true" default="0" comment="Base Tax Amount"/> - <column xsi:type="decimal" name="row_total" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="row_total" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Row Total"/> - <column xsi:type="decimal" name="base_row_total" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="base_row_total" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Base Row Total"/> - <column xsi:type="decimal" name="row_total_with_discount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="row_total_with_discount" scale="4" precision="20" unsigned="false" nullable="true" default="0" comment="Row Total With Discount"/> <column xsi:type="decimal" name="row_weight" scale="4" precision="12" unsigned="false" nullable="true" default="0" comment="Row Weight"/> <column xsi:type="varchar" name="product_type" nullable="true" length="255" comment="Product Type"/> - <column xsi:type="decimal" name="base_tax_before_discount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_tax_before_discount" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Tax Before Discount"/> - <column xsi:type="decimal" name="tax_before_discount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="tax_before_discount" scale="4" precision="20" unsigned="false" nullable="true" comment="Tax Before Discount"/> <column xsi:type="decimal" name="original_custom_price" scale="4" precision="12" unsigned="false" nullable="true" comment="Original Custom Price"/> <column xsi:type="varchar" name="redirect_url" nullable="true" length="255" comment="Redirect Url"/> <column xsi:type="decimal" name="base_cost" scale="4" precision="12" unsigned="false" nullable="true" comment="Base Cost"/> - <column xsi:type="decimal" name="price_incl_tax" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="price_incl_tax" scale="4" precision="20" unsigned="false" nullable="true" comment="Price Incl Tax"/> - <column xsi:type="decimal" name="base_price_incl_tax" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_price_incl_tax" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Price Incl Tax"/> - <column xsi:type="decimal" name="row_total_incl_tax" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="row_total_incl_tax" scale="4" precision="20" unsigned="false" nullable="true" comment="Row Total Incl Tax"/> - <column xsi:type="decimal" name="base_row_total_incl_tax" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_row_total_incl_tax" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Row Total Incl Tax"/> - <column xsi:type="decimal" name="discount_tax_compensation_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="discount_tax_compensation_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Discount Tax Compensation Amount"/> - <column xsi:type="decimal" name="base_discount_tax_compensation_amount" scale="4" precision="12" + <column xsi:type="decimal" name="base_discount_tax_compensation_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Discount Tax Compensation Amount"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="item_id"/> @@ -330,19 +330,19 @@ comment="Weight"/> <column xsi:type="decimal" name="qty" scale="4" precision="12" unsigned="false" nullable="false" default="0" comment="Qty"/> - <column xsi:type="decimal" name="discount_amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="discount_amount" scale="4" precision="20" unsigned="false" nullable="true" default="0" comment="Discount Amount"/> - <column xsi:type="decimal" name="tax_amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="tax_amount" scale="4" precision="20" unsigned="false" nullable="true" default="0" comment="Tax Amount"/> - <column xsi:type="decimal" name="row_total" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="row_total" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Row Total"/> - <column xsi:type="decimal" name="base_row_total" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="base_row_total" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Base Row Total"/> - <column xsi:type="decimal" name="row_total_with_discount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="row_total_with_discount" scale="4" precision="20" unsigned="false" nullable="true" default="0" comment="Row Total With Discount"/> - <column xsi:type="decimal" name="base_discount_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_discount_amount" scale="4" precision="20" unsigned="false" nullable="true" default="0" comment="Base Discount Amount"/> - <column xsi:type="decimal" name="base_tax_amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_tax_amount" scale="4" precision="20" unsigned="false" nullable="true" default="0" comment="Base Tax Amount"/> <column xsi:type="decimal" name="row_weight" scale="4" precision="12" unsigned="false" nullable="true" default="0" comment="Row Weight"/> @@ -352,6 +352,8 @@ comment="Super Product Id"/> <column xsi:type="int" name="parent_product_id" padding="10" unsigned="true" nullable="true" identity="false" comment="Parent Product Id"/> + <column xsi:type="smallint" name="store_id" padding="5" unsigned="true" nullable="true" identity="false" + comment="Store Id"/> <column xsi:type="varchar" name="sku" nullable="true" length="255" comment="Sku"/> <column xsi:type="varchar" name="image" nullable="true" length="255" comment="Image"/> <column xsi:type="varchar" name="name" nullable="true" length="255" comment="Name"/> @@ -370,17 +372,17 @@ comment="Base Price"/> <column xsi:type="decimal" name="base_cost" scale="4" precision="12" unsigned="false" nullable="true" comment="Base Cost"/> - <column xsi:type="decimal" name="price_incl_tax" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="price_incl_tax" scale="4" precision="20" unsigned="false" nullable="true" comment="Price Incl Tax"/> - <column xsi:type="decimal" name="base_price_incl_tax" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_price_incl_tax" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Price Incl Tax"/> - <column xsi:type="decimal" name="row_total_incl_tax" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="row_total_incl_tax" scale="4" precision="20" unsigned="false" nullable="true" comment="Row Total Incl Tax"/> - <column xsi:type="decimal" name="base_row_total_incl_tax" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_row_total_incl_tax" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Row Total Incl Tax"/> - <column xsi:type="decimal" name="discount_tax_compensation_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="discount_tax_compensation_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Discount Tax Compensation Amount"/> - <column xsi:type="decimal" name="base_discount_tax_compensation_amount" scale="4" precision="12" + <column xsi:type="decimal" name="base_discount_tax_compensation_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Discount Tax Compensation Amount"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="address_item_id"/> @@ -403,6 +405,9 @@ <index referenceId="QUOTE_ADDRESS_ITEM_QUOTE_ITEM_ID" indexType="btree"> <column name="quote_item_id"/> </index> + <index referenceId="QUOTE_ADDRESS_ITEM_STORE_ID" indexType="btree"> + <column name="store_id"/> + </index> </table> <table name="quote_item_option" resource="checkout" engine="innodb" comment="Sales Flat Quote Item Option"> <column xsi:type="int" name="option_id" padding="10" unsigned="true" nullable="false" identity="true" @@ -472,7 +477,7 @@ <column xsi:type="varchar" name="code" nullable="true" length="255" comment="Code"/> <column xsi:type="varchar" name="method" nullable="true" length="255" comment="Method"/> <column xsi:type="text" name="method_description" nullable="true" comment="Method Description"/> - <column xsi:type="decimal" name="price" scale="4" precision="12" unsigned="false" nullable="false" default="0" + <column xsi:type="decimal" name="price" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Price"/> <column xsi:type="text" name="error_message" nullable="true" comment="Error Message"/> <column xsi:type="text" name="method_title" nullable="true" comment="Method Title"/> diff --git a/app/code/Magento/Quote/etc/db_schema_whitelist.json b/app/code/Magento/Quote/etc/db_schema_whitelist.json index c2cc34293dc..5667a9a5b46 100644 --- a/app/code/Magento/Quote/etc/db_schema_whitelist.json +++ b/app/code/Magento/Quote/etc/db_schema_whitelist.json @@ -212,6 +212,7 @@ "product_id": true, "super_product_id": true, "parent_product_id": true, + "store_id": true, "sku": true, "image": true, "name": true, @@ -233,7 +234,8 @@ "index": { "QUOTE_ADDRESS_ITEM_QUOTE_ADDRESS_ID": true, "QUOTE_ADDRESS_ITEM_PARENT_ITEM_ID": true, - "QUOTE_ADDRESS_ITEM_QUOTE_ITEM_ID": true + "QUOTE_ADDRESS_ITEM_QUOTE_ITEM_ID": true, + "QUOTE_ADDRESS_ITEM_STORE_ID": true }, "constraint": { "PRIMARY": true, diff --git a/app/code/Magento/Quote/etc/fieldset.xml b/app/code/Magento/Quote/etc/fieldset.xml index 55ec76a647f..85ee20c7f85 100644 --- a/app/code/Magento/Quote/etc/fieldset.xml +++ b/app/code/Magento/Quote/etc/fieldset.xml @@ -186,6 +186,11 @@ <aspect name="to_order_address" /> </field> </fieldset> + <fieldset id="quote_convert_address_item"> + <field name="quote_item_id"> + <aspect name="to_order_item" /> + </field> + </fieldset> <fieldset id="quote_convert_item"> <field name="sku"> <aspect name="to_order_item" /> diff --git a/app/code/Magento/Quote/etc/frontend/di.xml b/app/code/Magento/Quote/etc/frontend/di.xml index 125afb96f20..ecad94fbbc2 100644 --- a/app/code/Magento/Quote/etc/frontend/di.xml +++ b/app/code/Magento/Quote/etc/frontend/di.xml @@ -12,6 +12,9 @@ <argument name="checkoutSession" xsi:type="object">Magento\Checkout\Model\Session\Proxy</argument> </arguments> </type> + <type name="Magento\Store\Model\StoreSwitcherInterface"> + <plugin name="update_quote_item_store_after_switch_store_view" type="Magento\Quote\Plugin\UpdateQuoteItemStore"/> + </type> <type name="Magento\Store\Api\StoreCookieManagerInterface"> <plugin name="update_quote_store_after_switch_store_view" type="Magento\Quote\Plugin\UpdateQuoteStore"/> </type> diff --git a/app/code/Magento/Quote/etc/sales.xml b/app/code/Magento/Quote/etc/sales.xml index 3d54a6375c8..3db72a12262 100644 --- a/app/code/Magento/Quote/etc/sales.xml +++ b/app/code/Magento/Quote/etc/sales.xml @@ -9,7 +9,7 @@ <section name="quote"> <group name="totals"> <item name="subtotal" instance="Magento\Quote\Model\Quote\Address\Total\Subtotal" sort_order="100"/> - <item name="shipping" instance="Magento\Quote\Model\Quote\Address\Total\Shipping" sort_order="250"/> + <item name="shipping" instance="Magento\Quote\Model\Quote\Address\Total\Shipping" sort_order="350"/> <item name="grand_total" instance="Magento\Quote\Model\Quote\Address\Total\Grand" sort_order="550"/> </group> </section> diff --git a/app/code/Magento/QuoteAnalytics/README.md b/app/code/Magento/QuoteAnalytics/README.md index d4adcc93132..c5a3857c7af 100644 --- a/app/code/Magento/QuoteAnalytics/README.md +++ b/app/code/Magento/QuoteAnalytics/README.md @@ -1,3 +1,3 @@ # Magento_QuoteAnalytics -The Magento_QuoteAnalytics module configures data definitions for a data collection related to the Quote module entities to be used in [Advanced Reporting](http://devdocs.magento.com/guides/v2.2/advanced-reporting/modules.html). +The Magento_QuoteAnalytics module configures data definitions for a data collection related to the Quote module entities to be used in [Advanced Reporting](https://devdocs.magento.com/guides/v2.2/advanced-reporting/modules.html). diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/ExtractDataFromCart.php b/app/code/Magento/QuoteGraphQl/Model/Cart/ExtractDataFromCart.php index faefa686606..438f2898091 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/ExtractDataFromCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/ExtractDataFromCart.php @@ -40,8 +40,11 @@ public function execute(Quote $cart): array ]; } + $appliedCoupon = $cart->getCouponCode(); + return [ 'items' => $items, + 'applied_coupon' => $appliedCoupon ? ['code' => $appliedCoupon] : null ]; } } diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/Cart.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/Cart.php new file mode 100644 index 00000000000..5023c186f1e --- /dev/null +++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/Cart.php @@ -0,0 +1,67 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\QuoteGraphQl\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\GetCartForUser; +use Magento\QuoteGraphQl\Model\Cart\ExtractDataFromCart; + +/** + * @inheritdoc + */ +class Cart implements ResolverInterface +{ + /** + * @var ExtractDataFromCart + */ + private $extractDataFromCart; + + /** + * @var GetCartForUser + */ + private $getCartForUser; + + /** + * @param GetCartForUser $getCartForUser + * @param ExtractDataFromCart $extractDataFromCart + */ + public function __construct( + GetCartForUser $getCartForUser, + ExtractDataFromCart $extractDataFromCart + ) { + $this->getCartForUser = $getCartForUser; + $this->extractDataFromCart = $extractDataFromCart; + } + + /** + * @inheritdoc + */ + public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null) + { + if (!isset($args['cart_id'])) { + throw new GraphQlInputException(__('Required parameter "cart_id" is missing')); + } + $maskedCartId = $args['cart_id']; + + $currentUserId = $context->getUserId(); + $cart = $this->getCartForUser->execute($maskedCartId, $currentUserId); + + $data = array_merge( + [ + 'cart_id' => $maskedCartId, + 'model' => $cart + ], + $this->extractDataFromCart->execute($cart) + ); + + return $data; + } +} diff --git a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls index edc643973ce..4c1101a5f90 100644 --- a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls +++ b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls @@ -2,7 +2,7 @@ # See COPYING.txt for license details. type Query { - getAvailableShippingMethodsOnCart(input: AvailableShippingMethodsOnCartInput): AvailableShippingMethodsOnCartOutput @doc(description:"Returns available shipping methods for cart by address/address_id") + Cart(cart_id: String!): Cart @resolver (class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\Cart") @doc(description:"Returns information about shopping cart") } type Mutation { @@ -84,10 +84,6 @@ input AvailableShippingMethodsOnCartInput { address: CartAddressInput } -type AvailableShippingMethodsOnCartOutput { - available_shipping_methods: [CheckoutShippingMethod] -} - input ApplyCouponToCartInput { cart_id: String! coupon_code: String! diff --git a/app/code/Magento/ReleaseNotification/README.md b/app/code/Magento/ReleaseNotification/README.md index df3206a176f..1f6cac764b5 100644 --- a/app/code/Magento/ReleaseNotification/README.md +++ b/app/code/Magento/ReleaseNotification/README.md @@ -50,4 +50,4 @@ A clickable link to internal or external content in any text field will be creat ### Link Format Example: -The text: `http://devdocs.magento.com/ [Magento DevDocs].` will appear as [Magento DevDocs](http://devdocs.magento.com/). +The text: `https://devdocs.magento.com/ [Magento DevDocs].` will appear as [Magento DevDocs](https://devdocs.magento.com/). diff --git a/app/code/Magento/ReleaseNotification/i18n/en_US.csv b/app/code/Magento/ReleaseNotification/i18n/en_US.csv index 426cdc0e863..178482dc7a9 100644 --- a/app/code/Magento/ReleaseNotification/i18n/en_US.csv +++ b/app/code/Magento/ReleaseNotification/i18n/en_US.csv @@ -5,11 +5,11 @@ "<![CDATA[ <p>We’ll try to show it again the next time you sign in to Magento Admin.</p> <p>To learn more about new features, see our latest Release Notes in - <a href=""http://devdocs.magento.com/magento-release-information.html"" + <a href=""https://devdocs.magento.com/magento-release-information.html"" target=""_blank"">DevDocs' Release Information</a>. </p>]]>","<![CDATA[ <p>We’ll try to show it again the next time you sign in to Magento Admin.</p> <p>To learn more about new features, see our latest Release Notes in - <a href=""http://devdocs.magento.com/magento-release-information.html"" + <a href=""https://devdocs.magento.com/magento-release-information.html"" target=""_blank"">DevDocs' Release Information</a>. </p>]]>" diff --git a/app/code/Magento/ReleaseNotification/view/adminhtml/ui_component/release_notification.xml b/app/code/Magento/ReleaseNotification/view/adminhtml/ui_component/release_notification.xml index 3134b2a8af2..9c6d152bed2 100644 --- a/app/code/Magento/ReleaseNotification/view/adminhtml/ui_component/release_notification.xml +++ b/app/code/Magento/ReleaseNotification/view/adminhtml/ui_component/release_notification.xml @@ -67,7 +67,7 @@ <item name="text" xsi:type="string" translate="true"><![CDATA[ <p>We’ll try to show it again the next time you sign in to Magento Admin.</p> <p>To learn more about new features, see our latest Release Notes in - <a href="http://devdocs.magento.com/magento-release-information.html" + <a href="https://devdocs.magento.com/magento-release-information.html" target="_blank">DevDocs' Release Information</a>. </p>]]></item> </item> @@ -127,7 +127,7 @@ <item name="text" xsi:type="string" translate="true"><![CDATA[ <p>We’ll try to show it again the next time you sign in to Magento Admin.</p> <p>To learn more about new features, see our latest Release Notes in - <a href="http://devdocs.magento.com/magento-release-information.html" + <a href="https://devdocs.magento.com/magento-release-information.html" target="_blank">DevDocs' Release Information</a>. </p>]]></item> </item> @@ -208,7 +208,7 @@ <item name="text" xsi:type="string" translate="true"><![CDATA[ <p>We’ll try to show it again the next time you sign in to Magento Admin.</p> <p>To learn more about new features, see our latest Release Notes in - <a href="http://devdocs.magento.com/magento-release-information.html" + <a href="https://devdocs.magento.com/magento-release-information.html" target="_blank">DevDocs' Release Information</a>. </p>]]></item> </item> @@ -289,7 +289,7 @@ <item name="text" xsi:type="string" translate="true"><![CDATA[ <p>We’ll try to show it again the next time you sign in to Magento Admin.</p> <p>To learn more about new features, see our latest Release Notes in - <a href="http://devdocs.magento.com/magento-release-information.html" + <a href="https://devdocs.magento.com/magento-release-information.html" target="_blank">DevDocs' Release Information</a>. </p>]]></item> </item> diff --git a/app/code/Magento/Reports/Block/Adminhtml/Sales/Sales/Grid.php b/app/code/Magento/Reports/Block/Adminhtml/Sales/Sales/Grid.php index 9f5f784df67..25a4aa1b88c 100644 --- a/app/code/Magento/Reports/Block/Adminhtml/Sales/Sales/Grid.php +++ b/app/code/Magento/Reports/Block/Adminhtml/Sales/Sales/Grid.php @@ -6,23 +6,58 @@ namespace Magento\Reports\Block\Adminhtml\Sales\Sales; +use Magento\Framework\DataObject; use Magento\Reports\Block\Adminhtml\Grid\Column\Renderer\Currency; +use Magento\Framework\App\ObjectManager; +use Magento\Sales\Model\Order\ConfigFactory; +use Magento\Sales\Model\Order; /** * Adminhtml sales report grid block * - * @author Magento Core Team <core@magentocommerce.com> * @SuppressWarnings(PHPMD.DepthOfInheritance) */ class Grid extends \Magento\Reports\Block\Adminhtml\Grid\AbstractGrid { /** - * GROUP BY criteria - * * @var string */ protected $_columnGroupBy = 'period'; + /** + * @var ConfigFactory + */ + private $configFactory; + + /** + * @param \Magento\Backend\Block\Template\Context $context + * @param \Magento\Backend\Helper\Data $backendHelper + * @param \Magento\Reports\Model\ResourceModel\Report\Collection\Factory $resourceFactory + * @param \Magento\Reports\Model\Grouped\CollectionFactory $collectionFactory + * @param \Magento\Reports\Helper\Data $reportsData + * @param array $data + * @param ConfigFactory|null $configFactory + */ + public function __construct( + \Magento\Backend\Block\Template\Context $context, + \Magento\Backend\Helper\Data $backendHelper, + \Magento\Reports\Model\ResourceModel\Report\Collection\Factory $resourceFactory, + \Magento\Reports\Model\Grouped\CollectionFactory $collectionFactory, + \Magento\Reports\Helper\Data $reportsData, + array $data = [], + ConfigFactory $configFactory = null + ) { + parent::__construct( + $context, + $backendHelper, + $resourceFactory, + $collectionFactory, + $reportsData, + $data + ); + $this->configFactory = $configFactory ?: ObjectManager::getInstance()->get(ConfigFactory::class); + } + /** * Reports grid constructor * @@ -331,4 +366,30 @@ protected function _prepareColumns() return parent::_prepareColumns(); } + + /** + * @inheritdoc + * + * Filter canceled statuses for orders. + * + * @return Grid + */ + protected function _prepareCollection() + { + /** @var DataObject $filterData */ + $filterData = $this->getData('filter_data'); + if (!$filterData->hasData('order_statuses')) { + $orderConfig = $this->configFactory->create(); + $statusValues = []; + $canceledStatuses = $orderConfig->getStateStatuses(Order::STATE_CANCELED); + $statusCodes = array_keys($orderConfig->getStatuses()); + foreach ($statusCodes as $code) { + if (!isset($canceledStatuses[$code])) { + $statusValues[] = $code; + } + } + $filterData->setData('order_statuses', $statusValues); + } + return parent::_prepareCollection(); + } } diff --git a/app/code/Magento/Reports/Controller/Adminhtml/Report/Statistics.php b/app/code/Magento/Reports/Controller/Adminhtml/Report/Statistics.php index 6ba5b71b6c0..f4d2b962b9c 100644 --- a/app/code/Magento/Reports/Controller/Adminhtml/Report/Statistics.php +++ b/app/code/Magento/Reports/Controller/Adminhtml/Report/Statistics.php @@ -4,21 +4,19 @@ * See COPYING.txt for license details. */ -/** - * Report statistics admin controller - * - * @author Magento Core Team <core@magentocommerce.com> - */ namespace Magento\Reports\Controller\Adminhtml\Report; use Magento\Backend\Model\Auth\Session as AuthSession; use Magento\Backend\Model\Session; +use Magento\Framework\App\Action\HttpGetActionInterface; /** + * Report statistics admin controller. + * * @api * @since 100.0.2 */ -abstract class Statistics extends \Magento\Backend\App\Action +abstract class Statistics extends \Magento\Backend\App\Action implements HttpGetActionInterface { /** * Authorization level of a basic admin session @@ -49,7 +47,7 @@ abstract class Statistics extends \Magento\Backend\App\Action /** * @param \Magento\Backend\App\Action\Context $context * @param \Magento\Framework\Stdlib\DateTime\Filter\Date $dateFilter - * @param [] $reportTypes + * @param array $reportTypes */ public function __construct( \Magento\Backend\App\Action\Context $context, diff --git a/app/code/Magento/Reports/Model/ResourceModel/Order/Collection.php b/app/code/Magento/Reports/Model/ResourceModel/Order/Collection.php index 82ebc74a046..fd9adbe7341 100644 --- a/app/code/Magento/Reports/Model/ResourceModel/Order/Collection.php +++ b/app/code/Magento/Reports/Model/ResourceModel/Order/Collection.php @@ -3,7 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - namespace Magento\Reports\Model\ResourceModel\Order; use Magento\Framework\DB\Select; @@ -81,7 +80,7 @@ class Collection extends \Magento\Sales\Model\ResourceModel\Order\Collection * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate * @param \Magento\Sales\Model\Order\Config $orderConfig * @param \Magento\Sales\Model\ResourceModel\Report\OrderFactory $reportOrderFactory - * @param null $connection + * @param \Magento\Framework\DB\Adapter\AdapterInterface $connection * @param \Magento\Framework\Model\ResourceModel\Db\AbstractDb $resource * * @SuppressWarnings(PHPMD.ExcessiveParameterList) @@ -825,7 +824,7 @@ protected function getTotalsExpression( ) { $template = ($storeId != 0) ? '(main_table.base_subtotal - %2$s - %1$s - ABS(main_table.base_discount_amount) - %3$s)' - : '((main_table.base_subtotal - %1$s - %2$s - ABS(main_table.base_discount_amount) - %3$s) ' + : '((main_table.base_subtotal - %1$s - %2$s - ABS(main_table.base_discount_amount) + %3$s) ' . ' * main_table.base_to_global_rate)'; return sprintf($template, $baseSubtotalRefunded, $baseSubtotalCanceled, $baseDiscountCanceled); } diff --git a/app/code/Magento/Reports/Observer/CatalogProductCompareClearObserver.php b/app/code/Magento/Reports/Observer/CatalogProductCompareClearObserver.php index bbe431aeeef..26559dc27cc 100644 --- a/app/code/Magento/Reports/Observer/CatalogProductCompareClearObserver.php +++ b/app/code/Magento/Reports/Observer/CatalogProductCompareClearObserver.php @@ -24,6 +24,7 @@ class CatalogProductCompareClearObserver implements ObserverInterface /** * @param \Magento\Reports\Model\Product\Index\ComparedFactory $productCompFactory + * @param \Magento\Reports\Model\ReportStatus $reportStatus */ public function __construct( \Magento\Reports\Model\Product\Index\ComparedFactory $productCompFactory, @@ -39,7 +40,7 @@ public function __construct( * Reset count of compared products cache * * @param \Magento\Framework\Event\Observer $observer - * @return $this + * @return void * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function execute(\Magento\Framework\Event\Observer $observer) diff --git a/app/code/Magento/Reports/Observer/CatalogProductViewObserver.php b/app/code/Magento/Reports/Observer/CatalogProductViewObserver.php index 7797dda8eab..b3ec141ef01 100644 --- a/app/code/Magento/Reports/Observer/CatalogProductViewObserver.php +++ b/app/code/Magento/Reports/Observer/CatalogProductViewObserver.php @@ -10,6 +10,7 @@ /** * Reports Event observer model + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) */ class CatalogProductViewObserver implements ObserverInterface { @@ -49,6 +50,7 @@ class CatalogProductViewObserver implements ObserverInterface * @param \Magento\Customer\Model\Session $customerSession * @param \Magento\Customer\Model\Visitor $customerVisitor * @param EventSaver $eventSaver + * @param \Magento\Reports\Model\ReportStatus $reportStatus */ public function __construct( \Magento\Store\Model\StoreManagerInterface $storeManager, diff --git a/app/code/Magento/Reports/Observer/CheckoutCartAddProductObserver.php b/app/code/Magento/Reports/Observer/CheckoutCartAddProductObserver.php index 718cc02349c..6a3b7832bd4 100644 --- a/app/code/Magento/Reports/Observer/CheckoutCartAddProductObserver.php +++ b/app/code/Magento/Reports/Observer/CheckoutCartAddProductObserver.php @@ -25,6 +25,7 @@ class CheckoutCartAddProductObserver implements ObserverInterface /** * @param EventSaver $eventSaver + * @param \Magento\Reports\Model\ReportStatus $reportStatus */ public function __construct( EventSaver $eventSaver, diff --git a/app/code/Magento/Reports/Observer/CustomerLogoutObserver.php b/app/code/Magento/Reports/Observer/CustomerLogoutObserver.php index 833834d06bc..95d17ddacef 100644 --- a/app/code/Magento/Reports/Observer/CustomerLogoutObserver.php +++ b/app/code/Magento/Reports/Observer/CustomerLogoutObserver.php @@ -38,7 +38,7 @@ public function __construct( * Customer logout processing * * @param \Magento\Framework\Event\Observer $observer - * @return $this + * @return void * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function execute(\Magento\Framework\Event\Observer $observer) diff --git a/app/code/Magento/Reports/Observer/SendfriendProductObserver.php b/app/code/Magento/Reports/Observer/SendfriendProductObserver.php index 0583b45d2d0..ca70b23d55e 100644 --- a/app/code/Magento/Reports/Observer/SendfriendProductObserver.php +++ b/app/code/Magento/Reports/Observer/SendfriendProductObserver.php @@ -25,6 +25,7 @@ class SendfriendProductObserver implements ObserverInterface /** * @param EventSaver $eventSaver + * @param \Magento\Reports\Model\ReportStatus $reportStatus */ public function __construct( EventSaver $eventSaver, diff --git a/app/code/Magento/Reports/Observer/WishlistAddProductObserver.php b/app/code/Magento/Reports/Observer/WishlistAddProductObserver.php index 3fd868abbd9..e4c57cf3ef2 100644 --- a/app/code/Magento/Reports/Observer/WishlistAddProductObserver.php +++ b/app/code/Magento/Reports/Observer/WishlistAddProductObserver.php @@ -25,6 +25,7 @@ class WishlistAddProductObserver implements ObserverInterface /** * @param EventSaver $eventSaver + * @param \Magento\Reports\Model\ReportStatus $reportStatus */ public function __construct( EventSaver $eventSaver, diff --git a/app/code/Magento/Reports/Observer/WishlistShareObserver.php b/app/code/Magento/Reports/Observer/WishlistShareObserver.php index 2c4926ac12a..de6e55ceb3f 100644 --- a/app/code/Magento/Reports/Observer/WishlistShareObserver.php +++ b/app/code/Magento/Reports/Observer/WishlistShareObserver.php @@ -25,6 +25,7 @@ class WishlistShareObserver implements ObserverInterface /** * @param EventSaver $eventSaver + * @param \Magento\Reports\Model\ReportStatus $reportStatus */ public function __construct( EventSaver $eventSaver, diff --git a/app/code/Magento/Reports/Test/Mftf/ActionGroup/GenerateOrderReportActionGroup.xml b/app/code/Magento/Reports/Test/Mftf/ActionGroup/GenerateOrderReportActionGroup.xml new file mode 100644 index 00000000000..d367b2deb59 --- /dev/null +++ b/app/code/Magento/Reports/Test/Mftf/ActionGroup/GenerateOrderReportActionGroup.xml @@ -0,0 +1,35 @@ +<?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="GenerateOrderReportActionGroup"> + <arguments> + <argument name="orderFromDate" type="string"/> + <argument name="orderToDate" type="string"/> + </arguments> + <click selector="{{OrderReportMainSection.here}}" stepKey="clickOnHere" /> + <fillField selector="{{OrderReportFilterSection.dateFrom}}" userInput="{{orderFromDate}}" stepKey="fillFromDate"/> + <fillField selector="{{OrderReportFilterSection.dateTo}}" userInput="{{orderToDate}}" stepKey="fillToDate"/> + <selectOption selector="{{OrderReportFilterSection.orderStatus}}" userInput="Any" stepKey="selectAnyOption" /> + <click selector="{{OrderReportMainSection.showReport}}" stepKey="showReport" /> + </actionGroup> + <actionGroup name="GenerateOrderReportForNotCancelActionGroup"> + <arguments> + <argument name="orderFromDate" type="string"/> + <argument name="orderToDate" type="string"/> + <argument name="statuses" type="string"/> + </arguments> + <click selector="{{OrderReportMainSection.here}}" stepKey="clickOnHere" /> + <fillField selector="{{OrderReportFilterSection.dateFrom}}" userInput="{{orderFromDate}}" stepKey="fillFromDate"/> + <fillField selector="{{OrderReportFilterSection.dateTo}}" userInput="{{orderToDate}}" stepKey="fillToDate"/> + <selectOption selector="{{OrderReportFilterSection.orderStatus}}" userInput="Specified" stepKey="selectSpecifiedOption" /> + <selectOption selector="{{OrderReportFilterSection.orderStatusSpecified}}" parameterArray="{{statuses}}" stepKey="selectSpecifiedOptionStatus" /> + <click selector="{{OrderReportMainSection.showReport}}" stepKey="showReport" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Reports/Test/Mftf/Page/OrdersReportPage.xml b/app/code/Magento/Reports/Test/Mftf/Page/OrdersReportPage.xml new file mode 100644 index 00000000000..46509089b97 --- /dev/null +++ b/app/code/Magento/Reports/Test/Mftf/Page/OrdersReportPage.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="OrdersReportPage" url="reports/report_sales/sales/" area="admin" module="Reports"> + <section name="OrderReportFilterSection"/> + <section name="OrderReportMainSection"/> + <section name="GeneratedReportSection" /> + </page> +</pages> diff --git a/app/code/Magento/Reports/Test/Mftf/Section/OrderReportMainSection.xml b/app/code/Magento/Reports/Test/Mftf/Section/OrderReportMainSection.xml new file mode 100644 index 00000000000..7ad9bdfa8c1 --- /dev/null +++ b/app/code/Magento/Reports/Test/Mftf/Section/OrderReportMainSection.xml @@ -0,0 +1,29 @@ +<?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="OrderReportMainSection"> + <element name="showReport" type="button" selector="#filter_form_submit"/> + <element name="here" type="text" selector="//a[contains(text(), 'here')]"/> + </section> + + <section name="OrderReportFilterSection"> + <element name="dateFrom" type="input" selector="#sales_report_from"/> + <element name="dateTo" type="input" selector="#sales_report_to"/> + <element name="orderStatus" type="select" selector="#sales_report_show_order_statuses"/> + <element name="optionAny" type="option" selector="//select[@id='sales_report_show_order_statuses']/option[contains(text(), 'Any')]"/> + <element name="optionSpecified" type="option" selector="//select[@id='sales_report_show_order_statuses']/option[contains(text(), 'Specified')]"/> + <element name="orderStatusSpecified" type="select" selector="#sales_report_order_statuses"/> + </section> + + <section name="GeneratedReportSection"> + <element name="ordersCount" type="text" selector="//tr[@class='totals']/th[@class=' col-orders col-orders_count col-number']"/> + <element name="canceledOrders" type="text" selector="//tr[@class='totals']/th[@class=' col-canceled col-total_canceled_amount a-right']"/> + </section> +</sections> diff --git a/app/code/Magento/Reports/Test/Mftf/Test/CancelOrdersInOrderSalesReportTest.xml b/app/code/Magento/Reports/Test/Mftf/Test/CancelOrdersInOrderSalesReportTest.xml new file mode 100644 index 00000000000..7cb23e54aa1 --- /dev/null +++ b/app/code/Magento/Reports/Test/Mftf/Test/CancelOrdersInOrderSalesReportTest.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="CancelOrdersInOrderSalesReportTest"> + <annotations> + <features value="Reports"/> + <stories value="Order Sales Report includes canceled orders"/> + <group value="reports"/> + <title value="Canceled orders in order sales report"/> + <description value="Verify canceling of orders in order sales report"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-95960"/> + <useCaseId value="MAGETWO-95823"/> + </annotations> + + <before> + <!-- log in as admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + + <!-- create new product --> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="SimpleProduct" stepKey="createSimpleProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + + <!-- create new customer--> + <createData entity="Simple_US_Customer" stepKey="createCustomer"/> + </before> + + <after> + <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteProduct"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + + <!-- Create completed order --> + <actionGroup ref="CreateOrderActionGroup" stepKey="createOrderd"> + <argument name="product" value="$$createSimpleProduct$$"/> + <argument name="customer" value="$$createCustomer$$"/> + </actionGroup> + + <click selector="{{AdminOrderDetailsMainActionsSection.invoice}}" stepKey="clickInvoiceAction"/> + <see selector="{{AdminHeaderSection.pageTitle}}" userInput="New Invoice" stepKey="seePageNameNewInvoicePage"/> + <click selector="{{AdminInvoiceMainActionsSection.submitInvoice}}" stepKey="clickSubmitInvoice"/> + + <click selector="{{AdminOrderDetailsMainActionsSection.ship}}" stepKey="clickShipAction"/> + <seeInCurrentUrl url="{{AdminShipmentNewPage.url}}" stepKey="seeOrderShipmentUrl"/> + <click selector="{{AdminShipmentMainActionsSection.submitShipment}}" stepKey="clickSubmitShipment"/> + + <!-- Create Order --> + <actionGroup ref="CreateOrderActionGroup" stepKey="createOrder"> + <argument name="product" value="$$createSimpleProduct$$"/> + <argument name="customer" value="$$createCustomer$$"/> + </actionGroup> + + <!-- Cancel order --> + <actionGroup ref="cancelPendingOrder" stepKey="cancelOrder"/> + + <!-- Generate Order report for statuses --> + <amOnPage url="{{OrdersReportPage.url}}" stepKey="goToOrdersReportPage1"/> + <!-- Get date --> + <generateDate stepKey="generateEndDate" date="+0 day" format="m/d/Y"/> + <generateDate stepKey="generateStartDate" date="-1 day" format="m/d/Y"/> + <actionGroup ref="GenerateOrderReportForNotCancelActionGroup" stepKey="generateReportAfterCancelOrderBefore"> + <argument name="orderFromDate" value="$generateStartDate"/> + <argument name="orderToDate" value="$generateEndDate"/> + <argument name="statuses" value="['closed', 'complete', 'fraud', 'holded', 'payment_review', 'paypal_canceled_reversal', 'paypal_reversed', 'processing']"/> + </actionGroup> + <waitForElement selector="{{GeneratedReportSection.ordersCount}}" stepKey="waitForOrdersCountBefore"/> + <grabTextFrom selector="{{GeneratedReportSection.ordersCount}}" stepKey="grabCanceledOrdersSpecified"/> + <!-- Generate Order report --> + <amOnPage url="{{OrdersReportPage.url}}" stepKey="goToOrdersReportPage2"/> + <!-- Get date --> + <actionGroup ref="GenerateOrderReportActionGroup" stepKey="generateReportAfterCancelOrder"> + <argument name="orderFromDate" value="$generateStartDate"/> + <argument name="orderToDate" value="$generateEndDate"/> + </actionGroup> + <waitForElement selector="{{GeneratedReportSection.ordersCount}}" stepKey="waitForOrdersCount"/> + <grabTextFrom selector="{{GeneratedReportSection.ordersCount}}" stepKey="grabCanceledOrdersAny"/> + + <!-- Compare canceled orders price --> + <assertEquals expected="{$grabCanceledOrdersSpecified}" expectedType="string" actual="{$grabCanceledOrdersAny}" actualType="string" stepKey="assertEquals"/> + </test> +</tests> diff --git a/app/code/Magento/Review/Block/Adminhtml/Add.php b/app/code/Magento/Review/Block/Adminhtml/Add.php index 96dd02e65f1..c5600fe0610 100644 --- a/app/code/Magento/Review/Block/Adminhtml/Add.php +++ b/app/code/Magento/Review/Block/Adminhtml/Add.php @@ -56,7 +56,7 @@ protected function _construct() }, loadProductData : function() { jQuery.ajax({ - type: "POST", + type: "GET", url: review.productInfoUrl, data: { form_key: FORM_KEY diff --git a/app/code/Magento/Review/view/frontend/layout/catalog_product_view.xml b/app/code/Magento/Review/view/frontend/layout/catalog_product_view.xml index 6fcf5b0c82b..a6b46f8f25a 100644 --- a/app/code/Magento/Review/view/frontend/layout/catalog_product_view.xml +++ b/app/code/Magento/Review/view/frontend/layout/catalog_product_view.xml @@ -19,6 +19,9 @@ </referenceContainer> <referenceBlock name="product.info.details"> <block class="Magento\Review\Block\Product\Review" name="reviews.tab" as="reviews" template="Magento_Review::review.phtml" group="detailed_info" ifconfig="catalog/review/active"> + <arguments> + <argument name="sort_order" xsi:type="string">30</argument> + </arguments> <block class="Magento\Review\Block\Form" name="product.review.form" as="review_form" ifconfig="catalog/review/active"> <container name="product.review.form.fields.before" as="form_fields_before" label="Review Form Fields Before"/> </block> diff --git a/app/code/Magento/ReviewAnalytics/README.md b/app/code/Magento/ReviewAnalytics/README.md index b078083dfb7..a8894f99ed0 100644 --- a/app/code/Magento/ReviewAnalytics/README.md +++ b/app/code/Magento/ReviewAnalytics/README.md @@ -1,3 +1,3 @@ # Magento_ReviewAnalytics module -The Magento_ReviewAnalytics module configures data definitions for a data collection related to the Review module entities to be used in [Advanced Reporting](http://devdocs.magento.com/guides/v2.2/advanced-reporting/modules.html). +The Magento_ReviewAnalytics module configures data definitions for a data collection related to the Review module entities to be used in [Advanced Reporting](https://devdocs.magento.com/guides/v2.2/advanced-reporting/modules.html). diff --git a/app/code/Magento/Rule/Model/Condition/AbstractCondition.php b/app/code/Magento/Rule/Model/Condition/AbstractCondition.php index a1987f67e47..d2be99757df 100644 --- a/app/code/Magento/Rule/Model/Condition/AbstractCondition.php +++ b/app/code/Magento/Rule/Model/Condition/AbstractCondition.php @@ -62,7 +62,8 @@ abstract class AbstractCondition extends \Magento\Framework\DataObject implement protected $_layout; /** - * Base name for hidden elements + * Base name for hidden elements. + * * @var string */ protected $elementName = 'rule'; @@ -116,8 +117,9 @@ public function getDefaultOperatorInputByType() } /** - * Default operator options getter - * Provides all possible operator options + * Default operator options getter. + * + * Provides all possible operator options. * * @return array */ @@ -141,6 +143,8 @@ public function getDefaultOperatorOptions() } /** + * Get rule form. + * * @return Form */ public function getForm() @@ -149,6 +153,8 @@ public function getForm() } /** + * Get condition as array. + * * @param array $arrAttributes * @return array * @SuppressWarnings(PHPMD.UnusedFormalParameter) @@ -195,6 +201,8 @@ public function getMappedSqlField() } /** + * Get condition as xml. + * * @return string */ public function asXml() @@ -214,6 +222,8 @@ public function asXml() } /** + * Load condition from array. + * * @param array $arr * @return $this * @SuppressWarnings(PHPMD.NPathComplexity) @@ -229,6 +239,8 @@ public function loadArray($arr) } /** + * Load condition from xml. + * * @param string|array $xml * @return $this */ @@ -242,6 +254,8 @@ public function loadXml($xml) } /** + * Load attribute options. + * * @return $this */ public function loadAttributeOptions() @@ -250,6 +264,8 @@ public function loadAttributeOptions() } /** + * Get attribute options. + * * @return array */ public function getAttributeOptions() @@ -258,6 +274,8 @@ public function getAttributeOptions() } /** + * Get attribute select options. + * * @return array */ public function getAttributeSelectOptions() @@ -270,6 +288,8 @@ public function getAttributeSelectOptions() } /** + * Get attribute name. + * * @return string */ public function getAttributeName() @@ -278,6 +298,8 @@ public function getAttributeName() } /** + * Load operator options. + * * @return $this */ public function loadOperatorOptions() @@ -300,6 +322,8 @@ public function getInputType() } /** + * Get operator select options. + * * @return array */ public function getOperatorSelectOptions() @@ -316,6 +340,8 @@ public function getOperatorSelectOptions() } /** + * Get operator name. + * * @return array */ public function getOperatorName() @@ -324,6 +350,8 @@ public function getOperatorName() } /** + * Load value options. + * * @return $this */ public function loadValueOptions() @@ -333,6 +361,8 @@ public function loadValueOptions() } /** + * Get value select options. + * * @return array */ public function getValueSelectOptions() @@ -380,6 +410,8 @@ public function isArrayOperatorType() } /** + * Get value. + * * @return mixed */ public function getValue() @@ -395,6 +427,8 @@ public function getValue() } /** + * Get value name. + * * @return array|string * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ @@ -446,6 +480,8 @@ public function getNewChildSelectOptions() } /** + * Get new child name. + * * @return string */ public function getNewChildName() @@ -454,6 +490,8 @@ public function getNewChildName() } /** + * Get this condition as html. + * * @return string */ public function asHtml() @@ -467,6 +505,8 @@ public function asHtml() } /** + * Get this condition with subconditions as html. + * * @return string */ public function asHtmlRecursive() @@ -475,6 +515,8 @@ public function asHtmlRecursive() } /** + * Get type element. + * * @return AbstractElement */ public function getTypeElement() @@ -493,6 +535,8 @@ public function getTypeElement() } /** + * Get type element html. + * * @return string */ public function getTypeElementHtml() @@ -501,6 +545,8 @@ public function getTypeElementHtml() } /** + * Get attribute element. + * * @return $this */ public function getAttributeElement() @@ -528,6 +574,8 @@ public function getAttributeElement() } /** + * Get attribute element html. + * * @return string */ public function getAttributeElementHtml() @@ -536,8 +584,9 @@ public function getAttributeElementHtml() } /** - * Retrieve Condition Operator element Instance - * If the operator value is empty - define first available operator value as default + * Retrieve Condition Operator element Instance. + * + * If the operator value is empty - define first available operator value as default. * * @return \Magento\Framework\Data\Form\Element\Select */ @@ -568,6 +617,8 @@ public function getOperatorElement() } /** + * Get operator element html. + * * @return string */ public function getOperatorElementHtml() @@ -587,6 +638,8 @@ public function getValueElementType() } /** + * Get value element renderer. + * * @return \Magento\Rule\Block\Editable */ public function getValueElementRenderer() @@ -598,6 +651,8 @@ public function getValueElementRenderer() } /** + * Get value element. + * * @return $this */ public function getValueElement() @@ -615,6 +670,9 @@ public function getValueElement() // date format intentionally hard-coded $elementParams['input_format'] = \Magento\Framework\Stdlib\DateTime::DATE_INTERNAL_FORMAT; $elementParams['date_format'] = \Magento\Framework\Stdlib\DateTime::DATE_INTERNAL_FORMAT; + $elementParams['placeholder'] = \Magento\Framework\Stdlib\DateTime::DATE_INTERNAL_FORMAT; + $elementParams['autocomplete'] = 'off'; + $elementParams['readonly'] = 'true'; } return $this->getForm()->addField( $this->getPrefix() . '__' . $this->getId() . '__value', @@ -626,6 +684,8 @@ public function getValueElement() } /** + * Get value element html. + * * @return string */ public function getValueElementHtml() @@ -634,6 +694,8 @@ public function getValueElementHtml() } /** + * Get add link html. + * * @return string */ public function getAddLinkHtml() @@ -643,6 +705,8 @@ public function getAddLinkHtml() } /** + * Get remove link html. + * * @return string */ public function getRemoveLinkHtml() @@ -655,6 +719,8 @@ public function getRemoveLinkHtml() } /** + * Get chooser container html. + * * @return string */ public function getChooserContainerHtml() @@ -664,6 +730,8 @@ public function getChooserContainerHtml() } /** + * Get this condition as string. + * * @param string $format * @return string * @SuppressWarnings(PHPMD.UnusedFormalParameter) @@ -674,6 +742,8 @@ public function asString($format = '') } /** + * Get this condition with subconditions as string. + * * @param int $level * @return string */ @@ -816,6 +886,8 @@ protected function _compareValues($validatedValue, $value, $strict = true) } /** + * Validate model. + * * @param \Magento\Framework\Model\AbstractModel $model * @return bool */ diff --git a/app/code/Magento/Rule/view/adminhtml/web/rules.js b/app/code/Magento/Rule/view/adminhtml/web/rules.js index b094b981836..8e36562ebd7 100644 --- a/app/code/Magento/Rule/view/adminhtml/web/rules.js +++ b/app/code/Magento/Rule/view/adminhtml/web/rules.js @@ -220,6 +220,8 @@ define([ var elem = Element.down(elemContainer, 'input.input-text'); + jQuery(elem).trigger('contentUpdated'); + if (elem) { elem.focus(); diff --git a/app/code/Magento/Sales/Api/CreditmemoRepositoryInterface.php b/app/code/Magento/Sales/Api/CreditmemoRepositoryInterface.php index 354b55eb259..3c61384d8b8 100644 --- a/app/code/Magento/Sales/Api/CreditmemoRepositoryInterface.php +++ b/app/code/Magento/Sales/Api/CreditmemoRepositoryInterface.php @@ -20,7 +20,7 @@ interface CreditmemoRepositoryInterface * Lists credit memos that match specified search criteria. * * This call returns an array of objects, but detailed information about each object’s attributes might not be - * included. See http://devdocs.magento.com/codelinks/attributes.html#CreditmemoRepositoryInterface to + * included. See https://devdocs.magento.com/codelinks/attributes.html#CreditmemoRepositoryInterface to * determine which call to use to get detailed information about all attributes for an object. * * @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria The search criteria. diff --git a/app/code/Magento/Sales/Api/InvoiceRepositoryInterface.php b/app/code/Magento/Sales/Api/InvoiceRepositoryInterface.php index 9d4f11ed0f0..161b8405f11 100644 --- a/app/code/Magento/Sales/Api/InvoiceRepositoryInterface.php +++ b/app/code/Magento/Sales/Api/InvoiceRepositoryInterface.php @@ -18,7 +18,7 @@ interface InvoiceRepositoryInterface * Lists invoices that match specified search criteria. * * This call returns an array of objects, but detailed information about each object’s attributes might not be - * included. See http://devdocs.magento.com/codelinks/attributes.html#InvoiceRepositoryInterface to + * included. See https://devdocs.magento.com/codelinks/attributes.html#InvoiceRepositoryInterface to * determine which call to use to get detailed information about all attributes for an object. * * @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria The search criteria. diff --git a/app/code/Magento/Sales/Api/OrderItemRepositoryInterface.php b/app/code/Magento/Sales/Api/OrderItemRepositoryInterface.php index e731f6f0e98..3449d0054b7 100644 --- a/app/code/Magento/Sales/Api/OrderItemRepositoryInterface.php +++ b/app/code/Magento/Sales/Api/OrderItemRepositoryInterface.php @@ -20,7 +20,7 @@ interface OrderItemRepositoryInterface * Lists order items that match specified search criteria. * * This call returns an array of objects, but detailed information about each object’s attributes might not be - * included. See http://devdocs.magento.com/codelinks/attributes.html#OrderItemRepositoryInterface to + * included. See https://devdocs.magento.com/codelinks/attributes.html#OrderItemRepositoryInterface to * determine which call to use to get detailed information about all attributes for an object. * * @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria The search criteria. diff --git a/app/code/Magento/Sales/Api/OrderRepositoryInterface.php b/app/code/Magento/Sales/Api/OrderRepositoryInterface.php index a25f463ac86..0c3b6ab5cb0 100644 --- a/app/code/Magento/Sales/Api/OrderRepositoryInterface.php +++ b/app/code/Magento/Sales/Api/OrderRepositoryInterface.php @@ -20,7 +20,7 @@ interface OrderRepositoryInterface * Lists orders that match specified search criteria. * * This call returns an array of objects, but detailed information about each object’s attributes might not be - * included. See http://devdocs.magento.com/codelinks/attributes.html#OrderRepositoryInterface to + * included. See https://devdocs.magento.com/codelinks/attributes.html#OrderRepositoryInterface to * determine which call to use to get detailed information about all attributes for an object. * * @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria The search criteria. diff --git a/app/code/Magento/Sales/Api/ShipmentRepositoryInterface.php b/app/code/Magento/Sales/Api/ShipmentRepositoryInterface.php index bfdf17440eb..3b3c8221596 100644 --- a/app/code/Magento/Sales/Api/ShipmentRepositoryInterface.php +++ b/app/code/Magento/Sales/Api/ShipmentRepositoryInterface.php @@ -19,7 +19,7 @@ interface ShipmentRepositoryInterface * Lists shipments that match specified search criteria. * * This call returns an array of objects, but detailed information about each object’s attributes might not be - * included. See http://devdocs.magento.com/codelinks/attributes.html#ShipmentRepositoryInterface to + * included. See https://devdocs.magento.com/codelinks/attributes.html#ShipmentRepositoryInterface to * determine which call to use to get detailed information about all attributes for an object. * * @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria The search criteria. diff --git a/app/code/Magento/Sales/Api/TransactionRepositoryInterface.php b/app/code/Magento/Sales/Api/TransactionRepositoryInterface.php index de459662a83..e55b5d60d1f 100644 --- a/app/code/Magento/Sales/Api/TransactionRepositoryInterface.php +++ b/app/code/Magento/Sales/Api/TransactionRepositoryInterface.php @@ -18,7 +18,7 @@ interface TransactionRepositoryInterface * Lists transactions that match specified search criteria. * * This call returns an array of objects, but detailed information about each object’s attributes might not be - * included. See http://devdocs.magento.com/codelinks/attributes.html#TransactionRepositoryInterface to + * included. See https://devdocs.magento.com/codelinks/attributes.html#TransactionRepositoryInterface to * determine which call to use to get detailed information about all attributes for an object. * * @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria The search criteria. diff --git a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php index 26379a05fe6..bb24d2ae15a 100644 --- a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php +++ b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php @@ -9,6 +9,7 @@ use Magento\Framework\Api\ExtensibleDataObjectConverter; use Magento\Framework\Data\Form\Element\AbstractElement; use Magento\Framework\Pricing\PriceCurrencyInterface; +use Magento\Store\Model\ScopeInterface; /** * Create order account form @@ -132,15 +133,8 @@ protected function _prepareForm() $this->_addAttributesToForm($attributes, $fieldset); $this->_form->addFieldNameSuffix('order[account]'); - - $formValues = $this->getFormValues(); - foreach ($attributes as $code => $attribute) { - $defaultValue = $attribute->getDefaultValue(); - if (isset($defaultValue) && !isset($formValues[$code])) { - $formValues[$code] = $defaultValue; - } - } - $this->_form->setValues($formValues); + $storeId = (int)$this->_sessionQuote->getStoreId(); + $this->_form->setValues($this->extractValuesFromAttributes($attributes, $storeId)); return $this; } @@ -193,4 +187,42 @@ public function getFormValues() return $data; } + + /** + * Extract the form values from attributes. + * + * @param array $attributes + * @param int $storeId + * @return array + */ + private function extractValuesFromAttributes(array $attributes, int $storeId): array + { + $formValues = $this->getFormValues(); + foreach ($attributes as $code => $attribute) { + $defaultValue = $attribute->getDefaultValue(); + if (isset($defaultValue) && !isset($formValues[$code])) { + $formValues[$code] = $defaultValue; + } + if ($code === 'group_id' && empty($defaultValue)) { + $formValues[$code] = $this->getDefaultCustomerGroup($storeId); + } + } + + return $formValues; + } + + /** + * Gets default customer group. + * + * @param int $storeId + * @return string|null + */ + private function getDefaultCustomerGroup(int $storeId): ?string + { + return $this->_scopeConfig->getValue( + 'customer/create_account/default_group', + ScopeInterface::SCOPE_STORE, + $storeId + ); + } } diff --git a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Load.php b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Load.php index f21cc96be92..7cb46fcde2c 100644 --- a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Load.php +++ b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Load.php @@ -29,7 +29,7 @@ class Load extends \Magento\Framework\View\Element\Template /** * @param \Magento\Framework\View\Element\Template\Context $context * @param \Magento\Framework\Json\EncoderInterface $jsonEncoder - * @param \Magento\Framework\View\Helper\Js $adminhtmlJs + * @param \Magento\Framework\View\Helper\Js $jsHelper * @param array $data */ public function __construct( diff --git a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Sidebar/Cart.php b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Sidebar/Cart.php index 34d7a3f8ee2..f2200e1c1a1 100644 --- a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Sidebar/Cart.php +++ b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Sidebar/Cart.php @@ -3,8 +3,13 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Sales\Block\Adminhtml\Order\Create\Sidebar; +use Magento\Catalog\Model\Product; +use Magento\Catalog\Pricing\Price\FinalPrice; + /** * Adminhtml sales order create sidebar cart block * @@ -58,6 +63,17 @@ public function getItemCollection() return $collection; } + /** + * @inheritdoc + */ + public function getItemPrice(Product $product) + { + $customPrice = $this->getCartItemCustomPrice($product); + $price = $customPrice ?? $product->getPriceInfo()->getPrice(FinalPrice::PRICE_CODE)->getValue(); + + return $this->convertPrice($price); + } + /** * Retrieve display item qty availability * @@ -111,4 +127,23 @@ protected function _prepareLayout() return parent::_prepareLayout(); } + + /** + * Returns cart item custom price. + * + * @param Product $product + * @return float|null + */ + private function getCartItemCustomPrice(Product $product): ?float + { + $items = $this->getItemCollection(); + foreach ($items as $item) { + $productItemId = $this->getProduct($item)->getId(); + if ($productItemId === $product->getId() && $item->getCustomPrice()) { + return (float)$item->getCustomPrice(); + } + } + + return null; + } } diff --git a/app/code/Magento/Sales/Block/Adminhtml/Order/Creditmemo/Create/Adjustments.php b/app/code/Magento/Sales/Block/Adminhtml/Order/Creditmemo/Create/Adjustments.php index d73371d46da..9e13e9424d1 100644 --- a/app/code/Magento/Sales/Block/Adminhtml/Order/Creditmemo/Create/Adjustments.php +++ b/app/code/Magento/Sales/Block/Adminhtml/Order/Creditmemo/Create/Adjustments.php @@ -8,6 +8,8 @@ use Magento\Framework\Pricing\PriceCurrencyInterface; /** + * Credit memo adjustmets block + * * @api * @since 100.0.2 */ @@ -50,7 +52,7 @@ public function __construct( } /** - * Initialize creditmemo agjustment totals + * Initialize creditmemo adjustment totals * * @return $this */ diff --git a/app/code/Magento/Sales/Block/Adminhtml/Order/Creditmemo/Create/Form.php b/app/code/Magento/Sales/Block/Adminhtml/Order/Creditmemo/Create/Form.php index d2e42fe388d..ec959fc2863 100644 --- a/app/code/Magento/Sales/Block/Adminhtml/Order/Creditmemo/Create/Form.php +++ b/app/code/Magento/Sales/Block/Adminhtml/Order/Creditmemo/Create/Form.php @@ -26,7 +26,7 @@ public function getOrder() /** * Retrieve source * - * @return \Magento\Sales\Model\Order\Invoice + * @return \Magento\Sales\Model\Order\Creditmemo */ public function getSource() { diff --git a/app/code/Magento/Sales/Block/Adminhtml/Order/View/Items/Renderer/DefaultRenderer.php b/app/code/Magento/Sales/Block/Adminhtml/Order/View/Items/Renderer/DefaultRenderer.php index 04a9f9437ae..d19fc4992f0 100644 --- a/app/code/Magento/Sales/Block/Adminhtml/Order/View/Items/Renderer/DefaultRenderer.php +++ b/app/code/Magento/Sales/Block/Adminhtml/Order/View/Items/Renderer/DefaultRenderer.php @@ -94,6 +94,7 @@ public function getFieldIdPrefix() * Indicate that block can display container * * @return bool + * @SuppressWarnings(PHPMD.RequestAwareBlockMethod) */ public function canDisplayContainer() { @@ -196,7 +197,7 @@ public function getMessage() /** * Retrieve save url * - * @return array + * @return string */ public function getSaveUrl() { @@ -259,9 +260,11 @@ public function displayPriceInclTax(\Magento\Framework\DataObject $item) } /** + * Retrieve rendered column html content + * * @param \Magento\Framework\DataObject|Item $item * @param string $column - * @param null $field + * @param string $field * @return string * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @since 100.1.0 @@ -301,6 +304,8 @@ public function getColumnHtml(\Magento\Framework\DataObject $item, $column, $fie } /** + * Get columns data. + * * @return array * @since 100.1.0 */ diff --git a/app/code/Magento/Sales/Block/Adminhtml/Rss/Order/Grid/Link.php b/app/code/Magento/Sales/Block/Adminhtml/Rss/Order/Grid/Link.php index 512539824da..802ed1dc60f 100644 --- a/app/code/Magento/Sales/Block/Adminhtml/Rss/Order/Grid/Link.php +++ b/app/code/Magento/Sales/Block/Adminhtml/Rss/Order/Grid/Link.php @@ -7,6 +7,7 @@ /** * Class Link + * * @package Magento\Sales\Block\Adminhtml\Rss\Order\Grid */ class Link extends \Magento\Framework\View\Element\Template @@ -36,6 +37,8 @@ public function __construct( } /** + * Get url for link. + * * @return string */ public function getLink() @@ -44,6 +47,8 @@ public function getLink() } /** + * Get translatable label for link. + * * @return \Magento\Framework\Phrase */ public function getLabel() @@ -62,7 +67,9 @@ public function isRssAllowed() } /** - * @return string + * Get link type param. + * + * @return array */ protected function getLinkParams() { diff --git a/app/code/Magento/Sales/Block/Order/Email/Invoice/Items.php b/app/code/Magento/Sales/Block/Order/Email/Invoice/Items.php index a5785a19cc6..bc7756816d3 100644 --- a/app/code/Magento/Sales/Block/Order/Email/Invoice/Items.php +++ b/app/code/Magento/Sales/Block/Order/Email/Invoice/Items.php @@ -4,14 +4,11 @@ * See COPYING.txt for license details. */ -/** - * Sales Order Email Invoice items - * - * @author Magento Core Team <core@magentocommerce.com> - */ namespace Magento\Sales\Block\Order\Email\Invoice; /** + * Sales Order Email Invoice items + * * @api * @since 100.0.2 */ @@ -21,7 +18,7 @@ class Items extends \Magento\Sales\Block\Items\AbstractItems * Prepare item before output * * @param \Magento\Framework\View\Element\AbstractBlock $renderer - * @return \Magento\Sales\Block\Items\AbstractItems + * @return void */ protected function _prepareItem(\Magento\Framework\View\Element\AbstractBlock $renderer) { diff --git a/app/code/Magento/Sales/Block/Order/Email/Shipment/Items.php b/app/code/Magento/Sales/Block/Order/Email/Shipment/Items.php index 21c83e55a48..a4c9a7b80a0 100644 --- a/app/code/Magento/Sales/Block/Order/Email/Shipment/Items.php +++ b/app/code/Magento/Sales/Block/Order/Email/Shipment/Items.php @@ -4,14 +4,11 @@ * See COPYING.txt for license details. */ -/** - * Sales Order Email Shipment items - * - * @author Magento Core Team <core@magentocommerce.com> - */ namespace Magento\Sales\Block\Order\Email\Shipment; /** + * Sales Order Email Shipment items + * * @api * @since 100.0.2 */ @@ -21,7 +18,7 @@ class Items extends \Magento\Sales\Block\Items\AbstractItems * Prepare item before output * * @param \Magento\Framework\View\Element\AbstractBlock $renderer - * @return \Magento\Sales\Block\Items\AbstractItems + * @return void */ protected function _prepareItem(\Magento\Framework\View\Element\AbstractBlock $renderer) { diff --git a/app/code/Magento/Sales/Block/Order/Info/Buttons/Rss.php b/app/code/Magento/Sales/Block/Order/Info/Buttons/Rss.php index 2b84b8f1444..626dcf2a5a4 100644 --- a/app/code/Magento/Sales/Block/Order/Info/Buttons/Rss.php +++ b/app/code/Magento/Sales/Block/Order/Info/Buttons/Rss.php @@ -46,6 +46,8 @@ public function __construct( } /** + * Get link url. + * * @return string */ public function getLink() @@ -54,6 +56,8 @@ public function getLink() } /** + * Get translatable label for url. + * * @return \Magento\Framework\Phrase */ public function getLabel() @@ -91,7 +95,10 @@ protected function getUrlKey($order) } /** - * @return string + * Get type, secure and query params for link. + * + * @return array + * @SuppressWarnings(PHPMD.RequestAwareBlockMethod) */ protected function getLinkParams() { diff --git a/app/code/Magento/Sales/Block/Order/Item/Renderer/DefaultRenderer.php b/app/code/Magento/Sales/Block/Order/Item/Renderer/DefaultRenderer.php index 09464927117..83e66bbbce7 100644 --- a/app/code/Magento/Sales/Block/Order/Item/Renderer/DefaultRenderer.php +++ b/app/code/Magento/Sales/Block/Order/Item/Renderer/DefaultRenderer.php @@ -48,6 +48,8 @@ public function __construct( } /** + * Set item. + * * @param \Magento\Framework\DataObject $item * @return $this */ @@ -58,6 +60,8 @@ public function setItem(\Magento\Framework\DataObject $item) } /** + * Get item. + * * @return array|null */ public function getItem() @@ -76,6 +80,8 @@ public function getOrder() } /** + * Get order item. + * * @return array|null */ public function getOrderItem() @@ -88,6 +94,8 @@ public function getOrderItem() } /** + * Get item options. + * * @return array */ public function getItemOptions() diff --git a/app/code/Magento/Sales/Block/Order/PrintShipment.php b/app/code/Magento/Sales/Block/Order/PrintShipment.php index 335b6095d0c..0006a38f0f1 100644 --- a/app/code/Magento/Sales/Block/Order/PrintShipment.php +++ b/app/code/Magento/Sales/Block/Order/PrintShipment.php @@ -53,6 +53,8 @@ public function __construct( } /** + * Preparing global layout. + * * @return void */ protected function _prepareLayout() @@ -63,6 +65,8 @@ protected function _prepareLayout() } /** + * Get payment info child block html. + * * @return string */ public function getPaymentInfoHtml() @@ -71,6 +75,8 @@ public function getPaymentInfoHtml() } /** + * Retrieve current order from registry. + * * @return \Magento\Sales\Model\Order|null */ public function getOrder() @@ -104,6 +110,8 @@ public function getItems() } /** + * Prepare item before output. + * * @param AbstractBlock $renderer * @return $this */ @@ -116,7 +124,7 @@ protected function _prepareItem(AbstractBlock $renderer) /** * Returns string with formatted address * - * @param Address $address + * @param \Magento\Sales\Model\Order\Address $address * @return null|string */ public function getFormattedAddress(\Magento\Sales\Model\Order\Address $address) diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/Index.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/Index.php index 035dc787789..603aa2586b0 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/Index.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Create/Index.php @@ -7,12 +7,15 @@ use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface; +/** + * Order create index page controller. + */ class Index extends \Magento\Sales\Controller\Adminhtml\Order\Create implements HttpGetActionInterface { /** * Index page * - * @return void + * @return \Magento\Backend\Model\View\Result\Page */ public function execute() { diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/Save.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/Save.php index ab74a64b6fc..67a0dc46916 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/Save.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/Save.php @@ -18,6 +18,8 @@ use Magento\Sales\Model\Service\InvoiceService; /** + * Save invoice controller. + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Save extends \Magento\Backend\App\Action implements HttpPostActionInterface @@ -103,6 +105,7 @@ protected function _prepareShipment($invoice) /** * Save invoice + * * We can save only new invoice. Existing invoices are not editable * * @return \Magento\Framework\Controller\ResultInterface @@ -194,12 +197,6 @@ public function execute() } $transactionSave->save(); - if (!empty($data['do_shipment'])) { - $this->messageManager->addSuccessMessage(__('You created the invoice and shipment.')); - } else { - $this->messageManager->addSuccessMessage(__('The invoice has been created.')); - } - // send invoice/shipment emails try { if (!empty($data['send_email'])) { @@ -219,6 +216,11 @@ public function execute() $this->messageManager->addErrorMessage(__('We can\'t send the shipment right now.')); } } + if (!empty($data['do_shipment'])) { + $this->messageManager->addSuccessMessage(__('You created the invoice and shipment.')); + } else { + $this->messageManager->addSuccessMessage(__('The invoice has been created.')); + } $this->_objectManager->get(\Magento\Backend\Model\Session::class)->getCommentText(true); return $resultRedirect->setPath('sales/order/view', ['order_id' => $orderId]); } catch (LocalizedException $e) { diff --git a/app/code/Magento/Sales/Controller/Download/DownloadCustomOption.php b/app/code/Magento/Sales/Controller/Download/DownloadCustomOption.php index 0deddd9fb6e..d30839e96dc 100644 --- a/app/code/Magento/Sales/Controller/Download/DownloadCustomOption.php +++ b/app/code/Magento/Sales/Controller/Download/DownloadCustomOption.php @@ -7,6 +7,7 @@ namespace Magento\Sales\Controller\Download; +use Magento\Framework\App\Action\HttpGetActionInterface; use Magento\Framework\App\ObjectManager; use Magento\Framework\App\Action\Context; use Magento\Catalog\Model\Product\Type\AbstractType; @@ -14,9 +15,10 @@ /** * Class DownloadCustomOption + * * @package Magento\Sales\Controller\Download */ -class DownloadCustomOption extends \Magento\Framework\App\Action\Action +class DownloadCustomOption extends \Magento\Framework\App\Action\Action implements HttpGetActionInterface { /** * @var ForwardFactory @@ -95,10 +97,11 @@ public function execute() /** @var $productOption \Magento\Catalog\Model\Product\Option */ $productOption = $this->_objectManager->create( \Magento\Catalog\Model\Product\Option::class - )->load($optionId); + ); + $productOption->load($optionId); } - if (!$productOption || !$productOption->getId() || $productOption->getType() != 'file') { + if ($productOption->getId() && $productOption->getType() != 'file') { return $resultForward->forward('noroute'); } diff --git a/app/code/Magento/Sales/Model/Order.php b/app/code/Magento/Sales/Model/Order.php index 1ce23afe434..f3e926f109c 100644 --- a/app/code/Magento/Sales/Model/Order.php +++ b/app/code/Magento/Sales/Model/Order.php @@ -8,9 +8,11 @@ use Magento\Directory\Model\Currency; use Magento\Framework\Api\AttributeValueFactory; use Magento\Framework\App\ObjectManager; +use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Locale\ResolverInterface; use Magento\Framework\Pricing\PriceCurrencyInterface; use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Api\Data\OrderItemInterface; use Magento\Sales\Api\Data\OrderStatusHistoryInterface; use Magento\Sales\Model\Order\Payment; use Magento\Sales\Model\Order\ProductOption; @@ -22,6 +24,8 @@ use Magento\Sales\Model\ResourceModel\Order\Shipment\Collection as ShipmentCollection; use Magento\Sales\Model\ResourceModel\Order\Shipment\Track\Collection as TrackCollection; use Magento\Sales\Model\ResourceModel\Order\Status\History\Collection as HistoryCollection; +use Magento\Sales\Api\OrderItemRepositoryInterface; +use Magento\Framework\Api\SearchCriteriaBuilder; /** * Order model @@ -285,6 +289,16 @@ class Order extends AbstractModel implements EntityInterface, OrderInterface */ private $productOption; + /** + * @var OrderItemRepositoryInterface + */ + private $itemRepository; + + /** + * @var SearchCriteriaBuilder + */ + private $searchCriteriaBuilder; + /** * @param \Magento\Framework\Model\Context $context * @param \Magento\Framework\Registry $registry @@ -315,6 +329,8 @@ class Order extends AbstractModel implements EntityInterface, OrderInterface * @param array $data * @param ResolverInterface $localeResolver * @param ProductOption|null $productOption + * @param OrderItemRepositoryInterface $itemRepository + * @param SearchCriteriaBuilder $searchCriteriaBuilder * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -346,7 +362,9 @@ public function __construct( \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, array $data = [], ResolverInterface $localeResolver = null, - ProductOption $productOption = null + ProductOption $productOption = null, + OrderItemRepositoryInterface $itemRepository = null, + SearchCriteriaBuilder $searchCriteriaBuilder = null ) { $this->_storeManager = $storeManager; $this->_orderConfig = $orderConfig; @@ -370,6 +388,10 @@ public function __construct( $this->priceCurrency = $priceCurrency; $this->localeResolver = $localeResolver ?: ObjectManager::getInstance()->get(ResolverInterface::class); $this->productOption = $productOption ?: ObjectManager::getInstance()->get(ProductOption::class); + $this->itemRepository = $itemRepository ?: ObjectManager::getInstance() + ->get(OrderItemRepositoryInterface::class); + $this->searchCriteriaBuilder = $searchCriteriaBuilder ?: ObjectManager::getInstance() + ->get(SearchCriteriaBuilder::class); parent::__construct( $context, @@ -667,14 +689,14 @@ private function canCreditmemoForZeroTotalRefunded($totalRefunded) return true; } - + /** * Retrieve credit memo for zero total availability. * * @param float $totalRefunded * @return bool */ - public function canCreditmemoForZeroTotal($totalRefunded) + private function canCreditmemoForZeroTotal($totalRefunded) { $totalPaid = $this->getTotalPaid(); //check if total paid is less than grandtotal @@ -764,13 +786,25 @@ public function canShip() } foreach ($this->getAllItems() as $item) { - if ($item->getQtyToShip() > 0 && !$item->getIsVirtual() && !$item->getLockedDoShip()) { + if ($item->getQtyToShip() > 0 && !$item->getIsVirtual() && + !$item->getLockedDoShip() && !$this->isRefunded($item)) { return true; } } return false; } + /** + * Check if item is refunded. + * + * @param OrderItemInterface $item + * @return bool + */ + private function isRefunded(OrderItemInterface $item) + { + return $item->getQtyRefunded() == $item->getQtyOrdered(); + } + /** * Retrieve order edit availability * @@ -1027,10 +1061,21 @@ public function setState($state) return $this->setData(self::STATE, $state); } + /** + * Retrieve frontend label of order status + * + * @return string + */ + public function getFrontendStatusLabel() + { + return $this->getConfig()->getStatusFrontendLabel($this->getStatus()); + } + /** * Retrieve label of order status * * @return string + * @throws LocalizedException */ public function getStatusLabel() { @@ -1138,12 +1183,12 @@ public function place() * Hold order * * @return $this - * @throws \Magento\Framework\Exception\LocalizedException + * @throws LocalizedException */ public function hold() { if (!$this->canHold()) { - throw new \Magento\Framework\Exception\LocalizedException(__('A hold action is not available.')); + throw new LocalizedException(__('A hold action is not available.')); } $this->setHoldBeforeState($this->getState()); $this->setHoldBeforeStatus($this->getStatus()); @@ -1156,12 +1201,12 @@ public function hold() * Attempt to unhold the order * * @return $this - * @throws \Magento\Framework\Exception\LocalizedException + * @throws LocalizedException */ public function unhold() { if (!$this->canUnhold()) { - throw new \Magento\Framework\Exception\LocalizedException(__('You cannot remove the hold.')); + throw new LocalizedException(__('You cannot remove the hold.')); } $this->setState($this->getHoldBeforeState()) @@ -1205,7 +1250,7 @@ public function isFraudDetected() * @param string $comment * @param bool $graceful * @return $this - * @throws \Magento\Framework\Exception\LocalizedException + * @throws LocalizedException * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ public function registerCancellation($comment = '', $graceful = true) @@ -1244,7 +1289,7 @@ public function registerCancellation($comment = '', $graceful = true) $this->addStatusHistoryComment($comment, false); } } elseif (!$graceful) { - throw new \Magento\Framework\Exception\LocalizedException(__('We cannot cancel this order.')); + throw new LocalizedException(__('We cannot cancel this order.')); } return $this; } @@ -2063,9 +2108,12 @@ public function getIncrementId() public function getItems() { if ($this->getData(OrderInterface::ITEMS) == null) { + $this->searchCriteriaBuilder->addFilter(OrderItemInterface::ORDER_ID, $this->getId()); + + $searchCriteria = $this->searchCriteriaBuilder->create(); $this->setData( OrderInterface::ITEMS, - $this->getItemsCollection()->getItems() + $this->itemRepository->getList($searchCriteria)->getItems() ); } return $this->getData(OrderInterface::ITEMS); @@ -2906,7 +2954,7 @@ public function getDiscountTaxCompensationRefunded() } /** - * Return hold_before_state + * Returns hold_before_state * * @return string|null */ diff --git a/app/code/Magento/Sales/Model/Order/AddressRepository.php b/app/code/Magento/Sales/Model/Order/AddressRepository.php index 2aed6ef1681..af83dde99c6 100644 --- a/app/code/Magento/Sales/Model/Order/AddressRepository.php +++ b/app/code/Magento/Sales/Model/Order/AddressRepository.php @@ -90,7 +90,7 @@ public function get($id) * Find order addresses by criteria. * * @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria - * @return \Magento\Sales\Api\Data\OrderAddressInterface[] + * @return \Magento\Sales\Model\ResourceModel\Order\Address\Collection */ public function getList(\Magento\Framework\Api\SearchCriteriaInterface $searchCriteria) { diff --git a/app/code/Magento/Sales/Model/Order/Config.php b/app/code/Magento/Sales/Model/Order/Config.php index e00eda647dc..1b31caa573f 100644 --- a/app/code/Magento/Sales/Model/Order/Config.php +++ b/app/code/Magento/Sales/Model/Order/Config.php @@ -5,6 +5,8 @@ */ namespace Magento\Sales\Model\Order; +use Magento\Framework\Exception\LocalizedException; + /** * Order configuration model * @@ -73,6 +75,8 @@ public function __construct( } /** + * Get collection. + * * @return \Magento\Sales\Model\ResourceModel\Order\Status\Collection */ protected function _getCollection() @@ -84,8 +88,10 @@ protected function _getCollection() } /** + * Get state. + * * @param string $state - * @return Status|null + * @return Status */ protected function _getState($state) { @@ -101,9 +107,9 @@ protected function _getState($state) * Retrieve default status for state * * @param string $state - * @return string + * @return string|null */ - public function getStateDefaultStatus($state) + public function getStateDefaultStatus($state): ?string { $status = false; $stateNode = $this->_getState($state); @@ -115,24 +121,48 @@ public function getStateDefaultStatus($state) } /** - * Retrieve status label + * Get status label for a specified area * - * @param string $code - * @return string + * @param string|null $code + * @param string $area + * @return string|null */ - public function getStatusLabel($code) + private function getStatusLabelForArea(?string $code, string $area): ?string { - $area = $this->state->getAreaCode(); $code = $this->maskStatusForArea($area, $code); $status = $this->orderStatusFactory->create()->load($code); - if ($area == 'adminhtml') { + if ($area === 'adminhtml') { return $status->getLabel(); } return $status->getStoreLabel(); } + /** + * Retrieve status label for detected area + * + * @param string|null $code + * @return string|null + * @throws LocalizedException + */ + public function getStatusLabel($code) + { + $area = $this->state->getAreaCode() ?: \Magento\Framework\App\Area::AREA_FRONTEND; + return $this->getStatusLabelForArea($code, $area); + } + + /** + * Retrieve status label for area + * + * @param string|null $code + * @return string|null + */ + public function getStatusFrontendLabel(?string $code): ?string + { + return $this->getStatusLabelForArea($code, \Magento\Framework\App\Area::AREA_FRONTEND); + } + /** * Mask status for order for specified area * @@ -249,8 +279,9 @@ public function getInvisibleOnFrontStatuses() } /** - * Get existing order statuses - * Visible or invisible on frontend according to passed param + * Get existing order statuses. + * + * Visible or invisible on frontend according to passed param. * * @param bool $visibility * @return array diff --git a/app/code/Magento/Sales/Model/Order/Creditmemo.php b/app/code/Magento/Sales/Model/Order/Creditmemo.php index cba45be6dfb..708aee5e592 100644 --- a/app/code/Magento/Sales/Model/Order/Creditmemo.php +++ b/app/code/Magento/Sales/Model/Order/Creditmemo.php @@ -42,11 +42,6 @@ class Creditmemo extends AbstractModel implements EntityInterface, CreditmemoInt const REPORT_DATE_TYPE_REFUND_CREATED = 'refund_created'; - /** - * Allow Zero Grandtotal for Creditmemo path - */ - const XML_PATH_ALLOW_ZERO_GRANDTOTAL = 'sales/zerograndtotal_creditmemo/allow_zero_grandtotal'; - /** * Identifier for order history item * @@ -655,10 +650,10 @@ public function isValidGrandTotal() * * @return bool */ - public function isAllowZeroGrandTotal() + private function isAllowZeroGrandTotal() { $isAllowed = $this->scopeConfig->getValue( - self::XML_PATH_ALLOW_ZERO_GRANDTOTAL, + 'sales/zerograndtotal_creditmemo/allow_zero_grandtotal', \Magento\Store\Model\ScopeInterface::SCOPE_STORE ); return $isAllowed; diff --git a/app/code/Magento/Sales/Model/Order/Creditmemo/Item.php b/app/code/Magento/Sales/Model/Order/Creditmemo/Item.php index 5c3f563a4f0..35244b26613 100644 --- a/app/code/Magento/Sales/Model/Order/Creditmemo/Item.php +++ b/app/code/Magento/Sales/Model/Order/Creditmemo/Item.php @@ -10,6 +10,8 @@ use Magento\Sales\Model\AbstractModel; /** + * Creditmemo item model. + * * @api * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @SuppressWarnings(PHPMD.ExcessivePublicCount) @@ -189,6 +191,8 @@ public function register() } /** + * Calculate qty for creditmemo item. + * * @return int|float * @throws \Magento\Framework\Exception\LocalizedException */ @@ -212,6 +216,8 @@ private function processQty() } /** + * Cancel creaditmemeo item. + * * @return $this */ public function cancel() @@ -236,7 +242,7 @@ public function cancel() /** * Invoice item row total calculation * - * @return \Magento\Sales\Model\Order\Invoice\Item + * @return $this */ public function calcRowTotal() { @@ -608,7 +614,7 @@ public function getWeeeTaxRowDisposition() //@codeCoverageIgnoreStart /** - * {@inheritdoc} + * @inheritdoc */ public function setParentId($id) { @@ -616,7 +622,7 @@ public function setParentId($id) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBasePrice($price) { @@ -624,7 +630,7 @@ public function setBasePrice($price) } /** - * {@inheritdoc} + * @inheritdoc */ public function setTaxAmount($amount) { @@ -632,7 +638,7 @@ public function setTaxAmount($amount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseRowTotal($amount) { @@ -640,7 +646,7 @@ public function setBaseRowTotal($amount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setDiscountAmount($amount) { @@ -648,7 +654,7 @@ public function setDiscountAmount($amount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setRowTotal($amount) { @@ -656,7 +662,7 @@ public function setRowTotal($amount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseDiscountAmount($amount) { @@ -664,7 +670,7 @@ public function setBaseDiscountAmount($amount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setPriceInclTax($amount) { @@ -672,7 +678,7 @@ public function setPriceInclTax($amount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseTaxAmount($amount) { @@ -680,7 +686,7 @@ public function setBaseTaxAmount($amount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBasePriceInclTax($amount) { @@ -688,7 +694,7 @@ public function setBasePriceInclTax($amount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseCost($baseCost) { @@ -696,7 +702,7 @@ public function setBaseCost($baseCost) } /** - * {@inheritdoc} + * @inheritdoc */ public function setPrice($price) { @@ -704,7 +710,7 @@ public function setPrice($price) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseRowTotalInclTax($amount) { @@ -712,7 +718,7 @@ public function setBaseRowTotalInclTax($amount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setRowTotalInclTax($amount) { @@ -720,7 +726,7 @@ public function setRowTotalInclTax($amount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setProductId($id) { @@ -728,7 +734,7 @@ public function setProductId($id) } /** - * {@inheritdoc} + * @inheritdoc */ public function setOrderItemId($id) { @@ -736,7 +742,7 @@ public function setOrderItemId($id) } /** - * {@inheritdoc} + * @inheritdoc */ public function setAdditionalData($additionalData) { @@ -744,7 +750,7 @@ public function setAdditionalData($additionalData) } /** - * {@inheritdoc} + * @inheritdoc */ public function setDescription($description) { @@ -752,7 +758,7 @@ public function setDescription($description) } /** - * {@inheritdoc} + * @inheritdoc */ public function setSku($sku) { @@ -760,7 +766,7 @@ public function setSku($sku) } /** - * {@inheritdoc} + * @inheritdoc */ public function setName($name) { @@ -768,7 +774,7 @@ public function setName($name) } /** - * {@inheritdoc} + * @inheritdoc */ public function setDiscountTaxCompensationAmount($amount) { @@ -776,7 +782,7 @@ public function setDiscountTaxCompensationAmount($amount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseDiscountTaxCompensationAmount($amount) { @@ -784,7 +790,7 @@ public function setBaseDiscountTaxCompensationAmount($amount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setWeeeTaxDisposition($weeeTaxDisposition) { @@ -792,7 +798,7 @@ public function setWeeeTaxDisposition($weeeTaxDisposition) } /** - * {@inheritdoc} + * @inheritdoc */ public function setWeeeTaxRowDisposition($weeeTaxRowDisposition) { @@ -800,7 +806,7 @@ public function setWeeeTaxRowDisposition($weeeTaxRowDisposition) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseWeeeTaxDisposition($baseWeeeTaxDisposition) { @@ -808,7 +814,7 @@ public function setBaseWeeeTaxDisposition($baseWeeeTaxDisposition) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseWeeeTaxRowDisposition($baseWeeeTaxRowDisposition) { @@ -816,7 +822,7 @@ public function setBaseWeeeTaxRowDisposition($baseWeeeTaxRowDisposition) } /** - * {@inheritdoc} + * @inheritdoc */ public function setWeeeTaxApplied($weeeTaxApplied) { @@ -824,7 +830,7 @@ public function setWeeeTaxApplied($weeeTaxApplied) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseWeeeTaxAppliedAmount($amount) { @@ -832,7 +838,7 @@ public function setBaseWeeeTaxAppliedAmount($amount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseWeeeTaxAppliedRowAmnt($amnt) { @@ -840,7 +846,7 @@ public function setBaseWeeeTaxAppliedRowAmnt($amnt) } /** - * {@inheritdoc} + * @inheritdoc */ public function setWeeeTaxAppliedAmount($amount) { @@ -848,7 +854,7 @@ public function setWeeeTaxAppliedAmount($amount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setWeeeTaxAppliedRowAmount($amount) { @@ -856,7 +862,7 @@ public function setWeeeTaxAppliedRowAmount($amount) } /** - * {@inheritdoc} + * @inheritdoc * * @return \Magento\Sales\Api\Data\CreditmemoItemExtensionInterface|null */ @@ -866,7 +872,7 @@ public function getExtensionAttributes() } /** - * {@inheritdoc} + * @inheritdoc * * @param \Magento\Sales\Api\Data\CreditmemoItemExtensionInterface $extensionAttributes * @return $this diff --git a/app/code/Magento/Sales/Model/Order/Creditmemo/ItemCreation.php b/app/code/Magento/Sales/Model/Order/Creditmemo/ItemCreation.php index d6da44c5cb5..3fd3eaaa11a 100644 --- a/app/code/Magento/Sales/Model/Order/Creditmemo/ItemCreation.php +++ b/app/code/Magento/Sales/Model/Order/Creditmemo/ItemCreation.php @@ -28,7 +28,7 @@ class ItemCreation implements CreditmemoItemCreationInterface private $extensionAttributes; /** - * {@inheritdoc} + * @inheritdoc */ public function getOrderItemId() { @@ -36,7 +36,7 @@ public function getOrderItemId() } /** - * {@inheritdoc} + * @inheritdoc */ public function setOrderItemId($orderItemId) { @@ -45,7 +45,7 @@ public function setOrderItemId($orderItemId) } /** - * {@inheritdoc} + * @inheritdoc */ public function getQty() { @@ -53,7 +53,7 @@ public function getQty() } /** - * {@inheritdoc} + * @inheritdoc */ public function setQty($qty) { diff --git a/app/code/Magento/Sales/Model/Order/CreditmemoFactory.php b/app/code/Magento/Sales/Model/Order/CreditmemoFactory.php index e61c2597975..73afd0a06f7 100644 --- a/app/code/Magento/Sales/Model/Order/CreditmemoFactory.php +++ b/app/code/Magento/Sales/Model/Order/CreditmemoFactory.php @@ -200,6 +200,7 @@ protected function initData($creditmemo, $data) { if (isset($data['shipping_amount'])) { $creditmemo->setBaseShippingAmount((double)$data['shipping_amount']); + $creditmemo->setBaseShippingInclTax((double)$data['shipping_amount']); } if (isset($data['adjustment_positive'])) { $creditmemo->setAdjustmentPositive($data['adjustment_positive']); @@ -210,6 +211,8 @@ protected function initData($creditmemo, $data) } /** + * Calculate product options. + * * @param Item $orderItem * @param int $parentQty * @return int diff --git a/app/code/Magento/Sales/Model/Order/Email/SenderBuilder.php b/app/code/Magento/Sales/Model/Order/Email/SenderBuilder.php index 7ec089b8829..a7d749ec04c 100644 --- a/app/code/Magento/Sales/Model/Order/Email/SenderBuilder.php +++ b/app/code/Magento/Sales/Model/Order/Email/SenderBuilder.php @@ -5,12 +5,14 @@ */ namespace Magento\Sales\Model\Order\Email; -use Magento\Framework\App\ObjectManager; use Magento\Framework\Mail\Template\TransportBuilder; use Magento\Framework\Mail\Template\TransportBuilderByStore; use Magento\Sales\Model\Order\Email\Container\IdentityInterface; use Magento\Sales\Model\Order\Email\Container\Template; +/** + * Sender Builder + */ class SenderBuilder { /** @@ -29,11 +31,8 @@ class SenderBuilder protected $transportBuilder; /** - * @var TransportBuilderByStore - */ - private $transportBuilderByStore; - - /** + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * * @param Template $templateContainer * @param IdentityInterface $identityContainer * @param TransportBuilder $transportBuilder @@ -48,9 +47,6 @@ public function __construct( $this->templateContainer = $templateContainer; $this->identityContainer = $identityContainer; $this->transportBuilder = $transportBuilder; - $this->transportBuilderByStore = $transportBuilderByStore ?: ObjectManager::getInstance()->get( - TransportBuilderByStore::class - ); } /** @@ -110,7 +106,7 @@ protected function configureEmailTemplate() $this->transportBuilder->setTemplateIdentifier($this->templateContainer->getTemplateId()); $this->transportBuilder->setTemplateOptions($this->templateContainer->getTemplateOptions()); $this->transportBuilder->setTemplateVars($this->templateContainer->getTemplateVars()); - $this->transportBuilderByStore->setFromByStore( + $this->transportBuilder->setFromByStore( $this->identityContainer->getEmailIdentity(), $this->identityContainer->getStore()->getId() ); diff --git a/app/code/Magento/Sales/Model/Order/Payment.php b/app/code/Magento/Sales/Model/Order/Payment.php index 97040c0a578..fc39755c94e 100644 --- a/app/code/Magento/Sales/Model/Order/Payment.php +++ b/app/code/Magento/Sales/Model/Order/Payment.php @@ -264,6 +264,7 @@ public function getParentTransactionId() /** * Returns transaction parent * + * @param string $txnId * @return string * @since 100.1.0 */ @@ -299,6 +300,8 @@ public function canCapture() } /** + * Check refund availability + * * @return bool */ public function canRefund() @@ -307,6 +310,8 @@ public function canRefund() } /** + * Check partial refund availability for invoice + * * @return bool */ public function canRefundPartialPerInvoice() @@ -315,6 +320,8 @@ public function canRefundPartialPerInvoice() } /** + * Check partial capture availability + * * @return bool */ public function canCapturePartial() @@ -324,6 +331,7 @@ public function canCapturePartial() /** * Authorize or authorize and capture payment on gateway, if applicable + * * This method is supposed to be called only when order is placed * * @return $this @@ -539,7 +547,8 @@ public function cancelInvoice($invoice) /** * Create new invoice with maximum qty for invoice for each item - * register this invoice and capture + * + * Register this invoice and capture * * @return Invoice */ @@ -849,6 +858,7 @@ public function cancelCreditmemo($creditmemo) /** * Order cancellation hook for payment method instance + * * Adds void transaction if needed * * @return $this @@ -884,6 +894,8 @@ public function canReviewPayment() } /** + * Check fetch transaction info availability + * * @return bool */ public function canFetchTransactionInfo() @@ -1191,6 +1203,11 @@ public function addTransaction($type, $salesDocument = null, $failSafe = false) } /** + * Add message to the specified transaction. + * + * @param Transaction|null $transaction + * @param string $message + * @return void */ public function addTransactionCommentsToOrder($transaction, $message) { @@ -1227,6 +1244,7 @@ public function importTransactionInfo(Transaction $transactionTo) /** * Totals updater utility method + * * Updates self totals by keys in data array('key' => $delta) * * @param array $data @@ -1261,6 +1279,7 @@ protected function _appendTransactionToMessage($transaction, $message) /** * Prepend a "prepared_message" that may be set to the payment instance before, to the specified message + * * Prepends value to the specified string or to the comment of specified order status history item instance * * @param string|\Magento\Sales\Model\Order\Status\History $messagePrependTo @@ -1303,6 +1322,7 @@ public function formatAmount($amount, $asFloat = false) /** * Format price with currency sign + * * @param float $amount * @return string */ @@ -1313,6 +1333,7 @@ public function formatPrice($amount) /** * Lookup an authorization transaction using parent transaction id, if set + * * @return Transaction|false */ public function getAuthorizationTransaction() @@ -1384,8 +1405,8 @@ public function resetTransactionAdditionalInfo() /** * Prepare credit memo * - * @param $amount - * @param $baseGrandTotal + * @param float $amount + * @param float $baseGrandTotal * @param false|Invoice $invoice * @return mixed */ @@ -1454,6 +1475,8 @@ protected function _getInvoiceForTransactionId($transactionId) } /** + * Get order state resolver instance. + * * @deprecated 100.2.0 * @return OrderStateResolverInterface */ @@ -1992,7 +2015,7 @@ public function getShippingRefunded() } /** - * {@inheritdoc} + * @inheritdoc */ public function setParentId($id) { @@ -2000,7 +2023,7 @@ public function setParentId($id) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseShippingCaptured($baseShippingCaptured) { @@ -2008,7 +2031,7 @@ public function setBaseShippingCaptured($baseShippingCaptured) } /** - * {@inheritdoc} + * @inheritdoc */ public function setShippingCaptured($shippingCaptured) { @@ -2016,7 +2039,7 @@ public function setShippingCaptured($shippingCaptured) } /** - * {@inheritdoc} + * @inheritdoc */ public function setAmountRefunded($amountRefunded) { @@ -2024,7 +2047,7 @@ public function setAmountRefunded($amountRefunded) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseAmountPaid($baseAmountPaid) { @@ -2032,7 +2055,7 @@ public function setBaseAmountPaid($baseAmountPaid) } /** - * {@inheritdoc} + * @inheritdoc */ public function setAmountCanceled($amountCanceled) { @@ -2040,7 +2063,7 @@ public function setAmountCanceled($amountCanceled) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseAmountAuthorized($baseAmountAuthorized) { @@ -2048,7 +2071,7 @@ public function setBaseAmountAuthorized($baseAmountAuthorized) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseAmountPaidOnline($baseAmountPaidOnline) { @@ -2056,7 +2079,7 @@ public function setBaseAmountPaidOnline($baseAmountPaidOnline) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseAmountRefundedOnline($baseAmountRefundedOnline) { @@ -2064,7 +2087,7 @@ public function setBaseAmountRefundedOnline($baseAmountRefundedOnline) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseShippingAmount($amount) { @@ -2072,7 +2095,7 @@ public function setBaseShippingAmount($amount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setShippingAmount($amount) { @@ -2080,7 +2103,7 @@ public function setShippingAmount($amount) } /** - * {@inheritdoc} + * @inheritdoc */ public function setAmountPaid($amountPaid) { @@ -2088,7 +2111,7 @@ public function setAmountPaid($amountPaid) } /** - * {@inheritdoc} + * @inheritdoc */ public function setAmountAuthorized($amountAuthorized) { @@ -2096,7 +2119,7 @@ public function setAmountAuthorized($amountAuthorized) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseAmountOrdered($baseAmountOrdered) { @@ -2104,7 +2127,7 @@ public function setBaseAmountOrdered($baseAmountOrdered) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseShippingRefunded($baseShippingRefunded) { @@ -2112,7 +2135,7 @@ public function setBaseShippingRefunded($baseShippingRefunded) } /** - * {@inheritdoc} + * @inheritdoc */ public function setShippingRefunded($shippingRefunded) { @@ -2120,7 +2143,7 @@ public function setShippingRefunded($shippingRefunded) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseAmountRefunded($baseAmountRefunded) { @@ -2128,7 +2151,7 @@ public function setBaseAmountRefunded($baseAmountRefunded) } /** - * {@inheritdoc} + * @inheritdoc */ public function setAmountOrdered($amountOrdered) { @@ -2136,7 +2159,7 @@ public function setAmountOrdered($amountOrdered) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBaseAmountCanceled($baseAmountCanceled) { @@ -2144,7 +2167,7 @@ public function setBaseAmountCanceled($baseAmountCanceled) } /** - * {@inheritdoc} + * @inheritdoc */ public function setQuotePaymentId($id) { @@ -2152,7 +2175,7 @@ public function setQuotePaymentId($id) } /** - * {@inheritdoc} + * @inheritdoc */ public function setAdditionalData($additionalData) { @@ -2160,7 +2183,7 @@ public function setAdditionalData($additionalData) } /** - * {@inheritdoc} + * @inheritdoc */ public function setCcExpMonth($ccExpMonth) { @@ -2168,7 +2191,7 @@ public function setCcExpMonth($ccExpMonth) } /** - * {@inheritdoc} + * @inheritdoc * @deprecated 100.1.0 unused */ public function setCcSsStartYear($ccSsStartYear) @@ -2177,7 +2200,7 @@ public function setCcSsStartYear($ccSsStartYear) } /** - * {@inheritdoc} + * @inheritdoc */ public function setEcheckBankName($echeckBankName) { @@ -2185,7 +2208,7 @@ public function setEcheckBankName($echeckBankName) } /** - * {@inheritdoc} + * @inheritdoc */ public function setMethod($method) { @@ -2193,7 +2216,7 @@ public function setMethod($method) } /** - * {@inheritdoc} + * @inheritdoc */ public function setCcDebugRequestBody($ccDebugRequestBody) { @@ -2201,7 +2224,7 @@ public function setCcDebugRequestBody($ccDebugRequestBody) } /** - * {@inheritdoc} + * @inheritdoc */ public function setCcSecureVerify($ccSecureVerify) { @@ -2209,7 +2232,7 @@ public function setCcSecureVerify($ccSecureVerify) } /** - * {@inheritdoc} + * @inheritdoc */ public function setProtectionEligibility($protectionEligibility) { @@ -2217,7 +2240,7 @@ public function setProtectionEligibility($protectionEligibility) } /** - * {@inheritdoc} + * @inheritdoc */ public function setCcApproval($ccApproval) { @@ -2225,7 +2248,7 @@ public function setCcApproval($ccApproval) } /** - * {@inheritdoc} + * @inheritdoc */ public function setCcLast4($ccLast4) { @@ -2233,7 +2256,7 @@ public function setCcLast4($ccLast4) } /** - * {@inheritdoc} + * @inheritdoc */ public function setCcStatusDescription($description) { @@ -2241,7 +2264,7 @@ public function setCcStatusDescription($description) } /** - * {@inheritdoc} + * @inheritdoc */ public function setEcheckType($echeckType) { @@ -2249,7 +2272,7 @@ public function setEcheckType($echeckType) } /** - * {@inheritdoc} + * @inheritdoc */ public function setCcDebugResponseSerialized($ccDebugResponseSerialized) { @@ -2257,7 +2280,7 @@ public function setCcDebugResponseSerialized($ccDebugResponseSerialized) } /** - * {@inheritdoc} + * @inheritdoc * @deprecated 100.1.0 unused */ public function setCcSsStartMonth($ccSsStartMonth) @@ -2266,7 +2289,7 @@ public function setCcSsStartMonth($ccSsStartMonth) } /** - * {@inheritdoc} + * @inheritdoc */ public function setEcheckAccountType($echeckAccountType) { @@ -2274,7 +2297,7 @@ public function setEcheckAccountType($echeckAccountType) } /** - * {@inheritdoc} + * @inheritdoc */ public function setLastTransId($id) { @@ -2282,7 +2305,7 @@ public function setLastTransId($id) } /** - * {@inheritdoc} + * @inheritdoc */ public function setCcCidStatus($ccCidStatus) { @@ -2290,7 +2313,7 @@ public function setCcCidStatus($ccCidStatus) } /** - * {@inheritdoc} + * @inheritdoc */ public function setCcOwner($ccOwner) { @@ -2298,7 +2321,7 @@ public function setCcOwner($ccOwner) } /** - * {@inheritdoc} + * @inheritdoc */ public function setCcType($ccType) { @@ -2306,7 +2329,7 @@ public function setCcType($ccType) } /** - * {@inheritdoc} + * @inheritdoc */ public function setPoNumber($poNumber) { @@ -2314,7 +2337,7 @@ public function setPoNumber($poNumber) } /** - * {@inheritdoc} + * @inheritdoc */ public function setCcExpYear($ccExpYear) { @@ -2322,7 +2345,7 @@ public function setCcExpYear($ccExpYear) } /** - * {@inheritdoc} + * @inheritdoc */ public function setCcStatus($ccStatus) { @@ -2330,7 +2353,7 @@ public function setCcStatus($ccStatus) } /** - * {@inheritdoc} + * @inheritdoc */ public function setEcheckRoutingNumber($echeckRoutingNumber) { @@ -2338,7 +2361,7 @@ public function setEcheckRoutingNumber($echeckRoutingNumber) } /** - * {@inheritdoc} + * @inheritdoc */ public function setAccountStatus($accountStatus) { @@ -2346,7 +2369,7 @@ public function setAccountStatus($accountStatus) } /** - * {@inheritdoc} + * @inheritdoc */ public function setAnetTransMethod($anetTransMethod) { @@ -2354,7 +2377,7 @@ public function setAnetTransMethod($anetTransMethod) } /** - * {@inheritdoc} + * @inheritdoc */ public function setCcDebugResponseBody($ccDebugResponseBody) { @@ -2362,7 +2385,7 @@ public function setCcDebugResponseBody($ccDebugResponseBody) } /** - * {@inheritdoc} + * @inheritdoc * @deprecated 100.1.0 unused */ public function setCcSsIssue($ccSsIssue) @@ -2371,7 +2394,7 @@ public function setCcSsIssue($ccSsIssue) } /** - * {@inheritdoc} + * @inheritdoc */ public function setEcheckAccountName($echeckAccountName) { @@ -2379,7 +2402,7 @@ public function setEcheckAccountName($echeckAccountName) } /** - * {@inheritdoc} + * @inheritdoc */ public function setCcAvsStatus($ccAvsStatus) { @@ -2387,7 +2410,7 @@ public function setCcAvsStatus($ccAvsStatus) } /** - * {@inheritdoc} + * @inheritdoc */ public function setCcNumberEnc($ccNumberEnc) { @@ -2395,7 +2418,7 @@ public function setCcNumberEnc($ccNumberEnc) } /** - * {@inheritdoc} + * @inheritdoc */ public function setCcTransId($id) { @@ -2403,7 +2426,7 @@ public function setCcTransId($id) } /** - * {@inheritdoc} + * @inheritdoc */ public function setAddressStatus($addressStatus) { @@ -2411,7 +2434,7 @@ public function setAddressStatus($addressStatus) } /** - * {@inheritdoc} + * @inheritdoc * * @return \Magento\Sales\Api\Data\OrderPaymentExtensionInterface|null */ @@ -2421,7 +2444,7 @@ public function getExtensionAttributes() } /** - * {@inheritdoc} + * @inheritdoc * * @param \Magento\Sales\Api\Data\OrderPaymentExtensionInterface $extensionAttributes * @return $this @@ -2505,6 +2528,7 @@ public function getShouldCloseParentTransaction() /** * Set payment parent transaction id and current transaction id if it not set + * * @param Transaction $transaction * @return void */ @@ -2526,6 +2550,7 @@ private function setTransactionIdsForRefund(Transaction $transaction) /** * Collects order invoices totals by provided keys. + * * Returns result as {key: amount}. * * @param Order $order diff --git a/app/code/Magento/Sales/Model/Order/Payment/Transaction.php b/app/code/Magento/Sales/Model/Order/Payment/Transaction.php index a8acb9aa469..57d6c204dca 100644 --- a/app/code/Magento/Sales/Model/Order/Payment/Transaction.php +++ b/app/code/Magento/Sales/Model/Order/Payment/Transaction.php @@ -560,7 +560,7 @@ public function getOrderId() /** * Retrieve order instance * - * @return \Magento\Sales\Model\Order + * @return \Magento\Sales\Model\Order\Payment */ public function getOrder() { diff --git a/app/code/Magento/Sales/Model/Order/Pdf/AbstractPdf.php b/app/code/Magento/Sales/Model/Order/Pdf/AbstractPdf.php index 8cdc90972bb..85e34f560bb 100644 --- a/app/code/Magento/Sales/Model/Order/Pdf/AbstractPdf.php +++ b/app/code/Magento/Sales/Model/Order/Pdf/AbstractPdf.php @@ -363,38 +363,6 @@ protected function _calcAddressHeight($address) return $y; } - /** - * Detect an input string is Arabic - * - * @param string $subject - * @return bool - */ - private function isArabic(string $subject): bool - { - return (preg_match('/\p{Arabic}/u', $subject) > 0); - } - - /** - * Reverse text with Arabic characters - * - * @param string $string - * @return string - */ - private function reverseArabicText($string) - { - $splitText = explode(' ', $string); - for ($i = 0; $i < count($splitText); $i++) { - if ($this->isArabic($splitText[$i])) { - for ($j = $i + 1; $j < count($splitText); $j++) { - $tmp = $this->string->strrev($splitText[$j]); - $splitText[$j] = $this->string->strrev($splitText[$i]); - $splitText[$i] = $tmp; - } - } - } - return implode(' ', $splitText); - } - /** * Insert order to pdf page * @@ -506,7 +474,7 @@ protected function insertOrder(&$page, $obj, $putOrderId = true) if ($value !== '') { $text = []; foreach ($this->string->split($value, 45, true, true) as $_value) { - $text[] = ($this->isArabic($_value)) ? $this->reverseArabicText($_value) : $_value; + $text[] = $_value; } foreach ($text as $part) { $page->drawText(strip_tags(ltrim($part)), 35, $this->y, 'UTF-8'); @@ -523,7 +491,7 @@ protected function insertOrder(&$page, $obj, $putOrderId = true) if ($value !== '') { $text = []; foreach ($this->string->split($value, 45, true, true) as $_value) { - $text[] = ($this->isArabic($_value)) ? $this->reverseArabicText($_value) : $_value; + $text[] = $_value; } foreach ($text as $part) { $page->drawText(strip_tags(ltrim($part)), 285, $this->y, 'UTF-8'); diff --git a/app/code/Magento/Sales/Model/Order/Shipment.php b/app/code/Magento/Sales/Model/Order/Shipment.php index cecee428364..ef9c6fc628d 100644 --- a/app/code/Magento/Sales/Model/Order/Shipment.php +++ b/app/code/Magento/Sales/Model/Order/Shipment.php @@ -356,12 +356,11 @@ public function getTracksCollection() if ($this->tracksCollection === null) { $this->tracksCollection = $this->_trackCollectionFactory->create(); - if ($this->getId()) { - $this->tracksCollection->setShipmentFilter($this->getId()); + $id = $this->getId() ?: 0; + $this->tracksCollection->setShipmentFilter($id); - foreach ($this->tracksCollection as $item) { - $item->setShipment($this); - } + foreach ($this->tracksCollection as $item) { + $item->setShipment($this); } } diff --git a/app/code/Magento/Sales/Model/Order/Webapi/ChangeOutputArray.php b/app/code/Magento/Sales/Model/Order/Webapi/ChangeOutputArray.php index 57566bfd789..38728d88ff4 100644 --- a/app/code/Magento/Sales/Model/Order/Webapi/ChangeOutputArray.php +++ b/app/code/Magento/Sales/Model/Order/Webapi/ChangeOutputArray.php @@ -9,6 +9,7 @@ use Magento\Sales\Api\Data\OrderItemInterface; use Magento\Sales\Block\Adminhtml\Items\Column\DefaultColumn; +use Magento\Sales\Block\Order\Item\Renderer\DefaultRenderer; /** * Class for changing row total in response. @@ -20,13 +21,21 @@ class ChangeOutputArray */ private $priceRenderer; + /** + * @var DefaultRenderer + */ + private $defaultRenderer; + /** * @param DefaultColumn $priceRenderer + * @param DefaultRenderer $defaultRenderer */ public function __construct( - DefaultColumn $priceRenderer + DefaultColumn $priceRenderer, + DefaultRenderer $defaultRenderer ) { $this->priceRenderer = $priceRenderer; + $this->defaultRenderer = $defaultRenderer; } /** @@ -42,6 +51,12 @@ public function execute( ): array { $result[OrderItemInterface::ROW_TOTAL] = $this->priceRenderer->getTotalAmount($dataObject); $result[OrderItemInterface::BASE_ROW_TOTAL] = $this->priceRenderer->getBaseTotalAmount($dataObject); + $result[OrderItemInterface::ROW_TOTAL_INCL_TAX] = $this->defaultRenderer->getTotalAmount($dataObject); + $result[OrderItemInterface::BASE_ROW_TOTAL_INCL_TAX] = $dataObject->getBaseRowTotal() + + $dataObject->getBaseTaxAmount() + + $dataObject->getBaseDiscountTaxCompensationAmount() + + $dataObject->getBaseWeeeTaxAppliedAmount() + - $dataObject->getBaseDiscountAmount(); return $result; } diff --git a/app/code/Magento/Sales/Model/OrderRepository.php b/app/code/Magento/Sales/Model/OrderRepository.php index a98d6193402..9a1392fbe90 100644 --- a/app/code/Magento/Sales/Model/OrderRepository.php +++ b/app/code/Magento/Sales/Model/OrderRepository.php @@ -18,6 +18,9 @@ use Magento\Sales\Model\ResourceModel\Metadata; use Magento\Framework\App\ObjectManager; use Magento\Tax\Api\OrderTaxManagementInterface; +use Magento\Payment\Api\Data\PaymentAdditionalInfoInterface; +use Magento\Payment\Api\Data\PaymentAdditionalInfoInterfaceFactory; +use Magento\Framework\Serialize\Serializer\Json as JsonSerializer; /** * Repository class @@ -61,6 +64,16 @@ class OrderRepository implements \Magento\Sales\Api\OrderRepositoryInterface */ private $orderTaxManagement; + /** + * @var PaymentAdditionalInfoFactory + */ + private $paymentAdditionalInfoFactory; + + /** + * @var JsonSerializer + */ + private $serializer; + /** * Constructor * @@ -69,13 +82,17 @@ class OrderRepository implements \Magento\Sales\Api\OrderRepositoryInterface * @param CollectionProcessorInterface|null $collectionProcessor * @param \Magento\Sales\Api\Data\OrderExtensionFactory|null $orderExtensionFactory * @param OrderTaxManagementInterface|null $orderTaxManagement + * @param PaymentAdditionalInfoInterfaceFactory|null $paymentAdditionalInfoFactory + * @param JsonSerializer|null $serializer */ public function __construct( Metadata $metadata, SearchResultFactory $searchResultFactory, CollectionProcessorInterface $collectionProcessor = null, \Magento\Sales\Api\Data\OrderExtensionFactory $orderExtensionFactory = null, - OrderTaxManagementInterface $orderTaxManagement = null + OrderTaxManagementInterface $orderTaxManagement = null, + PaymentAdditionalInfoInterfaceFactory $paymentAdditionalInfoFactory = null, + JsonSerializer $serializer = null ) { $this->metadata = $metadata; $this->searchResultFactory = $searchResultFactory; @@ -85,6 +102,10 @@ public function __construct( ->get(\Magento\Sales\Api\Data\OrderExtensionFactory::class); $this->orderTaxManagement = $orderTaxManagement ?: ObjectManager::getInstance() ->get(OrderTaxManagementInterface::class); + $this->paymentAdditionalInfoFactory = $paymentAdditionalInfoFactory ?: ObjectManager::getInstance() + ->get(PaymentAdditionalInfoInterfaceFactory::class); + $this->serializer = $serializer ?: ObjectManager::getInstance() + ->get(JsonSerializer::class); } /** @@ -110,6 +131,7 @@ public function get($id) } $this->setOrderTaxDetails($entity); $this->setShippingAssignments($entity); + $this->setPaymentAdditionalInfo($entity); $this->registry[$id] = $entity; } return $this->registry[$id]; @@ -138,6 +160,34 @@ private function setOrderTaxDetails(OrderInterface $order) $order->setExtensionAttributes($extensionAttributes); } + /** + * Set additional info to the order. + * + * @param OrderInterface $order + * @return void + */ + private function setPaymentAdditionalInfo(OrderInterface $order): void + { + $extensionAttributes = $order->getExtensionAttributes(); + $paymentAdditionalInformation = $order->getPayment()->getAdditionalInformation(); + + $objects = []; + foreach ($paymentAdditionalInformation as $key => $value) { + /** @var PaymentAdditionalInfoInterface $additionalInformationObject */ + $additionalInformationObject = $this->paymentAdditionalInfoFactory->create(); + $additionalInformationObject->setKey($key); + + if (!is_string($value)) { + $value = $this->serializer->serialize($value); + } + $additionalInformationObject->setValue($value); + + $objects[] = $additionalInformationObject; + } + $extensionAttributes->setPaymentAdditionalInfo($objects); + $order->setExtensionAttributes($extensionAttributes); + } + /** * Find entities by criteria * @@ -153,6 +203,7 @@ public function getList(\Magento\Framework\Api\SearchCriteriaInterface $searchCr foreach ($searchResult->getItems() as $order) { $this->setShippingAssignments($order); $this->setOrderTaxDetails($order); + $this->setPaymentAdditionalInfo($order); } return $searchResult; } diff --git a/app/code/Magento/Sales/Model/ResourceModel/Order/Collection.php b/app/code/Magento/Sales/Model/ResourceModel/Order/Collection.php index 5dca2383642..6ad8ebc3bb8 100644 --- a/app/code/Magento/Sales/Model/ResourceModel/Order/Collection.php +++ b/app/code/Magento/Sales/Model/ResourceModel/Order/Collection.php @@ -48,7 +48,7 @@ class Collection extends AbstractCollection implements OrderSearchResultInterfac * @param \Magento\Framework\Event\ManagerInterface $eventManager * @param \Magento\Framework\Model\ResourceModel\Db\VersionControl\Snapshot $entitySnapshot * @param \Magento\Framework\DB\Helper $coreResourceHelper - * @param string|null $connection + * @param \Magento\Framework\DB\Adapter\AdapterInterface $connection * @param \Magento\Framework\Model\ResourceModel\Db\AbstractDb $resource */ public function __construct( @@ -138,6 +138,7 @@ protected function _getAllIdsSelect($limit = null, $offset = null) /** * Join table sales_order_address to select for billing and shipping order addresses. + * * Create correlation map * * @return $this diff --git a/app/code/Magento/Sales/Model/ResourceModel/Order/Handler/State.php b/app/code/Magento/Sales/Model/ResourceModel/Order/Handler/State.php index 3b127abbda7..de15a627583 100644 --- a/app/code/Magento/Sales/Model/ResourceModel/Order/Handler/State.php +++ b/app/code/Magento/Sales/Model/ResourceModel/Order/Handler/State.php @@ -14,7 +14,7 @@ class State { /** - * Check order status before save + * Check order status and adjust the status before save * * @param Order $order * @return $this @@ -23,24 +23,24 @@ class State */ public function check(Order $order) { - if (!$order->isCanceled() && !$order->canUnhold() && !$order->canInvoice() && !$order->canShip()) { - if (0 == $order->getBaseGrandTotal() || $order->canCreditmemo()) { - if ($order->getState() !== Order::STATE_COMPLETE) { - $order->setState(Order::STATE_COMPLETE) - ->setStatus($order->getConfig()->getStateDefaultStatus(Order::STATE_COMPLETE)); - } - } elseif ((float)$order->getTotalRefunded() - || !$order->getTotalRefunded() && $order->hasForcedCanCreditmemo() - ) { - if ($order->getState() !== Order::STATE_CLOSED) { - $order->setState(Order::STATE_CLOSED) - ->setStatus($order->getConfig()->getStateDefaultStatus(Order::STATE_CLOSED)); - } - } - } - if ($order->getState() == Order::STATE_NEW && $order->getIsInProcess()) { + $currentState = $order->getState(); + if ($currentState == Order::STATE_NEW && $order->getIsInProcess()) { $order->setState(Order::STATE_PROCESSING) ->setStatus($order->getConfig()->getStateDefaultStatus(Order::STATE_PROCESSING)); + $currentState = Order::STATE_PROCESSING; + } + + if (!$order->isCanceled() && !$order->canUnhold() && !$order->canInvoice()) { + if (in_array($currentState, [Order::STATE_PROCESSING, Order::STATE_COMPLETE]) + && !$order->canCreditmemo() + && !$order->canShip() + ) { + $order->setState(Order::STATE_CLOSED) + ->setStatus($order->getConfig()->getStateDefaultStatus(Order::STATE_CLOSED)); + } elseif ($currentState === Order::STATE_PROCESSING && !$order->canShip()) { + $order->setState(Order::STATE_COMPLETE) + ->setStatus($order->getConfig()->getStateDefaultStatus(Order::STATE_COMPLETE)); + } } return $this; } diff --git a/app/code/Magento/Sales/Model/ResourceModel/Order/Payment/Collection.php b/app/code/Magento/Sales/Model/ResourceModel/Order/Payment/Collection.php index 521db7f1f3a..fead4f39f4c 100644 --- a/app/code/Magento/Sales/Model/ResourceModel/Order/Payment/Collection.php +++ b/app/code/Magento/Sales/Model/ResourceModel/Order/Payment/Collection.php @@ -33,7 +33,7 @@ class Collection extends AbstractCollection implements OrderPaymentSearchResultI * @param \Magento\Framework\Data\Collection\Db\FetchStrategyInterface $fetchStrategy * @param \Magento\Framework\Event\ManagerInterface $eventManager * @param \Magento\Framework\Model\ResourceModel\Db\VersionControl\Snapshot $entitySnapshot - * @param null $connection + * @param \Magento\Framework\DB\Adapter\AdapterInterface $connection * @param \Magento\Framework\Model\ResourceModel\Db\AbstractDb $resource */ public function __construct( diff --git a/app/code/Magento/Sales/Model/ResourceModel/Report/Collection/AbstractCollection.php b/app/code/Magento/Sales/Model/ResourceModel/Report/Collection/AbstractCollection.php index 86d86255a0c..9c4c87c2e2e 100644 --- a/app/code/Magento/Sales/Model/ResourceModel/Report/Collection/AbstractCollection.php +++ b/app/code/Magento/Sales/Model/ResourceModel/Report/Collection/AbstractCollection.php @@ -25,7 +25,7 @@ class AbstractCollection extends \Magento\Reports\Model\ResourceModel\Report\Col * @param \Magento\Framework\Data\Collection\Db\FetchStrategyInterface $fetchStrategy * @param \Magento\Framework\Event\ManagerInterface $eventManager * @param \Magento\Sales\Model\ResourceModel\Report $resource - * @param null $connection + * @param \Magento\Framework\DB\Adapter\AdapterInterface $connection */ public function __construct( \Magento\Framework\Data\Collection\EntityFactory $entityFactory, diff --git a/app/code/Magento/Sales/Model/Service/CreditmemoService.php b/app/code/Magento/Sales/Model/Service/CreditmemoService.php index 76db7171613..e4435d3481a 100644 --- a/app/code/Magento/Sales/Model/Service/CreditmemoService.php +++ b/app/code/Magento/Sales/Model/Service/CreditmemoService.php @@ -98,7 +98,7 @@ public function __construct( * Cancel an existing creditmemo * * @param int $id Credit Memo Id - * @return bool + * @return void * @throws \Magento\Framework\Exception\LocalizedException * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ diff --git a/app/code/Magento/Sales/Model/Service/InvoiceService.php b/app/code/Magento/Sales/Model/Service/InvoiceService.php index 2806f76b138..ba6ae7eb14b 100644 --- a/app/code/Magento/Sales/Model/Service/InvoiceService.php +++ b/app/code/Magento/Sales/Model/Service/InvoiceService.php @@ -7,9 +7,14 @@ use Magento\Sales\Api\InvoiceManagementInterface; use Magento\Sales\Model\Order; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Serialize\Serializer\Json; +use Magento\Catalog\Model\Product\Type; /** * Class InvoiceService + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class InvoiceService implements InvoiceManagementInterface { @@ -58,6 +63,13 @@ class InvoiceService implements InvoiceManagementInterface */ protected $orderConverter; + /** + * Serializer interface instance. + * + * @var Json + */ + private $serializer; + /** * Constructor * @@ -68,6 +80,7 @@ class InvoiceService implements InvoiceManagementInterface * @param \Magento\Sales\Model\Order\InvoiceNotifier $notifier * @param \Magento\Sales\Api\OrderRepositoryInterface $orderRepository * @param \Magento\Sales\Model\Convert\Order $orderConverter + * @param Json|null $serializer */ public function __construct( \Magento\Sales\Api\InvoiceRepositoryInterface $repository, @@ -76,7 +89,8 @@ public function __construct( \Magento\Framework\Api\FilterBuilder $filterBuilder, \Magento\Sales\Model\Order\InvoiceNotifier $notifier, \Magento\Sales\Api\OrderRepositoryInterface $orderRepository, - \Magento\Sales\Model\Convert\Order $orderConverter + \Magento\Sales\Model\Convert\Order $orderConverter, + Json $serializer = null ) { $this->repository = $repository; $this->commentRepository = $commentRepository; @@ -85,6 +99,7 @@ public function __construct( $this->invoiceNotifier = $notifier; $this->orderRepository = $orderRepository; $this->orderConverter = $orderConverter; + $this->serializer = $serializer ?: ObjectManager::getInstance()->get(Json::class); } /** @@ -142,10 +157,10 @@ public function prepareInvoice(Order $order, array $qtys = []) continue; } $item = $this->orderConverter->itemToInvoiceItem($orderItem); - if ($orderItem->isDummy()) { - $qty = $orderItem->getQtyOrdered() ? $orderItem->getQtyOrdered() : 1; - } elseif (isset($qtys[$orderItem->getId()])) { + if (isset($qtys[$orderItem->getId()])) { $qty = (double) $qtys[$orderItem->getId()]; + } elseif ($orderItem->isDummy()) { + $qty = $orderItem->getQtyOrdered() ? $orderItem->getQtyOrdered() : 1; } elseif (empty($qtys)) { $qty = $orderItem->getQtyToInvoice(); } else { @@ -172,25 +187,69 @@ private function prepareItemsQty(Order $order, array $qtys = []) { foreach ($order->getAllItems() as $orderItem) { if (empty($qtys[$orderItem->getId()])) { - continue; + if ($orderItem->getProductType() == Type::TYPE_BUNDLE && !$orderItem->isShipSeparately()) { + $qtys[$orderItem->getId()] = $orderItem->getQtyOrdered() - $orderItem->getQtyInvoiced(); + } else { + continue; + } } - if ($orderItem->isDummy()) { - if ($orderItem->getHasChildren()) { - foreach ($orderItem->getChildrenItems() as $child) { - if (!isset($qtys[$child->getId()])) { - $qtys[$child->getId()] = $child->getQtyToInvoice(); - } + + $this->prepareItemQty($orderItem, $qtys); + } + + return $qtys; + } + + /** + * Prepare qty_invoiced for order item + * + * @param \Magento\Sales\Api\Data\OrderItemInterface $orderItem + * @param array $qtys + */ + private function prepareItemQty(\Magento\Sales\Api\Data\OrderItemInterface $orderItem, &$qtys) + { + $this->prepareBundleQty($orderItem, $qtys); + + if ($orderItem->isDummy()) { + if ($orderItem->getHasChildren()) { + foreach ($orderItem->getChildrenItems() as $child) { + if (!isset($qtys[$child->getId()])) { + $qtys[$child->getId()] = $child->getQtyToInvoice(); } - } elseif ($orderItem->getParentItem()) { - $parent = $orderItem->getParentItem(); - if (!isset($qtys[$parent->getId()])) { - $qtys[$parent->getId()] = $parent->getQtyToInvoice(); + $parentId = $orderItem->getParentItemId(); + if ($parentId && array_key_exists($parentId, $qtys)) { + $qtys[$orderItem->getId()] = $qtys[$parentId]; + } else { + continue; } } + } elseif ($orderItem->getParentItem()) { + $parent = $orderItem->getParentItem(); + if (!isset($qtys[$parent->getId()])) { + $qtys[$parent->getId()] = $parent->getQtyToInvoice(); + } } } + } - return $qtys; + /** + * Prepare qty to invoice for bundle products + * + * @param \Magento\Sales\Api\Data\OrderItemInterface $orderItem + * @param array $qtys + */ + private function prepareBundleQty(\Magento\Sales\Api\Data\OrderItemInterface $orderItem, &$qtys) + { + if ($orderItem->getProductType() == Type::TYPE_BUNDLE && !$orderItem->isShipSeparately()) { + foreach ($orderItem->getChildrenItems() as $childItem) { + $bundleSelectionAttributes = $childItem->getProductOptionByCode('bundle_selection_attributes'); + if (is_string($bundleSelectionAttributes)) { + $bundleSelectionAttributes = $this->serializer->unserialize($bundleSelectionAttributes); + } + + $qtys[$childItem->getId()] = $qtys[$orderItem->getId()] * $bundleSelectionAttributes['qty']; + } + } } /** diff --git a/app/code/Magento/Sales/Observer/AssignOrderToCustomerObserver.php b/app/code/Magento/Sales/Observer/AssignOrderToCustomerObserver.php index cade86d18e9..5883bde1751 100644 --- a/app/code/Magento/Sales/Observer/AssignOrderToCustomerObserver.php +++ b/app/code/Magento/Sales/Observer/AssignOrderToCustomerObserver.php @@ -31,7 +31,7 @@ public function __construct(OrderRepositoryInterface $orderRepository) } /** - * {@inheritdoc} + * @inheritdoc */ public function execute(Observer $observer) { @@ -44,9 +44,16 @@ public function execute(Observer $observer) $orderId = $delegateData['__sales_assign_order_id']; $order = $this->orderRepository->get($orderId); if (!$order->getCustomerId()) { - //if customer ID wasn't already assigned then assigning. - $order->setCustomerId($customer->getId()); - $order->setCustomerIsGuest(0); + //assign customer info to order after customer creation. + $order->setCustomerId($customer->getId()) + ->setCustomerIsGuest(0) + ->setCustomerEmail($customer->getEmail()) + ->setCustomerFirstname($customer->getFirstname()) + ->setCustomerLastname($customer->getLastname()) + ->setCustomerMiddlename($customer->getMiddlename()) + ->setCustomerPrefix($customer->getPrefix()) + ->setCustomerSuffix($customer->getSuffix()) + ->setCustomerGroupId($customer->getGroupId()); $this->orderRepository->save($order); } } diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderActionGroup.xml index 2bb8b34aa01..aea04c8abfa 100644 --- a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderActionGroup.xml +++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderActionGroup.xml @@ -59,6 +59,15 @@ <see selector="{{AdminHeaderSection.pageTitle}}" userInput="Create New Order" stepKey="seeNewOrderPageTitle"/> </actionGroup> + <!--Navigate to New Order Page for existing Customer And Store--> + <actionGroup name="NavigateToNewOrderPageExistingCustomerAndStoreActionGroup" extends="navigateToNewOrderPageExistingCustomer" > + <arguments> + <argument name="storeView" defaultValue="_defaultStore"/> + </arguments> + <click selector="{{AdminOrderStoreScopeTreeSection.storeOption(storeView.name)}}" stepKey="selectStoreView" after="waitForCreateOrderPageLoad"/> + <waitForPageLoad stepKey="waitForLoad" after="selectStoreView"/> + </actionGroup> + <!--Check the required fields are actually required--> <actionGroup name="checkRequiredFieldsNewOrderForm"> <seeElement selector="{{AdminOrderFormAccountSection.requiredGroup}}" stepKey="seeCustomerGroupRequired"/> @@ -341,12 +350,15 @@ <!--Cancel order that is in pending status--> <actionGroup name="cancelPendingOrder"> + <arguments> + <argument name="orderStatus" type="string" defaultValue="Canceled"/> + </arguments> <click selector="{{AdminOrderDetailsMainActionsSection.cancel}}" stepKey="clickCancelOrder"/> <waitForElement selector="{{AdminConfirmationModalSection.message}}" stepKey="waitForCancelConfirmation"/> <see selector="{{AdminConfirmationModalSection.message}}" userInput="Are you sure you want to cancel this order?" stepKey="seeConfirmationMessage"/> <click selector="{{AdminConfirmationModalSection.ok}}" stepKey="confirmOrderCancel"/> <see selector="{{AdminMessagesSection.success}}" userInput="You canceled the order." stepKey="seeCancelSuccessMessage"/> - <see selector="{{AdminOrderDetailsInformationSection.orderStatus}}" userInput="Canceled" stepKey="seeOrderStatusCanceled"/> + <see selector="{{AdminOrderDetailsInformationSection.orderStatus}}" userInput="{{orderStatus}}" stepKey="seeOrderStatusCanceled"/> </actionGroup> <!--Select Check Money payment method--> @@ -354,4 +366,26 @@ <waitForElementVisible selector="{{AdminOrderFormPaymentSection.paymentBlock}}" stepKey="waitForPaymentOptions"/> <conditionalClick selector="{{AdminOrderFormPaymentSection.checkMoneyOption}}" dependentSelector="{{AdminOrderFormPaymentSection.checkMoneyOption}}" visible="true" stepKey="checkCheckMoneyOption"/> </actionGroup> + + <!-- Create Order --> + <actionGroup name="CreateOrderActionGroup"> + <arguments> + <argument name="product"/> + <argument name="customer"/> + </arguments> + <amOnPage stepKey="navigateToNewOrderPage" url="{{AdminOrderCreatePage.url}}"/> + <click stepKey="chooseCustomer" selector="{{AdminOrdersGridSection.customerInOrdersSection(customer.firstname)}}"/> + <waitForPageLoad stepKey="waitForStoresPageOpened"/> + <click selector="{{OrdersGridSection.addProducts}}" stepKey="clickOnAddProducts"/> + <waitForPageLoad stepKey="waitForProductsListForOrder"/> + <click selector="{{AdminOrdersGridSection.productForOrder(product.sku)}}" stepKey="chooseTheProduct"/> + <click selector="{{AdminOrderFormItemsSection.addSelected}}" stepKey="addSelectedProductToOrder"/> + <waitForPageLoad stepKey="waitForProductAddedInOrder"/> + <click selector="{{AdminInvoicePaymentShippingSection.getShippingMethodAndRates}}" stepKey="openShippingMethod"/> + <waitForPageLoad stepKey="waitForShippingMethods"/> + <click selector="{{AdminInvoicePaymentShippingSection.shippingMethod}}" stepKey="chooseShippingMethod"/> + <waitForPageLoad stepKey="waitForShippingMethodsThickened"/> + <click selector="{{OrdersGridSection.submitOrder}}" stepKey="submitOrder"/> + <see stepKey="seeSuccessMessageForOrder" userInput="You created the order."/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/CreateNewOrderActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/CreateNewOrderActionGroup.xml similarity index 70% rename from app/code/Magento/Braintree/Test/Mftf/ActionGroup/CreateNewOrderActionGroup.xml rename to app/code/Magento/Sales/Test/Mftf/ActionGroup/CreateNewOrderActionGroup.xml index 17d634c009b..abc5698cc71 100644 --- a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/CreateNewOrderActionGroup.xml +++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/CreateNewOrderActionGroup.xml @@ -5,35 +5,35 @@ * See COPYING.txt for license details. */ --> -<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="useBraintreeForMasterCard"> <click stepKey="chooseBraintree" selector="{{NewOrderSection.creditCardBraintree}}"/> - <waitForPageLoad stepKey="waitForBraintreeConfigs" time="5"/> + <waitForPageLoad stepKey="waitForBraintreeConfigs"/> <click stepKey="openCardTypes" selector="{{NewOrderSection.openCardTypes}}"/> - <waitForPageLoad stepKey="waitForCardTypes" time="3"/> + <waitForPageLoad stepKey="waitForCardTypes"/> <click stepKey="chooseCardType" selector="{{NewOrderSection.masterCard}}"/> - <waitForPageLoad stepKey="waitForCardSelected" time="3"/> + <waitForPageLoad stepKey="waitForCardSelected"/> <switchToIFrame stepKey="switchToCardNumber" selector="{{NewOrderSection.cardFrame}}"/> + <waitForElementVisible selector="{{NewOrderSection.creditCardNumber}}" stepKey="waitForFillCardNumber"/> <fillField stepKey="fillCardNumber" selector="{{NewOrderSection.creditCardNumber}}" userInput="{{PaymentAndShippingInfo.cardNumber}}"/> - <waitForPageLoad stepKey="waitForFillCardNumber" time="1"/> <switchToIFrame stepKey="switchBackFromCard"/> <switchToIFrame stepKey="switchToExpirationMonth" selector="{{NewOrderSection.monthFrame}}"/> + <waitForElementVisible selector="{{NewOrderSection.expirationMonth}}" stepKey="waitForFillMonth"/> <fillField stepKey="fillMonth" selector="{{NewOrderSection.expirationMonth}}" userInput="{{PaymentAndShippingInfo.month}}"/> - <waitForPageLoad stepKey="waitForFillMonth" time="1"/> <switchToIFrame stepKey="switchBackFromMonth"/> <switchToIFrame stepKey="switchToExpirationYear" selector="{{NewOrderSection.yearFrame}}"/> + <waitForElementVisible selector="{{NewOrderSection.expirationYear}}" stepKey="waitForFillYear"/> <fillField stepKey="fillYear" selector="{{NewOrderSection.expirationYear}}" userInput="{{PaymentAndShippingInfo.year}}"/> - <waitForPageLoad stepKey="waitForFillYear" time="1"/> <switchToIFrame stepKey="switchBackFromYear"/> <switchToIFrame stepKey="switchToCVV" selector="{{NewOrderSection.cvvFrame}}"/> + <waitForElementVisible selector="{{NewOrderSection.cvv}}" stepKey="waitForFillCVV"/> <fillField stepKey="fillCVV" selector="{{NewOrderSection.cvv}}" userInput="{{PaymentAndShippingInfo.cvv}}"/> - <wait stepKey="waitForFillCVV" time="1"/> <switchToIFrame stepKey="switchBackFromCVV"/> </actionGroup> -</actionGroups> \ No newline at end of file +</actionGroups> diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/OrderAndReturnActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/StorefrontOrderActionGroupActionGroup.xml similarity index 64% rename from app/code/Magento/Sales/Test/Mftf/ActionGroup/OrderAndReturnActionGroup.xml rename to app/code/Magento/Sales/Test/Mftf/ActionGroup/StorefrontOrderActionGroupActionGroup.xml index c46dd612022..fcea25f9975 100644 --- a/app/code/Magento/Sales/Test/Mftf/ActionGroup/OrderAndReturnActionGroup.xml +++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/StorefrontOrderActionGroupActionGroup.xml @@ -9,11 +9,11 @@ <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <!--Fill order information fields and click continue--> - <actionGroup name="StorefrontFillOrderInformationActionGroup"> + <actionGroup name="StorefrontSearchGuestOrderActionGroup"> <arguments> <argument name="orderId" type="string"/> - <argument name="orderLastName"/> - <argument name="orderEmail"/> + <argument name="orderLastName" type="string"/> + <argument name="orderEmail" type="string"/> </arguments> <amOnPage url="{{StorefrontOrdersAndReturnsPage.url}}" stepKey="navigateToOrderAndReturnPage"/> <waitForPageLoad stepKey="waitForPageLoad"/> @@ -24,13 +24,4 @@ <waitForPageLoad stepKey="waitForOrderInformationPageLoad"/> <seeInCurrentUrl url="{{StorefrontOrderInformationPage.url}}" stepKey="seeOrderInformationUrl"/> </actionGroup> - - <!--Enter quantity to return and submit--> - <actionGroup name="StorefrontFillQuantityToReturnActionGroup"> - <click selector="{{StorefrontOrderInformationMainSection.return}}" stepKey="gotToCreateNewReturnPage"/> - <waitForPageLoad stepKey="waitForReturnPageLoad"/> - <fillField selector="{{StorefrontCreateNewReturnMainSection.quantityToReturn}}" userInput="1" stepKey="fillQuantityToReturn"/> - <click selector="{{StorefrontCreateNewReturnMainSection.submit}}" stepKey="clickSubmit"/> - <waitForPageLoad stepKey="waitForPageLoad"/> - </actionGroup> </actionGroups> diff --git a/app/code/Magento/Sales/Test/Mftf/Data/SalesEnableRMAStorefrontConfigData.xml b/app/code/Magento/Sales/Test/Mftf/Data/SalesEnableRMAStorefrontConfigData.xml deleted file mode 100644 index 76ff2081348..00000000000 --- a/app/code/Magento/Sales/Test/Mftf/Data/SalesEnableRMAStorefrontConfigData.xml +++ /dev/null @@ -1,23 +0,0 @@ -<?xml version="1.0"?> -<!-- -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> -<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> - <entity name="EnableRMA" type="sales_rma_config"> - <requiredEntity type="enabled">EnableRMAStorefront</requiredEntity> - </entity> - <entity name="EnableRMAStorefront" type="enabled"> - <data key="value">1</data> - </entity> - - <entity name="DisableRMA" type="sales_rma_config"> - <requiredEntity type="enabled">DisableRMAStorefront</requiredEntity> - </entity> - <entity name="DisableRMAStorefront" type="enabled"> - <data key="value">0</data> - </entity> -</entities> diff --git a/app/code/Magento/Sales/Test/Mftf/Metadata/sales_enable_rma_config-meta.xml b/app/code/Magento/Sales/Test/Mftf/Metadata/sales_enable_rma_config-meta.xml deleted file mode 100644 index 86226265dd1..00000000000 --- a/app/code/Magento/Sales/Test/Mftf/Metadata/sales_enable_rma_config-meta.xml +++ /dev/null @@ -1,20 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> -<operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> - <operation name="SalesRMAConfig" dataType="sales_rma_config" type="create" auth="adminFormKey" url="/admin/system_config/save/section/sales/" method="POST"> - <object key="groups" dataType="sales_rma_config"> - <object key="rma" dataType="sales_rma_config"> - <object key="fields" dataType="sales_rma_config"> - <object key="enabled" dataType="enabled"> - <field key="value">string</field> - </object> - </object> - </object> - </object> - </operation> -</operations> diff --git a/app/code/Magento/Sales/Test/Mftf/Page/AdminOrderPage.xml b/app/code/Magento/Sales/Test/Mftf/Page/AdminOrderPage.xml new file mode 100644 index 00000000000..6abe265a37b --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/Page/AdminOrderPage.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="AdminOrderPage" url="sales/order/view/order_id/{{var1}}" area="admin" module="Magento_Sales" parameterized="true"> + </page> +</pages> diff --git a/app/code/Magento/Sales/Test/Mftf/Page/StorefrontOrdersAndReturnsPage.xml b/app/code/Magento/Sales/Test/Mftf/Page/StorefrontOrdersAndReturnsPage.xml index 32d94c31758..ee546174d96 100644 --- a/app/code/Magento/Sales/Test/Mftf/Page/StorefrontOrdersAndReturnsPage.xml +++ b/app/code/Magento/Sales/Test/Mftf/Page/StorefrontOrdersAndReturnsPage.xml @@ -9,7 +9,6 @@ <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="StorefrontOrdersAndReturnsPage" url="sales/guest/form" area="guest" module="Magento_Sales"> - <section name="OrderAndReturnsMainSection"/> - <section name="OrderInformationSection"/> + <section name="StorefrontOrderAndReturnInformationSection"/> </page> </pages> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminCreditMemoTotalSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminCreditMemoTotalSection.xml index 84fcc8fc47d..731c529f2ae 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminCreditMemoTotalSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminCreditMemoTotalSection.xml @@ -21,5 +21,6 @@ <element name="submitRefundOffline" type="button" selector=".order-totals-actions button[data-ui-id='order-items-submit-button']" timeout="30"/> <element name="creditMemoItem" type="text" selector="#sales_order_view_tabs_order_creditmemos"/> <element name="viewMemo" type="text" selector="div#sales_order_view_tabs_order_creditmemos_content a.action-menu-item"/> + <element name="refundOffline" type="button" selector=".order-totals-actions button[data-ui-id='order-items-submit-offline']"/> </section> -</sections> \ No newline at end of file +</sections> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoiceItemsSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoiceItemsSection.xml index bc0d1cffd5d..92c01cf3807 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoiceItemsSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoiceItemsSection.xml @@ -29,4 +29,4 @@ <element name="totalColumn" type="text" selector=".order-invoice-tables .col-total .price"/> <element name="updateQty" type="button" selector=".order-invoice-tables tfoot button[data-ui-id='order-items-update-button']"/> </section> -</sections> \ No newline at end of file +</sections> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderDetailsMainActionsSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderDetailsMainActionsSection.xml index 578022217f3..6fa5d9a9a37 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderDetailsMainActionsSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderDetailsMainActionsSection.xml @@ -18,5 +18,6 @@ <element name="ship" type="button" selector="#order_ship" timeout="30"/> <element name="reorder" type="button" selector="#order_reorder" timeout="30"/> <element name="edit" type="button" selector="#order_edit" timeout="30"/> + <element name="modalOk" type="button" selector=".action-accept"/> </section> </sections> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormPaymentSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormPaymentSection.xml index 22bff9c286d..1a12a68a687 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormPaymentSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormPaymentSection.xml @@ -16,5 +16,6 @@ <element name="freeShippingOption" type="radio" selector="#s_method_freeshipping_freeshipping" timeout="30"/> <element name="checkMoneyOption" type="radio" selector="#p_method_checkmo" timeout="30"/> <element name="paymentBlock" type="text" selector="#order-billing_method" /> + <element name="paymentError" type="text" selector="#payment[method]-error"/> </section> </sections> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormShippingAddressSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormShippingAddressSection.xml index b79d9332687..0f1461b121e 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormShippingAddressSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormShippingAddressSection.xml @@ -10,6 +10,7 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminOrderFormShippingAddressSection"> <element name="SameAsBilling" type="checkbox" selector="#order-shipping_same_as_billing"/> + <element name="SelectFromExistingCustomerAddress" type="select" selector="#order-shipping_address_customer_address_id"/> <element name="NamePrefix" type="input" selector="#order-shipping_address_prefix"/> <element name="FirstName" type="input" selector="#order-shipping_address_firstname"/> <element name="MiddleName" type="input" selector="#order-shipping_address_middlename"/> diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/ConfigurationListSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/ConfigurationListSection.xml similarity index 72% rename from app/code/Magento/Braintree/Test/Mftf/Section/ConfigurationListSection.xml rename to app/code/Magento/Sales/Test/Mftf/Section/ConfigurationListSection.xml index 100407438ea..bce5f95cf78 100644 --- a/app/code/Magento/Braintree/Test/Mftf/Section/ConfigurationListSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/ConfigurationListSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="ConfigurationListSection"> <element name="sales" type="button" selector="//div[contains(@class, 'admin__page-nav-title title _collapsible')]/strong[text()='Sales']"/> <element name="salesPaymentMethods" type="button" selector="//span[text()='Payment Methods']"/> diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/NewOrderSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/NewOrderSection.xml similarity index 89% rename from app/code/Magento/Braintree/Test/Mftf/Section/NewOrderSection.xml rename to app/code/Magento/Sales/Test/Mftf/Section/NewOrderSection.xml index 13f59ad2cf1..26e00bf4c0a 100644 --- a/app/code/Magento/Braintree/Test/Mftf/Section/NewOrderSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/NewOrderSection.xml @@ -5,7 +5,9 @@ * See COPYING.txt for license details. */ --> -<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="NewOrderSection"> <element name="createNewOrder" type="button" selector="#add"/> <element name="customer" type="button" selector="//td[contains(text(), 'Abgar')]"/> @@ -30,6 +32,5 @@ <element name="cvv" type="input" selector="#cvv"/> <element name="submitOrder" type="input" selector="#submit_order_top_button"/> <element name="successMessage" type="input" selector="#messages"/> - </section> -</sections> \ No newline at end of file +</sections> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/StorefrontCreateNewReturnMainSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/StorefrontCreateNewReturnMainSection.xml deleted file mode 100644 index fe8391cf3c2..00000000000 --- a/app/code/Magento/Sales/Test/Mftf/Section/StorefrontCreateNewReturnMainSection.xml +++ /dev/null @@ -1,18 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> - <section name="StorefrontCreateNewReturnMainSection"> - <element name="quantityToReturn" type="input" selector="#items:qty_requested0"/> - <element name="submit" type="submit" selector="//span[contains(text(), 'Submit')]"/> - <element name="resolutionError" type="text" selector="//*[@id='items:resolution0']/following-sibling::div[contains(text(),'Please select an option')]"/> - <element name="conditionError" type="text" selector="//*[@id='items:condition0']/following-sibling::div[contains(text(),'Please select an option')]"/> - <element name="reasonError" type="text" selector="//*[@id='items:reason0']/following-sibling::div[contains(text(),'Please select an option')]"/> - </section> -</sections> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCorrectnessInvoicedItemInBundleProductTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCorrectnessInvoicedItemInBundleProductTest.xml new file mode 100644 index 00000000000..7c83f35468c --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCorrectnessInvoicedItemInBundleProductTest.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="AdminCorrectnessInvoicedItemInBundleProductTest"> + <annotations> + <features value="Sales"/> + <title value="Check correctness of invoiced items in a Bundle Product"/> + <description value="Check correctness of invoiced items in a Bundle Product"/> + <severity value="CRITICAL"/> + <testCaseId value="MC-11059"/> + <useCaseId value="MC-10969"/> + <group value="sales"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + + <!--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="createBundleProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="DropDownBundleOption" stepKey="bundleOption"> + <requiredEntity createDataKey="createBundleProduct"/> + </createData> + <createData entity="ApiBundleLink" stepKey="createBundleLink1"> + <requiredEntity createDataKey="createBundleProduct"/> + <requiredEntity createDataKey="bundleOption"/> + <requiredEntity createDataKey="createSimpleProduct"/> + <field key="qty">10</field> + </createData> + </before> + <after> + <!--Delete created data--> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> + <deleteData createDataKey="createBundleProduct" stepKey="deleteBundleProduct"/> + + <actionGroup ref="logout" stepKey="logOut"/> + </after> + + <!--Complete Bundle product creation--> + <amOnPage url="{{AdminProductEditPage.url($$createBundleProduct.id$$)}}" stepKey="goToProductEditPage"/> + <actionGroup ref="saveProductForm" stepKey="saveProduct"/> + + <!--Go to bundle product page--> + <amOnPage url="{{StorefrontProductPage.url($$createCategory.name$$)}}" stepKey="navigateToBundleProductPage"/> + + <!--Place order bundle product with 10 options--> + <actionGroup ref="StorefrontAddCategoryBundleProductToCartActionGroup" stepKey="addBundleProductToCart"> + <argument name="product" value="$$createBundleProduct$$"/> + <argument name="quantity" value="10"/> + </actionGroup> + <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="goToCheckoutFromMinicart" /> + <actionGroup ref="GuestCheckoutFillingShippingSectionActionGroup" stepKey="guestCheckoutFillingShipping"/> + <actionGroup ref="CheckoutPlaceOrderActionGroup" stepKey="placeOrder"> + <argument name="orderNumberMessage" value="CONST.successGuestCheckoutOrderNumberMessage" /> + <argument name="emailYouMessage" value="CONST.successCheckoutEmailYouMessage"/> + </actionGroup> + + <!--Go to order page submit invoice--> + <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber}}" stepKey="grabOrderNumber"/> + <amOnPage url="{{AdminOrdersPage.url}}" stepKey="onOrdersPage"/> + <actionGroup ref="filterOrderGridById" stepKey="filterOrderGridById"> + <argument name="orderId" value="$grabOrderNumber"/> + </actionGroup> + <click selector="{{AdminOrdersGridSection.firstRow}}" stepKey="clickOrderRow"/> + <waitForPageLoad stepKey="waitForCreatedOrderPageOpened"/> + <actionGroup ref="goToInvoiceIntoOrder" stepKey="goToInvoiceIntoOrderPage"/> + <fillField selector="{{AdminInvoiceItemsSection.qtyToInvoiceColumn}}" userInput="5" stepKey="ChangeQtyToInvoice"/> + <click selector="{{AdminInvoiceItemsSection.updateQty}}" stepKey="updateQunatity"/> + <waitForPageLoad stepKey="waitPageToBeLoaded"/> + <actionGroup ref="submitInvoiceIntoOrder" stepKey="submitInvoice"/> + + <!--Verify invoiced items qty in ship tab--> + <actionGroup ref="goToShipmentIntoOrder" stepKey="goToShipment"/> + <grabTextFrom selector="{{AdminShipmentItemsSection.itemQtyInvoiced('1')}}" stepKey="grabInvoicedItemQty"/> + <assertEquals expected="5" expectedType="string" actual="$grabInvoicedItemQty" stepKey="assertInvoicedItemsQty"/> + </test> +</tests> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateInvoiceTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateInvoiceTest.xml index 94e99d25dbb..099cf7fbce9 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateInvoiceTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateInvoiceTest.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="AdminCreateInvoiceTest"> <annotations> <features value="Sales"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithBundleProductTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithBundleProductTest.xml index f15f5de5df6..d087b291de8 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithBundleProductTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithBundleProductTest.xml @@ -5,8 +5,9 @@ * See COPYING.txt for license details. */ --> + <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminCreateOrderWithBundleProductTest"> <annotations> <title value="Create Order in Admin and update bundle product configuration"/> @@ -108,4 +109,4 @@ <deleteData createDataKey="simple2" stepKey="deleteSimple2" before="delete"/> </after> </test> -</tests> \ No newline at end of file +</tests> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminSubmitConfigurableProductOrderTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminSubmitConfigurableProductOrderTest.xml index 041252af0ac..63607e59c41 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminSubmitConfigurableProductOrderTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminSubmitConfigurableProductOrderTest.xml @@ -5,8 +5,9 @@ * See COPYING.txt for license details. */ --> + <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminSubmitConfigurableProductOrderTest"> <annotations> <title value="Create Order in Admin and update product configuration"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminSubmitsOrderPaymentMethodValidationTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminSubmitsOrderPaymentMethodValidationTest.xml new file mode 100644 index 00000000000..e487c62b967 --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminSubmitsOrderPaymentMethodValidationTest.xml @@ -0,0 +1,77 @@ +<?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="AdminSubmitsOrderPaymentMethodValidationTest"> + <annotations> + <features value="Sales"/> + <stories value="MC-5537: No UI validation for Payment methods when creating an order from admin"/> + <title value="UI validation for Payment methods when creating an order from admin"/> + <description value="Admin should not be able to submit orders without selecting a payment method when there is more than one"/> + <severity value="AVERAGE"/> + <testCaseId value="MC-6029"/> + <group value="sales"/> + </annotations> + <before> + <magentoCLI stepKey="allowSpecificValue" command="config:set payment/cashondelivery/active 1" /> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="_defaultProduct" stepKey="createSimpleProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <after> + <magentoCLI stepKey="allowSpecificValue" command="config:set payment/cashondelivery/active 0" /> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteProduct"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <amOnPage url="admin/admin/auth/logout/" stepKey="amOnLogoutPage"/> + </after> + <!--Create order via Admin--> + <comment userInput="Admin creates order" stepKey="adminCreateOrderComment"/> + <amOnPage url="{{AdminOrdersPage.url}}" stepKey="navigateToOrderIndexPage"/> + <waitForPageLoad stepKey="waitForIndexPageLoad"/> + <see selector="{{AdminHeaderSection.pageTitle}}" userInput="Orders" stepKey="seeIndexPageTitle"/> + <click selector="{{AdminOrdersGridSection.createNewOrder}}" stepKey="clickCreateNewOrder"/> + <click selector="{{AdminOrderFormActionSection.CreateNewCustomer}}" stepKey="clickCreateCustomer"/> + <see selector="{{AdminHeaderSection.pageTitle}}" userInput="Create New Order" stepKey="seeNewOrderPageTitle"/> + + <!--Check if order can be submitted without the required fields--> + <actionGroup ref="addSimpleProductToOrder" stepKey="addSimpleProductToOrder" after="seeNewOrderPageTitle"> + <argument name="product" value="_defaultProduct"/> + </actionGroup> + <actionGroup ref="checkRequiredFieldsNewOrderForm" stepKey="checkRequiredFieldsNewOrder" after="addSimpleProductToOrder"/> + <see selector="{{AdminOrderFormPaymentSection.paymentError}}" userInput="Please select one of the options." stepKey="seePaymentMethodRequired" after="checkRequiredFieldsNewOrder"/> + <scrollToTopOfPage stepKey="scrollToTopOfOrderFormPage" after="seePaymentMethodRequired"/> + + <!--Fill customer group and customer email--> + <selectOption selector="{{AdminOrderFormAccountSection.group}}" userInput="{{GeneralCustomerGroup.code}}" stepKey="selectCustomerGroup" after="scrollToTopOfOrderFormPage"/> + <fillField selector="{{AdminOrderFormAccountSection.email}}" userInput="{{Simple_US_Customer.email}}" stepKey="fillCustomerEmail" after="selectCustomerGroup"/> + + <!--Fill customer address information--> + <actionGroup ref="fillOrderCustomerInformation" stepKey="fillCustomerAddress" after="fillCustomerEmail"> + <argument name="customer" value="Simple_US_Customer"/> + <argument name="address" value="US_Address_TX"/> + </actionGroup> + + <!-- Select payment and shipping --> + <waitForElementVisible selector="{{AdminOrderFormPaymentSection.checkMoneyOption}}" stepKey="waitForPaymentOptions"/> + <selectOption selector="{{AdminOrderFormPaymentSection.checkMoneyOption}}" userInput="checkmo" stepKey="checkPaymentOption"/> + <actionGroup ref="orderSelectFlatRateShipping" stepKey="selectFlatRateShipping" after="fillCustomerAddress"/> + + <!--Verify totals on Order page--> + <see selector="{{AdminOrderFormTotalSection.total('Subtotal')}}" userInput="${{AdminOrderSimpleProduct.subtotal}}" stepKey="seeOrderSubTotal" after="selectFlatRateShipping"/> + <see selector="{{AdminOrderFormTotalSection.total('Shipping')}}" userInput="${{AdminOrderSimpleProduct.shipping}}" stepKey="seeOrderShipping" after="seeOrderSubTotal"/> + <scrollTo selector="{{AdminOrderFormTotalSection.grandTotal}}" stepKey="scrollToOrderGrandTotal"/> + <see selector="{{AdminOrderFormTotalSection.grandTotal}}" userInput="${{AdminOrderSimpleProduct.grandTotal}}" stepKey="seeCorrectGrandTotal" after="scrollToOrderGrandTotal"/> + + <!--Submit Order and verify information--> + <click selector="{{AdminOrderFormActionSection.SubmitOrder}}" stepKey="clickSubmitOrder" after="seeCorrectGrandTotal"/> + <seeInCurrentUrl url="{{AdminOrderDetailsPage.url}}" stepKey="seeViewOrderPage" after="clickSubmitOrder"/> + <see selector="{{AdminOrderDetailsMessagesSection.successMessage}}" userInput="You created the order." stepKey="seeSuccessMessage" after="seeViewOrderPage"/> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Sales/Test/Mftf/Test/CreditMemoTotalAfterShippingDiscountTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/CreditMemoTotalAfterShippingDiscountTest.xml index d8ab034b647..dfbdc536779 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/CreditMemoTotalAfterShippingDiscountTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/CreditMemoTotalAfterShippingDiscountTest.xml @@ -124,6 +124,7 @@ <click selector="{{AdminInvoiceMainActionsSection.submitInvoice}}" stepKey="clickSubmitInvoice"/> <see selector="{{AdminOrderDetailsMessagesSection.successMessage}}" userInput="The invoice has been created." stepKey="seeSuccessMessage1"/> + <see selector="{{AdminOrderDetailsInformationSection.orderStatus}}" userInput="Processing" stepKey="seeOrderProcessing"/> <!--Create Credit Memo--> <comment userInput="Admin creates credit memo" stepKey="createCreditMemoComment"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/StorefrontRedirectToOrderHistory.xml b/app/code/Magento/Sales/Test/Mftf/Test/StorefrontRedirectToOrderHistory.xml index 9790b5dfc47..ad3a411d924 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/StorefrontRedirectToOrderHistory.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/StorefrontRedirectToOrderHistory.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="StorefrontRedirectToOrderHistory"> <annotations> <features value="Redirection Rules"/> diff --git a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Creditmemo/SaveTest.php b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Creditmemo/SaveTest.php index 4ad2e314c83..c3ff8a2acaf 100644 --- a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Creditmemo/SaveTest.php +++ b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Creditmemo/SaveTest.php @@ -203,10 +203,9 @@ public function testSaveActionWithNegativeCreditmemo() $creditmemoMock = $this->createPartialMock( \Magento\Sales\Model\Order\Creditmemo::class, - ['load', 'getGrandTotal', 'isAllowZeroGrandTotal', '__wakeup'] + ['load', 'isValidGrandTotal', '__wakeup'] ); - $creditmemoMock->expects($this->once())->method('getGrandTotal')->will($this->returnValue('0')); - $creditmemoMock->expects($this->once())->method('isAllowZeroGrandTotal')->will($this->returnValue(false)); + $creditmemoMock->expects($this->once())->method('isValidGrandTotal')->will($this->returnValue(false)); $this->memoLoaderMock->expects( $this->once() )->method( diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/CreditmemoTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/CreditmemoTest.php index f299cd5accd..4d8dd00ac65 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/Order/CreditmemoTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/CreditmemoTest.php @@ -117,27 +117,9 @@ public function testIsValidGrandTotalGrandTotalEmpty() public function testIsValidGrandTotalGrandTotal() { $this->creditmemo->setGrandTotal(0); - $this->creditmemo->isAllowZeroGrandTotal(true); $this->assertFalse($this->creditmemo->isValidGrandTotal()); } - /** - * Test for isAllowZeroGrandTotal method. - * - * @return void - */ - public function testIsAllowZeroGrandTotal() - { - $isAllowed = 0; - $this->scopeConfigMock->expects($this->once()) - ->method('getValue') - ->with( - 'sales/zerograndtotal_creditmemo/allow_zero_grandtotal', - \Magento\Store\Model\ScopeInterface::SCOPE_STORE - )->willReturn($isAllowed); - $this->assertEquals($isAllowed, $this->creditmemo->isAllowZeroGrandTotal()); - } - public function testIsValidGrandTotal() { $this->creditmemo->setGrandTotal(1); diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/SenderBuilderTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/SenderBuilderTest.php index 38209bb22ae..759d60d9e66 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/SenderBuilderTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/SenderBuilderTest.php @@ -6,7 +6,6 @@ namespace Magento\Sales\Test\Unit\Model\Order\Email; -use Magento\Framework\Mail\Template\TransportBuilderByStore; use Magento\Sales\Model\Order\Email\SenderBuilder; class SenderBuilderTest extends \PHPUnit\Framework\TestCase @@ -36,11 +35,6 @@ class SenderBuilderTest extends \PHPUnit\Framework\TestCase */ private $storeMock; - /** - * @var \PHPUnit_Framework_MockObject_MockObject - */ - private $transportBuilderByStore; - protected function setUp() { $templateId = 'test_template_id'; @@ -82,11 +76,10 @@ protected function setUp() 'setTemplateIdentifier', 'setTemplateOptions', 'setTemplateVars', + 'setFromByStore', ] ); - $this->transportBuilderByStore = $this->createMock(TransportBuilderByStore::class); - $this->templateContainerMock->expects($this->once()) ->method('getTemplateId') ->will($this->returnValue($templateId)); @@ -109,9 +102,9 @@ protected function setUp() $this->identityContainerMock->expects($this->once()) ->method('getEmailIdentity') ->will($this->returnValue($emailIdentity)); - $this->transportBuilderByStore->expects($this->once()) + $this->transportBuilder->expects($this->once()) ->method('setFromByStore') - ->with($this->equalTo($emailIdentity)); + ->with($this->equalTo($emailIdentity), 1); $this->identityContainerMock->expects($this->once()) ->method('getEmailCopyTo') @@ -120,8 +113,7 @@ protected function setUp() $this->senderBuilder = new SenderBuilder( $this->templateContainerMock, $this->identityContainerMock, - $this->transportBuilder, - $this->transportBuilderByStore + $this->transportBuilder ); } @@ -129,6 +121,8 @@ public function testSend() { $customerName = 'test_name'; $customerEmail = 'test_email'; + $identity = 'email_identity_test'; + $transportMock = $this->createMock( \Magento\Sales\Test\Unit\Model\Order\Email\Stub\TransportInterfaceMock::class ); @@ -151,6 +145,9 @@ public function testSend() $this->storeMock->expects($this->once()) ->method('getId') ->willReturn(1); + $this->transportBuilder->expects($this->once()) + ->method('setFromByStore') + ->with($identity, 1); $this->transportBuilder->expects($this->once()) ->method('addTo') ->with($this->equalTo($customerEmail), $this->equalTo($customerName)); @@ -164,6 +161,7 @@ public function testSend() public function testSendCopyTo() { + $identity = 'email_identity_test'; $transportMock = $this->createMock( \Magento\Sales\Test\Unit\Model\Order\Email\Stub\TransportInterfaceMock::class ); @@ -177,6 +175,9 @@ public function testSendCopyTo() $this->transportBuilder->expects($this->once()) ->method('addTo') ->with($this->equalTo('example@mail.com')); + $this->transportBuilder->expects($this->once()) + ->method('setFromByStore') + ->with($identity, 1); $this->identityContainerMock->expects($this->once()) ->method('getStore') ->willReturn($this->storeMock); diff --git a/app/code/Magento/Sales/Test/Unit/Model/OrderRepositoryTest.php b/app/code/Magento/Sales/Test/Unit/Model/OrderRepositoryTest.php index 2e82d8064a9..7f0c0639d21 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/OrderRepositoryTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/OrderRepositoryTest.php @@ -10,6 +10,7 @@ use Magento\Sales\Api\Data\OrderSearchResultInterfaceFactory as SearchResultFactory; use Magento\Sales\Model\ResourceModel\Metadata; use Magento\Tax\Api\OrderTaxManagementInterface; +use Magento\Payment\Api\Data\PaymentAdditionalInfoInterfaceFactory; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -46,6 +47,11 @@ class OrderRepositoryTest extends \PHPUnit\Framework\TestCase */ private $orderTaxManagementMock; + /** + * @var PaymentAdditionalInfoInterfaceFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $paymentAdditionalInfoFactory; + /** * Setup the test * @@ -69,6 +75,8 @@ protected function setUp() $this->orderTaxManagementMock = $this->getMockBuilder(OrderTaxManagementInterface::class) ->disableOriginalConstructor() ->getMockForAbstractClass(); + $this->paymentAdditionalInfoFactory = $this->getMockBuilder(PaymentAdditionalInfoInterfaceFactory::class) + ->disableOriginalConstructor()->setMethods(['create'])->getMockForAbstractClass(); $this->orderRepository = $this->objectManager->getObject( \Magento\Sales\Model\OrderRepository::class, [ @@ -76,7 +84,8 @@ protected function setUp() 'searchResultFactory' => $this->searchResultFactory, 'collectionProcessor' => $this->collectionProcessor, 'orderExtensionFactory' => $orderExtensionFactoryMock, - 'orderTaxManagement' => $this->orderTaxManagementMock + 'orderTaxManagement' => $this->orderTaxManagementMock, + 'paymentAdditionalInfoFactory' => $this->paymentAdditionalInfoFactory ] ); } @@ -95,12 +104,16 @@ public function testGetList() $orderTaxDetailsMock = $this->getMockBuilder(\Magento\Tax\Api\Data\OrderTaxDetailsInterface::class) ->disableOriginalConstructor() ->setMethods(['getAppliedTaxes', 'getItems'])->getMockForAbstractClass(); + $paymentMock = $this->getMockBuilder(\Magento\Sales\Api\Data\OrderPaymentInterface::class) + ->disableOriginalConstructor()->getMockForAbstractClass(); + $paymentAdditionalInfo = $this->getMockBuilder(\Magento\Payment\Api\Data\PaymentAdditionalInfoInterface::class) + ->disableOriginalConstructor()->setMethods(['setKey', 'setValue'])->getMockForAbstractClass(); $extensionAttributes = $this->createPartialMock( \Magento\Sales\Api\Data\OrderExtension::class, [ 'getShippingAssignments', 'setShippingAssignments', 'setConvertingFromQuote', - 'setAppliedTaxes', 'setItemAppliedTaxes' + 'setAppliedTaxes', 'setItemAppliedTaxes', 'setPaymentAdditionalInfo' ] ); $shippingAssignmentBuilder = $this->createMock( @@ -111,6 +124,13 @@ public function testGetList() ->method('process') ->with($searchCriteriaMock, $collectionMock); $itemsMock->expects($this->atLeastOnce())->method('getExtensionAttributes')->willReturn($extensionAttributes); + $itemsMock->expects($this->atleastOnce())->method('getPayment')->willReturn($paymentMock); + $paymentMock->expects($this->atLeastOnce())->method('getAdditionalInformation') + ->willReturn(['method' => 'checkmo']); + $this->paymentAdditionalInfoFactory->expects($this->atLeastOnce())->method('create') + ->willReturn($paymentAdditionalInfo); + $paymentAdditionalInfo->expects($this->atLeastOnce())->method('setKey')->willReturnSelf(); + $paymentAdditionalInfo->expects($this->atLeastOnce())->method('setValue')->willReturnSelf(); $this->orderTaxManagementMock->expects($this->atLeastOnce())->method('getOrderTaxDetails') ->willReturn($orderTaxDetailsMock); $extensionAttributes->expects($this->any()) diff --git a/app/code/Magento/Sales/Test/Unit/Model/OrderTest.php b/app/code/Magento/Sales/Test/Unit/Model/OrderTest.php index f724136eb51..705d2face23 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/OrderTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/OrderTest.php @@ -11,7 +11,12 @@ use Magento\Framework\Stdlib\DateTime\TimezoneInterface; use Magento\Sales\Api\Data\OrderInterface; use Magento\Sales\Model\Order; +use Magento\Sales\Model\ResourceModel\Order\Item\Collection; use Magento\Sales\Model\ResourceModel\Order\Status\History\CollectionFactory as HistoryCollectionFactory; +use Magento\Sales\Api\OrderItemRepositoryInterface; +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Framework\Api\SearchCriteria; +use Magento\Sales\Api\Data\OrderItemSearchResultInterface; /** * Test class for \Magento\Sales\Model\Order @@ -87,6 +92,16 @@ class OrderTest extends \PHPUnit\Framework\TestCase */ private $timezone; + /** + * @var OrderItemRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $itemRepository; + + /** + * @var SearchCriteriaBuilder|\PHPUnit_Framework_MockObject_MockObject + */ + private $searchCriteriaBuilder; + protected function setUp() { $helper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); @@ -144,6 +159,15 @@ protected function setUp() $this->eventManager = $this->createMock(\Magento\Framework\Event\Manager::class); $context = $this->createPartialMock(\Magento\Framework\Model\Context::class, ['getEventDispatcher']); $context->expects($this->any())->method('getEventDispatcher')->willReturn($this->eventManager); + + $this->itemRepository = $this->getMockBuilder(OrderItemRepositoryInterface::class) + ->setMethods(['getList']) + ->disableOriginalConstructor()->getMockForAbstractClass(); + + $this->searchCriteriaBuilder = $this->getMockBuilder(SearchCriteriaBuilder::class) + ->setMethods(['addFilter', 'create']) + ->disableOriginalConstructor()->getMockForAbstractClass(); + $this->order = $helper->getObject( \Magento\Sales\Model\Order::class, [ @@ -157,37 +181,80 @@ protected function setUp() 'productListFactory' => $this->productCollectionFactoryMock, 'localeResolver' => $this->localeResolver, 'timezone' => $this->timezone, + 'itemRepository' => $this->itemRepository, + 'searchCriteriaBuilder' => $this->searchCriteriaBuilder ] ); } - public function testGetItemById() + /** + * Test testGetItems method. + */ + public function testGetItems() { - $realOrderItemId = 1; - $fakeOrderItemId = 2; + $orderItems = [$this->item]; - $orderItem = $this->createMock(\Magento\Sales\Model\Order\Item::class); + $this->searchCriteriaBuilder->expects($this->once())->method('addFilter')->willReturnSelf(); + + $searchCriteria = $this->getMockBuilder(SearchCriteria::class) + ->disableOriginalConstructor()->getMockForAbstractClass(); + $this->searchCriteriaBuilder->expects($this->once())->method('create')->willReturn($searchCriteria); + + $itemsCollection = $this->getMockBuilder(OrderItemSearchResultInterface::class) + ->setMethods(['getItems']) + ->disableOriginalConstructor()->getMockForAbstractClass(); + $itemsCollection->expects($this->once())->method('getItems')->willReturn($orderItems); + $this->itemRepository->expects($this->once())->method('getList')->willReturn($itemsCollection); + + $this->assertEquals($orderItems, $this->order->getItems()); + } + /** + * Prepare order item mock. + * + * @param int $orderId + * @return void + */ + private function prepareOrderItem(int $orderId = 0) + { $this->order->setData( \Magento\Sales\Api\Data\OrderInterface::ITEMS, [ - $realOrderItemId => $orderItem + $orderId => $this->item ] ); + } + + /** + * Test GetItemById method. + * + * @return void + */ + public function testGetItemById() + { + $realOrderItemId = 1; + $fakeOrderItemId = 2; + + $this->prepareOrderItem($realOrderItemId); - $this->assertEquals($orderItem, $this->order->getItemById($realOrderItemId)); + $this->assertEquals($this->item, $this->order->getItemById($realOrderItemId)); $this->assertEquals(null, $this->order->getItemById($fakeOrderItemId)); } /** + * Test GetItemByQuoteItemId method. + * * @param int|null $gettingQuoteItemId * @param int|null $quoteItemId * @param string|null $result * * @dataProvider dataProviderGetItemByQuoteItemId + * @return void */ public function testGetItemByQuoteItemId($gettingQuoteItemId, $quoteItemId, $result) { + $this->prepareOrderItem(); + $this->item->expects($this->any()) ->method('getQuoteItemId') ->willReturn($gettingQuoteItemId); @@ -212,14 +279,19 @@ public function dataProviderGetItemByQuoteItemId() } /** + * Test getAllVisibleItems method. + * * @param bool $isDeleted * @param int|null $parentItemId * @param array $result * * @dataProvider dataProviderGetAllVisibleItems + * @return void */ public function testGetAllVisibleItems($isDeleted, $parentItemId, array $result) { + $this->prepareOrderItem(); + $this->item->expects($this->once()) ->method('isDeleted') ->willReturn($isDeleted); @@ -263,8 +335,15 @@ public function testCanCancelIsPaymentReview() $this->assertFalse($this->order->canCancel()); } + /** + * Test CanInvoice method. + * + * @return void + */ public function testCanInvoice() { + $this->prepareOrderItem(); + $this->item->expects($this->any()) ->method('getQtyToInvoice') ->willReturn(42); @@ -304,8 +383,15 @@ public function testCanNotInvoiceWhenActionInvoiceFlagIsFalse() $this->assertFalse($this->order->canInvoice()); } + /** + * Test CanNotInvoice method when invoice is locked. + * + * @return void + */ public function testCanNotInvoiceWhenLockedInvoice() { + $this->prepareOrderItem(); + $this->item->expects($this->any()) ->method('getQtyToInvoice') ->willReturn(42); @@ -315,8 +401,15 @@ public function testCanNotInvoiceWhenLockedInvoice() $this->assertFalse($this->order->canInvoice()); } + /** + * Test CanNotInvoice method when didn't have qty to invoice. + * + * @return void + */ public function testCanNotInvoiceWhenDidNotHaveQtyToInvoice() { + $this->prepareOrderItem(); + $this->item->expects($this->any()) ->method('getQtyToInvoice') ->willReturn(0); @@ -329,29 +422,16 @@ public function testCanNotInvoiceWhenDidNotHaveQtyToInvoice() public function testCanCreditMemo() { $totalPaid = 10; + $this->prepareOrderItem(); $this->order->setTotalPaid($totalPaid); $this->priceCurrency->expects($this->once())->method('round')->with($totalPaid)->willReturnArgument(0); $this->assertTrue($this->order->canCreditmemo()); } - /** - * Test canCreditMemo method when grand total and paid total are zero. - * - * @return void - */ - public function testCanCreditMemoForZeroTotal() - { - $grandTotal = 0; - $totalPaid = 0; - $totalRefunded = 0; - $this->order->setGrandTotal($grandTotal); - $this->order->setTotalPaid($totalPaid); - $this->assertFalse($this->order->canCreditmemoForZeroTotal($totalRefunded)); - } - public function testCanNotCreditMemoWithTotalNull() { $totalPaid = 0; + $this->prepareOrderItem(); $this->order->setTotalPaid($totalPaid); $this->priceCurrency->expects($this->once())->method('round')->with($totalPaid)->willReturnArgument(0); $this->assertFalse($this->order->canCreditmemo()); @@ -363,6 +443,7 @@ public function testCanNotCreditMemoWithAdjustmentNegative() $adjustmentNegative = 10; $totalRefunded = 90; + $this->prepareOrderItem(); $this->order->setTotalPaid($totalPaid); $this->order->setTotalRefunded($totalRefunded); $this->order->setAdjustmentNegative($adjustmentNegative); @@ -377,6 +458,7 @@ public function testCanCreditMemoWithAdjustmentNegativeLowerThanTotalPaid() $adjustmentNegative = 9; $totalRefunded = 90; + $this->prepareOrderItem(); $this->order->setTotalPaid($totalPaid); $this->order->setTotalRefunded($totalRefunded); $this->order->setAdjustmentNegative($adjustmentNegative); @@ -601,8 +683,15 @@ public function testCanCancelCanReviewPayment() $this->assertFalse($this->order->canCancel()); } + /** + * Test CanCancelAllInvoiced method. + * + * @return void + */ public function testCanCancelAllInvoiced() { + $this->prepareOrderItem(); + $paymentMock = $this->getMockBuilder(\Magento\Sales\Model\ResourceModel\Order\Payment::class) ->disableOriginalConstructor() ->setMethods(['isDeleted', 'canReviewPayment', 'canFetchTransactionInfo', '__wakeUp']) @@ -662,11 +751,16 @@ public function testCanCancelState() } /** + * Test CanCancelActionFlag method. + * * @param bool $cancelActionFlag * @dataProvider dataProviderActionFlag + * @return void */ public function testCanCancelActionFlag($cancelActionFlag) { + $this->prepareOrderItem(); + $paymentMock = $this->getMockBuilder(\Magento\Sales\Model\ResourceModel\Order\Payment::class) ->disableOriginalConstructor() ->setMethods(['isDeleted', 'canReviewPayment', 'canFetchTransactionInfo', '__wakeUp']) diff --git a/app/code/Magento/Sales/Test/Unit/Model/ResourceModel/Order/Handler/StateTest.php b/app/code/Magento/Sales/Test/Unit/Model/ResourceModel/Order/Handler/StateTest.php index e120d613e32..99a411c43c2 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/ResourceModel/Order/Handler/StateTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/ResourceModel/Order/Handler/StateTest.php @@ -24,7 +24,9 @@ class StateTest extends \PHPUnit\Framework\TestCase protected function setUp() { - $this->orderMock = $this->createPartialMock(\Magento\Sales\Model\Order::class, [ + $this->orderMock = $this->createPartialMock( + \Magento\Sales\Model\Order::class, + [ '__wakeup', 'getId', 'hasCustomerNoteNotify', @@ -35,13 +37,12 @@ protected function setUp() 'canShip', 'getBaseGrandTotal', 'canCreditmemo', - 'getState', - 'setState', 'getTotalRefunded', 'hasForcedCanCreditmemo', 'getIsInProcess', 'getConfig', - ]); + ] + ); $this->orderMock->expects($this->any()) ->method('getConfig') ->willReturnSelf(); @@ -53,127 +54,88 @@ protected function setUp() } /** - * test check order - order without id - */ - public function testCheckOrderEmpty() - { - $this->orderMock->expects($this->once()) - ->method('getBaseGrandTotal') - ->willReturn(100); - $this->orderMock->expects($this->never()) - ->method('setState'); - - $this->state->check($this->orderMock); - } - - /** - * test check order - set state complete + * @param bool $isCanceled + * @param bool $canUnhold + * @param bool $canInvoice + * @param bool $canShip + * @param int $callCanSkipNum + * @param bool $canCreditmemo + * @param int $callCanCreditmemoNum + * @param string $currentState + * @param string $expectedState + * @param int $callSetStateNum + * @dataProvider stateCheckDataProvider + * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ - public function testCheckSetStateComplete() - { + public function testCheck( + bool $canCreditmemo, + int $callCanCreditmemoNum, + bool $canShip, + int $callCanSkipNum, + string $currentState, + string $expectedState = '', + bool $isInProcess = false, + int $callGetIsInProcessNum = 0, + bool $isCanceled = false, + bool $canUnhold = false, + bool $canInvoice = false + ) { + $this->orderMock->setState($currentState); $this->orderMock->expects($this->any()) - ->method('getId') - ->will($this->returnValue(1)); - $this->orderMock->expects($this->once()) ->method('isCanceled') - ->will($this->returnValue(false)); - $this->orderMock->expects($this->once()) - ->method('canUnhold') - ->will($this->returnValue(false)); - $this->orderMock->expects($this->once()) - ->method('canInvoice') - ->will($this->returnValue(false)); - $this->orderMock->expects($this->once()) - ->method('canShip') - ->will($this->returnValue(false)); - $this->orderMock->expects($this->once()) - ->method('getBaseGrandTotal') - ->will($this->returnValue(100)); - $this->orderMock->expects($this->once()) - ->method('canCreditmemo') - ->will($this->returnValue(true)); - $this->orderMock->expects($this->exactly(2)) - ->method('getState') - ->will($this->returnValue(Order::STATE_PROCESSING)); - $this->orderMock->expects($this->once()) - ->method('setState') - ->with(Order::STATE_COMPLETE) - ->will($this->returnSelf()); - $this->assertEquals($this->state, $this->state->check($this->orderMock)); - } - - /** - * test check order - set state closed - */ - public function testCheckSetStateClosed() - { + ->willReturn($isCanceled); $this->orderMock->expects($this->any()) - ->method('getId') - ->will($this->returnValue(1)); - $this->orderMock->expects($this->once()) - ->method('isCanceled') - ->will($this->returnValue(false)); - $this->orderMock->expects($this->once()) ->method('canUnhold') - ->will($this->returnValue(false)); - $this->orderMock->expects($this->once()) + ->willReturn($canUnhold); + $this->orderMock->expects($this->any()) ->method('canInvoice') - ->will($this->returnValue(false)); - $this->orderMock->expects($this->once()) + ->willReturn($canInvoice); + $this->orderMock->expects($this->exactly($callCanSkipNum)) ->method('canShip') - ->will($this->returnValue(false)); - $this->orderMock->expects($this->once()) - ->method('getBaseGrandTotal') - ->will($this->returnValue(100)); - $this->orderMock->expects($this->once()) + ->willReturn($canShip); + $this->orderMock->expects($this->exactly($callCanCreditmemoNum)) ->method('canCreditmemo') - ->will($this->returnValue(false)); - $this->orderMock->expects($this->exactly(2)) - ->method('getTotalRefunded') - ->will($this->returnValue(null)); - $this->orderMock->expects($this->once()) - ->method('hasForcedCanCreditmemo') - ->will($this->returnValue(true)); - $this->orderMock->expects($this->exactly(2)) - ->method('getState') - ->will($this->returnValue(Order::STATE_PROCESSING)); - $this->orderMock->expects($this->once()) - ->method('setState') - ->with(Order::STATE_CLOSED) - ->will($this->returnSelf()); - $this->assertEquals($this->state, $this->state->check($this->orderMock)); + ->willReturn($canCreditmemo); + $this->orderMock->expects($this->exactly($callGetIsInProcessNum)) + ->method('getIsInProcess') + ->willReturn($isInProcess); + $this->state->check($this->orderMock); + $this->assertEquals($expectedState, $this->orderMock->getState()); } - /** - * test check order - set state processing - */ - public function testCheckSetStateProcessing() + public function stateCheckDataProvider() { - $this->orderMock->expects($this->any()) - ->method('getId') - ->will($this->returnValue(1)); - $this->orderMock->expects($this->once()) - ->method('isCanceled') - ->will($this->returnValue(false)); - $this->orderMock->expects($this->once()) - ->method('canUnhold') - ->will($this->returnValue(false)); - $this->orderMock->expects($this->once()) - ->method('canInvoice') - ->will($this->returnValue(false)); - $this->orderMock->expects($this->once()) - ->method('canShip') - ->will($this->returnValue(true)); - $this->orderMock->expects($this->once()) - ->method('getState') - ->will($this->returnValue(Order::STATE_NEW)); - $this->orderMock->expects($this->once()) - ->method('getIsInProcess') - ->will($this->returnValue(true)); - $this->orderMock->expects($this->once()) - ->method('setState') - ->with(Order::STATE_PROCESSING) - ->will($this->returnSelf()); - $this->assertEquals($this->state, $this->state->check($this->orderMock)); + return [ + 'processing - !canCreditmemo!canShip -> closed' => + [false, 1, false, 1, Order::STATE_PROCESSING, Order::STATE_CLOSED], + 'complete - !canCreditmemo,!canShip -> closed' => + [false, 1, false, 1, Order::STATE_COMPLETE, Order::STATE_CLOSED], + 'processing - !canCreditmemo,canShip -> processing' => + [false, 1, true, 2, Order::STATE_PROCESSING, Order::STATE_PROCESSING], + 'complete - !canCreditmemo,canShip -> complete' => + [false, 1, true, 1, Order::STATE_COMPLETE, Order::STATE_COMPLETE], + 'processing - canCreditmemo,!canShip -> complete' => + [true, 1, false, 1, Order::STATE_PROCESSING, Order::STATE_COMPLETE], + 'complete - canCreditmemo,!canShip -> complete' => + [true, 1, false, 0, Order::STATE_COMPLETE, Order::STATE_COMPLETE], + 'processing - canCreditmemo, canShip -> processing' => + [true, 1, true, 1, Order::STATE_PROCESSING, Order::STATE_PROCESSING], + 'complete - canCreditmemo, canShip -> complete' => + [true, 1, true, 0, Order::STATE_COMPLETE, Order::STATE_COMPLETE], + 'new - canCreditmemo, canShip, IsInProcess -> processing' => + [true, 1, true, 1, Order::STATE_NEW, Order::STATE_PROCESSING, true, 1], + 'new - canCreditmemo, !canShip, IsInProcess -> processing' => + [true, 1, false, 1, Order::STATE_NEW, Order::STATE_COMPLETE, true, 1], + 'new - canCreditmemo, canShip, !IsInProcess -> new' => + [true, 0, true, 0, Order::STATE_NEW, Order::STATE_NEW, false, 1], + 'hold - canUnhold -> hold' => + [true, 0, true, 0, Order::STATE_HOLDED, Order::STATE_HOLDED, false, 0, false, true], + 'payment_review - canUnhold -> payment_review' => + [true, 0, true, 0, Order::STATE_PAYMENT_REVIEW, Order::STATE_PAYMENT_REVIEW, false, 0, false, true], + 'pending_payment - canUnhold -> pending_payment' => + [true, 0, true, 0, Order::STATE_PENDING_PAYMENT, Order::STATE_PENDING_PAYMENT, false, 0, false, true], + 'cancelled - isCanceled -> cancelled' => + [true, 0, true, 0, Order::STATE_HOLDED, Order::STATE_HOLDED, false, 0, true], + ]; } } diff --git a/app/code/Magento/Sales/Test/Unit/Observer/AssignOrderToCustomerObserverTest.php b/app/code/Magento/Sales/Test/Unit/Observer/AssignOrderToCustomerObserverTest.php index c6e02151b9b..e919b45667f 100644 --- a/app/code/Magento/Sales/Test/Unit/Observer/AssignOrderToCustomerObserverTest.php +++ b/app/code/Magento/Sales/Test/Unit/Observer/AssignOrderToCustomerObserverTest.php @@ -69,6 +69,17 @@ public function testAssignOrderToCustomerAfterGuestOrder($customerId) $orderMock->expects($this->once())->method('getCustomerId')->willReturn($customerId); $this->orderRepositoryMock->expects($this->once())->method('get')->with($orderId) ->willReturn($orderMock); + + $orderMock->expects($this->once())->method('setCustomerId')->willReturn($orderMock); + $orderMock->expects($this->once())->method('setCustomerIsGuest')->willReturn($orderMock); + $orderMock->expects($this->once())->method('setCustomerEmail')->willReturn($orderMock); + $orderMock->expects($this->once())->method('setCustomerFirstname')->willReturn($orderMock); + $orderMock->expects($this->once())->method('setCustomerLastname')->willReturn($orderMock); + $orderMock->expects($this->once())->method('setCustomerMiddlename')->willReturn($orderMock); + $orderMock->expects($this->once())->method('setCustomerPrefix')->willReturn($orderMock); + $orderMock->expects($this->once())->method('setCustomerSuffix')->willReturn($orderMock); + $orderMock->expects($this->once())->method('setCustomerGroupId')->willReturn($orderMock); + if (!$customerId) { $this->orderRepositoryMock->expects($this->once())->method('save')->with($orderMock); $this->sut->execute($observerMock); diff --git a/app/code/Magento/Sales/Ui/Component/Listing/Column/Status.php b/app/code/Magento/Sales/Ui/Component/Listing/Column/Status.php index 2fd792fb4ae..48b8740d86f 100644 --- a/app/code/Magento/Sales/Ui/Component/Listing/Column/Status.php +++ b/app/code/Magento/Sales/Ui/Component/Listing/Column/Status.php @@ -44,7 +44,7 @@ public function __construct( * Prepare Data Source * * @param array $dataSource - * @return void + * @return array */ public function prepareDataSource(array $dataSource) { diff --git a/app/code/Magento/Sales/etc/adminhtml/system.xml b/app/code/Magento/Sales/etc/adminhtml/system.xml index 1b2f8b88d7d..2dc467d6ca2 100644 --- a/app/code/Magento/Sales/etc/adminhtml/system.xml +++ b/app/code/Magento/Sales/etc/adminhtml/system.xml @@ -89,6 +89,11 @@ <label>Minimum Amount</label> <comment>Subtotal after discount</comment> </field> + <field id="include_discount_amount" translate="label" sortOrder="12" type="select" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> + <label>Include Discount Amount</label> + <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> + <comment>Choosing yes will be used subtotal after discount, otherwise only subtotal will be used</comment> + </field> <field id="tax_including" translate="label" sortOrder="15" type="select" showInDefault="1" showInWebsite="1" showInStore="0" canRestore="1"> <label>Include Tax to Amount</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> diff --git a/app/code/Magento/Sales/etc/config.xml b/app/code/Magento/Sales/etc/config.xml index 5be06fa3836..2480da4ad21 100644 --- a/app/code/Magento/Sales/etc/config.xml +++ b/app/code/Magento/Sales/etc/config.xml @@ -22,6 +22,7 @@ <allow_zero_grandtotal>1</allow_zero_grandtotal> </zerograndtotal_creditmemo> <minimum_order> + <include_discount_amount>1</include_discount_amount> <tax_including>1</tax_including> </minimum_order> <orders> diff --git a/app/code/Magento/Sales/etc/db_schema.xml b/app/code/Magento/Sales/etc/db_schema.xml index da6d2bba552..d6ea9b7d548 100644 --- a/app/code/Magento/Sales/etc/db_schema.xml +++ b/app/code/Magento/Sales/etc/db_schema.xml @@ -22,119 +22,119 @@ comment="Store Id"/> <column xsi:type="int" name="customer_id" padding="10" unsigned="true" nullable="true" identity="false" comment="Customer Id"/> - <column xsi:type="decimal" name="base_discount_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_discount_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Discount Amount"/> - <column xsi:type="decimal" name="base_discount_canceled" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_discount_canceled" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Discount Canceled"/> - <column xsi:type="decimal" name="base_discount_invoiced" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_discount_invoiced" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Discount Invoiced"/> - <column xsi:type="decimal" name="base_discount_refunded" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_discount_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Discount Refunded"/> - <column xsi:type="decimal" name="base_grand_total" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_grand_total" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Grand Total"/> - <column xsi:type="decimal" name="base_shipping_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_shipping_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Shipping Amount"/> - <column xsi:type="decimal" name="base_shipping_canceled" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_shipping_canceled" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Shipping Canceled"/> - <column xsi:type="decimal" name="base_shipping_invoiced" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_shipping_invoiced" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Shipping Invoiced"/> - <column xsi:type="decimal" name="base_shipping_refunded" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_shipping_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Shipping Refunded"/> - <column xsi:type="decimal" name="base_shipping_tax_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_shipping_tax_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Shipping Tax Amount"/> - <column xsi:type="decimal" name="base_shipping_tax_refunded" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_shipping_tax_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Shipping Tax Refunded"/> - <column xsi:type="decimal" name="base_subtotal" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_subtotal" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Subtotal"/> - <column xsi:type="decimal" name="base_subtotal_canceled" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_subtotal_canceled" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Subtotal Canceled"/> - <column xsi:type="decimal" name="base_subtotal_invoiced" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_subtotal_invoiced" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Subtotal Invoiced"/> - <column xsi:type="decimal" name="base_subtotal_refunded" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_subtotal_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Subtotal Refunded"/> - <column xsi:type="decimal" name="base_tax_amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_tax_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Tax Amount"/> - <column xsi:type="decimal" name="base_tax_canceled" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_tax_canceled" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Tax Canceled"/> - <column xsi:type="decimal" name="base_tax_invoiced" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_tax_invoiced" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Tax Invoiced"/> - <column xsi:type="decimal" name="base_tax_refunded" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_tax_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Tax Refunded"/> - <column xsi:type="decimal" name="base_to_global_rate" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_to_global_rate" scale="4" precision="20" unsigned="false" nullable="true" comment="Base To Global Rate"/> - <column xsi:type="decimal" name="base_to_order_rate" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_to_order_rate" scale="4" precision="20" unsigned="false" nullable="true" comment="Base To Order Rate"/> - <column xsi:type="decimal" name="base_total_canceled" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_total_canceled" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Total Canceled"/> - <column xsi:type="decimal" name="base_total_invoiced" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_total_invoiced" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Total Invoiced"/> - <column xsi:type="decimal" name="base_total_invoiced_cost" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_total_invoiced_cost" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Total Invoiced Cost"/> - <column xsi:type="decimal" name="base_total_offline_refunded" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_total_offline_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Total Offline Refunded"/> - <column xsi:type="decimal" name="base_total_online_refunded" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_total_online_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Total Online Refunded"/> - <column xsi:type="decimal" name="base_total_paid" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_total_paid" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Total Paid"/> <column xsi:type="decimal" name="base_total_qty_ordered" scale="4" precision="12" unsigned="false" nullable="true" comment="Base Total Qty Ordered"/> - <column xsi:type="decimal" name="base_total_refunded" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_total_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Total Refunded"/> - <column xsi:type="decimal" name="discount_amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="discount_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Discount Amount"/> - <column xsi:type="decimal" name="discount_canceled" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="discount_canceled" scale="4" precision="20" unsigned="false" nullable="true" comment="Discount Canceled"/> - <column xsi:type="decimal" name="discount_invoiced" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="discount_invoiced" scale="4" precision="20" unsigned="false" nullable="true" comment="Discount Invoiced"/> - <column xsi:type="decimal" name="discount_refunded" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="discount_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Discount Refunded"/> - <column xsi:type="decimal" name="grand_total" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="grand_total" scale="4" precision="20" unsigned="false" nullable="true" comment="Grand Total"/> - <column xsi:type="decimal" name="shipping_amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="shipping_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Shipping Amount"/> - <column xsi:type="decimal" name="shipping_canceled" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="shipping_canceled" scale="4" precision="20" unsigned="false" nullable="true" comment="Shipping Canceled"/> - <column xsi:type="decimal" name="shipping_invoiced" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="shipping_invoiced" scale="4" precision="20" unsigned="false" nullable="true" comment="Shipping Invoiced"/> - <column xsi:type="decimal" name="shipping_refunded" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="shipping_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Shipping Refunded"/> - <column xsi:type="decimal" name="shipping_tax_amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="shipping_tax_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Shipping Tax Amount"/> - <column xsi:type="decimal" name="shipping_tax_refunded" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="shipping_tax_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Shipping Tax Refunded"/> <column xsi:type="decimal" name="store_to_base_rate" scale="4" precision="12" unsigned="false" nullable="true" comment="Store To Base Rate"/> <column xsi:type="decimal" name="store_to_order_rate" scale="4" precision="12" unsigned="false" nullable="true" comment="Store To Order Rate"/> - <column xsi:type="decimal" name="subtotal" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="subtotal" scale="4" precision="20" unsigned="false" nullable="true" comment="Subtotal"/> - <column xsi:type="decimal" name="subtotal_canceled" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="subtotal_canceled" scale="4" precision="20" unsigned="false" nullable="true" comment="Subtotal Canceled"/> - <column xsi:type="decimal" name="subtotal_invoiced" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="subtotal_invoiced" scale="4" precision="20" unsigned="false" nullable="true" comment="Subtotal Invoiced"/> - <column xsi:type="decimal" name="subtotal_refunded" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="subtotal_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Subtotal Refunded"/> - <column xsi:type="decimal" name="tax_amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="tax_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Tax Amount"/> - <column xsi:type="decimal" name="tax_canceled" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="tax_canceled" scale="4" precision="20" unsigned="false" nullable="true" comment="Tax Canceled"/> - <column xsi:type="decimal" name="tax_invoiced" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="tax_invoiced" scale="4" precision="20" unsigned="false" nullable="true" comment="Tax Invoiced"/> - <column xsi:type="decimal" name="tax_refunded" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="tax_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Tax Refunded"/> - <column xsi:type="decimal" name="total_canceled" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="total_canceled" scale="4" precision="20" unsigned="false" nullable="true" comment="Total Canceled"/> - <column xsi:type="decimal" name="total_invoiced" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="total_invoiced" scale="4" precision="20" unsigned="false" nullable="true" comment="Total Invoiced"/> - <column xsi:type="decimal" name="total_offline_refunded" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="total_offline_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Total Offline Refunded"/> - <column xsi:type="decimal" name="total_online_refunded" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="total_online_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Total Online Refunded"/> - <column xsi:type="decimal" name="total_paid" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="total_paid" scale="4" precision="20" unsigned="false" nullable="true" comment="Total Paid"/> <column xsi:type="decimal" name="total_qty_ordered" scale="4" precision="12" unsigned="false" nullable="true" comment="Total Qty Ordered"/> - <column xsi:type="decimal" name="total_refunded" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="total_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Total Refunded"/> <column xsi:type="smallint" name="can_ship_partially" padding="5" unsigned="true" nullable="true" identity="false" comment="Can Ship Partially"/> @@ -163,27 +163,27 @@ comment="Quote Id"/> <column xsi:type="int" name="shipping_address_id" padding="11" unsigned="false" nullable="true" identity="false" comment="Shipping Address Id"/> - <column xsi:type="decimal" name="adjustment_negative" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="adjustment_negative" scale="4" precision="20" unsigned="false" nullable="true" comment="Adjustment Negative"/> - <column xsi:type="decimal" name="adjustment_positive" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="adjustment_positive" scale="4" precision="20" unsigned="false" nullable="true" comment="Adjustment Positive"/> - <column xsi:type="decimal" name="base_adjustment_negative" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_adjustment_negative" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Adjustment Negative"/> - <column xsi:type="decimal" name="base_adjustment_positive" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_adjustment_positive" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Adjustment Positive"/> - <column xsi:type="decimal" name="base_shipping_discount_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_shipping_discount_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Shipping Discount Amount"/> - <column xsi:type="decimal" name="base_subtotal_incl_tax" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_subtotal_incl_tax" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Subtotal Incl Tax"/> - <column xsi:type="decimal" name="base_total_due" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_total_due" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Total Due"/> - <column xsi:type="decimal" name="payment_authorization_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="payment_authorization_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Payment Authorization Amount"/> - <column xsi:type="decimal" name="shipping_discount_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="shipping_discount_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Shipping Discount Amount"/> - <column xsi:type="decimal" name="subtotal_incl_tax" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="subtotal_incl_tax" scale="4" precision="20" unsigned="false" nullable="true" comment="Subtotal Incl Tax"/> - <column xsi:type="decimal" name="total_due" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="total_due" scale="4" precision="20" unsigned="false" nullable="true" comment="Total Due"/> <column xsi:type="decimal" name="weight" scale="4" precision="12" unsigned="false" nullable="true" comment="Weight"/> @@ -230,25 +230,25 @@ identity="false" default="0" comment="Total Item Count"/> <column xsi:type="int" name="customer_gender" padding="11" unsigned="false" nullable="true" identity="false" comment="Customer Gender"/> - <column xsi:type="decimal" name="discount_tax_compensation_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="discount_tax_compensation_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Discount Tax Compensation Amount"/> - <column xsi:type="decimal" name="base_discount_tax_compensation_amount" scale="4" precision="12" + <column xsi:type="decimal" name="base_discount_tax_compensation_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Discount Tax Compensation Amount"/> - <column xsi:type="decimal" name="shipping_discount_tax_compensation_amount" scale="4" precision="12" + <column xsi:type="decimal" name="shipping_discount_tax_compensation_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Shipping Discount Tax Compensation Amount"/> - <column xsi:type="decimal" name="base_shipping_discount_tax_compensation_amnt" scale="4" precision="12" + <column xsi:type="decimal" name="base_shipping_discount_tax_compensation_amnt" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Shipping Discount Tax Compensation Amount"/> - <column xsi:type="decimal" name="discount_tax_compensation_invoiced" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="discount_tax_compensation_invoiced" scale="4" precision="20" unsigned="false" nullable="true" comment="Discount Tax Compensation Invoiced"/> - <column xsi:type="decimal" name="base_discount_tax_compensation_invoiced" scale="4" precision="12" + <column xsi:type="decimal" name="base_discount_tax_compensation_invoiced" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Discount Tax Compensation Invoiced"/> - <column xsi:type="decimal" name="discount_tax_compensation_refunded" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="discount_tax_compensation_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Discount Tax Compensation Refunded"/> - <column xsi:type="decimal" name="base_discount_tax_compensation_refunded" scale="4" precision="12" + <column xsi:type="decimal" name="base_discount_tax_compensation_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Discount Tax Compensation Refunded"/> - <column xsi:type="decimal" name="shipping_incl_tax" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="shipping_incl_tax" scale="4" precision="20" unsigned="false" nullable="true" comment="Shipping Incl Tax"/> - <column xsi:type="decimal" name="base_shipping_incl_tax" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_shipping_incl_tax" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Shipping Incl Tax"/> <column xsi:type="varchar" name="coupon_rule_name" nullable="true" length="255" comment="Coupon Sales Rule Name"/> @@ -304,13 +304,13 @@ <column xsi:type="varchar" name="store_name" nullable="true" length="255" comment="Store Name"/> <column xsi:type="int" name="customer_id" padding="10" unsigned="true" nullable="true" identity="false" comment="Customer Id"/> - <column xsi:type="decimal" name="base_grand_total" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_grand_total" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Grand Total"/> - <column xsi:type="decimal" name="base_total_paid" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_total_paid" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Total Paid"/> - <column xsi:type="decimal" name="grand_total" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="grand_total" scale="4" precision="20" unsigned="false" nullable="true" comment="Grand Total"/> - <column xsi:type="decimal" name="total_paid" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="total_paid" scale="4" precision="20" unsigned="false" nullable="true" comment="Total Paid"/> <column xsi:type="varchar" name="increment_id" nullable="true" length="50" comment="Increment Id"/> <column xsi:type="varchar" name="base_currency_code" nullable="true" length="3" comment="Base Currency Code"/> @@ -326,13 +326,13 @@ comment="Shipping Method Name"/> <column xsi:type="varchar" name="customer_email" nullable="true" length="255" comment="Customer Email"/> <column xsi:type="varchar" name="customer_group" nullable="true" length="255" comment="Customer Group"/> - <column xsi:type="decimal" name="subtotal" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="subtotal" scale="4" precision="20" unsigned="false" nullable="true" comment="Subtotal"/> - <column xsi:type="decimal" name="shipping_and_handling" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="shipping_and_handling" scale="4" precision="20" unsigned="false" nullable="true" comment="Shipping and handling amount"/> <column xsi:type="varchar" name="customer_name" nullable="true" length="255" comment="Customer Name"/> <column xsi:type="varchar" name="payment_method" nullable="true" length="255" comment="Payment Method"/> - <column xsi:type="decimal" name="total_refunded" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="total_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Total Refunded"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="entity_id"/> @@ -513,78 +513,78 @@ comment="Base Original Price"/> <column xsi:type="decimal" name="tax_percent" scale="4" precision="12" unsigned="false" nullable="true" default="0" comment="Tax Percent"/> - <column xsi:type="decimal" name="tax_amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="tax_amount" scale="4" precision="20" unsigned="false" nullable="true" default="0" comment="Tax Amount"/> - <column xsi:type="decimal" name="base_tax_amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_tax_amount" scale="4" precision="20" unsigned="false" nullable="true" default="0" comment="Base Tax Amount"/> - <column xsi:type="decimal" name="tax_invoiced" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="tax_invoiced" scale="4" precision="20" unsigned="false" nullable="true" default="0" comment="Tax Invoiced"/> - <column xsi:type="decimal" name="base_tax_invoiced" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_tax_invoiced" scale="4" precision="20" unsigned="false" nullable="true" default="0" comment="Base Tax Invoiced"/> <column xsi:type="decimal" name="discount_percent" scale="4" precision="12" unsigned="false" nullable="true" default="0" comment="Discount Percent"/> - <column xsi:type="decimal" name="discount_amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="discount_amount" scale="4" precision="20" unsigned="false" nullable="true" default="0" comment="Discount Amount"/> - <column xsi:type="decimal" name="base_discount_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_discount_amount" scale="4" precision="20" unsigned="false" nullable="true" default="0" comment="Base Discount Amount"/> - <column xsi:type="decimal" name="discount_invoiced" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="discount_invoiced" scale="4" precision="20" unsigned="false" nullable="true" default="0" comment="Discount Invoiced"/> - <column xsi:type="decimal" name="base_discount_invoiced" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_discount_invoiced" scale="4" precision="20" unsigned="false" nullable="true" default="0" comment="Base Discount Invoiced"/> - <column xsi:type="decimal" name="amount_refunded" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="amount_refunded" scale="4" precision="20" unsigned="false" nullable="true" default="0" comment="Amount Refunded"/> - <column xsi:type="decimal" name="base_amount_refunded" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_amount_refunded" scale="4" precision="20" unsigned="false" nullable="true" default="0" comment="Base Amount Refunded"/> - <column xsi:type="decimal" name="row_total" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="row_total" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Row Total"/> - <column xsi:type="decimal" name="base_row_total" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="base_row_total" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Base Row Total"/> - <column xsi:type="decimal" name="row_invoiced" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="row_invoiced" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Row Invoiced"/> - <column xsi:type="decimal" name="base_row_invoiced" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="base_row_invoiced" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Base Row Invoiced"/> <column xsi:type="decimal" name="row_weight" scale="4" precision="12" unsigned="false" nullable="true" default="0" comment="Row Weight"/> - <column xsi:type="decimal" name="base_tax_before_discount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_tax_before_discount" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Tax Before Discount"/> - <column xsi:type="decimal" name="tax_before_discount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="tax_before_discount" scale="4" precision="20" unsigned="false" nullable="true" comment="Tax Before Discount"/> <column xsi:type="varchar" name="ext_order_item_id" nullable="true" length="255" comment="Ext Order Item Id"/> <column xsi:type="smallint" name="locked_do_invoice" padding="5" unsigned="true" nullable="true" identity="false" comment="Locked Do Invoice"/> <column xsi:type="smallint" name="locked_do_ship" padding="5" unsigned="true" nullable="true" identity="false" comment="Locked Do Ship"/> - <column xsi:type="decimal" name="price_incl_tax" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="price_incl_tax" scale="4" precision="20" unsigned="false" nullable="true" comment="Price Incl Tax"/> - <column xsi:type="decimal" name="base_price_incl_tax" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_price_incl_tax" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Price Incl Tax"/> - <column xsi:type="decimal" name="row_total_incl_tax" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="row_total_incl_tax" scale="4" precision="20" unsigned="false" nullable="true" comment="Row Total Incl Tax"/> - <column xsi:type="decimal" name="base_row_total_incl_tax" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_row_total_incl_tax" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Row Total Incl Tax"/> - <column xsi:type="decimal" name="discount_tax_compensation_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="discount_tax_compensation_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Discount Tax Compensation Amount"/> - <column xsi:type="decimal" name="base_discount_tax_compensation_amount" scale="4" precision="12" + <column xsi:type="decimal" name="base_discount_tax_compensation_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Discount Tax Compensation Amount"/> - <column xsi:type="decimal" name="discount_tax_compensation_invoiced" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="discount_tax_compensation_invoiced" scale="4" precision="20" unsigned="false" nullable="true" comment="Discount Tax Compensation Invoiced"/> - <column xsi:type="decimal" name="base_discount_tax_compensation_invoiced" scale="4" precision="12" + <column xsi:type="decimal" name="base_discount_tax_compensation_invoiced" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Discount Tax Compensation Invoiced"/> - <column xsi:type="decimal" name="discount_tax_compensation_refunded" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="discount_tax_compensation_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Discount Tax Compensation Refunded"/> - <column xsi:type="decimal" name="base_discount_tax_compensation_refunded" scale="4" precision="12" + <column xsi:type="decimal" name="base_discount_tax_compensation_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Discount Tax Compensation Refunded"/> <column xsi:type="decimal" name="tax_canceled" scale="4" precision="12" unsigned="false" nullable="true" comment="Tax Canceled"/> - <column xsi:type="decimal" name="discount_tax_compensation_canceled" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="discount_tax_compensation_canceled" scale="4" precision="20" unsigned="false" nullable="true" comment="Discount Tax Compensation Canceled"/> - <column xsi:type="decimal" name="tax_refunded" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="tax_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Tax Refunded"/> - <column xsi:type="decimal" name="base_tax_refunded" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_tax_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Tax Refunded"/> - <column xsi:type="decimal" name="discount_refunded" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="discount_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Discount Refunded"/> - <column xsi:type="decimal" name="base_discount_refunded" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_discount_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Discount Refunded"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="item_id"/> @@ -605,41 +605,41 @@ comment="Entity Id"/> <column xsi:type="int" name="parent_id" padding="10" unsigned="true" nullable="false" identity="false" comment="Parent Id"/> - <column xsi:type="decimal" name="base_shipping_captured" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_shipping_captured" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Shipping Captured"/> - <column xsi:type="decimal" name="shipping_captured" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="shipping_captured" scale="4" precision="20" unsigned="false" nullable="true" comment="Shipping Captured"/> - <column xsi:type="decimal" name="amount_refunded" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="amount_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Amount Refunded"/> - <column xsi:type="decimal" name="base_amount_paid" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_amount_paid" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Amount Paid"/> - <column xsi:type="decimal" name="amount_canceled" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="amount_canceled" scale="4" precision="20" unsigned="false" nullable="true" comment="Amount Canceled"/> - <column xsi:type="decimal" name="base_amount_authorized" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_amount_authorized" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Amount Authorized"/> - <column xsi:type="decimal" name="base_amount_paid_online" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_amount_paid_online" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Amount Paid Online"/> - <column xsi:type="decimal" name="base_amount_refunded_online" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_amount_refunded_online" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Amount Refunded Online"/> - <column xsi:type="decimal" name="base_shipping_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_shipping_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Shipping Amount"/> - <column xsi:type="decimal" name="shipping_amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="shipping_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Shipping Amount"/> - <column xsi:type="decimal" name="amount_paid" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="amount_paid" scale="4" precision="20" unsigned="false" nullable="true" comment="Amount Paid"/> - <column xsi:type="decimal" name="amount_authorized" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="amount_authorized" scale="4" precision="20" unsigned="false" nullable="true" comment="Amount Authorized"/> - <column xsi:type="decimal" name="base_amount_ordered" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_amount_ordered" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Amount Ordered"/> - <column xsi:type="decimal" name="base_shipping_refunded" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_shipping_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Shipping Refunded"/> - <column xsi:type="decimal" name="shipping_refunded" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="shipping_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Shipping Refunded"/> - <column xsi:type="decimal" name="base_amount_refunded" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_amount_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Amount Refunded"/> - <column xsi:type="decimal" name="amount_ordered" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="amount_ordered" scale="4" precision="20" unsigned="false" nullable="true" comment="Amount Ordered"/> - <column xsi:type="decimal" name="base_amount_canceled" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_amount_canceled" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Amount Canceled"/> <column xsi:type="int" name="quote_payment_id" padding="11" unsigned="false" nullable="true" identity="false" comment="Quote Payment Id"/> @@ -840,9 +840,9 @@ comment="Entity Id"/> <column xsi:type="int" name="parent_id" padding="10" unsigned="true" nullable="false" identity="false" comment="Parent Id"/> - <column xsi:type="decimal" name="row_total" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="row_total" scale="4" precision="20" unsigned="false" nullable="true" comment="Row Total"/> - <column xsi:type="decimal" name="price" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="price" scale="4" precision="20" unsigned="false" nullable="true" comment="Price"/> <column xsi:type="decimal" name="weight" scale="4" precision="12" unsigned="false" nullable="true" comment="Weight"/> @@ -929,43 +929,43 @@ comment="Entity Id"/> <column xsi:type="smallint" name="store_id" padding="5" unsigned="true" nullable="true" identity="false" comment="Store Id"/> - <column xsi:type="decimal" name="base_grand_total" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_grand_total" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Grand Total"/> - <column xsi:type="decimal" name="shipping_tax_amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="shipping_tax_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Shipping Tax Amount"/> - <column xsi:type="decimal" name="tax_amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="tax_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Tax Amount"/> - <column xsi:type="decimal" name="base_tax_amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_tax_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Tax Amount"/> - <column xsi:type="decimal" name="store_to_order_rate" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="store_to_order_rate" scale="4" precision="20" unsigned="false" nullable="true" comment="Store To Order Rate"/> - <column xsi:type="decimal" name="base_shipping_tax_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_shipping_tax_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Shipping Tax Amount"/> - <column xsi:type="decimal" name="base_discount_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_discount_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Discount Amount"/> - <column xsi:type="decimal" name="base_to_order_rate" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_to_order_rate" scale="4" precision="20" unsigned="false" nullable="true" comment="Base To Order Rate"/> - <column xsi:type="decimal" name="grand_total" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="grand_total" scale="4" precision="20" unsigned="false" nullable="true" comment="Grand Total"/> - <column xsi:type="decimal" name="shipping_amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="shipping_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Shipping Amount"/> - <column xsi:type="decimal" name="subtotal_incl_tax" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="subtotal_incl_tax" scale="4" precision="20" unsigned="false" nullable="true" comment="Subtotal Incl Tax"/> - <column xsi:type="decimal" name="base_subtotal_incl_tax" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_subtotal_incl_tax" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Subtotal Incl Tax"/> - <column xsi:type="decimal" name="store_to_base_rate" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="store_to_base_rate" scale="4" precision="20" unsigned="false" nullable="true" comment="Store To Base Rate"/> - <column xsi:type="decimal" name="base_shipping_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_shipping_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Shipping Amount"/> <column xsi:type="decimal" name="total_qty" scale="4" precision="12" unsigned="false" nullable="true" comment="Total Qty"/> - <column xsi:type="decimal" name="base_to_global_rate" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_to_global_rate" scale="4" precision="20" unsigned="false" nullable="true" comment="Base To Global Rate"/> - <column xsi:type="decimal" name="subtotal" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="subtotal" scale="4" precision="20" unsigned="false" nullable="true" comment="Subtotal"/> - <column xsi:type="decimal" name="base_subtotal" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_subtotal" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Subtotal"/> - <column xsi:type="decimal" name="discount_amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="discount_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Discount Amount"/> <column xsi:type="int" name="billing_address_id" padding="11" unsigned="false" nullable="true" identity="false" comment="Billing Address Id"/> @@ -994,19 +994,19 @@ comment="Created At"/> <column xsi:type="timestamp" name="updated_at" on_update="true" nullable="false" default="CURRENT_TIMESTAMP" comment="Updated At"/> - <column xsi:type="decimal" name="discount_tax_compensation_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="discount_tax_compensation_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Discount Tax Compensation Amount"/> - <column xsi:type="decimal" name="base_discount_tax_compensation_amount" scale="4" precision="12" + <column xsi:type="decimal" name="base_discount_tax_compensation_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Discount Tax Compensation Amount"/> - <column xsi:type="decimal" name="shipping_discount_tax_compensation_amount" scale="4" precision="12" + <column xsi:type="decimal" name="shipping_discount_tax_compensation_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Shipping Discount Tax Compensation Amount"/> - <column xsi:type="decimal" name="base_shipping_discount_tax_compensation_amnt" scale="4" precision="12" + <column xsi:type="decimal" name="base_shipping_discount_tax_compensation_amnt" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Shipping Discount Tax Compensation Amount"/> - <column xsi:type="decimal" name="shipping_incl_tax" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="shipping_incl_tax" scale="4" precision="20" unsigned="false" nullable="true" comment="Shipping Incl Tax"/> - <column xsi:type="decimal" name="base_shipping_incl_tax" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_shipping_incl_tax" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Shipping Incl Tax"/> - <column xsi:type="decimal" name="base_total_refunded" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_total_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Total Refunded"/> <column xsi:type="varchar" name="discount_description" nullable="true" length="255" comment="Discount Description"/> @@ -1077,15 +1077,15 @@ <column xsi:type="varchar" name="shipping_address" nullable="true" length="255" comment="Shipping Address"/> <column xsi:type="varchar" name="shipping_information" nullable="true" length="255" comment="Shipping Method Name"/> - <column xsi:type="decimal" name="subtotal" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="subtotal" scale="4" precision="20" unsigned="false" nullable="true" comment="Subtotal"/> - <column xsi:type="decimal" name="shipping_and_handling" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="shipping_and_handling" scale="4" precision="20" unsigned="false" nullable="true" comment="Shipping and handling amount"/> - <column xsi:type="decimal" name="grand_total" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="grand_total" scale="4" precision="20" unsigned="false" nullable="true" comment="Grand Total"/> <column xsi:type="timestamp" name="created_at" on_update="false" nullable="true" comment="Created At"/> <column xsi:type="timestamp" name="updated_at" on_update="false" nullable="true" comment="Updated At"/> - <column xsi:type="decimal" name="base_grand_total" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_grand_total" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Grand Total"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="entity_id"/> @@ -1143,11 +1143,11 @@ comment="Base Price"/> <column xsi:type="decimal" name="tax_amount" scale="4" precision="12" unsigned="false" nullable="true" comment="Tax Amount"/> - <column xsi:type="decimal" name="base_row_total" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_row_total" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Row Total"/> <column xsi:type="decimal" name="discount_amount" scale="4" precision="12" unsigned="false" nullable="true" comment="Discount Amount"/> - <column xsi:type="decimal" name="row_total" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="row_total" scale="4" precision="20" unsigned="false" nullable="true" comment="Row Total"/> <column xsi:type="decimal" name="base_discount_amount" scale="4" precision="12" unsigned="false" nullable="true" comment="Base Discount Amount"/> @@ -1220,53 +1220,53 @@ comment="Entity Id"/> <column xsi:type="smallint" name="store_id" padding="5" unsigned="true" nullable="true" identity="false" comment="Store Id"/> - <column xsi:type="decimal" name="adjustment_positive" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="adjustment_positive" scale="4" precision="20" unsigned="false" nullable="true" comment="Adjustment Positive"/> - <column xsi:type="decimal" name="base_shipping_tax_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_shipping_tax_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Shipping Tax Amount"/> - <column xsi:type="decimal" name="store_to_order_rate" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="store_to_order_rate" scale="4" precision="20" unsigned="false" nullable="true" comment="Store To Order Rate"/> - <column xsi:type="decimal" name="base_discount_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_discount_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Discount Amount"/> - <column xsi:type="decimal" name="base_to_order_rate" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_to_order_rate" scale="4" precision="20" unsigned="false" nullable="true" comment="Base To Order Rate"/> - <column xsi:type="decimal" name="grand_total" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="grand_total" scale="4" precision="20" unsigned="false" nullable="true" comment="Grand Total"/> - <column xsi:type="decimal" name="base_adjustment_negative" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_adjustment_negative" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Adjustment Negative"/> - <column xsi:type="decimal" name="base_subtotal_incl_tax" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_subtotal_incl_tax" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Subtotal Incl Tax"/> - <column xsi:type="decimal" name="shipping_amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="shipping_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Shipping Amount"/> - <column xsi:type="decimal" name="subtotal_incl_tax" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="subtotal_incl_tax" scale="4" precision="20" unsigned="false" nullable="true" comment="Subtotal Incl Tax"/> - <column xsi:type="decimal" name="adjustment_negative" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="adjustment_negative" scale="4" precision="20" unsigned="false" nullable="true" comment="Adjustment Negative"/> - <column xsi:type="decimal" name="base_shipping_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_shipping_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Shipping Amount"/> - <column xsi:type="decimal" name="store_to_base_rate" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="store_to_base_rate" scale="4" precision="20" unsigned="false" nullable="true" comment="Store To Base Rate"/> - <column xsi:type="decimal" name="base_to_global_rate" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_to_global_rate" scale="4" precision="20" unsigned="false" nullable="true" comment="Base To Global Rate"/> - <column xsi:type="decimal" name="base_adjustment" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_adjustment" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Adjustment"/> - <column xsi:type="decimal" name="base_subtotal" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_subtotal" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Subtotal"/> - <column xsi:type="decimal" name="discount_amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="discount_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Discount Amount"/> - <column xsi:type="decimal" name="subtotal" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="subtotal" scale="4" precision="20" unsigned="false" nullable="true" comment="Subtotal"/> - <column xsi:type="decimal" name="adjustment" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="adjustment" scale="4" precision="20" unsigned="false" nullable="true" comment="Adjustment"/> - <column xsi:type="decimal" name="base_grand_total" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_grand_total" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Grand Total"/> - <column xsi:type="decimal" name="base_adjustment_positive" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_adjustment_positive" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Adjustment Positive"/> - <column xsi:type="decimal" name="base_tax_amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_tax_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Tax Amount"/> - <column xsi:type="decimal" name="shipping_tax_amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="shipping_tax_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Shipping Tax Amount"/> - <column xsi:type="decimal" name="tax_amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="tax_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Tax Amount"/> <column xsi:type="int" name="order_id" padding="10" unsigned="true" nullable="false" identity="false" comment="Order Id"/> @@ -1295,17 +1295,17 @@ comment="Created At"/> <column xsi:type="timestamp" name="updated_at" on_update="true" nullable="false" default="CURRENT_TIMESTAMP" comment="Updated At"/> - <column xsi:type="decimal" name="discount_tax_compensation_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="discount_tax_compensation_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Discount Tax Compensation Amount"/> - <column xsi:type="decimal" name="base_discount_tax_compensation_amount" scale="4" precision="12" + <column xsi:type="decimal" name="base_discount_tax_compensation_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Discount Tax Compensation Amount"/> - <column xsi:type="decimal" name="shipping_discount_tax_compensation_amount" scale="4" precision="12" + <column xsi:type="decimal" name="shipping_discount_tax_compensation_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Shipping Discount Tax Compensation Amount"/> - <column xsi:type="decimal" name="base_shipping_discount_tax_compensation_amnt" scale="4" precision="12" + <column xsi:type="decimal" name="base_shipping_discount_tax_compensation_amnt" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Shipping Discount Tax Compensation Amount"/> - <column xsi:type="decimal" name="shipping_incl_tax" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="shipping_incl_tax" scale="4" precision="20" unsigned="false" nullable="true" comment="Shipping Incl Tax"/> - <column xsi:type="decimal" name="base_shipping_incl_tax" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="base_shipping_incl_tax" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Shipping Incl Tax"/> <column xsi:type="varchar" name="discount_description" nullable="true" length="255" comment="Discount Description"/> @@ -1362,7 +1362,7 @@ <column xsi:type="varchar" name="billing_name" nullable="true" length="255" comment="Billing Name"/> <column xsi:type="int" name="state" padding="11" unsigned="false" nullable="true" identity="false" comment="Status"/> - <column xsi:type="decimal" name="base_grand_total" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_grand_total" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Grand Total"/> <column xsi:type="varchar" name="order_status" nullable="true" length="32" comment="Order Status"/> <column xsi:type="smallint" name="store_id" padding="5" unsigned="true" nullable="true" identity="false" @@ -1376,15 +1376,15 @@ <column xsi:type="varchar" name="payment_method" nullable="true" length="32" comment="Payment Method"/> <column xsi:type="varchar" name="shipping_information" nullable="true" length="255" comment="Shipping Method Name"/> - <column xsi:type="decimal" name="subtotal" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="subtotal" scale="4" precision="20" unsigned="false" nullable="true" comment="Subtotal"/> - <column xsi:type="decimal" name="shipping_and_handling" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="shipping_and_handling" scale="4" precision="20" unsigned="false" nullable="true" comment="Shipping and handling amount"/> - <column xsi:type="decimal" name="adjustment_positive" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="adjustment_positive" scale="4" precision="20" unsigned="false" nullable="true" comment="Adjustment Positive"/> - <column xsi:type="decimal" name="adjustment_negative" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="adjustment_negative" scale="4" precision="20" unsigned="false" nullable="true" comment="Adjustment Negative"/> - <column xsi:type="decimal" name="order_base_grand_total" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="order_base_grand_total" scale="4" precision="20" unsigned="false" nullable="true" comment="Order Grand Total"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="entity_id"/> @@ -1593,31 +1593,31 @@ default="0" comment="Total Qty Ordered"/> <column xsi:type="decimal" name="total_qty_invoiced" scale="4" precision="12" unsigned="false" nullable="false" default="0" comment="Total Qty Invoiced"/> - <column xsi:type="decimal" name="total_income_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="total_income_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Income Amount"/> - <column xsi:type="decimal" name="total_revenue_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="total_revenue_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Revenue Amount"/> - <column xsi:type="decimal" name="total_profit_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="total_profit_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Profit Amount"/> - <column xsi:type="decimal" name="total_invoiced_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="total_invoiced_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Invoiced Amount"/> - <column xsi:type="decimal" name="total_canceled_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="total_canceled_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Canceled Amount"/> - <column xsi:type="decimal" name="total_paid_amount" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="total_paid_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Paid Amount"/> - <column xsi:type="decimal" name="total_refunded_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="total_refunded_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Refunded Amount"/> - <column xsi:type="decimal" name="total_tax_amount" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="total_tax_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Tax Amount"/> - <column xsi:type="decimal" name="total_tax_amount_actual" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="total_tax_amount_actual" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Tax Amount Actual"/> - <column xsi:type="decimal" name="total_shipping_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="total_shipping_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Shipping Amount"/> - <column xsi:type="decimal" name="total_shipping_amount_actual" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="total_shipping_amount_actual" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Shipping Amount Actual"/> - <column xsi:type="decimal" name="total_discount_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="total_discount_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Discount Amount"/> - <column xsi:type="decimal" name="total_discount_amount_actual" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="total_discount_amount_actual" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Discount Amount Actual"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="id"/> @@ -1647,31 +1647,31 @@ default="0" comment="Total Qty Ordered"/> <column xsi:type="decimal" name="total_qty_invoiced" scale="4" precision="12" unsigned="false" nullable="false" default="0" comment="Total Qty Invoiced"/> - <column xsi:type="decimal" name="total_income_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="total_income_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Income Amount"/> - <column xsi:type="decimal" name="total_revenue_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="total_revenue_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Revenue Amount"/> - <column xsi:type="decimal" name="total_profit_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="total_profit_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Profit Amount"/> - <column xsi:type="decimal" name="total_invoiced_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="total_invoiced_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Invoiced Amount"/> - <column xsi:type="decimal" name="total_canceled_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="total_canceled_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Canceled Amount"/> - <column xsi:type="decimal" name="total_paid_amount" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="total_paid_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Paid Amount"/> - <column xsi:type="decimal" name="total_refunded_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="total_refunded_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Refunded Amount"/> - <column xsi:type="decimal" name="total_tax_amount" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="total_tax_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Tax Amount"/> - <column xsi:type="decimal" name="total_tax_amount_actual" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="total_tax_amount_actual" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Tax Amount Actual"/> - <column xsi:type="decimal" name="total_shipping_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="total_shipping_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Shipping Amount"/> - <column xsi:type="decimal" name="total_shipping_amount_actual" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="total_shipping_amount_actual" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Shipping Amount Actual"/> - <column xsi:type="decimal" name="total_discount_amount" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="total_discount_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Discount Amount"/> - <column xsi:type="decimal" name="total_discount_amount_actual" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="total_discount_amount_actual" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Discount Amount Actual"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="id"/> @@ -1737,11 +1737,11 @@ <column xsi:type="varchar" name="order_status" nullable="false" length="50" comment="Order Status"/> <column xsi:type="int" name="orders_count" padding="11" unsigned="false" nullable="false" identity="false" default="0" comment="Orders Count"/> - <column xsi:type="decimal" name="refunded" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Refunded"/> - <column xsi:type="decimal" name="online_refunded" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="online_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Online Refunded"/> - <column xsi:type="decimal" name="offline_refunded" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="offline_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Offline Refunded"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="id"/> @@ -1767,11 +1767,11 @@ <column xsi:type="varchar" name="order_status" nullable="true" length="50" comment="Order Status"/> <column xsi:type="int" name="orders_count" padding="11" unsigned="false" nullable="false" identity="false" default="0" comment="Orders Count"/> - <column xsi:type="decimal" name="refunded" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Refunded"/> - <column xsi:type="decimal" name="online_refunded" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="online_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Online Refunded"/> - <column xsi:type="decimal" name="offline_refunded" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="offline_refunded" scale="4" precision="20" unsigned="false" nullable="true" comment="Offline Refunded"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="id"/> @@ -1798,9 +1798,9 @@ comment="Shipping Description"/> <column xsi:type="int" name="orders_count" padding="11" unsigned="false" nullable="false" identity="false" default="0" comment="Orders Count"/> - <column xsi:type="decimal" name="total_shipping" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="total_shipping" scale="4" precision="20" unsigned="false" nullable="true" comment="Total Shipping"/> - <column xsi:type="decimal" name="total_shipping_actual" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="total_shipping_actual" scale="4" precision="20" unsigned="false" nullable="true" comment="Total Shipping Actual"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="id"/> @@ -1829,9 +1829,9 @@ comment="Shipping Description"/> <column xsi:type="int" name="orders_count" padding="11" unsigned="false" nullable="false" identity="false" default="0" comment="Orders Count"/> - <column xsi:type="decimal" name="total_shipping" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="total_shipping" scale="4" precision="20" unsigned="false" nullable="true" comment="Total Shipping"/> - <column xsi:type="decimal" name="total_shipping_actual" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="total_shipping_actual" scale="4" precision="20" unsigned="false" nullable="true" comment="Total Shipping Actual"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="id"/> @@ -1957,17 +1957,17 @@ <column xsi:type="varchar" name="title" nullable="true" length="255" comment="Title"/> <column xsi:type="decimal" name="percent" scale="4" precision="12" unsigned="false" nullable="true" comment="Percent"/> - <column xsi:type="decimal" name="amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Amount"/> <column xsi:type="int" name="priority" padding="11" unsigned="false" nullable="false" identity="false" comment="Priority"/> <column xsi:type="int" name="position" padding="11" unsigned="false" nullable="false" identity="false" comment="Position"/> - <column xsi:type="decimal" name="base_amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Amount"/> <column xsi:type="smallint" name="process" padding="6" unsigned="false" nullable="false" identity="false" comment="Process"/> - <column xsi:type="decimal" name="base_real_amount" scale="4" precision="12" unsigned="false" nullable="true" + <column xsi:type="decimal" name="base_real_amount" scale="4" precision="20" unsigned="false" nullable="true" comment="Base Real Amount"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="tax_id"/> @@ -1987,13 +1987,13 @@ comment="Item Id"/> <column xsi:type="decimal" name="tax_percent" scale="4" precision="12" unsigned="false" nullable="false" comment="Real Tax Percent For Item"/> - <column xsi:type="decimal" name="amount" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="amount" scale="4" precision="20" unsigned="false" nullable="false" comment="Tax amount for the item and tax rate"/> - <column xsi:type="decimal" name="base_amount" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="base_amount" scale="4" precision="20" unsigned="false" nullable="false" comment="Base tax amount for the item and tax rate"/> - <column xsi:type="decimal" name="real_amount" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="real_amount" scale="4" precision="20" unsigned="false" nullable="false" comment="Real tax amount for the item and tax rate"/> - <column xsi:type="decimal" name="real_base_amount" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="real_base_amount" scale="4" precision="20" unsigned="false" nullable="false" comment="Real base tax amount for the item and tax rate"/> <column xsi:type="int" name="associated_item_id" padding="10" unsigned="true" nullable="true" identity="false" comment="Id of the associated item"/> diff --git a/app/code/Magento/Sales/etc/extension_attributes.xml b/app/code/Magento/Sales/etc/extension_attributes.xml index 7280a1a0715..222f61cdc73 100644 --- a/app/code/Magento/Sales/etc/extension_attributes.xml +++ b/app/code/Magento/Sales/etc/extension_attributes.xml @@ -10,4 +10,7 @@ <extension_attributes for="Magento\Sales\Api\Data\OrderInterface"> <attribute code="shipping_assignments" type="Magento\Sales\Api\Data\ShippingAssignmentInterface[]" /> </extension_attributes> + <extension_attributes for="Magento\Sales\Api\Data\OrderInterface"> + <attribute code="payment_additional_info" type="Magento\Payment\Api\Data\PaymentAdditionalInfoInterface[]" /> + </extension_attributes> </config> diff --git a/app/code/Magento/Sales/etc/webapi.xml b/app/code/Magento/Sales/etc/webapi.xml index cee245e3483..492dff80570 100644 --- a/app/code/Magento/Sales/etc/webapi.xml +++ b/app/code/Magento/Sales/etc/webapi.xml @@ -10,271 +10,271 @@ <route url="/V1/orders/:id" method="GET"> <service class="Magento\Sales\Api\OrderRepositoryInterface" method="get"/> <resources> - <resource ref="Magento_Sales::sales" /> + <resource ref="Magento_Sales::actions_view" /> </resources> </route> <route url="/V1/orders" method="GET"> <service class="Magento\Sales\Api\OrderRepositoryInterface" method="getList"/> <resources> - <resource ref="Magento_Sales::sales" /> + <resource ref="Magento_Sales::actions_view" /> </resources> </route> <route url="/V1/orders/:id/statuses" method="GET"> <service class="Magento\Sales\Api\OrderManagementInterface" method="getStatus"/> <resources> - <resource ref="Magento_Sales::sales" /> + <resource ref="Magento_Sales::actions_view" /> </resources> </route> <route url="/V1/orders/:id/cancel" method="POST"> <service class="Magento\Sales\Api\OrderManagementInterface" method="cancel"/> <resources> - <resource ref="Magento_Sales::sales" /> + <resource ref="Magento_Sales::cancel" /> </resources> </route> <route url="/V1/orders/:id/emails" method="POST"> <service class="Magento\Sales\Api\OrderManagementInterface" method="notify"/> <resources> - <resource ref="Magento_Sales::sales" /> + <resource ref="Magento_Sales::emails" /> </resources> </route> <route url="/V1/orders/:id/hold" method="POST"> <service class="Magento\Sales\Api\OrderManagementInterface" method="hold"/> <resources> - <resource ref="Magento_Sales::sales" /> + <resource ref="Magento_Sales::hold" /> </resources> </route> <route url="/V1/orders/:id/unhold" method="POST"> <service class="Magento\Sales\Api\OrderManagementInterface" method="unHold"/> <resources> - <resource ref="Magento_Sales::sales" /> + <resource ref="Magento_Sales::unhold" /> </resources> </route> <route url="/V1/orders/:id/comments" method="POST"> <service class="Magento\Sales\Api\OrderManagementInterface" method="addComment"/> <resources> - <resource ref="Magento_Sales::sales" /> + <resource ref="Magento_Sales::comment" /> </resources> </route> <route url="/V1/orders/:id/comments" method="GET"> <service class="Magento\Sales\Api\OrderManagementInterface" method="getCommentsList"/> <resources> - <resource ref="Magento_Sales::sales" /> + <resource ref="Magento_Sales::actions_view" /> </resources> </route> <route url="/V1/orders/create" method="PUT"> <service class="Magento\Sales\Api\OrderRepositoryInterface" method="save"/> <resources> - <resource ref="Magento_Sales::sales" /> + <resource ref="Magento_Sales::create" /> </resources> </route> <route url="/V1/orders/:parent_id" method="PUT"> <service class="Magento\Sales\Api\OrderAddressRepositoryInterface" method="save"/> <resources> - <resource ref="Magento_Sales::sales" /> + <resource ref="Magento_Sales::create" /> </resources> </route> <route url="/V1/orders/items/:id" method="GET"> <service class="Magento\Sales\Api\OrderItemRepositoryInterface" method="get"/> <resources> - <resource ref="Magento_Sales::sales" /> + <resource ref="Magento_Sales::actions_view" /> </resources> </route> <route url="/V1/orders/items" method="GET"> <service class="Magento\Sales\Api\OrderItemRepositoryInterface" method="getList"/> <resources> - <resource ref="Magento_Sales::sales" /> + <resource ref="Magento_Sales::actions_view" /> </resources> </route> <route url="/V1/invoices/:id" method="GET"> <service class="Magento\Sales\Api\InvoiceRepositoryInterface" method="get"/> <resources> - <resource ref="Magento_Sales::sales" /> + <resource ref="Magento_Sales::sales_invoice" /> </resources> </route> <route url="/V1/invoices" method="GET"> <service class="Magento\Sales\Api\InvoiceRepositoryInterface" method="getList"/> <resources> - <resource ref="Magento_Sales::sales" /> + <resource ref="Magento_Sales::sales_invoice" /> </resources> </route> <route url="/V1/invoices/:id/comments" method="GET"> <service class="Magento\Sales\Api\InvoiceManagementInterface" method="getCommentsList"/> <resources> - <resource ref="Magento_Sales::sales" /> + <resource ref="Magento_Sales::sales_invoice" /> </resources> </route> <route url="/V1/invoices/:id/emails" method="POST"> <service class="Magento\Sales\Api\InvoiceManagementInterface" method="notify"/> <resources> - <resource ref="Magento_Sales::sales" /> + <resource ref="Magento_Sales::sales_invoice" /> </resources> </route> <route url="/V1/invoices/:id/void" method="POST"> <service class="Magento\Sales\Api\InvoiceManagementInterface" method="setVoid"/> <resources> - <resource ref="Magento_Sales::sales" /> + <resource ref="Magento_Sales::sales_invoice" /> </resources> </route> <route url="/V1/invoices/:id/capture" method="POST"> <service class="Magento\Sales\Api\InvoiceManagementInterface" method="setCapture"/> <resources> - <resource ref="Magento_Sales::sales" /> + <resource ref="Magento_Sales::sales_invoice" /> </resources> </route> <route url="/V1/invoices/comments" method="POST"> <service class="Magento\Sales\Api\InvoiceCommentRepositoryInterface" method="save"/> <resources> - <resource ref="Magento_Sales::sales" /> + <resource ref="Magento_Sales::sales_invoice" /> </resources> </route> <route url="/V1/invoices/" method="POST"> <service class="Magento\Sales\Api\InvoiceRepositoryInterface" method="save"/> <resources> - <resource ref="Magento_Sales::sales" /> + <resource ref="Magento_Sales::sales_invoice" /> </resources> </route> <route url="/V1/invoice/:invoiceId/refund" method="POST"> <service class="Magento\Sales\Api\RefundInvoiceInterface" method="execute"/> <resources> - <resource ref="Magento_Sales::sales" /> + <resource ref="Magento_Sales::sales_invoice" /> </resources> </route> <route url="/V1/creditmemo/:id/comments" method="GET"> <service class="Magento\Sales\Api\CreditmemoManagementInterface" method="getCommentsList"/> <resources> - <resource ref="Magento_Sales::sales" /> + <resource ref="Magento_Sales::sales_creditmemo" /> </resources> </route> <route url="/V1/creditmemos" method="GET"> <service class="Magento\Sales\Api\CreditmemoRepositoryInterface" method="getList"/> <resources> - <resource ref="Magento_Sales::sales" /> + <resource ref="Magento_Sales::sales_creditmemo" /> </resources> </route> <route url="/V1/creditmemo/:id" method="GET"> <service class="Magento\Sales\Api\CreditmemoRepositoryInterface" method="get"/> <resources> - <resource ref="Magento_Sales::sales" /> + <resource ref="Magento_Sales::sales_creditmemo" /> </resources> </route> <route url="/V1/creditmemo/:id" method="PUT"> <service class="Magento\Sales\Api\CreditmemoManagementInterface" method="cancel"/> <resources> - <resource ref="Magento_Sales::sales" /> + <resource ref="Magento_Sales::sales_creditmemo" /> </resources> </route> <route url="/V1/creditmemo/:id/emails" method="POST"> <service class="Magento\Sales\Api\CreditmemoManagementInterface" method="notify"/> <resources> - <resource ref="Magento_Sales::sales" /> + <resource ref="Magento_Sales::sales_creditmemo" /> </resources> </route> <route url="/V1/creditmemo/refund" method="POST"> <service class="Magento\Sales\Api\CreditmemoManagementInterface" method="refund"/> <resources> - <resource ref="Magento_Sales::sales" /> + <resource ref="Magento_Sales::sales_creditmemo" /> </resources> </route> <route url="/V1/creditmemo/:id/comments" method="POST"> <service class="Magento\Sales\Api\CreditmemoCommentRepositoryInterface" method="save"/> <resources> - <resource ref="Magento_Sales::sales" /> + <resource ref="Magento_Sales::sales_creditmemo" /> </resources> </route> <route url="/V1/creditmemo" method="POST"> <service class="Magento\Sales\Api\CreditmemoRepositoryInterface" method="save"/> <resources> - <resource ref="Magento_Sales::sales" /> + <resource ref="Magento_Sales::sales_creditmemo" /> </resources> </route> <route url="/V1/order/:orderId/refund" method="POST"> <service class="Magento\Sales\Api\RefundOrderInterface" method="execute"/> <resources> - <resource ref="Magento_Sales::sales" /> + <resource ref="Magento_Sales::creditmemo" /> </resources> </route> <route url="/V1/shipment/:id" method="GET"> <service class="Magento\Sales\Api\ShipmentRepositoryInterface" method="get"/> <resources> - <resource ref="Magento_Sales::sales" /> + <resource ref="Magento_Sales::shipment" /> </resources> </route> <route url="/V1/shipments" method="GET"> <service class="Magento\Sales\Api\ShipmentRepositoryInterface" method="getList"/> <resources> - <resource ref="Magento_Sales::sales" /> + <resource ref="Magento_Sales::shipment" /> </resources> </route> <route url="/V1/shipment/:id/comments" method="GET"> <service class="Magento\Sales\Api\ShipmentManagementInterface" method="getCommentsList"/> <resources> - <resource ref="Magento_Sales::sales" /> + <resource ref="Magento_Sales::shipment" /> </resources> </route> <route url="/V1/shipment/:id/comments" method="POST"> <service class="Magento\Sales\Api\ShipmentCommentRepositoryInterface" method="save"/> <resources> - <resource ref="Magento_Sales::sales" /> + <resource ref="Magento_Sales::shipment" /> </resources> </route> <route url="/V1/shipment/:id/emails" method="POST"> <service class="Magento\Sales\Api\ShipmentManagementInterface" method="notify"/> <resources> - <resource ref="Magento_Sales::sales" /> + <resource ref="Magento_Sales::shipment" /> </resources> </route> <route url="/V1/shipment/track" method="POST"> <service class="Magento\Sales\Api\ShipmentTrackRepositoryInterface" method="save"/> <resources> - <resource ref="Magento_Sales::sales" /> + <resource ref="Magento_Sales::shipment" /> </resources> </route> <route url="/V1/shipment/track/:id" method="DELETE"> <service class="Magento\Sales\Api\ShipmentTrackRepositoryInterface" method="deleteById"/> <resources> - <resource ref="Magento_Sales::sales" /> + <resource ref="Magento_Sales::shipment" /> </resources> </route> <route url="/V1/shipment/" method="POST"> <service class="Magento\Sales\Api\ShipmentRepositoryInterface" method="save"/> <resources> - <resource ref="Magento_Sales::sales" /> + <resource ref="Magento_Sales::shipment" /> </resources> </route> <route url="/V1/shipment/:id/label" method="GET"> <service class="Magento\Sales\Api\ShipmentManagementInterface" method="getLabel"/> <resources> - <resource ref="Magento_Sales::sales" /> + <resource ref="Magento_Sales::shipment" /> </resources> </route> <route url="/V1/order/:orderId/ship" method="POST"> <service class="Magento\Sales\Api\ShipOrderInterface" method="execute"/> <resources> - <resource ref="Magento_Sales::sales" /> + <resource ref="Magento_Sales::ship" /> </resources> </route> <route url="/V1/orders/" method="POST"> <service class="Magento\Sales\Api\OrderRepositoryInterface" method="save"/> <resources> - <resource ref="Magento_Sales::sales" /> + <resource ref="Magento_Sales::create" /> </resources> </route> <route url="/V1/transactions/:id" method="GET"> <service class="Magento\Sales\Api\TransactionRepositoryInterface" method="get"/> <resources> - <resource ref="Magento_Sales::sales" /> + <resource ref="Magento_Sales::transactions_fetch" /> </resources> </route> <route url="/V1/transactions" method="GET"> <service class="Magento\Sales\Api\TransactionRepositoryInterface" method="getList"/> <resources> - <resource ref="Magento_Sales::sales" /> + <resource ref="Magento_Sales::transactions_fetch" /> </resources> </route> <route url="/V1/order/:orderId/invoice" method="POST"> <service class="Magento\Sales\Api\InvoiceOrderInterface" method="execute"/> <resources> - <resource ref="Magento_Sales::sales" /> + <resource ref="Magento_Sales::invoice" /> </resources> </route> </routes> diff --git a/app/code/Magento/Sales/etc/webapi_rest/di.xml b/app/code/Magento/Sales/etc/webapi_rest/di.xml index 6435445e0ef..f2cbd14eb80 100644 --- a/app/code/Magento/Sales/etc/webapi_rest/di.xml +++ b/app/code/Magento/Sales/etc/webapi_rest/di.xml @@ -18,7 +18,7 @@ <type name="Magento\Framework\Reflection\DataObjectProcessor"> <arguments> <argument name="processors" xsi:type="array"> - <item name="\Magento\Sales\Model\Order\Item" xsi:type="object">Magento\Sales\Model\Order\Webapi\ChangeOutputArray\Proxy</item> + <item name="Magento\Sales\Model\Order\Item" xsi:type="object">Magento\Sales\Model\Order\Webapi\ChangeOutputArray\Proxy</item> </argument> </arguments> </type> diff --git a/app/code/Magento/Sales/etc/webapi_soap/di.xml b/app/code/Magento/Sales/etc/webapi_soap/di.xml index 6435445e0ef..f2cbd14eb80 100644 --- a/app/code/Magento/Sales/etc/webapi_soap/di.xml +++ b/app/code/Magento/Sales/etc/webapi_soap/di.xml @@ -18,7 +18,7 @@ <type name="Magento\Framework\Reflection\DataObjectProcessor"> <arguments> <argument name="processors" xsi:type="array"> - <item name="\Magento\Sales\Model\Order\Item" xsi:type="object">Magento\Sales\Model\Order\Webapi\ChangeOutputArray\Proxy</item> + <item name="Magento\Sales\Model\Order\Item" xsi:type="object">Magento\Sales\Model\Order\Webapi\ChangeOutputArray\Proxy</item> </argument> </arguments> </type> diff --git a/app/code/Magento/Sales/view/adminhtml/layout/sales_order_create_customer_block.xml b/app/code/Magento/Sales/view/adminhtml/layout/sales_order_create_customer_block.xml index c321bee460e..0f5a3559f30 100644 --- a/app/code/Magento/Sales/view/adminhtml/layout/sales_order_create_customer_block.xml +++ b/app/code/Magento/Sales/view/adminhtml/layout/sales_order_create_customer_block.xml @@ -80,13 +80,13 @@ <argument name="align" xsi:type="string">center</argument> </arguments> </block> - </block> - <block class="Magento\Backend\Block\Widget\Grid\Column" name="adminhtml.customer.grid.columnSet.website_name" as="website_name"> - <arguments> - <argument name="header" xsi:type="string" translate="true">Website</argument> - <argument name="index" xsi:type="string">website_name</argument> - <argument name="align" xsi:type="string">center</argument> - </arguments> + <block class="Magento\Backend\Block\Widget\Grid\Column" name="adminhtml.customer.grid.columnSet.website_name" as="website_name"> + <arguments> + <argument name="header" xsi:type="string" translate="true">Website</argument> + <argument name="index" xsi:type="string">website_name</argument> + <argument name="align" xsi:type="string">center</argument> + </arguments> + </block> </block> </block> </referenceBlock> diff --git a/app/code/Magento/Sales/view/adminhtml/templates/order/create/billing/method/form.phtml b/app/code/Magento/Sales/view/adminhtml/templates/order/create/billing/method/form.phtml index c69d453fb81..00fa55d38f5 100644 --- a/app/code/Magento/Sales/view/adminhtml/templates/order/create/billing/method/form.phtml +++ b/app/code/Magento/Sales/view/adminhtml/templates/order/create/billing/method/form.phtml @@ -7,7 +7,7 @@ ?> <?php if ($block->hasMethods()) : ?> <div id="order-billing_method_form"> - <dl class="admin__payment-methods"> + <dl class="admin__payment-methods control"> <?php $_methods = $block->getMethods(); $_methodsCount = count($_methods); @@ -28,8 +28,8 @@ <?php if ($currentSelectedMethod == $_code) : ?> checked="checked" <?php endif; ?> - <?php $className = ($_counter == $_methodsCount) ? ' validate-one-required-by-name' : ''; ?> - class="admin__control-radio<?= $block->escapeHtml($className); ?>"/> + data-validate="{'validate-one-required-by-name':true}" + class="admin__control-radio"/> <?php else :?> <span class="no-display"> <input id="p_method_<?= $block->escapeHtml($_code); ?>" diff --git a/app/code/Magento/Sales/view/adminhtml/templates/order/invoice/create/items.phtml b/app/code/Magento/Sales/view/adminhtml/templates/order/invoice/create/items.phtml index fa5ea056801..4a77c3b166d 100644 --- a/app/code/Magento/Sales/view/adminhtml/templates/order/invoice/create/items.phtml +++ b/app/code/Magento/Sales/view/adminhtml/templates/order/invoice/create/items.phtml @@ -31,9 +31,9 @@ <?php if ($block->canEditQty()): ?> <tfoot> <tr> - <td colspan="2"> </td> - <td colspan="3"><?= $block->getUpdateButtonHtml() ?></td> <td colspan="3"> </td> + <td><?= $block->getUpdateButtonHtml() ?></td> + <td colspan="4"> </td> </tr> </tfoot> <?php endif; ?> diff --git a/app/code/Magento/Sales/view/adminhtml/templates/order/view/info.phtml b/app/code/Magento/Sales/view/adminhtml/templates/order/view/info.phtml index 5384a00dc89..bbd6394097f 100644 --- a/app/code/Magento/Sales/view/adminhtml/templates/order/view/info.phtml +++ b/app/code/Magento/Sales/view/adminhtml/templates/order/view/info.phtml @@ -104,7 +104,7 @@ $customerUrl = $block->getCustomerViewUrl(); <?php if ($order->getBaseCurrencyCode() != $order->getOrderCurrencyCode()): ?> <tr> <th><?= $block->escapeHtml(__('%1 / %2 rate:', $order->getOrderCurrencyCode(), $order->getBaseCurrencyCode())) ?></th> - <th><?= $block->escapeHtml($order->getBaseToOrderRate()) ?></th> + <td><?= $block->escapeHtml($order->getBaseToOrderRate()) ?></td> </tr> <?php endif; ?> </table> diff --git a/app/code/Magento/Sales/view/adminhtml/ui_component/sales_order_grid.xml b/app/code/Magento/Sales/view/adminhtml/ui_component/sales_order_grid.xml index fe67f4d5e2d..e1f047b372c 100644 --- a/app/code/Magento/Sales/view/adminhtml/ui_component/sales_order_grid.xml +++ b/app/code/Magento/Sales/view/adminhtml/ui_component/sales_order_grid.xml @@ -17,6 +17,7 @@ <url path="sales/order_create/start"/> <class>primary</class> <label translate="true">Create New Order</label> + <aclResource>Magento_Sales::create</aclResource> </button> </buttons> <spinner>sales_order_columns</spinner> diff --git a/app/code/Magento/Sales/view/frontend/email/creditmemo_update.html b/app/code/Magento/Sales/view/frontend/email/creditmemo_update.html index 3a4aab19e9e..a6a10fb49e3 100644 --- a/app/code/Magento/Sales/view/frontend/email/creditmemo_update.html +++ b/app/code/Magento/Sales/view/frontend/email/creditmemo_update.html @@ -11,7 +11,7 @@ "var this.getUrl($store, 'customer/account/')":"Customer Account URL", "var order.getCustomerName()":"Customer Name", "var order.increment_id":"Order Id", -"var order.getStatusLabel()":"Order Status" +"var order.getFrontendStatusLabel()":"Order Status" } @--> {{template config_path="design/email/header_template"}} @@ -24,7 +24,7 @@ "Your order #%increment_id has been updated with a status of <strong>%order_status</strong>." increment_id=$order.increment_id - order_status=$order.getStatusLabel() + order_status=$order.getFrontendStatusLabel() |raw}} </p> <p>{{trans 'You can check the status of your order by <a href="%account_url">logging into your account</a>.' account_url=$this.getUrl($store,'customer/account/',[_nosid:1]) |raw}}</p> diff --git a/app/code/Magento/Sales/view/frontend/email/creditmemo_update_guest.html b/app/code/Magento/Sales/view/frontend/email/creditmemo_update_guest.html index bc7c079d7f2..b7411d80d2b 100644 --- a/app/code/Magento/Sales/view/frontend/email/creditmemo_update_guest.html +++ b/app/code/Magento/Sales/view/frontend/email/creditmemo_update_guest.html @@ -10,7 +10,7 @@ "var creditmemo.increment_id":"Credit Memo Id", "var billing.getName()":"Guest Customer Name", "var order.increment_id":"Order Id", -"var order.getStatusLabel()":"Order Status" +"var order.getFrontendStatusLabel()":"Order Status" } @--> {{template config_path="design/email/header_template"}} @@ -23,7 +23,7 @@ "Your order #%increment_id has been updated with a status of <strong>%order_status</strong>." increment_id=$order.increment_id - order_status=$order.getStatusLabel() + order_status=$order.getFrontendStatusLabel() |raw}} </p> <p> diff --git a/app/code/Magento/Sales/view/frontend/email/invoice_update.html b/app/code/Magento/Sales/view/frontend/email/invoice_update.html index cafdd65ff52..4043e59f9d7 100644 --- a/app/code/Magento/Sales/view/frontend/email/invoice_update.html +++ b/app/code/Magento/Sales/view/frontend/email/invoice_update.html @@ -11,7 +11,7 @@ "var comment":"Invoice Comment", "var invoice.increment_id":"Invoice Id", "var order.increment_id":"Order Id", -"var order.getStatusLabel()":"Order Status" +"var order.getFrontendStatusLabel()":"Order Status" } @--> {{template config_path="design/email/header_template"}} @@ -24,7 +24,7 @@ "Your order #%increment_id has been updated with a status of <strong>%order_status</strong>." increment_id=$order.increment_id - order_status=$order.getStatusLabel() + order_status=$order.getFrontendStatusLabel() |raw}} </p> <p>{{trans 'You can check the status of your order by <a href="%account_url">logging into your account</a>.' account_url=$this.getUrl($store,'customer/account/',[_nosid:1]) |raw}}</p> diff --git a/app/code/Magento/Sales/view/frontend/email/invoice_update_guest.html b/app/code/Magento/Sales/view/frontend/email/invoice_update_guest.html index fafb533301e..40cdec7fb4c 100644 --- a/app/code/Magento/Sales/view/frontend/email/invoice_update_guest.html +++ b/app/code/Magento/Sales/view/frontend/email/invoice_update_guest.html @@ -10,7 +10,7 @@ "var comment":"Invoice Comment", "var invoice.increment_id":"Invoice Id", "var order.increment_id":"Order Id", -"var order.getStatusLabel()":"Order Status" +"var order.getFrontendStatusLabel()":"Order Status" } @--> {{template config_path="design/email/header_template"}} @@ -23,7 +23,7 @@ "Your order #%increment_id has been updated with a status of <strong>%order_status</strong>." increment_id=$order.increment_id - order_status=$order.getStatusLabel() + order_status=$order.getFrontendStatusLabel() |raw}} </p> <p> diff --git a/app/code/Magento/Sales/view/frontend/email/order_update.html b/app/code/Magento/Sales/view/frontend/email/order_update.html index a709a9ed8a7..a8f0068b70e 100644 --- a/app/code/Magento/Sales/view/frontend/email/order_update.html +++ b/app/code/Magento/Sales/view/frontend/email/order_update.html @@ -10,7 +10,7 @@ "var order.getCustomerName()":"Customer Name", "var comment":"Order Comment", "var order.increment_id":"Order Id", -"var order.getStatusLabel()":"Order Status" +"var order.getFrontendStatusLabel()":"Order Status" } @--> {{template config_path="design/email/header_template"}} @@ -23,7 +23,7 @@ "Your order #%increment_id has been updated with a status of <strong>%order_status</strong>." increment_id=$order.increment_id - order_status=$order.getStatusLabel() + order_status=$order.getFrontendStatusLabel() |raw}} </p> <p>{{trans 'You can check the status of your order by <a href="%account_url">logging into your account</a>.' account_url=$this.getUrl($store,'customer/account/',[_nosid:1]) |raw}}</p> diff --git a/app/code/Magento/Sales/view/frontend/email/order_update_guest.html b/app/code/Magento/Sales/view/frontend/email/order_update_guest.html index 5a39b01810c..749fa3b60ad 100644 --- a/app/code/Magento/Sales/view/frontend/email/order_update_guest.html +++ b/app/code/Magento/Sales/view/frontend/email/order_update_guest.html @@ -9,7 +9,7 @@ "var billing.getName()":"Guest Customer Name", "var comment":"Order Comment", "var order.increment_id":"Order Id", -"var order.getStatusLabel()":"Order Status" +"var order.getFrontendStatusLabel()":"Order Status" } @--> {{template config_path="design/email/header_template"}} @@ -22,7 +22,7 @@ "Your order #%increment_id has been updated with a status of <strong>%order_status</strong>." increment_id=$order.increment_id - order_status=$order.getStatusLabel() + order_status=$order.getFrontendStatusLabel() |raw}} </p> <p> diff --git a/app/code/Magento/Sales/view/frontend/email/shipment_update.html b/app/code/Magento/Sales/view/frontend/email/shipment_update.html index 6d9efc37004..9d1c9328754 100644 --- a/app/code/Magento/Sales/view/frontend/email/shipment_update.html +++ b/app/code/Magento/Sales/view/frontend/email/shipment_update.html @@ -10,7 +10,7 @@ "var order.getCustomerName()":"Customer Name", "var comment":"Order Comment", "var order.increment_id":"Order Id", -"var order.getStatusLabel()":"Order Status", +"var order.getFrontendStatusLabel()":"Order Status", "var shipment.increment_id":"Shipment Id" } @--> {{template config_path="design/email/header_template"}} @@ -24,7 +24,7 @@ "Your order #%increment_id has been updated with a status of <strong>%order_status</strong>." increment_id=$order.increment_id - order_status=$order.getStatusLabel() + order_status=$order.getFrontendStatusLabel() |raw}} </p> <p>{{trans 'You can check the status of your order by <a href="%account_url">logging into your account</a>.' account_url=$this.getUrl($store,'customer/account/',[_nosid:1]) |raw}}</p> diff --git a/app/code/Magento/Sales/view/frontend/email/shipment_update_guest.html b/app/code/Magento/Sales/view/frontend/email/shipment_update_guest.html index 4896a00b7bc..0d2dccd3377 100644 --- a/app/code/Magento/Sales/view/frontend/email/shipment_update_guest.html +++ b/app/code/Magento/Sales/view/frontend/email/shipment_update_guest.html @@ -9,7 +9,7 @@ "var billing.getName()":"Guest Customer Name", "var comment":"Order Comment", "var order.increment_id":"Order Id", -"var order.getStatusLabel()":"Order Status", +"var order.getFrontendStatusLabel()":"Order Status", "var shipment.increment_id":"Shipment Id" } @--> {{template config_path="design/email/header_template"}} @@ -23,7 +23,7 @@ "Your order #%increment_id has been updated with a status of <strong>%order_status</strong>." increment_id=$order.increment_id - order_status=$order.getStatusLabel() + order_status=$order.getFrontendStatusLabel() |raw}} </p> <p> diff --git a/app/code/Magento/Sales/view/frontend/templates/email/shipment/track.phtml b/app/code/Magento/Sales/view/frontend/templates/email/shipment/track.phtml index 9f7146ab084..f1cd5f2b998 100644 --- a/app/code/Magento/Sales/view/frontend/templates/email/shipment/track.phtml +++ b/app/code/Magento/Sales/view/frontend/templates/email/shipment/track.phtml @@ -9,22 +9,23 @@ ?> <?php $_shipment = $block->getShipment() ?> <?php $_order = $block->getOrder() ?> -<?php if ($_shipment && $_order && $_shipment->getAllTracks()): ?> -<br /> -<table class="shipment-track"> - <thead> +<?php $trackCollection = $_order->getTracksCollection($_shipment->getId()) ?> +<?php if ($_shipment && $_order && $trackCollection): ?> + <br /> + <table class="shipment-track"> + <thead> <tr> <th><?= /* @escapeNotVerified */ __('Shipped By') ?></th> <th><?= /* @escapeNotVerified */ __('Tracking Number') ?></th> </tr> - </thead> - <tbody> - <?php foreach ($_shipment->getAllTracks() as $_item): ?> - <tr> - <td><?= $block->escapeHtml($_item->getTitle()) ?>:</td> - <td><?= $block->escapeHtml($_item->getNumber()) ?></td> - </tr> - <?php endforeach ?> - </tbody> -</table> + </thead> + <tbody> + <?php foreach ($trackCollection as $_item): ?> + <tr> + <td><?= $block->escapeHtml($_item->getTitle()) ?>:</td> + <td><?= $block->escapeHtml($_item->getNumber()) ?></td> + </tr> + <?php endforeach ?> + </tbody> + </table> <?php endif; ?> diff --git a/app/code/Magento/Sales/view/frontend/templates/order/history.phtml b/app/code/Magento/Sales/view/frontend/templates/order/history.phtml index 1c02a5c31ea..b9a03221235 100644 --- a/app/code/Magento/Sales/view/frontend/templates/order/history.phtml +++ b/app/code/Magento/Sales/view/frontend/templates/order/history.phtml @@ -6,6 +6,8 @@ // @codingStandardsIgnoreFile +/** @var \Magento\Sales\Block\Order\History $block */ + ?> <?php $_orders = $block->getOrders(); ?> <?= $block->getChildHtml('info') ?> diff --git a/app/code/Magento/SalesAnalytics/README.md b/app/code/Magento/SalesAnalytics/README.md index 70f456c97d4..0b1f804b278 100644 --- a/app/code/Magento/SalesAnalytics/README.md +++ b/app/code/Magento/SalesAnalytics/README.md @@ -1,3 +1,3 @@ # Magento_SalesAnalytics module -The Magento_SalesAnalytics module configures data definitions for a data collection related to the Sales module entities to be used in [Advanced Reporting](http://devdocs.magento.com/guides/v2.2/advanced-reporting/modules.html). +The Magento_SalesAnalytics module configures data definitions for a data collection related to the Sales module entities to be used in [Advanced Reporting](https://devdocs.magento.com/guides/v2.2/advanced-reporting/modules.html). diff --git a/app/code/Magento/SalesRule/Api/CouponRepositoryInterface.php b/app/code/Magento/SalesRule/Api/CouponRepositoryInterface.php index d4cce37fd15..1a631886f1a 100644 --- a/app/code/Magento/SalesRule/Api/CouponRepositoryInterface.php +++ b/app/code/Magento/SalesRule/Api/CouponRepositoryInterface.php @@ -38,7 +38,7 @@ public function getById($couponId); * Retrieve a coupon using the specified search criteria. * * This call returns an array of objects, but detailed information about each object’s attributes might not be - * included. See http://devdocs.magento.com/codelinks/attributes.html#CouponRepositoryInterface to + * included. See https://devdocs.magento.com/codelinks/attributes.html#CouponRepositoryInterface to * determine which call to use to get detailed information about all attributes for an object. * * @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria diff --git a/app/code/Magento/SalesRule/Api/RuleRepositoryInterface.php b/app/code/Magento/SalesRule/Api/RuleRepositoryInterface.php index d0d275de63a..963edf5483e 100644 --- a/app/code/Magento/SalesRule/Api/RuleRepositoryInterface.php +++ b/app/code/Magento/SalesRule/Api/RuleRepositoryInterface.php @@ -38,7 +38,7 @@ public function getById($ruleId); * Retrieve sales rules that match te specified criteria. * * This call returns an array of objects, but detailed information about each object’s attributes might not be - * included. See http://devdocs.magento.com/codelinks/attributes.html#RuleRepositoryInterface to + * included. See https://devdocs.magento.com/codelinks/attributes.html#RuleRepositoryInterface to * determine which call to use to get detailed information about all attributes for an object. * * @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria diff --git a/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/Generate.php b/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/Generate.php index 12d34b8320d..da05fd98e60 100644 --- a/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/Generate.php +++ b/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/Generate.php @@ -1,15 +1,17 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\SalesRule\Controller\Adminhtml\Promo\Quote; -use Magento\Framework\App\ObjectManager; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\SalesRule\Model\CouponGenerator; -class Generate extends \Magento\SalesRule\Controller\Adminhtml\Promo\Quote +/** + * Generate promo quote + */ +class Generate extends \Magento\SalesRule\Controller\Adminhtml\Promo\Quote implements HttpPostActionInterface { /** * @var CouponGenerator diff --git a/app/code/Magento/SalesRule/Model/Coupon.php b/app/code/Magento/SalesRule/Model/Coupon.php index ee1eaff0830..3c96d08a38d 100644 --- a/app/code/Magento/SalesRule/Model/Coupon.php +++ b/app/code/Magento/SalesRule/Model/Coupon.php @@ -39,10 +39,10 @@ protected function _construct() /** * Set rule instance * - * @param \Magento\SalesRule\Model\Rule $rule + * @param Rule $rule * @return $this */ - public function setRule(\Magento\SalesRule\Model\Rule $rule) + public function setRule(Rule $rule) { $this->setRuleId($rule->getId()); return $this; @@ -51,7 +51,7 @@ public function setRule(\Magento\SalesRule\Model\Rule $rule) /** * Load primary coupon for specified rule * - * @param \Magento\SalesRule\Model\Rule|int $rule + * @param Rule|int $rule * @return $this */ public function loadPrimaryByRule($rule) @@ -190,6 +190,8 @@ public function getTimesUsed() } /** + * Set Times Used + * * @param int $timesUsed * @return $this */ @@ -273,6 +275,8 @@ public function getType() } /** + * Set type + * * @param int $type * @return $this */ diff --git a/app/code/Magento/SalesRule/Model/CouponRepository.php b/app/code/Magento/SalesRule/Model/CouponRepository.php index 99d6727a8be..8a77c3bb0f9 100644 --- a/app/code/Magento/SalesRule/Model/CouponRepository.php +++ b/app/code/Magento/SalesRule/Model/CouponRepository.php @@ -8,7 +8,6 @@ use Magento\Framework\Api\Search\FilterGroup; use Magento\Framework\Api\SearchCriteria\CollectionProcessorInterface; -use Magento\Framework\Api\SortOrder; use Magento\SalesRule\Model\ResourceModel\Coupon\Collection; /** @@ -197,13 +196,13 @@ public function deleteById($couponId) /** * Helper function that adds a FilterGroup to the collection. * - * @param \Magento\Framework\Api\Search\FilterGroup $filterGroup + * @param FilterGroup $filterGroup * @param Collection $collection * @deprecated 100.2.0 * @return void */ protected function addFilterGroupToCollection( - \Magento\Framework\Api\Search\FilterGroup $filterGroup, + FilterGroup $filterGroup, Collection $collection ) { $fields = []; diff --git a/app/code/Magento/SalesRule/Model/Data/Rule.php b/app/code/Magento/SalesRule/Model/Data/Rule.php index 72465b28503..58520831c01 100644 --- a/app/code/Magento/SalesRule/Model/Data/Rule.php +++ b/app/code/Magento/SalesRule/Model/Data/Rule.php @@ -16,8 +16,7 @@ * @SuppressWarnings(PHPMD.ExcessivePublicCount) * @codeCoverageIgnore */ -class Rule extends \Magento\Framework\Api\AbstractExtensibleObject implements - \Magento\SalesRule\Api\Data\RuleInterface +class Rule extends \Magento\Framework\Api\AbstractExtensibleObject implements RuleInterface { const KEY_RULE_ID = 'rule_id'; const KEY_NAME = 'name'; @@ -271,6 +270,8 @@ public function getIsAdvanced() } /** + * Set Is Advanced + * * @param bool $isAdvanced * @return $this */ @@ -374,6 +375,8 @@ public function getSortOrder() } /** + * Set Sort Order + * * @param int $sortOrder * @return $this */ @@ -617,7 +620,7 @@ public function setSimpleFreeShipping($simpleFreeShipping) } /** - * {@inheritdoc} + * @inheritdoc * * @return \Magento\SalesRule\Api\Data\RuleExtensionInterface|null */ @@ -627,7 +630,7 @@ public function getExtensionAttributes() } /** - * {@inheritdoc} + * @inheritdoc * * @param \Magento\SalesRule\Api\Data\RuleExtensionInterface $extensionAttributes * @return $this diff --git a/app/code/Magento/SalesRule/Model/Plugin/QuoteConfigProductAttributes.php b/app/code/Magento/SalesRule/Model/Plugin/QuoteConfigProductAttributes.php index 9705808c88a..b9d461037a2 100644 --- a/app/code/Magento/SalesRule/Model/Plugin/QuoteConfigProductAttributes.php +++ b/app/code/Magento/SalesRule/Model/Plugin/QuoteConfigProductAttributes.php @@ -5,10 +5,11 @@ */ namespace Magento\SalesRule\Model\Plugin; -use Magento\Store\Model\StoreManagerInterface; -use Magento\Customer\Model\Session; use Magento\SalesRule\Model\ResourceModel\Rule as RuleResource; +/** + * Quote Config Product Attributes Class + */ class QuoteConfigProductAttributes { /** diff --git a/app/code/Magento/SalesRule/Model/Quote/Address/Total/ShippingDiscount.php b/app/code/Magento/SalesRule/Model/Quote/Address/Total/ShippingDiscount.php new file mode 100644 index 00000000000..c37ca276e0e --- /dev/null +++ b/app/code/Magento/SalesRule/Model/Quote/Address/Total/ShippingDiscount.php @@ -0,0 +1,101 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\SalesRule\Model\Quote\Address\Total; + +use Magento\Quote\Api\Data\ShippingAssignmentInterface as ShippingAssignment; +use Magento\Quote\Model\Quote; +use Magento\Quote\Model\Quote\Address\Total; +use Magento\SalesRule\Model\Quote\Discount as DiscountCollector; +use Magento\SalesRule\Model\Validator; + +/** + * Total collector for shipping discounts. + */ +class ShippingDiscount extends \Magento\Quote\Model\Quote\Address\Total\AbstractTotal +{ + /** + * @var Validator + */ + private $calculator; + + /** + * @param Validator $calculator + */ + public function __construct(Validator $calculator) + { + $this->calculator = $calculator; + } + + /** + * @inheritdoc + * + * @param Quote $quote + * @param ShippingAssignment $shippingAssignment + * @param Total $total + * @return ShippingDiscount + */ + public function collect(Quote $quote, ShippingAssignment $shippingAssignment, Total $total): self + { + parent::collect($quote, $shippingAssignment, $total); + + $address = $shippingAssignment->getShipping()->getAddress(); + $this->calculator->reset($address); + + $items = $shippingAssignment->getItems(); + if (!count($items)) { + return $this; + } + + $address->setShippingDiscountAmount(0); + $address->setBaseShippingDiscountAmount(0); + if ($address->getShippingAmount()) { + $this->calculator->processShippingAmount($address); + $total->addTotalAmount(DiscountCollector::COLLECTOR_TYPE_CODE, -$address->getShippingDiscountAmount()); + $total->addBaseTotalAmount( + DiscountCollector::COLLECTOR_TYPE_CODE, + -$address->getBaseShippingDiscountAmount() + ); + $total->setShippingDiscountAmount($address->getShippingDiscountAmount()); + $total->setBaseShippingDiscountAmount($address->getBaseShippingDiscountAmount()); + + $this->calculator->prepareDescription($address); + $total->setDiscountDescription($address->getDiscountDescription()); + $total->setSubtotalWithDiscount($total->getSubtotal() + $total->getDiscountAmount()); + $total->setBaseSubtotalWithDiscount($total->getBaseSubtotal() + $total->getBaseDiscountAmount()); + + $address->setDiscountAmount($total->getDiscountAmount()); + $address->setBaseDiscountAmount($total->getBaseDiscountAmount()); + } + + return $this; + } + + /** + * @inheritdoc + * + * @param \Magento\Quote\Model\Quote $quote + * @param \Magento\Quote\Model\Quote\Address\Total $total + * @return array + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function fetch(Quote $quote, Total $total): array + { + $result = []; + $amount = $total->getDiscountAmount(); + + if ($amount != 0) { + $description = $total->getDiscountDescription() ?: ''; + $result = [ + 'code' => DiscountCollector::COLLECTOR_TYPE_CODE, + 'title' => strlen($description) ? __('Discount (%1)', $description) : __('Discount'), + 'value' => $amount + ]; + } + return $result; + } +} diff --git a/app/code/Magento/SalesRule/Model/Quote/Discount.php b/app/code/Magento/SalesRule/Model/Quote/Discount.php index 693a61b272f..315ce874513 100644 --- a/app/code/Magento/SalesRule/Model/Quote/Discount.php +++ b/app/code/Magento/SalesRule/Model/Quote/Discount.php @@ -5,8 +5,13 @@ */ namespace Magento\SalesRule\Model\Quote; +/** + * Discount totals calculation model. + */ class Discount extends \Magento\Quote\Model\Quote\Address\Total\AbstractTotal { + const COLLECTOR_TYPE_CODE = 'discount'; + /** * Discount calculation object * @@ -43,7 +48,7 @@ public function __construct( \Magento\SalesRule\Model\Validator $validator, \Magento\Framework\Pricing\PriceCurrencyInterface $priceCurrency ) { - $this->setCode('discount'); + $this->setCode(self::COLLECTOR_TYPE_CODE); $this->eventManager = $eventManager; $this->calculator = $validator; $this->storeManager = $storeManager; @@ -124,21 +129,14 @@ public function collect( } } - /** Process shipping amount discount */ - $address->setShippingDiscountAmount(0); - $address->setBaseShippingDiscountAmount(0); - if ($address->getShippingAmount()) { - $this->calculator->processShippingAmount($address); - $total->addTotalAmount($this->getCode(), -$address->getShippingDiscountAmount()); - $total->addBaseTotalAmount($this->getCode(), -$address->getBaseShippingDiscountAmount()); - $total->setShippingDiscountAmount($address->getShippingDiscountAmount()); - $total->setBaseShippingDiscountAmount($address->getBaseShippingDiscountAmount()); - } - $this->calculator->prepareDescription($address); $total->setDiscountDescription($address->getDiscountDescription()); $total->setSubtotalWithDiscount($total->getSubtotal() + $total->getDiscountAmount()); $total->setBaseSubtotalWithDiscount($total->getBaseSubtotal() + $total->getBaseDiscountAmount()); + + $address->setDiscountAmount($total->getDiscountAmount()); + $address->setBaseDiscountAmount($total->getBaseDiscountAmount()); + return $this; } diff --git a/app/code/Magento/SalesRule/Model/ResourceModel/ReadHandler.php b/app/code/Magento/SalesRule/Model/ResourceModel/ReadHandler.php index 193a9491081..22d8b844653 100644 --- a/app/code/Magento/SalesRule/Model/ResourceModel/ReadHandler.php +++ b/app/code/Magento/SalesRule/Model/ResourceModel/ReadHandler.php @@ -5,7 +5,6 @@ */ namespace Magento\SalesRule\Model\ResourceModel; -use Magento\SalesRule\Model\ResourceModel\Rule; use Magento\Framework\EntityManager\MetadataPool; use Magento\Framework\EntityManager\Operation\AttributeInterface; @@ -37,11 +36,14 @@ public function __construct( } /** + * Read handler + * * @param string $entityType * @param array $entityData * @param array $arguments * @return array * @throws \Exception + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function execute($entityType, $entityData, $arguments = []) { diff --git a/app/code/Magento/SalesRule/Model/ResourceModel/Rule/Collection.php b/app/code/Magento/SalesRule/Model/ResourceModel/Rule/Collection.php index 59f24fa8b6e..5e6f3847c8e 100644 --- a/app/code/Magento/SalesRule/Model/ResourceModel/Rule/Collection.php +++ b/app/code/Magento/SalesRule/Model/ResourceModel/Rule/Collection.php @@ -80,6 +80,8 @@ protected function _construct() } /** + * Map data for associated entities + * * @param string $entityType * @param string $objectField * @throws \Magento\Framework\Exception\LocalizedException @@ -114,6 +116,8 @@ protected function mapAssociatedEntities($entityType, $objectField) } /** + * Add website ids and customer group ids to rules data + * * @return $this * @throws \Exception * @since 100.1.0 @@ -158,60 +162,15 @@ public function setValidationFilter( $connection = $this->getConnection(); if (strlen($couponCode)) { - $select->joinLeft( - ['rule_coupons' => $this->getTable('salesrule_coupon')], - $connection->quoteInto( - 'main_table.rule_id = rule_coupons.rule_id AND main_table.coupon_type != ?', - \Magento\SalesRule\Model\Rule::COUPON_TYPE_NO_COUPON - ), - ['code'] - ); - $noCouponWhereCondition = $connection->quoteInto( - 'main_table.coupon_type = ? ', + 'main_table.coupon_type = ?', \Magento\SalesRule\Model\Rule::COUPON_TYPE_NO_COUPON ); - - $autoGeneratedCouponCondition = [ - $connection->quoteInto( - "main_table.coupon_type = ?", - \Magento\SalesRule\Model\Rule::COUPON_TYPE_AUTO - ), - $connection->quoteInto( - "rule_coupons.type = ?", - \Magento\SalesRule\Api\Data\CouponInterface::TYPE_GENERATED - ), - ]; - - $orWhereConditions = [ - "(" . implode($autoGeneratedCouponCondition, " AND ") . ")", - $connection->quoteInto( - '(main_table.coupon_type = ? AND main_table.use_auto_generation = 1 AND rule_coupons.type = 1)', - \Magento\SalesRule\Model\Rule::COUPON_TYPE_SPECIFIC - ), - $connection->quoteInto( - '(main_table.coupon_type = ? AND main_table.use_auto_generation = 0 AND rule_coupons.type = 0)', - \Magento\SalesRule\Model\Rule::COUPON_TYPE_SPECIFIC - ), - ]; - - $andWhereConditions = [ - $connection->quoteInto( - 'rule_coupons.code = ?', - $couponCode - ), - $connection->quoteInto( - '(rule_coupons.expiration_date IS NULL OR rule_coupons.expiration_date >= ?)', - $this->_date->date()->format('Y-m-d') - ), - ]; - - $orWhereCondition = implode(' OR ', $orWhereConditions); - $andWhereCondition = implode(' AND ', $andWhereConditions); + $relatedRulesIds = $this->getCouponRelatedRuleIds($couponCode); $select->where( - $noCouponWhereCondition . ' OR ((' . $orWhereCondition . ') AND ' . $andWhereCondition . ')', - null, + $noCouponWhereCondition . ' OR main_table.rule_id IN (?)', + $relatedRulesIds, Select::TYPE_CONDITION ); } else { @@ -227,6 +186,75 @@ public function setValidationFilter( return $this; } + /** + * Get rules ids related to coupon code + * + * @param string $couponCode + * @return array + */ + private function getCouponRelatedRuleIds(string $couponCode): array + { + $connection = $this->getConnection(); + $select = $connection->select()->from( + ['main_table' => $this->getTable('salesrule')], + 'rule_id' + ); + $select->joinLeft( + ['rule_coupons' => $this->getTable('salesrule_coupon')], + $connection->quoteInto( + 'main_table.rule_id = rule_coupons.rule_id AND main_table.coupon_type != ?', + \Magento\SalesRule\Model\Rule::COUPON_TYPE_NO_COUPON, + null + ) + ); + + $autoGeneratedCouponCondition = [ + $connection->quoteInto( + "main_table.coupon_type = ?", + \Magento\SalesRule\Model\Rule::COUPON_TYPE_AUTO + ), + $connection->quoteInto( + "rule_coupons.type = ?", + \Magento\SalesRule\Api\Data\CouponInterface::TYPE_GENERATED + ), + ]; + + $orWhereConditions = [ + "(" . implode($autoGeneratedCouponCondition, " AND ") . ")", + $connection->quoteInto( + '(main_table.coupon_type = ? AND main_table.use_auto_generation = 1 AND rule_coupons.type = 1)', + \Magento\SalesRule\Model\Rule::COUPON_TYPE_SPECIFIC + ), + $connection->quoteInto( + '(main_table.coupon_type = ? AND main_table.use_auto_generation = 0 AND rule_coupons.type = 0)', + \Magento\SalesRule\Model\Rule::COUPON_TYPE_SPECIFIC + ), + ]; + + $andWhereConditions = [ + $connection->quoteInto( + 'rule_coupons.code = ?', + $couponCode + ), + $connection->quoteInto( + '(rule_coupons.expiration_date IS NULL OR rule_coupons.expiration_date >= ?)', + $this->_date->date()->format('Y-m-d') + ), + ]; + + $orWhereCondition = implode(' OR ', $orWhereConditions); + $andWhereCondition = implode(' AND ', $andWhereConditions); + + $select->where( + '(' . $orWhereCondition . ') AND ' . $andWhereCondition, + null, + Select::TYPE_CONDITION + ); + $select->group('main_table.rule_id'); + + return $connection->fetchCol($select); + } + /** * Filter collection by website(s), customer group(s) and date. * Filter collection to only active rules. @@ -366,6 +394,8 @@ public function addCustomerGroupFilter($customerGroupId) } /** + * Getter for _associatedEntitiesMap property + * * @return array * @deprecated 100.1.0 */ @@ -380,6 +410,8 @@ private function getAssociatedEntitiesMap() } /** + * Getter for dateApplier property + * * @return DateApplier * @deprecated 100.1.0 */ diff --git a/app/code/Magento/SalesRule/Model/ResourceModel/SaveHandler.php b/app/code/Magento/SalesRule/Model/ResourceModel/SaveHandler.php index 685de1b581f..d4ef15f1801 100644 --- a/app/code/Magento/SalesRule/Model/ResourceModel/SaveHandler.php +++ b/app/code/Magento/SalesRule/Model/ResourceModel/SaveHandler.php @@ -5,7 +5,6 @@ */ namespace Magento\SalesRule\Model\ResourceModel; -use Magento\SalesRule\Model\ResourceModel\Rule; use Magento\Framework\EntityManager\MetadataPool; use Magento\Framework\EntityManager\Operation\AttributeInterface; @@ -37,11 +36,14 @@ public function __construct( } /** + * Save handler + * * @param string $entityType * @param array $entityData * @param array $arguments * @return array * @throws \Exception + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function execute($entityType, $entityData, $arguments = []) { diff --git a/app/code/Magento/SalesRule/Model/Rule/Action/Discount/CartFixed.php b/app/code/Magento/SalesRule/Model/Rule/Action/Discount/CartFixed.php index 3cd776fe99f..2ae1c1c7ac6 100644 --- a/app/code/Magento/SalesRule/Model/Rule/Action/Discount/CartFixed.php +++ b/app/code/Magento/SalesRule/Model/Rule/Action/Discount/CartFixed.php @@ -5,7 +5,6 @@ */ namespace Magento\SalesRule\Model\Rule\Action\Discount; -use Magento\Framework\App\ObjectManager; use Magento\Framework\Pricing\PriceCurrencyInterface; use Magento\SalesRule\Model\DeltaPriceRound; use Magento\SalesRule\Model\Validator; @@ -50,6 +49,8 @@ public function __construct( } /** + * Fixed discount for cart calculation + * * @param \Magento\SalesRule\Model\Rule $rule * @param \Magento\Quote\Model\Quote\Item\AbstractItem $item * @param float $qty diff --git a/app/code/Magento/SalesRule/Model/Rule/Condition/Address.php b/app/code/Magento/SalesRule/Model/Rule/Condition/Address.php index fd5953697c7..89ec2b84572 100644 --- a/app/code/Magento/SalesRule/Model/Rule/Condition/Address.php +++ b/app/code/Magento/SalesRule/Model/Rule/Condition/Address.php @@ -5,6 +5,9 @@ */ namespace Magento\SalesRule\Model\Rule\Condition; +/** + * Address rule condition data model. + */ class Address extends \Magento\Rule\Model\Condition\AbstractCondition { /** @@ -61,6 +64,7 @@ public function loadAttributeOptions() 'base_subtotal' => __('Subtotal'), 'total_qty' => __('Total Items Quantity'), 'weight' => __('Total Weight'), + 'payment_method' => __('Payment Method'), 'shipping_method' => __('Shipping Method'), 'postcode' => __('Shipping Postcode'), 'region' => __('Shipping Region'), diff --git a/app/code/Magento/SalesRule/Model/Rule/Condition/Product.php b/app/code/Magento/SalesRule/Model/Rule/Condition/Product.php index 9bda4793e86..ff83bb1ee91 100644 --- a/app/code/Magento/SalesRule/Model/Rule/Condition/Product.php +++ b/app/code/Magento/SalesRule/Model/Rule/Condition/Product.php @@ -35,12 +35,13 @@ protected function _addSpecialAttributes(array &$attributes) * * @return string */ - public function getAttribute() + public function getAttribute(): string { $attribute = $this->getData('attribute'); if (strpos($attribute, '::') !== false) { - list (, $attribute) = explode('::', $attribute); + list(, $attribute) = explode('::', $attribute); } + return $attribute; } @@ -53,6 +54,7 @@ public function getAttributeName() if ($this->getAttributeScope()) { $attribute = $this->getAttributeScope() . '::' . $attribute; } + return $this->getAttributeOption($attribute); } @@ -92,6 +94,7 @@ public function getAttributeElementHtml() { $html = parent::getAttributeElementHtml() . $this->getAttributeScopeElement()->getHtml(); + return $html; } @@ -100,7 +103,7 @@ public function getAttributeElementHtml() * * @return \Magento\Framework\Data\Form\Element\AbstractElement */ - private function getAttributeScopeElement() + private function getAttributeScopeElement(): \Magento\Framework\Data\Form\Element\AbstractElement { return $this->getForm()->addField( $this->getPrefix() . '__' . $this->getId() . '__attribute_scope', @@ -110,7 +113,7 @@ private function getAttributeScopeElement() 'value' => $this->getAttributeScope(), 'no_span' => true, 'class' => 'hidden', - 'data-form-part' => $this->getFormName() + 'data-form-part' => $this->getFormName(), ] ); } @@ -119,8 +122,9 @@ private function getAttributeScopeElement() * Set attribute value * * @param string $value + * @return void */ - public function setAttribute($value) + public function setAttribute(string $value) { if (strpos($value, '::') !== false) { list($scope, $attribute) = explode('::', $value); @@ -137,7 +141,8 @@ public function setAttribute($value) public function loadArray($arr) { parent::loadArray($arr); - $this->setAttributeScope(isset($arr['attribute_scope']) ? $arr['attribute_scope'] : null); + $this->setAttributeScope($arr['attribute_scope'] ?? null); + return $this; } @@ -148,6 +153,7 @@ public function asArray(array $arrAttributes = []) { $out = parent::asArray($arrAttributes); $out['attribute_scope'] = $this->getAttributeScope(); + return $out; } @@ -155,7 +161,9 @@ public function asArray(array $arrAttributes = []) * Validate Product Rule Condition * * @param \Magento\Framework\Model\AbstractModel $model + * * @return bool + * @throws \Magento\Framework\Exception\NoSuchEntityException */ public function validate(\Magento\Framework\Model\AbstractModel $model) { diff --git a/app/code/Magento/SalesRule/Model/Rule/Condition/Product/Subselect.php b/app/code/Magento/SalesRule/Model/Rule/Condition/Product/Subselect.php index 1e8fbf43ec3..5b02d3c0809 100644 --- a/app/code/Magento/SalesRule/Model/Rule/Condition/Product/Subselect.php +++ b/app/code/Magento/SalesRule/Model/Rule/Condition/Product/Subselect.php @@ -5,6 +5,9 @@ */ namespace Magento\SalesRule\Model\Rule\Condition\Product; +/** + * Subselect conditions for product. + */ class Subselect extends \Magento\SalesRule\Model\Rule\Condition\Product\Combine { /** @@ -161,7 +164,9 @@ public function validate(\Magento\Framework\Model\AbstractModel $model) } } if ($hasValidChild || parent::validate($item)) { - $total += (($hasValidChild && $useChildrenTotal) ? $childrenAttrTotal : $item->getData($attr)); + $total += ($hasValidChild && $useChildrenTotal) + ? $childrenAttrTotal * $item->getQty() + : $item->getData($attr); } } return $this->validateAttribute($total); diff --git a/app/code/Magento/SalesRule/Model/Rule/Metadata/ValueProvider.php b/app/code/Magento/SalesRule/Model/Rule/Metadata/ValueProvider.php index b57daac1f0d..fdd6c2b169a 100644 --- a/app/code/Magento/SalesRule/Model/Rule/Metadata/ValueProvider.php +++ b/app/code/Magento/SalesRule/Model/Rule/Metadata/ValueProvider.php @@ -5,7 +5,6 @@ */ namespace Magento\SalesRule\Model\Rule\Metadata; -use Magento\SalesRule\Model\ResourceModel\Rule\Collection; use Magento\SalesRule\Model\Rule; use Magento\Store\Model\System\Store; use Magento\Customer\Api\GroupRepositoryInterface; diff --git a/app/code/Magento/SalesRule/Model/RuleRepository.php b/app/code/Magento/SalesRule/Model/RuleRepository.php index a2279db85aa..2cff0d64dba 100644 --- a/app/code/Magento/SalesRule/Model/RuleRepository.php +++ b/app/code/Magento/SalesRule/Model/RuleRepository.php @@ -8,7 +8,6 @@ use Magento\Framework\Api\Search\FilterGroup; use Magento\Framework\Api\SearchCriteria\CollectionProcessorInterface; use Magento\Framework\Api\SearchCriteriaInterface; -use Magento\Framework\Api\SortOrder; use Magento\SalesRule\Model\ResourceModel\Rule\Collection; /** @@ -107,7 +106,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function save(\Magento\SalesRule\Api\Data\RuleInterface $rule) { @@ -119,7 +118,7 @@ public function save(\Magento\SalesRule\Api\Data\RuleInterface $rule) } /** - * {@inheritdoc} + * @inheritdoc */ public function getById($id) { @@ -136,7 +135,7 @@ public function getById($id) } /** - * {@inheritdoc} + * @inheritdoc */ public function getList(SearchCriteriaInterface $searchCriteria) { @@ -183,13 +182,13 @@ public function deleteById($id) /** * Helper function that adds a FilterGroup to the collection. * - * @param \Magento\Framework\Api\Search\FilterGroup $filterGroup + * @param FilterGroup $filterGroup * @param Collection $collection * @deprecated 100.2.0 * @return void */ protected function addFilterGroupToCollection( - \Magento\Framework\Api\Search\FilterGroup $filterGroup, + FilterGroup $filterGroup, Collection $collection ) { $fields = []; diff --git a/app/code/Magento/SalesRule/Setup/Patch/Data/ConvertSerializedDataToJson.php b/app/code/Magento/SalesRule/Setup/Patch/Data/ConvertSerializedDataToJson.php index 8a8c51e9d34..e863f2af335 100644 --- a/app/code/Magento/SalesRule/Setup/Patch/Data/ConvertSerializedDataToJson.php +++ b/app/code/Magento/SalesRule/Setup/Patch/Data/ConvertSerializedDataToJson.php @@ -6,12 +6,12 @@ namespace Magento\SalesRule\Setup\Patch\Data; -use Magento\Framework\App\ResourceConnection; use Magento\Framework\Setup\Patch\DataPatchInterface; use Magento\Framework\Setup\Patch\PatchVersionInterface; /** * Class ConvertSerializedDataToJson + * * @package Magento\SalesRule\Setup\Patch */ class ConvertSerializedDataToJson implements DataPatchInterface, PatchVersionInterface @@ -59,7 +59,7 @@ public function apply() } /** - * {@inheritdoc} + * @inheritdoc */ public static function getDependencies() { @@ -69,7 +69,7 @@ public static function getDependencies() } /** - * {@inheritdoc} + * @inheritdoc */ public static function getVersion() { @@ -77,7 +77,7 @@ public static function getVersion() } /** - * {@inheritdoc} + * @inheritdoc */ public function getAliases() { diff --git a/app/code/Magento/SalesRule/Setup/Patch/Data/FillSalesRuleProductAttributeTable.php b/app/code/Magento/SalesRule/Setup/Patch/Data/FillSalesRuleProductAttributeTable.php index 625d5769fdd..22cf4fe6b10 100644 --- a/app/code/Magento/SalesRule/Setup/Patch/Data/FillSalesRuleProductAttributeTable.php +++ b/app/code/Magento/SalesRule/Setup/Patch/Data/FillSalesRuleProductAttributeTable.php @@ -6,13 +6,13 @@ namespace Magento\SalesRule\Setup\Patch\Data; -use Magento\Framework\App\ResourceConnection; use Magento\Framework\App\State; use Magento\Framework\Setup\Patch\DataPatchInterface; use Magento\Framework\Setup\Patch\PatchVersionInterface; /** * Class FillSalesRuleProductAttributeTable + * * @package Magento\SalesRule\Setup\Patch */ class FillSalesRuleProductAttributeTable implements DataPatchInterface, PatchVersionInterface @@ -65,7 +65,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function apply() { @@ -102,7 +102,7 @@ public function fillSalesRuleProductAttributeTable() } /** - * {@inheritdoc} + * @inheritdoc */ public static function getDependencies() { @@ -112,7 +112,7 @@ public static function getDependencies() } /** - * {@inheritdoc} + * @inheritdoc */ public static function getVersion() { @@ -120,7 +120,7 @@ public static function getVersion() } /** - * {@inheritdoc} + * @inheritdoc */ public function getAliases() { diff --git a/app/code/Magento/SalesRule/Setup/Patch/Data/PrepareRuleModelSerializedData.php b/app/code/Magento/SalesRule/Setup/Patch/Data/PrepareRuleModelSerializedData.php index 2387f5f1ed7..cad83b6f5d1 100644 --- a/app/code/Magento/SalesRule/Setup/Patch/Data/PrepareRuleModelSerializedData.php +++ b/app/code/Magento/SalesRule/Setup/Patch/Data/PrepareRuleModelSerializedData.php @@ -7,33 +7,33 @@ namespace Magento\SalesRule\Setup\Patch\Data; use Magento\Framework\Setup\ModuleDataSetupInterface; -use Magento\Framework\App\ResourceConnection; use Magento\Framework\Setup\Patch\DataPatchInterface; use Magento\Framework\Setup\Patch\PatchVersionInterface; /** * Class PrepareRuleModelSerializedData + * * @package Magento\SalesRule\Setup\Patch */ class PrepareRuleModelSerializedData implements DataPatchInterface, PatchVersionInterface { /** - * @var \Magento\Framework\Setup\ModuleDataSetupInterface + * @var ModuleDataSetupInterface */ private $moduleDataSetup; /** * PatchInitial constructor. - * @param \Magento\Framework\Setup\ModuleDataSetupInterface $moduleDataSetup + * @param ModuleDataSetupInterface $moduleDataSetup */ public function __construct( - \Magento\Framework\Setup\ModuleDataSetupInterface $moduleDataSetup + ModuleDataSetupInterface $moduleDataSetup ) { $this->moduleDataSetup = $moduleDataSetup; } /** - * {@inheritdoc} + * @inheritdoc */ public function apply() { @@ -59,7 +59,7 @@ public function apply() } /** - * {@inheritdoc} + * @inheritdoc */ public static function getDependencies() { @@ -67,7 +67,7 @@ public static function getDependencies() } /** - * {@inheritdoc} + * @inheritdoc */ public static function getVersion() { @@ -75,7 +75,7 @@ public static function getVersion() } /** - * {@inheritdoc} + * @inheritdoc */ public function getAliases() { diff --git a/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminCartPriceRuleActionGroup.xml b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminCartPriceRuleActionGroup.xml index a794c67e736..e5907e1e9c0 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminCartPriceRuleActionGroup.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminCartPriceRuleActionGroup.xml @@ -16,4 +16,26 @@ <!-- This actionGroup was created to be merged from B2B. Retailer Customer Group --> <selectOption selector="{{AdminCartPriceRulesFormSection.customerGroups}}" userInput="Retailer" stepKey="selectRetailerCustomerGroup"/> </actionGroup> + + <!--Set Subtotal condition for Customer Segment--> + <actionGroup name="SetCartAttributeConditionForCartPriceRuleActionGroup"> + <arguments> + <argument name="attributeName" type="string"/> + <argument name="operatorType" defaultValue="is" type="string"/> + <argument name="value" type="string"/> + </arguments> + <scrollTo selector="{{AdminCartPriceRulesFormSection.conditionsHeader}}" stepKey="scrollToActionTab"/> + <conditionalClick selector="{{AdminCartPriceRulesFormSection.conditionsHeader}}" dependentSelector="{{AdminCartPriceRulesFormSection.conditionsHeaderOpen}}" + visible="false" stepKey="openActionTab"/> + <click selector="{{AdminCartPriceRulesFormSection.conditions}}" stepKey="applyRuleForConditions"/> + <waitForPageLoad stepKey="waitForDropDownOpened"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.childAttribute}}" userInput="{{attributeName}}" stepKey="selectAttribute"/> + <waitForPageLoad stepKey="waitForOperatorOpened"/> + <click selector="{{AdminCartPriceRulesFormSection.condition('is')}}" stepKey="clickToChooseOption"/> + <selectOption userInput="{{operatorType}}" selector="{{AdminCartPriceRulesFormSection.conditionsOperator}}" stepKey="setOperatorType"/> + <click selector="{{AdminCartPriceRulesFormSection.condition('...')}}" stepKey="clickToChooseOption1"/> + <fillField userInput="{{value}}" selector="{{AdminCartPriceRulesFormSection.conditionsValue}}" stepKey="fillActionValue"/> + <click selector="{{AdminMainActionsSection.saveAndContinue}}" stepKey="clickSaveButton"/> + <see selector="{{AdminCartPriceRulesSection.messages}}" userInput="You saved the rule." stepKey="seeSuccessMessage"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminCreateCartPriceRuleActionGroup.xml b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminCreateCartPriceRuleActionGroup.xml index 224977e767c..cc165e0b5dc 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminCreateCartPriceRuleActionGroup.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminCreateCartPriceRuleActionGroup.xml @@ -1,4 +1,4 @@ -<?xml version="1.0" encoding="UTF-8"?> +<?xml version="1.0" encoding="UTF-8"?> <!-- /** * Copyright © Magento, Inc. All rights reserved. @@ -48,4 +48,47 @@ <click selector="{{AdminCartPriceRulesFormSection.delete}}" stepKey="clickDeleteButton"/> <click selector="{{AdminCartPriceRulesFormSection.modalAcceptButton}}" stepKey="confirmDelete"/> </actionGroup> + + <actionGroup name="AdminCreateCartPriceRuleWithConditions" extends="AdminCreateCartPriceRuleActionGroup"> + <arguments> + <argument name="condition1" type="string" defaultValue="Products subselection" /> + <argument name="condition2" type="string" defaultValue="Category" /> + <argument name="ruleToChange1" type="string" defaultValue="is" /> + <argument name="rule1" type="string" defaultValue="equals or greater than" /> + <argument name="ruleToChange2" type="string" defaultValue="..." /> + <argument name="rule2" type="string" defaultValue="2" /> + <argument name="categoryName" type="string" defaultValue="_defaultCategory.name" /> + </arguments> + <remove keyForRemoval="fillDiscountAmount" /> + <!--Go to Conditions section--> + <click selector="{{AdminCartPriceRulesFormSection.conditionsHeader}}" stepKey="openConditionsSection" after="selectActionType" /> + <click selector="{{AdminCartPriceRulesFormSection.addCondition('1')}}" stepKey="addFirstCondition" after="openConditionsSection" /> + <selectOption selector="{{AdminCartPriceRulesFormSection.ruleCondition('1')}}" userInput="{{condition1}}" stepKey="selectRule" after="addFirstCondition" /> + <waitForElementVisible selector="{{AdminCartPriceRulesFormSection.ruleParameter(ruleToChange1)}}" stepKey="waitForFirstRuleElement" after="selectRule" /> + <click selector="{{AdminCartPriceRulesFormSection.ruleParameter(ruleToChange1)}}" stepKey="clickToChangeRule" after="waitForFirstRuleElement" /> + <selectOption selector="{{AdminCartPriceRulesFormSection.ruleParameterSelect('1--1')}}" userInput="{{rule1}}" stepKey="selectRule1" after="clickToChangeRule" /> + <waitForElementVisible selector="{{AdminCartPriceRulesFormSection.ruleParameter(ruleToChange2)}}" stepKey="waitForSecondRuleElement" after="selectRule1" /> + <click selector="{{AdminCartPriceRulesFormSection.ruleParameter(ruleToChange2)}}" stepKey="clickToChangeRule1" after="waitForSecondRuleElement" /> + <fillField selector="{{AdminCartPriceRulesFormSection.ruleParameterInput('1--1')}}" userInput="{{rule2}}" stepKey="fillRule" after="clickToChangeRule1" /> + <click selector="{{AdminCartPriceRulesFormSection.addCondition('1--1')}}" stepKey="addSecondCondition" after="fillRule" /> + <selectOption selector="{{AdminCartPriceRulesFormSection.ruleCondition('1--1')}}" userInput="{{condition2}}" stepKey="selectSecondCondition" after="addSecondCondition" /> + <waitForElementVisible selector="{{AdminCartPriceRulesFormSection.ruleParameter(ruleToChange2)}}" stepKey="waitForThirdRuleElement" after="selectSecondCondition" /> + <click selector="{{AdminCartPriceRulesFormSection.ruleParameter(ruleToChange2)}}" stepKey="addThirdCondition" after="waitForThirdRuleElement" /> + <waitForElementVisible selector="{{AdminCartPriceRulesFormSection.openChooser('1--1--1')}}" stepKey="waitForForthRuleElement" after="addThirdCondition" /> + <click selector="{{AdminCartPriceRulesFormSection.openChooser('1--1--1')}}" stepKey="openChooser" after="waitForForthRuleElement" /> + <waitForElementVisible selector="{{AdminCartPriceRulesFormSection.categoryCheckbox(categoryName)}}" stepKey="waitForCategoryVisible" after="openChooser" /> + <checkOption selector="{{AdminCartPriceRulesFormSection.categoryCheckbox(categoryName)}}" stepKey="checkCategoryName" after="waitForCategoryVisible" /> + </actionGroup> + + <actionGroup name="CreateCartPriceRuleSecondWebsiteActionGroup"> + <arguments> + <argument name="ruleName"/> + </arguments> + <amOnPage url="{{AdminCartPriceRulesPage.url}}" stepKey="amOnCartPriceList"/> + <waitForPageLoad stepKey="waitForPriceList"/> + <click selector="{{AdminCartPriceRulesSection.addNewRuleButton}}" stepKey="clickAddNewRule"/> + <fillField selector="{{AdminCartPriceRulesFormSection.ruleName}}" userInput="{{ruleName.name}}" stepKey="fillRuleName"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.websites}}" userInput="Second Website" stepKey="selectWebsites"/> + + </actionGroup> </actionGroups> diff --git a/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/StorefrontSalesRuleActionGroup.xml b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/StorefrontSalesRuleActionGroup.xml index a196bbd61d0..3e55eb4f266 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/StorefrontSalesRuleActionGroup.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/StorefrontSalesRuleActionGroup.xml @@ -32,8 +32,8 @@ <!-- Check applied discount in cart summary --> <actionGroup name="StorefrontCheckCouponAppliedActionGroup"> <arguments> - <argument name="rule"/> - <argument name="discount" type="string"/> + <argument name="rule" /> + <argument name="discount" type="string" /> </arguments> <waitForElementVisible selector="{{CheckoutCartSummarySection.discountTotal}}" stepKey="waitForDiscountTotal"/> <see userInput="{{rule.store_labels[1][store_label]}}" selector="{{CheckoutCartSummarySection.discountLabel}}" stepKey="assertDiscountLabel"/> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleAddressConditionsData.xml b/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleAddressConditionsData.xml new file mode 100644 index 00000000000..cc695b347c4 --- /dev/null +++ b/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleAddressConditionsData.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="SalesRuleAddressConditions" type="SalesRuleConditionAttribute"> + <data key="subtotal">Magento\SalesRule\Model\Rule\Condition\Address|base_subtotal</data> + <data key="totalItemsQty">Magento\SalesRule\Model\Rule\Condition\Address|total_qty</data> + <data key="totalWeight">Magento\SalesRule\Model\Rule\Condition\Address|weight</data> + <data key="shippingMethod">Magento\SalesRule\Model\Rule\Condition\Address|shipping_method</data> + <data key="shippingPostCode">Magento\SalesRule\Model\Rule\Condition\Address|postcode</data> + <data key="shippingRegion">Magento\SalesRule\Model\Rule\Condition\Address|region</data> + <data key="shippingState">Magento\SalesRule\Model\Rule\Condition\Address|region_id</data> + <data key="shippingCountry">Magento\SalesRule\Model\Rule\Condition\Address|country_id</data> + </entity> +</entities> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleData.xml b/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleData.xml index 3b2e9da9e79..521734ab9f2 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleData.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleData.xml @@ -89,6 +89,16 @@ <data key="apply">Percent of product price discount</data> <data key="discountAmount">50</data> </entity> + <entity name="CatPriceRule" type="SalesRule"> + <data key="name" unique="suffix">CartPriceRule</data> + <data key="websites">Main Website</data> + <data key="customerGroups">'NOT LOGGED IN', 'General', 'Wholesale', 'Retailer'</data> + <data key="coupon_type">Specific Coupon</data> + <data key="coupon_code" unique="suffix">CouponCode</data> + <data key="apply">Percent of product price discount</data> + <data key="discountAmount">10</data> + </entity> + <entity name="SalesRuleSpecificCoupon" type="SalesRule"> <data key="name" unique="suffix">SimpleSalesRule</data> <data key="description">Sales Rule Description</data> @@ -144,4 +154,39 @@ <data key="uses_per_coupon">10</data> <data key="simple_free_shipping">1</data> </entity> + + <entity name="PriceRuleWithCondition" type="SalesRule"> + <data key="name" unique="suffix">SalesRule</data> + <data key="websites">Main Website</data> + <data key="customerGroups">'NOT LOGGED IN', 'General', 'Wholesale', 'Retailer'</data> + <data key="apply">Fixed amount discount for whole cart</data> + <data key="discountAmount">50</data> + </entity> + + <entity name="SalesRuleSpecificCouponWithPercentDiscount" type="SalesRule"> + <data key="name" unique="suffix">SimpleSalesRule</data> + <data key="description">Sales Rule Description</data> + <array key="website_ids"> + <item>1</item> + </array> + <array key="customer_group_ids"> + <item>1</item> + </array> + <data key="uses_per_customer">10</data> + <data key="is_active">true</data> + <data key="stop_rules_processing">false</data> + <data key="is_advanced">true</data> + <data key="sort_order">1</data> + <data key="simple_action">by_percent</data> + <data key="discount_amount">10</data> + <data key="discount_qty">10</data> + <data key="discount_step">0</data> + <data key="apply_to_shipping">false</data> + <data key="times_used">0</data> + <data key="is_rss">false</data> + <data key="coupon_type">SPECIFIC_COUPON</data> + <data key="use_auto_generation">false</data> + <data key="uses_per_coupon">10</data> + <data key="simple_free_shipping">1</data> + </entity> </entities> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Section/AdminCartPriceRulesFormSection.xml b/app/code/Magento/SalesRule/Test/Mftf/Section/AdminCartPriceRulesFormSection.xml index 070532500af..7628ecf4688 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Section/AdminCartPriceRulesFormSection.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Section/AdminCartPriceRulesFormSection.xml @@ -27,6 +27,19 @@ <element name="userPerCustomer" type="input" selector="//input[@name='uses_per_customer']"/> <element name="priority" type="input" selector="//*[@name='sort_order']"/> + <!-- Conditions sub-form --> + <element name="conditionsHeader" type="button" selector="div[data-index='conditions']" timeout="30"/> + <element name="conditionsHeaderOpen" type="button" selector="div[data-index='conditions'] div[data-state-collapsible='open']" timeout="30"/> + <element name="conditionsValue" type="input" selector=".rule-param-edit input"/> + <element name="conditionsOperator" type="select" selector=".rule-param-edit select"/> + <element name="addCondition" type="button" selector="//*[@id='conditions__{{arg}}__children']//span" parameterized="true"/> + <element name="ruleCondition" type="select" selector="rule[conditions][{{arg}}][new_child]" parameterized="true"/> + <element name="ruleParameter" type="text" selector="//span[@class='rule-param']/a[contains(text(), '{{arg}}')]" parameterized="true"/> + <element name="ruleParameterSelect" type="select" selector="rule[conditions][{{arg}}][operator]" parameterized="true"/> + <element name="ruleParameterInput" type="input" selector="rule[conditions][{{arg}}][value]" parameterized="true"/> + <element name="openChooser" type="button" selector="//label[@for='conditions__{{arg}}__value']" parameterized="true"/> + <element name="categoryCheckbox" type="checkbox" selector="//span[contains(text(), '{{arg}}')]/parent::a/preceding-sibling::input[@type='checkbox']" parameterized="true"/> + <!-- Actions sub-form --> <element name="actionsTab" type="text" selector="//div[@data-index='actions']//span[contains(.,'Actions')][1]"/> <element name="actionsHeader" type="button" selector="div[data-index='actions']" timeout="30"/> @@ -43,6 +56,7 @@ <element name="applyDiscountToShippingLabel" type="checkbox" selector="input[name='apply_to_shipping']+label"/> <element name="discountAmount" type="input" selector="input[name='discount_amount']"/> <element name="discountStep" type="input" selector="input[name='discount_step']"/> + <element name="addRewardPoints" type="input" selector="input[name='extension_attributes[reward_points_delta]']"/> <element name="freeShipping" type="select" selector="//select[@name='simple_free_shipping']"/> <!-- Manage Coupon Codes sub-form --> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Section/AdminCartPriceRulesSection.xml b/app/code/Magento/SalesRule/Test/Mftf/Section/AdminCartPriceRulesSection.xml index a32c50d9d86..14d3a734408 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Section/AdminCartPriceRulesSection.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Section/AdminCartPriceRulesSection.xml @@ -15,5 +15,8 @@ <element name="nameColumns" type="text" selector="td[data-column='name']"/> <element name="rowContainingText" type="text" selector="//*[@id='promo_quote_grid_table']/tbody/tr[td//text()[contains(., '{{var1}}')]]" parameterized="true" timeout="30"/> <element name="rowByIndex" type="text" selector="tr[data-role='row']:nth-of-type({{var1}})" parameterized="true" timeout="30"/> + <element name="rulesRow" type="text" selector="//tr[@data-role='row']"/> + <element name="pageCurrent" type="text" selector="//label[@for='promo_quote_grid_page-current']"/> + <element name="totalCount" type="text" selector="span[data-ui-id*='grid-total-count']"/> </section> </sections> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Section/CartPriceRulesSubmenuSection.xml b/app/code/Magento/SalesRule/Test/Mftf/Section/CartPriceRulesSubmenuSection.xml index f3d5e9627ef..eb4098d71dc 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Section/CartPriceRulesSubmenuSection.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Section/CartPriceRulesSubmenuSection.xml @@ -7,8 +7,8 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="CartPriceRulesSubmenuSection"> <element name="cartPriceRules" type="button" selector="//li[@data-ui-id='menu-magento-catalogrule-promo']//li[@data-ui-id='menu-magento-salesrule-promo-quote']"/> </section> -</sections> \ No newline at end of file +</sections> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Section/DiscountSection.xml b/app/code/Magento/SalesRule/Test/Mftf/Section/DiscountSection.xml index e14bfb554b3..7e2ef0b5120 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Section/DiscountSection.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Section/DiscountSection.xml @@ -12,5 +12,6 @@ <element name="CouponInput" type="input" selector="#coupon_code"/> <element name="DiscountInput" type="input" selector="#discount-code"/> <element name="ApplyCodeBtn" type="button" selector="//span[text()='Apply Discount']"/> + <element name="CancelCoupon" type="button" selector="//button[@value='Cancel Coupon']"/> </section> </sections> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCartRulesAppliedForProductInCartTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCartRulesAppliedForProductInCartTest.xml new file mode 100644 index 00000000000..fbcc871a69b --- /dev/null +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCartRulesAppliedForProductInCartTest.xml @@ -0,0 +1,120 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminCartRulesAppliedForProductInCartTest"> + <annotations> + <features value="SalesRule"/> + <stories value="The cart rule cannot effect the cart"/> + <title value="Check that cart rules applied for product in cart"/> + <description value="Check that cart rules applied for product in cart"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-96722"/> + <useCaseId value="MAGETWO-96410"/> + <group value="SalesRule"/> + </annotations> + + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + + <!--Create category and product--> + <createData entity="_defaultCategory" stepKey="defaultCategory"/> + <createData entity="SimpleProduct2" stepKey="simpleProduct"> + <field key="price">200</field> + <field key="quantity">500</field> + </createData> + </before> + <after> + <!--Delete created data--> + <deleteData createDataKey="defaultCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="simpleProduct" stepKey="deleteSimpleProduct"/> + + <actionGroup stepKey="deleteProduct1" ref="deleteProductBySku"> + <argument name="sku" value="{{BundleProduct.sku}}"/> + </actionGroup> + <actionGroup ref="DeleteCartPriceRuleByName" stepKey="deleteCartPriceRule"> + <argument name="ruleName" value="{{PriceRuleWithCondition.name}}"/> + </actionGroup> + + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Start creating a bundle product--> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="goToProductList"/> + <waitForPageLoad stepKey="waitForProductList"/> + <actionGroup ref="goToCreateProductPage" stepKey="goToCreateProduct"> + <argument name="product" value="BundleProduct"/> + </actionGroup> + <actionGroup ref="fillProductNameAndSkuInProductForm" stepKey="fillNameAndSku"> + <argument name="product" value="BundleProduct"/> + </actionGroup> + <pressKey selector="{{AdminProductFormSection.productSku}}" parameterArray="[\Facebook\WebDriver\WebDriverKeys::ENTER]" stepKey="enter"/> + + <!--Off dynamic price and set value--> + <click selector="{{AdminProductFormBundleSection.dynamicPrice}}" stepKey="offDynamicPrice"/> + <fillField selector="{{AdminProductFormBundleSection.priceField}}" userInput="0" stepKey="setProductPrice"/> + + <!-- Add category to product --> + <click selector="{{AdminProductFormBundleSection.categoriesDropDown}}" stepKey="dropDownCategories"/> + <fillField selector="{{AdminProductFormBundleSection.searchForCategory}}" userInput="$$defaultCategory.name$$" stepKey="searchForCategory"/> + <click selector="{{AdminProductFormBundleSection.selectCategory}}" stepKey="selectCategory"/> + <click selector="{{AdminProductFormBundleSection.categoriesLabel}}" stepKey="clickOnCategoriesLabelToCloseOptions"/> + + <!-- Add option, a "Radio Buttons" type option, with one product and set fixed price 200--> + <actionGroup ref="addBundleOptionWithOneProduct" stepKey="addBundleOptionWithOneProduct"> + <argument name="x" value="0"/> + <argument name="n" value="1"/> + <argument name="prodOneSku" value="$$simpleProduct.sku$$"/> + <argument name="prodTwoSku" value=""/> + <argument name="optionTitle" value="Option One"/> + <argument name="inputType" value="radio"/> + </actionGroup> + <selectOption selector="{{AdminProductFormBundleSection.bundlePriceType}}" userInput="Fixed" stepKey="selectPriceType"/> + <fillField selector="{{AdminProductFormBundleSection.bundlePriceValue}}" userInput="200" stepKey="fillPriceValue"/> + <actionGroup ref="saveProductForm" stepKey="saveProduct"/> + + <!--Create cart price rule--> + <actionGroup ref="AdminCreateCartPriceRuleWithConditions" stepKey="createRule"> + <argument name="ruleName" value="PriceRuleWithCondition"/> + <argument name="condition1" value="Products subselection"/> + <argument name="condition2" value="Category"/> + <argument name="ruleToChange1" value="is"/> + <argument name="rule1" value="equals or greater than"/> + <argument name="ruleToChange2" value="..."/> + <argument name="rule2" value="2"/> + <argument name="categoryName" value="{{_defaultCategory.name}}"/> + </actionGroup> + + <!--Go to Storefront and add product to cart and checkout from cart--> + <amOnPage url="/$$simpleProduct.name$$.html" stepKey="GoToProduct"/> + <fillField selector="{{StorefrontProductActionSection.quantity}}" userInput="2" stepKey="setQuantity"/> + <actionGroup ref="StorefrontAddToCartCustomOptionsProductPageActionGroup" stepKey="AddProductToCard"> + <argument name="productName" value="$$simpleProduct.name$$"/> + </actionGroup> + <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="goToCheckoutFromMinicart"/> + <actionGroup ref="GuestCheckoutFillingShippingSectionActionGroup" stepKey="guestCheckoutFillingShipping"/> + + <!--Check totals--> + <grabTextFrom selector="{{CheckoutPaymentSection.orderSummarySubtotal}}" stepKey="grabSubtotal"/> + <grabTextFrom selector="{{CheckoutPaymentSection.orderSummaryShippingTotal}}" stepKey="grabShippingTotal"/> + <grabTextFrom selector="{{CheckoutPaymentSection.orderSummaryTotal}}" stepKey="grabTotal"/> + <assertEquals stepKey="assertSubtotal"> + <expectedResult type="string">$400.00</expectedResult> + <actualResult type="variable">$grabSubtotal</actualResult> + </assertEquals> + <assertEquals stepKey="assertShippingTotal"> + <expectedResult type="string">$10.00</expectedResult> + <actualResult type="variable">$grabShippingTotal</actualResult> + </assertEquals> + <assertEquals stepKey="assertTotal"> + <expectedResult type="string">$410.00</expectedResult> + <actualResult type="variable">$grabTotal</actualResult> + </assertEquals> + </test> +</tests> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/EndToEndB2CGuestUserTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/EndToEndB2CGuestUserTest.xml index da9eb8e1979..0d365dc089e 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Test/EndToEndB2CGuestUserTest.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/EndToEndB2CGuestUserTest.xml @@ -29,30 +29,21 @@ <actionGroup ref="StorefrontCheckCouponAppliedActionGroup" stepKey="couponCheckAppliedDiscount" after="couponApplyCoupon"> <argument name="rule" value="$$createSalesRule$$"/> - <!-- @TODO: Change to scalar value after MQE-498 is implemented --> - <argument name="discount" value="E2EB2CQuoteWith10PercentDiscount.discount"/> + <argument name="discount" value="48.00"/> </actionGroup> <actionGroup ref="StorefrontCheckCartActionGroup" stepKey="couponCheckCartWithDiscount" after="couponCheckAppliedDiscount"> - <!-- @TODO: Change to scalar value after MQE-498 is implemented --> - <argument name="subtotal" value="E2EB2CQuoteWith10PercentDiscount.subtotal"/> - <!-- @TODO: Change to scalar value after MQE-498 is implemented --> - <argument name="shipping" value="E2EB2CQuoteWith10PercentDiscount.shipping"/> - <!-- @TODO: Change to scalar value after MQE-498 is implemented --> - <argument name="shippingMethod" value="E2EB2CQuoteWith10PercentDiscount.shippingMethod"/> - <!-- @TODO: Change to scalar value after MQE-498 is implemented --> - <argument name="total" value="E2EB2CQuoteWith10PercentDiscount.total"/> + <argument name="subtotal" value="480.00"/> + <argument name="shipping" value="15.00"/> + <argument name="shippingMethod" value="Flat Rate - Fixed"/> + <argument name="total" value="447.00"/> </actionGroup> <actionGroup ref="StorefrontCancelCouponActionGroup" stepKey="couponCancelCoupon" after="couponCheckCartWithDiscount"/> <actionGroup ref="StorefrontCheckCartActionGroup" stepKey="cartAssertCartAfterCancelCoupon" after="couponCancelCoupon"> - <!-- @TODO: Change to scalar value after MQE-498 is implemented --> - <argument name="subtotal" value="E2EB2CQuote.subtotal"/> - <!-- @TODO: Change to scalar value after MQE-498 is implemented --> - <argument name="shipping" value="E2EB2CQuote.shipping"/> - <!-- @TODO: Change to scalar value after MQE-498 is implemented --> - <argument name="shippingMethod" value="E2EB2CQuote.shippingMethod"/> - <!-- @TODO: Change to scalar value after MQE-498 is implemented --> - <argument name="total" value="E2EB2CQuote.total"/> + <argument name="subtotal" value="480.00"/> + <argument name="shipping" value="15.00"/> + <argument name="shippingMethod" value="Flat Rate - Fixed"/> + <argument name="total" value="495.00"/> </actionGroup> <comment userInput="End of using coupon code" stepKey="endOfUsingCouponCode" after="cartAssertCartAfterCancelCoupon" /> </test> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml index d735d5a73f0..7a995b1feee 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml @@ -29,30 +29,21 @@ <actionGroup ref="StorefrontCheckCouponAppliedActionGroup" stepKey="couponCheckAppliedDiscount" after="couponApplyCoupon"> <argument name="rule" value="$$createSalesRule$$"/> - <!-- @TODO: Change to scalar value after MQE-498 is implemented --> - <argument name="discount" value="E2EB2CQuoteWith10PercentDiscount.discount"/> + <argument name="discount" value="48.00"/> </actionGroup> <actionGroup ref="StorefrontCheckCartActionGroup" stepKey="couponCheckCartWithDiscount" after="couponCheckAppliedDiscount"> - <!-- @TODO: Change to scalar value after MQE-498 is implemented --> - <argument name="subtotal" value="E2EB2CQuoteWith10PercentDiscount.subtotal"/> - <!-- @TODO: Change to scalar value after MQE-498 is implemented --> - <argument name="shipping" value="E2EB2CQuoteWith10PercentDiscount.shipping"/> - <!-- @TODO: Change to scalar value after MQE-498 is implemented --> - <argument name="shippingMethod" value="E2EB2CQuoteWith10PercentDiscount.shippingMethod"/> - <!-- @TODO: Change to scalar value after MQE-498 is implemented --> - <argument name="total" value="E2EB2CQuoteWith10PercentDiscount.total"/> + <argument name="subtotal" value="480.00"/> + <argument name="shipping" value="15.00"/> + <argument name="shippingMethod" value="Flat Rate - Fixed"/> + <argument name="total" value="447.00"/> </actionGroup> <actionGroup ref="StorefrontCancelCouponActionGroup" stepKey="couponCancelCoupon" after="couponCheckCartWithDiscount"/> <actionGroup ref="StorefrontCheckCartActionGroup" stepKey="cartAssertCartAfterCancelCoupon" after="couponCancelCoupon"> - <!-- @TODO: Change to scalar value after MQE-498 is implemented --> - <argument name="subtotal" value="E2EB2CQuote.subtotal"/> - <!-- @TODO: Change to scalar value after MQE-498 is implemented --> - <argument name="shipping" value="E2EB2CQuote.shipping"/> - <!-- @TODO: Change to scalar value after MQE-498 is implemented --> - <argument name="shippingMethod" value="E2EB2CQuote.shippingMethod"/> - <!-- @TODO: Change to scalar value after MQE-498 is implemented --> - <argument name="total" value="E2EB2CQuote.total"/> + <argument name="subtotal" value="480.00"/> + <argument name="shipping" value="15.00"/> + <argument name="shippingMethod" value="Flat Rate - Fixed"/> + <argument name="total" value="495.00"/> </actionGroup> <comment userInput="End of using coupon code" stepKey="endOfUsingCouponCode" after="cartAssertCartAfterCancelCoupon"/> </test> diff --git a/app/code/Magento/SalesRule/Test/Unit/Block/Adminhtml/Promo/Quote/Edit/DeleteButtonTest.php b/app/code/Magento/SalesRule/Test/Unit/Block/Adminhtml/Promo/Quote/Edit/DeleteButtonTest.php index 7812444c686..ae59a1f90b8 100644 --- a/app/code/Magento/SalesRule/Test/Unit/Block/Adminhtml/Promo/Quote/Edit/DeleteButtonTest.php +++ b/app/code/Magento/SalesRule/Test/Unit/Block/Adminhtml/Promo/Quote/Edit/DeleteButtonTest.php @@ -5,7 +5,6 @@ */ namespace Magento\SalesRule\Test\Unit\Block\Adminhtml\Promo\Quote\Edit; -use Magento\SalesRule\Model\RegistryConstants; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; class DeleteButtonTest extends \PHPUnit\Framework\TestCase diff --git a/app/code/Magento/SalesRule/Test/Unit/Model/ResourceModel/ReadHandlerTest.php b/app/code/Magento/SalesRule/Test/Unit/Model/ResourceModel/ReadHandlerTest.php index 0d6c09b7f61..f7522746b21 100644 --- a/app/code/Magento/SalesRule/Test/Unit/Model/ResourceModel/ReadHandlerTest.php +++ b/app/code/Magento/SalesRule/Test/Unit/Model/ResourceModel/ReadHandlerTest.php @@ -16,7 +16,7 @@ class ReadHandlerTest extends \PHPUnit\Framework\TestCase { /** - * @var \Magento\SalesRule\Model\ResourceModel\ReadHandler + * @var ReadHandler */ protected $model; @@ -49,7 +49,7 @@ protected function setUp() $this->metadataPool = $this->createMock($className); $this->model = $this->objectManager->getObject( - \Magento\SalesRule\Model\ResourceModel\ReadHandler::class, + ReadHandler::class, [ 'ruleResource' => $this->ruleResource, 'metadataPool' => $this->metadataPool, diff --git a/app/code/Magento/SalesRule/Test/Unit/Model/ResourceModel/Rule/DateApplierTest.php b/app/code/Magento/SalesRule/Test/Unit/Model/ResourceModel/Rule/DateApplierTest.php index 528db1ea0b8..65f345cb78d 100644 --- a/app/code/Magento/SalesRule/Test/Unit/Model/ResourceModel/Rule/DateApplierTest.php +++ b/app/code/Magento/SalesRule/Test/Unit/Model/ResourceModel/Rule/DateApplierTest.php @@ -13,7 +13,7 @@ class DateApplierTest extends \PHPUnit\Framework\TestCase { /** - * @var \Magento\SalesRule\Model\ResourceModel\Rule\DateApplier|\PHPUnit_Framework_MockObject_MockObject + * @var DateApplier|\PHPUnit_Framework_MockObject_MockObject */ protected $model; @@ -29,10 +29,7 @@ protected function setUp() { $this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - $this->model = $this->objectManager->getObject( - \Magento\SalesRule\Model\ResourceModel\Rule\DateApplier::class, - [] - ); + $this->model = $this->objectManager->getObject(DateApplier::class, []); } /** diff --git a/app/code/Magento/SalesRule/etc/db_schema.xml b/app/code/Magento/SalesRule/etc/db_schema.xml index 8dbdf76387c..c7427e49219 100644 --- a/app/code/Magento/SalesRule/etc/db_schema.xml +++ b/app/code/Magento/SalesRule/etc/db_schema.xml @@ -208,17 +208,17 @@ <column xsi:type="varchar" name="coupon_code" nullable="true" length="50" comment="Coupon Code"/> <column xsi:type="int" name="coupon_uses" padding="11" unsigned="false" nullable="false" identity="false" default="0" comment="Coupon Uses"/> - <column xsi:type="decimal" name="subtotal_amount" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="subtotal_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Subtotal Amount"/> <column xsi:type="decimal" name="discount_amount" scale="4" precision="12" unsigned="false" nullable="false" default="0" comment="Discount Amount"/> - <column xsi:type="decimal" name="total_amount" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="total_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Amount"/> - <column xsi:type="decimal" name="subtotal_amount_actual" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="subtotal_amount_actual" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Subtotal Amount Actual"/> <column xsi:type="decimal" name="discount_amount_actual" scale="4" precision="12" unsigned="false" nullable="false" default="0" comment="Discount Amount Actual"/> - <column xsi:type="decimal" name="total_amount_actual" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="total_amount_actual" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Amount Actual"/> <column xsi:type="varchar" name="rule_name" nullable="true" length="255" comment="Rule Name"/> <constraint xsi:type="primary" referenceId="PRIMARY"> @@ -250,17 +250,17 @@ <column xsi:type="varchar" name="coupon_code" nullable="true" length="50" comment="Coupon Code"/> <column xsi:type="int" name="coupon_uses" padding="11" unsigned="false" nullable="false" identity="false" default="0" comment="Coupon Uses"/> - <column xsi:type="decimal" name="subtotal_amount" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="subtotal_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Subtotal Amount"/> <column xsi:type="decimal" name="discount_amount" scale="4" precision="12" unsigned="false" nullable="false" default="0" comment="Discount Amount"/> - <column xsi:type="decimal" name="total_amount" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="total_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Amount"/> - <column xsi:type="decimal" name="subtotal_amount_actual" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="subtotal_amount_actual" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Subtotal Amount Actual"/> <column xsi:type="decimal" name="discount_amount_actual" scale="4" precision="12" unsigned="false" nullable="false" default="0" comment="Discount Amount Actual"/> - <column xsi:type="decimal" name="total_amount_actual" scale="4" precision="12" unsigned="false" + <column xsi:type="decimal" name="total_amount_actual" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Amount Actual"/> <column xsi:type="varchar" name="rule_name" nullable="true" length="255" comment="Rule Name"/> <constraint xsi:type="primary" referenceId="PRIMARY"> @@ -292,11 +292,11 @@ <column xsi:type="varchar" name="coupon_code" nullable="true" length="50" comment="Coupon Code"/> <column xsi:type="int" name="coupon_uses" padding="11" unsigned="false" nullable="false" identity="false" default="0" comment="Coupon Uses"/> - <column xsi:type="decimal" name="subtotal_amount" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="subtotal_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Subtotal Amount"/> <column xsi:type="decimal" name="discount_amount" scale="4" precision="12" unsigned="false" nullable="false" default="0" comment="Discount Amount"/> - <column xsi:type="decimal" name="total_amount" scale="4" precision="12" unsigned="false" nullable="false" + <column xsi:type="decimal" name="total_amount" scale="4" precision="20" unsigned="false" nullable="false" default="0" comment="Total Amount"/> <column xsi:type="varchar" name="rule_name" nullable="true" length="255" comment="Rule Name"/> <constraint xsi:type="primary" referenceId="PRIMARY"> diff --git a/app/code/Magento/SalesRule/etc/di.xml b/app/code/Magento/SalesRule/etc/di.xml index a8c350457a5..27c9a41503b 100644 --- a/app/code/Magento/SalesRule/etc/di.xml +++ b/app/code/Magento/SalesRule/etc/di.xml @@ -179,7 +179,7 @@ </arguments> </type> - <type name="\Magento\Quote\Model\Cart\CartTotalRepository"> + <type name="Magento\Quote\Model\Cart\CartTotalRepository"> <plugin name="coupon_label_plugin" type="Magento\SalesRule\Plugin\CartTotalRepository" /> </type> </config> diff --git a/app/code/Magento/SalesRule/etc/sales.xml b/app/code/Magento/SalesRule/etc/sales.xml index 3ab197d40b0..d2db6642248 100644 --- a/app/code/Magento/SalesRule/etc/sales.xml +++ b/app/code/Magento/SalesRule/etc/sales.xml @@ -8,7 +8,8 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Sales:etc/sales.xsd"> <section name="quote"> <group name="totals"> - <item name="discount" instance="Magento\SalesRule\Model\Quote\Discount" sort_order="400"/> + <item name="discount" instance="Magento\SalesRule\Model\Quote\Discount" sort_order="300"/> + <item name="shipping_discount" instance="Magento\SalesRule\Model\Quote\Address\Total\ShippingDiscount" sort_order="400"/> </group> </section> </config> diff --git a/app/code/Magento/SalesSequence/Model/Manager.php b/app/code/Magento/SalesSequence/Model/Manager.php index d18a872bf3b..95b26ba425c 100644 --- a/app/code/Magento/SalesSequence/Model/Manager.php +++ b/app/code/Magento/SalesSequence/Model/Manager.php @@ -41,7 +41,9 @@ public function __construct( * * @param string $entityType * @param int $storeId + * * @return \Magento\Framework\DB\Sequence\SequenceInterface + * @throws \Magento\Framework\Exception\LocalizedException */ public function getSequence($entityType, $storeId) { diff --git a/app/code/Magento/SalesSequence/Model/ResourceModel/Meta.php b/app/code/Magento/SalesSequence/Model/ResourceModel/Meta.php index be566b6119b..7a4aabea856 100644 --- a/app/code/Magento/SalesSequence/Model/ResourceModel/Meta.php +++ b/app/code/Magento/SalesSequence/Model/ResourceModel/Meta.php @@ -96,7 +96,9 @@ public function loadByEntityTypeAndStore($entityType, $storeId) * Using for load sequence profile and setting it into metadata * * @param \Magento\Framework\Model\AbstractModel $object - * @return $this + * + * @return $this|\Magento\Framework\Model\ResourceModel\Db\AbstractDb + * @throws \Magento\Framework\Exception\LocalizedException */ protected function _afterLoad(\Magento\Framework\Model\AbstractModel $object) { @@ -137,7 +139,12 @@ protected function _beforeSave(\Magento\Framework\Model\AbstractModel $object) } /** - * @inheritdoc + * Perform actions after object save + * + * @param \Magento\Framework\Model\AbstractModel $object + * + * @return $this|\Magento\Framework\Model\ResourceModel\Db\AbstractDb + * @throws \Magento\Framework\Exception\AlreadyExistsException */ protected function _afterSave(\Magento\Framework\Model\AbstractModel $object) { diff --git a/app/code/Magento/SalesSequence/Model/ResourceModel/Profile.php b/app/code/Magento/SalesSequence/Model/ResourceModel/Profile.php index 368bdf6c26f..4f1788b0ca3 100644 --- a/app/code/Magento/SalesSequence/Model/ResourceModel/Profile.php +++ b/app/code/Magento/SalesSequence/Model/ResourceModel/Profile.php @@ -5,7 +5,6 @@ */ namespace Magento\SalesSequence\Model\ResourceModel; -use Magento\SalesSequence\Model\Meta as ModelMeta; use Magento\Framework\Model\ResourceModel\Db\Context as DatabaseContext; use Magento\SalesSequence\Model\ProfileFactory; diff --git a/app/code/Magento/SalesSequence/Observer/SequenceCreatorObserver.php b/app/code/Magento/SalesSequence/Observer/SequenceCreatorObserver.php index cc828e7d36a..cf56c96fb17 100644 --- a/app/code/Magento/SalesSequence/Observer/SequenceCreatorObserver.php +++ b/app/code/Magento/SalesSequence/Observer/SequenceCreatorObserver.php @@ -49,8 +49,12 @@ public function __construct( } /** + * Observer triggered during adding new store + * * @param EventObserver $observer - * @return $this + * + * @return $this|void + * @throws \Magento\Framework\Exception\AlreadyExistsException */ public function execute(EventObserver $observer) { diff --git a/app/code/Magento/SalesSequence/Setup/SequenceCreator.php b/app/code/Magento/SalesSequence/Setup/SequenceCreator.php index b0cbf545957..14df80224e2 100644 --- a/app/code/Magento/SalesSequence/Setup/SequenceCreator.php +++ b/app/code/Magento/SalesSequence/Setup/SequenceCreator.php @@ -49,6 +49,8 @@ public function __construct( /** * Creates sales sequences. + * + * @throws \Magento\Framework\Exception\AlreadyExistsException */ public function create() { diff --git a/app/code/Magento/SalesSequence/etc/db_schema.xml b/app/code/Magento/SalesSequence/etc/db_schema.xml index 0e580b85bd6..7ad48badf7b 100644 --- a/app/code/Magento/SalesSequence/etc/db_schema.xml +++ b/app/code/Magento/SalesSequence/etc/db_schema.xml @@ -41,7 +41,7 @@ <column xsi:type="varchar" name="entity_type" nullable="false" length="32" comment="Prefix"/> <column xsi:type="smallint" name="store_id" padding="5" unsigned="true" nullable="false" identity="false" comment="Store Id"/> - <column xsi:type="varchar" name="sequence_table" nullable="false" length="32" comment="table for sequence"/> + <column xsi:type="varchar" name="sequence_table" nullable="false" length="64" comment="table for sequence"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="meta_id"/> </constraint> diff --git a/app/code/Magento/SampleData/README.md b/app/code/Magento/SampleData/README.md index 5abcbaab148..1077038fc07 100644 --- a/app/code/Magento/SampleData/README.md +++ b/app/code/Magento/SampleData/README.md @@ -69,4 +69,4 @@ If you have deleted certain entities provided by sample data and want to restore The deleted sample data entities will be restored. Those entities, which were changed, will preserve these changes and will not be restored to the default view. ## Documentation -You can find the more detailed description of sample data manipulation procedures at [http://devdocs.magento.com/guides/v2.0/install-gde/install/cli/install-cli-sample-data.html](http://devdocs.magento.com/guides/v2.0/install-gde/install/cli/install-cli-sample-data.html) +You can find the more detailed description of sample data manipulation procedures at [https://devdocs.magento.com/guides/v2.0/install-gde/install/cli/install-cli-sample-data.html](https://devdocs.magento.com/guides/v2.0/install-gde/install/cli/install-cli-sample-data.html) diff --git a/app/code/Magento/Search/Model/ResourceModel/SynonymReader.php b/app/code/Magento/Search/Model/ResourceModel/SynonymReader.php index 46e794a1954..45eee0a4001 100644 --- a/app/code/Magento/Search/Model/ResourceModel/SynonymReader.php +++ b/app/code/Magento/Search/Model/ResourceModel/SynonymReader.php @@ -87,7 +87,7 @@ private function queryByPhrase($phrase) { $matchQuery = $this->fullTextSelect->getMatchQuery( ['synonyms' => 'synonyms'], - $phrase, + $this->escapePhrase($phrase), Fulltext::FULLTEXT_MODE_BOOLEAN ); $query = $this->getConnection()->select()->from( @@ -97,6 +97,18 @@ private function queryByPhrase($phrase) return $this->getConnection()->fetchAll($query); } + /** + * Cut trailing plus or minus sign, and @ symbol, using of which causes InnoDB to report a syntax error. + * + * @see https://dev.mysql.com/doc/refman/5.7/en/fulltext-boolean.html + * @param string $phrase + * @return string + */ + private function escapePhrase(string $phrase): string + { + return preg_replace('/@+|[@+-]+$/', '', $phrase); + } + /** * A private helper function to retrieve matching synonym groups per scope * diff --git a/app/code/Magento/Search/Test/Mftf/Section/StorefrontQuickSearchResultsSection.xml b/app/code/Magento/Search/Test/Mftf/Section/StorefrontQuickSearchResultsSection.xml new file mode 100644 index 00000000000..9e04bbb12a7 --- /dev/null +++ b/app/code/Magento/Search/Test/Mftf/Section/StorefrontQuickSearchResultsSection.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="StorefrontQuickSearchResultsSection"> + <element name="searchTextBox" type="text" selector="#search"/> + <element name="searchTextBoxButton" type="button" selector="button[class='action search']"/> + <element name="productLink" type="select" selector="a[class='product-item-link']"/> + <element name="asLowAsLabel" type="text" selector=".minimal-price-link > span"/> + <element name="textArea" type="text" selector="li[class='item']"/> + <element name="regularPrice" type="text" selector="li[class='item']"/> + </section> +</sections> diff --git a/app/code/Magento/Security/Model/SecurityChecker/Quantity.php b/app/code/Magento/Security/Model/SecurityChecker/Quantity.php index 9d86b55158b..5d72ba261f3 100644 --- a/app/code/Magento/Security/Model/SecurityChecker/Quantity.php +++ b/app/code/Magento/Security/Model/SecurityChecker/Quantity.php @@ -48,13 +48,13 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function check($securityEventType, $accountReference = null, $longIp = null) { $isEnabled = $this->securityConfig->getPasswordResetProtectionType() != ResetMethod::OPTION_NONE; $allowedAttemptsNumber = $this->securityConfig->getMaxNumberPasswordResetRequests(); - if ($isEnabled and $allowedAttemptsNumber) { + if ($isEnabled && $allowedAttemptsNumber) { $collection = $this->prepareCollection($securityEventType, $accountReference, $longIp); if ($collection->count() >= $allowedAttemptsNumber) { throw new SecurityViolationException( diff --git a/app/code/Magento/SendFriend/Model/SendFriend.php b/app/code/Magento/SendFriend/Model/SendFriend.php index c69d6342b48..38525a9f83a 100644 --- a/app/code/Magento/SendFriend/Model/SendFriend.php +++ b/app/code/Magento/SendFriend/Model/SendFriend.php @@ -16,6 +16,7 @@ * @method \Magento\SendFriend\Model\SendFriend setTime(int $value) * * @author Magento Core Team <core@magentocommerce.com> + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * * @api @@ -162,6 +163,8 @@ protected function _construct() } /** + * Send email. + * * @return $this * @throws CoreException */ @@ -236,7 +239,7 @@ public function validate() } $email = $this->getSender()->getEmail(); - if (empty($email) or !\Zend_Validate::is($email, \Magento\Framework\Validator\EmailAddress::class)) { + if (empty($email) || !\Zend_Validate::is($email, \Magento\Framework\Validator\EmailAddress::class)) { $errors[] = __('Invalid Sender Email'); } @@ -281,13 +284,13 @@ public function setRecipients($recipients) // validate array if (!is_array( $recipients - ) or !isset( + ) || !isset( $recipients['email'] - ) or !isset( + ) || !isset( $recipients['name'] - ) or !is_array( + ) || !is_array( $recipients['email'] - ) or !is_array( + ) || !is_array( $recipients['name'] ) ) { @@ -487,7 +490,7 @@ protected function _sentCountByCookies($increment = false) $oldTimes = explode(',', $oldTimes); foreach ($oldTimes as $oldTime) { $periodTime = $time - $this->_sendfriendData->getPeriod(); - if (is_numeric($oldTime) and $oldTime >= $periodTime) { + if (is_numeric($oldTime) && $oldTime >= $periodTime) { $newTimes[] = $oldTime; } } diff --git a/app/code/Magento/Shipping/Block/Adminhtml/Order/Packaging.php b/app/code/Magento/Shipping/Block/Adminhtml/Order/Packaging.php index b4ff445c63f..e5e419328ee 100644 --- a/app/code/Magento/Shipping/Block/Adminhtml/Order/Packaging.php +++ b/app/code/Magento/Shipping/Block/Adminhtml/Order/Packaging.php @@ -74,6 +74,7 @@ public function getShipment() * Configuration for popup window for packaging * * @return string + * @SuppressWarnings(PHPMD.RequestAwareBlockMethod) */ public function getConfigDataJson() { @@ -86,7 +87,7 @@ public function getConfigDataJson() $itemsName = []; $itemsWeight = []; $itemsProductId = []; - + $itemsOrderItemId = []; if ($shipmentId) { $urlParams['shipment_id'] = $shipmentId; $createLabelUrl = $this->getUrl('adminhtml/order_shipment/createLabel', $urlParams); diff --git a/app/code/Magento/Shipping/Controller/Adminhtml/Order/Shipment/Save.php b/app/code/Magento/Shipping/Controller/Adminhtml/Order/Shipment/Save.php index 8bd64ccf82d..100ba029bea 100644 --- a/app/code/Magento/Shipping/Controller/Adminhtml/Order/Shipment/Save.php +++ b/app/code/Magento/Shipping/Controller/Adminhtml/Order/Shipment/Save.php @@ -1,13 +1,13 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Shipping\Controller\Adminhtml\Order\Shipment; -use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; -use Magento\Backend\App\Action; +use Magento\Framework\App\Action\HttpPostActionInterface; +use Magento\Framework\Controller\ResultFactory; use Magento\Sales\Model\Order\Shipment\Validation\QuantityValidator; /** @@ -48,17 +48,22 @@ class Save extends \Magento\Backend\App\Action implements HttpPostActionInterfac * @param \Magento\Shipping\Controller\Adminhtml\Order\ShipmentLoader $shipmentLoader * @param \Magento\Shipping\Model\Shipping\LabelGenerator $labelGenerator * @param \Magento\Sales\Model\Order\Email\Sender\ShipmentSender $shipmentSender + * @param \Magento\Sales\Model\Order\Shipment\ShipmentValidatorInterface|null $shipmentValidator */ public function __construct( \Magento\Backend\App\Action\Context $context, \Magento\Shipping\Controller\Adminhtml\Order\ShipmentLoader $shipmentLoader, \Magento\Shipping\Model\Shipping\LabelGenerator $labelGenerator, - \Magento\Sales\Model\Order\Email\Sender\ShipmentSender $shipmentSender + \Magento\Sales\Model\Order\Email\Sender\ShipmentSender $shipmentSender, + \Magento\Sales\Model\Order\Shipment\ShipmentValidatorInterface $shipmentValidator = null ) { + parent::__construct($context); + $this->shipmentLoader = $shipmentLoader; $this->labelGenerator = $labelGenerator; $this->shipmentSender = $shipmentSender; - parent::__construct($context); + $this->shipmentValidator = $shipmentValidator ?: \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Magento\Sales\Model\Order\Shipment\ShipmentValidatorInterface::class); } /** @@ -84,9 +89,10 @@ protected function _saveShipment($shipment) /** * Save shipment + * * We can save only new shipment. Existing shipments are not editable * - * @return void + * @return \Magento\Framework\Controller\ResultInterface * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) */ @@ -98,7 +104,7 @@ public function execute() $formKeyIsValid = $this->_formKeyValidator->validate($this->getRequest()); $isPost = $this->getRequest()->isPost(); if (!$formKeyIsValid || !$isPost) { - $this->messageManager->addError(__('We can\'t save the shipment right now.')); + $this->messageManager->addErrorMessage(__('We can\'t save the shipment right now.')); return $resultRedirect->setPath('sales/order/index'); } @@ -118,8 +124,7 @@ public function execute() $this->shipmentLoader->setTracking($this->getRequest()->getParam('tracking')); $shipment = $this->shipmentLoader->load(); if (!$shipment) { - $this->_forward('noroute'); - return; + return $this->resultFactory->create(ResultFactory::TYPE_FORWARD)->forward('noroute'); } if (!empty($data['comment_text'])) { @@ -132,15 +137,13 @@ public function execute() $shipment->setCustomerNote($data['comment_text']); $shipment->setCustomerNoteNotify(isset($data['comment_customer_notify'])); } - $validationResult = $this->getShipmentValidator() - ->validate($shipment, [QuantityValidator::class]); + $validationResult = $this->shipmentValidator->validate($shipment, [QuantityValidator::class]); if ($validationResult->hasMessages()) { - $this->messageManager->addError( + $this->messageManager->addErrorMessage( __("Shipment Document Validation Error(s):\n" . implode("\n", $validationResult->getMessages())) ); - $this->_redirect('*/*/new', ['order_id' => $this->getRequest()->getParam('order_id')]); - return; + return $resultRedirect->setPath('*/*/new', ['order_id' => $this->getRequest()->getParam('order_id')]); } $shipment->register(); @@ -160,7 +163,7 @@ public function execute() $shipmentCreatedMessage = __('The shipment has been created.'); $labelCreatedMessage = __('You created the shipping label.'); - $this->messageManager->addSuccess( + $this->messageManager->addSuccessMessage( $isNeedCreateLabel ? $shipmentCreatedMessage . ' ' . $labelCreatedMessage : $shipmentCreatedMessage ); $this->_objectManager->get(\Magento\Backend\Model\Session::class)->getCommentText(true); @@ -169,8 +172,8 @@ public function execute() $responseAjax->setError(true); $responseAjax->setMessage($e->getMessage()); } else { - $this->messageManager->addError($e->getMessage()); - $this->_redirect('*/*/new', ['order_id' => $this->getRequest()->getParam('order_id')]); + $this->messageManager->addErrorMessage($e->getMessage()); + return $resultRedirect->setPath('*/*/new', ['order_id' => $this->getRequest()->getParam('order_id')]); } } catch (\Exception $e) { $this->_objectManager->get(\Psr\Log\LoggerInterface::class)->critical($e); @@ -178,29 +181,14 @@ public function execute() $responseAjax->setError(true); $responseAjax->setMessage(__('An error occurred while creating shipping label.')); } else { - $this->messageManager->addError(__('Cannot save shipment.')); - $this->_redirect('*/*/new', ['order_id' => $this->getRequest()->getParam('order_id')]); + $this->messageManager->addErrorMessage(__('Cannot save shipment.')); + return $resultRedirect->setPath('*/*/new', ['order_id' => $this->getRequest()->getParam('order_id')]); } } if ($isNeedCreateLabel) { - $this->getResponse()->representJson($responseAjax->toJson()); - } else { - $this->_redirect('sales/order/view', ['order_id' => $shipment->getOrderId()]); - } - } - - /** - * @return \Magento\Sales\Model\Order\Shipment\ShipmentValidatorInterface - * @deprecated 100.1.1 - */ - private function getShipmentValidator() - { - if ($this->shipmentValidator === null) { - $this->shipmentValidator = $this->_objectManager->get( - \Magento\Sales\Model\Order\Shipment\ShipmentValidatorInterface::class - ); + return $this->resultFactory->create(ResultFactory::TYPE_JSON)->setJsonData($responseAjax->toJson()); } - return $this->shipmentValidator; + return $resultRedirect->setPath('sales/order/view', ['order_id' => $shipment->getOrderId()]); } } diff --git a/app/code/Magento/Shipping/Test/Mftf/Data/FreeShippingMethodData.xml b/app/code/Magento/Shipping/Test/Mftf/Data/FreeShippingMethodData.xml index 512ef8389bb..d700aa622c1 100644 --- a/app/code/Magento/Shipping/Test/Mftf/Data/FreeShippingMethodData.xml +++ b/app/code/Magento/Shipping/Test/Mftf/Data/FreeShippingMethodData.xml @@ -13,6 +13,13 @@ <entity name="freeActiveEnable" type="active"> <data key="value">1</data> </entity> + <!-- Disable Free Shipping method --> + <entity name="FreeShippingMethodDisableConfig" type="free_shipping_method"> + <requiredEntity type="active">freeActiveDisable</requiredEntity> + </entity> + <entity name="freeActiveDisable" type="active"> + <data key="value">0</data> + </entity> <!-- Free Shipping method default setup --> <entity name="FreeShippinMethodDefault" type="free_shipping_method"> <requiredEntity type="active">freeActiveDefault</requiredEntity> diff --git a/app/code/Magento/Shipping/Test/Mftf/Section/AdminShipmentItemsSection.xml b/app/code/Magento/Shipping/Test/Mftf/Section/AdminShipmentItemsSection.xml index a7bf82588f7..0345c3f2949 100644 --- a/app/code/Magento/Shipping/Test/Mftf/Section/AdminShipmentItemsSection.xml +++ b/app/code/Magento/Shipping/Test/Mftf/Section/AdminShipmentItemsSection.xml @@ -15,5 +15,6 @@ <element name="itemQtyToShip" type="input" selector=".order-shipment-table tbody:nth-of-type({{var1}}) .col-qty input.qty-item" parameterized="true"/> <element name="nameColumn" type="text" selector=".order-shipment-table .col-product .product-title"/> <element name="skuColumn" type="text" selector=".order-shipment-table .col-product .product-sku-block"/> + <element name="itemQtyInvoiced" type="text" selector="(//*[@class='col-ordered-qty']//th[contains(text(), 'Invoiced')]/following-sibling::td)[{{var}}]" parameterized="true"/> </section> -</sections> \ No newline at end of file +</sections> diff --git a/app/code/Magento/Shipping/Test/Unit/Controller/Adminhtml/Order/Shipment/SaveTest.php b/app/code/Magento/Shipping/Test/Unit/Controller/Adminhtml/Order/Shipment/SaveTest.php index f841728416f..c253900501d 100644 --- a/app/code/Magento/Shipping/Test/Unit/Controller/Adminhtml/Order/Shipment/SaveTest.php +++ b/app/code/Magento/Shipping/Test/Unit/Controller/Adminhtml/Order/Shipment/SaveTest.php @@ -142,7 +142,7 @@ protected function setUp() ); $this->messageManager = $this->createPartialMock( \Magento\Framework\Message\Manager::class, - ['addSuccess', 'addError'] + ['addSuccessMessage', 'addErrorMessage'] ); $this->session = $this->createPartialMock( \Magento\Backend\Model\Session::class, @@ -236,7 +236,7 @@ public function testExecute($formKeyIsValid, $isPost) if (!$formKeyIsValid || !$isPost) { $this->messageManager->expects($this->once()) - ->method('addError'); + ->method('addErrorMessage'); $this->resultRedirect->expects($this->once()) ->method('setPath') @@ -325,12 +325,11 @@ public function testExecute($formKeyIsValid, $isPost) ->method('get') ->with(\Magento\Backend\Model\Session::class) ->will($this->returnValue($this->session)); - $path = 'sales/order/view'; $arguments = ['order_id' => $orderId]; $shipment->expects($this->once()) ->method('getOrderId') ->will($this->returnValue($orderId)); - $this->prepareRedirect($path, $arguments); + $this->prepareRedirect($arguments); $this->shipmentValidatorMock->expects($this->once()) ->method('validate') @@ -360,10 +359,9 @@ public function executeDataProvider() } /** - * @param string $path * @param array $arguments */ - protected function prepareRedirect($path, array $arguments = []) + protected function prepareRedirect(array $arguments = []) { $this->actionFlag->expects($this->any()) ->method('get') @@ -372,14 +370,8 @@ protected function prepareRedirect($path, array $arguments = []) $this->session->expects($this->any()) ->method('setIsUrlNotice') ->with(true); - - $url = $path . '/' . (!empty($arguments) ? $arguments['order_id'] : ''); - $this->helper->expects($this->atLeastOnce()) - ->method('getUrl') - ->with($path, $arguments) - ->will($this->returnValue($url)); - $this->response->expects($this->atLeastOnce()) - ->method('setRedirect') - ->with($url); + $this->resultRedirect->expects($this->once()) + ->method('setPath') + ->with('sales/order/view', $arguments); } } diff --git a/app/code/Magento/Shipping/view/adminhtml/templates/order/packaging/popup_content.phtml b/app/code/Magento/Shipping/view/adminhtml/templates/order/packaging/popup_content.phtml index c32b63bddab..db0739d127b 100644 --- a/app/code/Magento/Shipping/view/adminhtml/templates/order/packaging/popup_content.phtml +++ b/app/code/Magento/Shipping/view/adminhtml/templates/order/packaging/popup_content.phtml @@ -61,7 +61,7 @@ <?php else: ?> class="admin__control-select" <?php endif; ?>> - <?php foreach ($block->getContainers() as $key => $value): ?> + <?php foreach ($containers as $key => $value): ?> <option value="<?= /* @escapeNotVerified */ $key ?>" > <?= /* @escapeNotVerified */ $value ?> </option> diff --git a/app/code/Magento/Signifyd/README.md b/app/code/Magento/Signifyd/README.md index 9479972cb21..753ace3128d 100644 --- a/app/code/Magento/Signifyd/README.md +++ b/app/code/Magento/Signifyd/README.md @@ -47,7 +47,7 @@ The following interfaces (marked with the `@api` annotation) provide methods tha - might be used by `Magento\Signifyd\Api\CaseRepositoryInterface` to retrieve a list of case entities by specific conditions -For information about a public API in Magento 2, see [Public interfaces & APIs](http://devdocs.magento.com/guides/v2.1/extension-dev-guide/api-concepts.html). +For information about a public API in Magento 2, see [Public interfaces & APIs](https://devdocs.magento.com/guides/v2.1/extension-dev-guide/api-concepts.html). ## Additional information @@ -67,12 +67,12 @@ The Debug Mode may be enabled in the module configuration. This logs the communi The Magento_Signifyd module does not introduce backward incompatible changes. -You can track [backward incompatible changes in patch releases](http://devdocs.magento.com/guides/v2.0/release-notes/changes/ee_changes.html). +You can track [backward incompatible changes in patch releases](https://devdocs.magento.com/guides/v2.0/release-notes/changes/ee_changes.html). ### Processing supplementary payment information To improve the accuracy of Signifyd's transaction estimation, you may perform these operations (links lead to the Magento Developer Documentation Portal): -- [Provide custom AVS/CVV mapping](http://devdocs.magento.com/guides/v2.2/payments-integrations/signifyd/signifyd.html#provide-avscvv-response-codes) +- [Provide custom AVS/CVV mapping](https://devdocs.magento.com/guides/v2.2/payments-integrations/signifyd/signifyd.html#provide-avscvv-response-codes) -- [Retrieve payment method for a placed order](http://devdocs.magento.com/guides/v2.2/payments-integrations/signifyd/signifyd.html#retrieve-payment-method-for-a-placed-order) +- [Retrieve payment method for a placed order](https://devdocs.magento.com/guides/v2.2/payments-integrations/signifyd/signifyd.html#retrieve-payment-method-for-a-placed-order) diff --git a/app/code/Magento/Store/Model/Store.php b/app/code/Magento/Store/Model/Store.php index 29a1f4a9c66..c1ad5bdcfc0 100644 --- a/app/code/Magento/Store/Model/Store.php +++ b/app/code/Magento/Store/Model/Store.php @@ -33,6 +33,7 @@ * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @SuppressWarnings(PHPMD.ExcessivePublicCount) + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) * @since 100.0.2 */ class Store extends AbstractExtensibleModel implements @@ -897,7 +898,10 @@ public function setCurrentCurrencyCode($code) if (in_array($code, $this->getAvailableCurrencyCodes())) { $this->_getSession()->setCurrencyCode($code); - $defaultCode = $this->_storeManager->getWebsite()->getDefaultStore()->getDefaultCurrency()->getCode(); + $defaultCode = ($this->_storeManager->getStore() !== null) + ? $this->_storeManager->getStore()->getDefaultCurrency()->getCode() + : $this->_storeManager->getWebsite()->getDefaultStore()->getDefaultCurrency()->getCode(); + $this->_httpContext->setValue(Context::CONTEXT_CURRENCY, $code, $defaultCode); } return $this; diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateStoreViewActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateStoreViewActionGroup.xml index 6cbbb7ae220..9b942109785 100644 --- a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateStoreViewActionGroup.xml +++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateStoreViewActionGroup.xml @@ -15,6 +15,7 @@ </arguments> <amOnPage url="{{AdminSystemStoreViewPage.url}}" stepKey="navigateToNewStoreView"/> <waitForPageLoad stepKey="waitForPageLoad1" /> + <comment userInput="Creating Store View" stepKey="storeViewCreationComment" /> <!--Create Store View--> <selectOption selector="{{AdminNewStoreSection.storeGrpDropdown}}" userInput="{{StoreGroup.name}}" stepKey="selectStore" /> <fillField selector="{{AdminNewStoreSection.storeNameTextField}}" userInput="{{customStore.name}}" stepKey="enterStoreViewName" /> @@ -22,8 +23,9 @@ <selectOption selector="{{AdminNewStoreSection.statusDropdown}}" userInput="Enabled" stepKey="setStatus" /> <click selector="{{AdminNewStoreViewActionsSection.saveButton}}" stepKey="clickSaveStoreView" /> <waitForElementVisible selector="{{AdminConfirmationModalSection.ok}}" stepKey="waitForModal" /> - <see selector="{{AdminConfirmationModalSection.title}}" userInput="Warning message" stepKey="seeWarning" /> - <click selector="{{AdminConfirmationModalSection.ok}}" stepKey="dismissModal" /> + <see selector="{{AdminConfirmationModalSection.title}}" userInput="Warning message" stepKey="seeWarningAboutTakingALongTimeToComplete" /> + <click selector="{{AdminConfirmationModalSection.ok}}" stepKey="confirmModal" /> + <waitForPageLoad stepKey="waitForStorePageLoad" /> <waitForElementNotVisible selector="{{AdminNewStoreViewActionsSection.loadingMask}}" stepKey="waitForElementVisible"/> </actionGroup> <!--Save the Store view--> @@ -37,4 +39,27 @@ <waitForLoadingMaskToDisappear stepKey="waitForForm"/> <see userInput="Store with the same code already exists." stepKey="seeMessage" /> </actionGroup> + <actionGroup name="navigateToAdminContentManagementPage"> + <amOnPage url="{{AdminContentManagementPage.url}}" stepKey="navigateToConfigurationPage"/> + <waitForPageLoad stepKey="waitForPageLoad1"/> + </actionGroup> + <actionGroup name="saveStoreConfiguration"> + <comment userInput="saveStoreConfiguration" stepKey="comment"/> + <waitForElementVisible selector="{{StoreConfigSection.Save}}" stepKey="waitForSaveButton"/> + <click selector="{{StoreConfigSection.Save}}" stepKey="clickSaveButton"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + </actionGroup> + <actionGroup name="saveStoreConfigurationAndValidateFieldError"> + <arguments> + <argument name="inputFieldError" type="string"/> + <argument name="errorMessageSelector" type="string"/> + <argument name="errorMessage" type="string"/> + </arguments> + <comment userInput="saveStoreConfigurationAndValidateFieldError" stepKey="comment"/> + <waitForElementVisible selector="{{StoreConfigSection.Save}}" stepKey="waitForSaveButton"/> + <click selector="{{StoreConfigSection.Save}}" stepKey="clickSaveButton"/> + <waitForElement selector="{{inputFieldError}}" stepKey="waitForErrorField"/> + <waitForElementVisible selector="{{errorMessageSelector}}" stepKey="waitForErrorMessage"/> + <see selector="{{errorMessageSelector}}" userInput="{{errorMessage}}" stepKey="seeErrorMessage"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminDeleteStoreViewActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminDeleteStoreViewActionGroup.xml index 849dc91efed..58e1781d69e 100644 --- a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminDeleteStoreViewActionGroup.xml +++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminDeleteStoreViewActionGroup.xml @@ -23,7 +23,7 @@ <click selector="{{AdminNewStoreViewActionsSection.delete}}" stepKey="clickDeleteStoreViewAgain"/> <waitForElementVisible selector="{{AdminConfirmationModalSection.title}}" stepKey="waitingForWarningModal"/> <click selector="{{AdminConfirmationModalSection.ok}}" stepKey="confirmStoreDelete"/> - <wait time="10" stepKey="extraWait"/> + <waitForPageLoad stepKey="waitForSuccessMessage"/> <see userInput="You deleted the store view." stepKey="seeDeleteMessage"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminStoreGroupCreateActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminStoreGroupCreateActionGroup.xml index 023d5fc3587..1a7f24ed2aa 100644 --- a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminStoreGroupCreateActionGroup.xml +++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminStoreGroupCreateActionGroup.xml @@ -24,4 +24,24 @@ <waitForElementVisible selector="{{AdminStoresGridSection.storeFilterTextField}}" stepKey="waitForPageReload"/> <see userInput="You saved the store." stepKey="seeSavedMessage" /> </actionGroup> + + <actionGroup name="AdminAddCustomWebSiteToStoreGroup"> + <arguments> + <argument name="storeGroup" defaultValue="customStoreGroup"/> + <argument name="website" defaultValue="customWebsite"/> + </arguments> + <amOnPage url="{{AdminSystemStorePage.url}}" stepKey="amOnAdminSystemStorePage"/> + <click selector="{{AdminStoresGridSection.resetButton}}" stepKey="resetSearchFilter"/> + <fillField userInput="{{storeGroup.name}}" selector="{{AdminStoresGridSection.storeGrpFilterTextField}}" stepKey="fillSearchStoreGroupField"/> + <click selector="{{AdminStoresGridSection.searchButton}}" stepKey="clickSearchButton"/> + <see userInput="{{storeGroup.name}}" selector="{{AdminStoresGridSection.storeGrpNameInFirstRow}}" stepKey="verifyThatCorrectStoreGroupFound"/> + <click selector="{{AdminStoresGridSection.storeGrpNameInFirstRow}}" stepKey="clickEditExistingStoreRow"/> + <waitForPageLoad stepKey="waitForStoreGroupPageLoad" /> + <selectOption selector="{{AdminNewStoreGroupSection.storeGrpWebsiteDropdown}}" userInput="{{website.name}}" stepKey="selectWebsite" /> + <selectOption selector="{{AdminNewStoreGroupSection.storeRootCategoryDropdown}}" userInput="Default Category" stepKey="chooseRootCategory" /> + <click selector="{{AdminNewStoreGroupActionsSection.saveButton}}" stepKey="clickSaveStoreGroup" /> + <conditionalClick selector="{{AdminNewStoreGroupSection.acceptNewStoreGroupCreation}}" dependentSelector="{{AdminNewStoreGroupSection.acceptNewStoreGroupCreation}}" visible="true" stepKey="clickAcceptNewStoreGroupCreationButton"/> + <waitForElementVisible selector="{{AdminStoresGridSection.storeFilterTextField}}" stepKey="waitForPageReload"/> + <see userInput="You saved the store." stepKey="seeSavedMessage" /> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminSwitchStoreViewActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminSwitchStoreViewActionGroup.xml index 860f094a48e..ac8e9d717fd 100644 --- a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminSwitchStoreViewActionGroup.xml +++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminSwitchStoreViewActionGroup.xml @@ -20,4 +20,8 @@ <waitForPageLoad stepKey="waitForStoreViewSwitched"/> <see userInput="{{storeView}}" selector="{{AdminMainActionsSection.storeSwitcher}}" stepKey="seeNewStoreViewName"/> </actionGroup> + <actionGroup name="AdminSwitchToAllStoreViewActionGroup" extends="AdminSwitchStoreViewActionGroup"> + <click selector="{{AdminMainActionsSection.allStoreViews}}" stepKey="clickStoreViewByName" after="waitForStoreViewsAreVisible"/> + <see selector="{{AdminMainActionsSection.storeSwitcher}}" userInput="All Store Views" stepKey="seeNewStoreViewName" after="waitForStoreViewSwitched"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminSwitchWebsiteActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminSwitchWebsiteActionGroup.xml new file mode 100644 index 00000000000..cfb2c7e6347 --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminSwitchWebsiteActionGroup.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminSwitchWebsiteActionGroup"> + <arguments> + <argument name="website"/> + </arguments> + <click selector="{{AdminMainActionsSection.storeViewDropdown}}" stepKey="clickWebsiteSwitchDropdown"/> + <waitForElementVisible selector="{{AdminMainActionsSection.websiteByName('Main Website')}}" stepKey="waitForWebsiteAreVisible"/> + <click selector="{{AdminMainActionsSection.websiteByName(website.name)}}" stepKey="clickWebsiteByName"/> + <waitForElementVisible selector="{{AdminConfirmationModalSection.ok}}" stepKey="waitingForInformationModal"/> + <click selector="{{AdminConfirmationModalSection.ok}}" stepKey="confirmStoreSwitch"/> + <see userInput="{{website.name}}" selector="{{AdminMainActionsSection.storeSwitcher}}" stepKey="seeNewWebsiteName"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/CreateCustomStoreViewActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/CreateCustomStoreViewActionGroup.xml index 31bbe7550e5..86d3963bc42 100644 --- a/app/code/Magento/Store/Test/Mftf/ActionGroup/CreateCustomStoreViewActionGroup.xml +++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/CreateCustomStoreViewActionGroup.xml @@ -5,6 +5,7 @@ * 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="CreateCustomStoreViewActionGroup"> @@ -18,7 +19,24 @@ <fillField userInput="{{customStore.code}}" selector="{{AdminNewStoreSection.storeCodeTextField}}" stepKey="fillStoreViewCode"/> <selectOption userInput="{{customStore.is_active}}" selector="{{AdminNewStoreSection.statusDropdown}}" stepKey="selectStoreViewStatus"/> <click selector="{{AdminStoresMainActionsSection.saveButton}}" stepKey="clickSaveStoreViewButton"/> + <waitForPageLoad stepKey="waitForPageLoad2"/> + <conditionalClick selector="{{AdminNewStoreSection.acceptNewStoreViewCreation}}" dependentSelector="{{AdminNewStoreSection.acceptNewStoreViewCreation}}" visible="true" stepKey="clickAcceptNewStoreViewCreationButton"/> + </actionGroup> + <actionGroup name="CreateStoreView"> + <arguments> + <argument name="storeView" defaultValue="customStore"/> + <argument name="storeGroupName" defaultValue="_defaultStoreGroup.name"/> + <argument name="storeViewStatus" defaultValue="_defaultStore.is_active"/> + </arguments> + <amOnPage url="{{AdminSystemStoreViewPage.url}}" stepKey="amOnAdminSystemStoreViewPage"/> + <waitForPageLoad stepKey="waitForProductPageLoad"/> + <selectOption userInput="{{storeGroupName}}" selector="{{AdminNewStoreSection.storeGrpDropdown}}" stepKey="selectStoreGroup"/> + <fillField userInput="{{storeView.name}}" selector="{{AdminNewStoreSection.storeNameTextField}}" stepKey="fillStoreViewName"/> + <fillField userInput="{{storeView.code}}" selector="{{AdminNewStoreSection.storeCodeTextField}}" stepKey="fillStoreViewCode"/> + <selectOption userInput="{{storeViewStatus}}" selector="{{AdminNewStoreSection.statusDropdown}}" stepKey="selectStoreViewStatus"/> + <click selector="{{AdminStoresMainActionsSection.saveButton}}" stepKey="clickSaveStoreViewButton"/> <waitForElementVisible selector="{{AdminNewStoreSection.acceptNewStoreViewCreation}}" stepKey="waitForAcceptNewStoreViewCreationButton" /> <conditionalClick selector="{{AdminNewStoreSection.acceptNewStoreViewCreation}}" dependentSelector="{{AdminNewStoreSection.acceptNewStoreViewCreation}}" visible="true" stepKey="clickAcceptNewStoreViewCreationButton"/> + <see userInput="You saved the store view." stepKey="seeSavedMessage"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/StorefrontSwitchStoreViewActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/StorefrontSwitchStoreViewActionGroup.xml index cfcd25086e0..efd3a8c6b8c 100644 --- a/app/code/Magento/Store/Test/Mftf/ActionGroup/StorefrontSwitchStoreViewActionGroup.xml +++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/StorefrontSwitchStoreViewActionGroup.xml @@ -14,7 +14,7 @@ </arguments> <click selector="{{StorefrontHeaderSection.storeViewSwitcher}}" stepKey="clickStoreViewSwitcher"/> <waitForElementVisible selector="{{StorefrontHeaderSection.storeViewDropdown}}" stepKey="waitForStoreViewDropdown"/> - <click selector="{{StorefrontHeaderSection.storeViewOption(storeView.name)}}" stepKey="clickSelectStoreView"/> + <click selector="{{StorefrontHeaderSection.storeViewOption(storeView.code)}}" stepKey="clickSelectStoreView"/> <waitForPageLoad stepKey="waitForPageLoad"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/Store/Test/Mftf/Data/StoreData.xml b/app/code/Magento/Store/Test/Mftf/Data/StoreData.xml index 9fae618020f..4e043f9ff27 100644 --- a/app/code/Magento/Store/Test/Mftf/Data/StoreData.xml +++ b/app/code/Magento/Store/Test/Mftf/Data/StoreData.xml @@ -22,8 +22,8 @@ <requiredEntity type="storeGroup">customStoreGroup</requiredEntity> </entity> <entity name="customStoreEN" type="store"> - <data key="name">EN</data> - <data key="code">en</data> + <data key="name" unique="suffix">EN</data> + <data key="code" unique="suffix">en</data> <data key="is_active">1</data> <data key="store_id">null</data> <data key="store_action">add</data> @@ -31,14 +31,32 @@ <requiredEntity type="storeGroup">customStoreGroup</requiredEntity> </entity> <entity name="customStoreFR" type="store"> - <data key="name">FR</data> - <data key="code">fr</data> + <data key="name" unique="suffix">FR</data> + <data key="code" unique="suffix">fr</data> <data key="is_active">1</data> <data key="store_id">null</data> <data key="store_action">add</data> <data key="store_type">group</data> <requiredEntity type="storeGroup">customStoreGroup</requiredEntity> </entity> + <entity name="customStoreENNotUnique" type="store"> + <data key="name">EN</data> + <data key="code">en</data> + <data key="is_active">1</data> + <data key="store_id">null</data> + <data key="store_action">add</data> + <data key="store_type">store</data> + <requiredEntity type="storeGroup">customStoreGroup</requiredEntity> + </entity> + <entity name="customStoreNLNotUnique" type="store"> + <data key="name">NL</data> + <data key="code">nl</data> + <data key="is_active">1</data> + <data key="store_id">null</data> + <data key="store_action">add</data> + <data key="store_type">store</data> + <requiredEntity type="storeGroup">customStoreGroup</requiredEntity> + </entity> <entity name="staticStore" type="store"> <!--data key="group_id">customStoreGroup.id</data--> <data key="name">Second Store View</data> @@ -65,4 +83,87 @@ <data key="name" unique="suffix">StoreView</data> <data key="code" unique="suffix">StoreViewCode</data> </entity> + + <!-- For creation 10 Store Views--> + <entity name="storeViewData" type="store"> + <data key="group_id">1</data> + <data key="name" unique="suffix">storeView</data> + <data key="code" unique="suffix">storeView</data> + <data key="is_active">1</data> + <data key="store_id">null</data> + <data key="store_type">store</data> + <data key="store_action">add</data> + </entity> + <entity name="storeViewData1" type="store"> + <data key="group_id">1</data> + <data key="name" unique="suffix">storeView</data> + <data key="code" unique="suffix">storeView</data> + <data key="is_active">1</data> + <data key="store_id">null</data> + <data key="store_type">store</data> + <data key="store_action">add</data> + </entity> + <entity name="storeViewData2" type="store"> + <data key="group_id">1</data> + <data key="name" unique="suffix">storeView</data> + <data key="code" unique="suffix">storeView</data> + <data key="is_active">1</data> + <data key="store_id">null</data> + <data key="store_type">store</data> + <data key="store_action">add</data> + </entity> + <entity name="storeViewData3" type="store"> + <data key="group_id">1</data> + <data key="name" unique="suffix">storeView</data> + <data key="code" unique="suffix">storeView</data> + <data key="is_active">1</data> + <data key="store_id">null</data> + <data key="store_type">store</data> + <data key="store_action">add</data> + </entity> + <entity name="storeViewData4" type="store"> + <data key="group_id">1</data> + <data key="name" unique="suffix">storeView</data> + <data key="code" unique="suffix">storeView</data> + <data key="is_active">1</data> + <data key="store_id">null</data> + <data key="store_type">store</data> + <data key="store_action">add</data> + </entity> + <entity name="storeViewData5" type="store"> + <data key="group_id">1</data> + <data key="name" unique="suffix">storeView</data> + <data key="code" unique="suffix">storeView</data> + <data key="is_active">1</data> + <data key="store_id">null</data> + <data key="store_type">store</data> + <data key="store_action">add</data> + </entity> + <entity name="storeViewData6" type="store"> + <data key="group_id">1</data> + <data key="name" unique="suffix">storeView</data> + <data key="code" unique="suffix">storeView</data> + <data key="is_active">1</data> + <data key="store_id">null</data> + <data key="store_type">store</data> + <data key="store_action">add</data> + </entity> + <entity name="storeViewData7" type="store"> + <data key="group_id">1</data> + <data key="name" unique="suffix">storeView</data> + <data key="code" unique="suffix">storeView</data> + <data key="is_active">1</data> + <data key="store_id">null</data> + <data key="store_type">store</data> + <data key="store_action">add</data> + </entity> + <entity name="SecondStoreUnique" type="store"> + <data key="name" unique="suffix">Second Store View </data> + <data key="code" unique="suffix">second_store_view_</data> + <data key="is_active">1</data> + <data key="store_id">null</data> + <data key="store_action">add</data> + <data key="store_type">store</data> + <requiredEntity type="storeGroup">SecondStoreGroup</requiredEntity> + </entity> </entities> diff --git a/app/code/Magento/Store/Test/Mftf/Data/StoreGroupData.xml b/app/code/Magento/Store/Test/Mftf/Data/StoreGroupData.xml index e575ca3317a..7cbd686dea0 100644 --- a/app/code/Magento/Store/Test/Mftf/Data/StoreGroupData.xml +++ b/app/code/Magento/Store/Test/Mftf/Data/StoreGroupData.xml @@ -21,12 +21,41 @@ <data key="store_action">add</data> <data key="store_type">group</data> </entity> + <entity name="SecondStoreGroup" type="group"> + <data key="group_id">null</data> + <data key="name">Second Store</data> + <data key="code">second_store</data> + <var key="root_category_id" entityKey="id" entityType="category"/> + <data key="store_action">add</data> + <data key="store_type">group</data> + </entity> + <entity name="SecondStoreGroupUnique" type="group"> + <data key="group_id">null</data> + <data key="name" unique="suffix">Second Store </data> + <data key="code" unique="suffix">second_store_</data> + <var key="root_category_id" entityKey="id" entityType="category"/> + <data key="store_action">add</data> + <data key="store_type">group</data> + </entity> <entity name="staticStoreGroup" type="group"> <data key="name">NewStore</data> <data key="code" unique="suffix">Base12</data> <data key="root_category_id">2</data> <data key="website_id">1</data> </entity> + <entity name="finnishStoreGroup" type="group"> + <data key="name">Finnish</data> + <data key="code">fin</data> + <data key="root_category_id">2</data> + <data key="website_id">1</data> + </entity> + <entity name="swedishStoreGroup" type="group"> + <data key="name">Swedish</data> + <data key="code">swd</data> + <data key="root_category_id">2</data> + <data key="website_id">1</data> + </entity> + <entity name="staticFirstStoreGroup" extends="staticStoreGroup"> <data key="name">NewStore</data> <data key="code">Base1</data> diff --git a/app/code/Magento/Store/Test/Mftf/Data/StoreShippingMethodsData.xml b/app/code/Magento/Store/Test/Mftf/Data/StoreShippingMethodsData.xml index 6e3e7ce7eb0..bc9746c132d 100644 --- a/app/code/Magento/Store/Test/Mftf/Data/StoreShippingMethodsData.xml +++ b/app/code/Magento/Store/Test/Mftf/Data/StoreShippingMethodsData.xml @@ -27,4 +27,18 @@ <entity name="disableFreeShipping" type="disableFreeShipping"> <data key="value">1</data> </entity> + + <entity name="MinimumOrderAmount90" type="minimum_order_amount"> + <requiredEntity type="free_shipping_subtotal">Price</requiredEntity> + </entity> + <entity name="Price" type="free_shipping_subtotal"> + <data key="value">90</data> + </entity> + + <entity name="DefaultMinimumOrderAmount" type="minimum_order_amount"> + <requiredEntity type="free_shipping_subtotal">DefaultPrice</requiredEntity> + </entity> + <entity name="DefaultPrice" type="free_shipping_subtotal"> + <data key="value">0</data> + </entity> </entities> diff --git a/app/code/Magento/Store/Test/Mftf/Metadata/store_shipping_methods-meta.xml b/app/code/Magento/Store/Test/Mftf/Metadata/store_shipping_methods-meta.xml index 83288ecfdaf..6f88bca7602 100644 --- a/app/code/Magento/Store/Test/Mftf/Metadata/store_shipping_methods-meta.xml +++ b/app/code/Magento/Store/Test/Mftf/Metadata/store_shipping_methods-meta.xml @@ -34,5 +34,16 @@ </object> </operation> + <operation name="MinimumOrderAmount" dataType="minimum_order_amount" type="create" auth="adminFormKey" url="/admin/system_config/save/section/carriers/" method="POST"> + <object key="groups" dataType="minimum_order_amount"> + <object key="freeshipping" dataType="minimum_order_amount"> + <object key="fields" dataType="minimum_order_amount"> + <object key="free_shipping_subtotal" dataType="free_shipping_subtotal"> + <field key="value">string</field> + </object> + </object> + </object> + </object> + </operation> </operations> diff --git a/app/code/Magento/Store/Test/Mftf/Section/AdminMainActionsSection.xml b/app/code/Magento/Store/Test/Mftf/Section/AdminMainActionsSection.xml index fda182246db..14160835af3 100644 --- a/app/code/Magento/Store/Test/Mftf/Section/AdminMainActionsSection.xml +++ b/app/code/Magento/Store/Test/Mftf/Section/AdminMainActionsSection.xml @@ -11,6 +11,8 @@ <section name="AdminMainActionsSection"> <element name="storeSwitcher" type="text" selector=".store-switcher"/> <element name="storeViewDropdown" type="button" selector="#store-change-button"/> - <element name="storeViewByName" type="button" selector="//*[@class='store-switcher-store-view ']/a[contains(text(), '{{storeViewName}}')]" timeout="30" parameterized="true"/> + <element name="storeViewByName" type="button" selector="//*[contains(@class,'store-switcher-store-view')]/*[contains(text(), '{{storeViewName}}')]" timeout="30" parameterized="true"/> + <element name="websiteByName" type="button" selector="//*[@class='store-switcher-website ']/a[contains(text(), '{{websiteName}}')]" timeout="30" parameterized="true"/> + <element name="allStoreViews" type="button" selector=".store-switcher .store-switcher-all" timeout="30"/> </section> </sections> diff --git a/app/code/Magento/Store/Test/Mftf/Section/AdminNewStoreGroupSection.xml b/app/code/Magento/Store/Test/Mftf/Section/AdminNewStoreGroupSection.xml index ea5d9aab8b2..fb98c669837 100644 --- a/app/code/Magento/Store/Test/Mftf/Section/AdminNewStoreGroupSection.xml +++ b/app/code/Magento/Store/Test/Mftf/Section/AdminNewStoreGroupSection.xml @@ -11,5 +11,6 @@ <element name="storeGrpNameTextField" type="input" selector="#group_name"/> <element name="storeGrpCodeTextField" type="input" selector="#group_code"/> <element name="storeRootCategoryDropdown" type="select" selector="#group_root_category_id"/> + <element name="acceptNewStoreGroupCreation" type="button" selector=".action-primary.action-accept" /> </section> </sections> diff --git a/app/code/Magento/Store/Test/Mftf/Section/AdminStoresGridSection.xml b/app/code/Magento/Store/Test/Mftf/Section/AdminStoresGridSection.xml index 1b84027a5dd..b02e9adaed4 100644 --- a/app/code/Magento/Store/Test/Mftf/Section/AdminStoresGridSection.xml +++ b/app/code/Magento/Store/Test/Mftf/Section/AdminStoresGridSection.xml @@ -14,7 +14,7 @@ <section name="AdminStoresGridSection"> <element name="storeGrpFilterTextField" type="input" selector="#storeGrid_filter_group_title"/> <element name="websiteFilterTextField" type="input" selector="#storeGrid_filter_website_title"/> - <element name="storeFilterTextField" type="input" selector="#storeGrid_filter_store_title"/> + <element name="storeFilterTextField" type="input" selector="#storeGrid_filter_store_title" timeout="90"/> <element name="searchButton" type="button" selector=".admin__data-grid-header button[title=Search]" timeout="30"/> <element name="resetButton" type="button" selector="button[title='Reset Filter']" timeout="30"/> <element name="websiteNameInFirstRow" type="text" selector=".col-website_title>a"/> diff --git a/app/code/Magento/Store/Test/Mftf/Section/AdminStoresMainActionsSection.xml b/app/code/Magento/Store/Test/Mftf/Section/AdminStoresMainActionsSection.xml index 98ad1db4673..e40aa76967b 100644 --- a/app/code/Magento/Store/Test/Mftf/Section/AdminStoresMainActionsSection.xml +++ b/app/code/Magento/Store/Test/Mftf/Section/AdminStoresMainActionsSection.xml @@ -10,7 +10,7 @@ <element name="createStoreViewButton" type="button" selector="#add_store" timeout="30"/> <element name="createStoreButton" type="button" selector="#add_group" timeout="30"/> <element name="createWebsiteButton" type="button" selector="#add" timeout="30"/> - <element name="saveButton" type="button" selector="#save" timeout="30"/> + <element name="saveButton" type="button" selector="#save" timeout="90"/> <element name="backButton" type="button" selector="#back" timeout="30"/> <element name="deleteButton" type="button" selector="#delete" timeout="30"/> </section> diff --git a/app/code/Magento/Store/Test/Mftf/Section/StorefrontHeaderSection.xml b/app/code/Magento/Store/Test/Mftf/Section/StorefrontHeaderSection.xml index af18e858e10..bee9a79abeb 100644 --- a/app/code/Magento/Store/Test/Mftf/Section/StorefrontHeaderSection.xml +++ b/app/code/Magento/Store/Test/Mftf/Section/StorefrontHeaderSection.xml @@ -11,5 +11,7 @@ <element name="storeViewSwitcher" type="button" selector="#switcher-language-trigger"/> <element name="storeViewDropdown" type="button" selector="ul.switcher-dropdown"/> <element name="storeViewOption" type="button" selector="li.view-{{var1}}>a" parameterized="true"/> + <element name="storeView" type="button" selector="//div[@class='actions dropdown options switcher-options active']//ul//li//a[contains(text(),'{{var}}')]" parameterized="true"/> + <element name="storeViewList" type="button" selector="//li[contains(.,'{{storeViewName}}')]//a" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/Store/Test/Mftf/Test/AdminCreateStoreViewTest.xml b/app/code/Magento/Store/Test/Mftf/Test/AdminCreateStoreViewTest.xml index e7a3d03f337..af8aceed07f 100644 --- a/app/code/Magento/Store/Test/Mftf/Test/AdminCreateStoreViewTest.xml +++ b/app/code/Magento/Store/Test/Mftf/Test/AdminCreateStoreViewTest.xml @@ -33,6 +33,9 @@ <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createStoreView2" /> <actionGroup ref="AdminCreateStoreViewCodeUniquenessActionGroup" stepKey="createStoreViewCode" /> <after> + <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteStoreView"> + <argument name="customStore" value="customStore"/> + </actionGroup> <actionGroup ref="logout" stepKey="logout"/> </after> </test> diff --git a/app/code/Magento/Store/etc/di.xml b/app/code/Magento/Store/etc/di.xml index be005264b7b..defe0694d01 100644 --- a/app/code/Magento/Store/etc/di.xml +++ b/app/code/Magento/Store/etc/di.xml @@ -236,6 +236,7 @@ <type name="Magento\Framework\App\ScopeResolverPool"> <arguments> <argument name="scopeResolvers" xsi:type="array"> + <item name="default" xsi:type="object">Magento\Framework\App\ScopeResolver</item> <item name="store" xsi:type="object">Magento\Store\Model\Resolver\Store</item> <item name="stores" xsi:type="object">Magento\Store\Model\Resolver\Store</item> <item name="group" xsi:type="object">Magento\Store\Model\Resolver\Group</item> diff --git a/app/code/Magento/Store/etc/frontend/di.xml b/app/code/Magento/Store/etc/frontend/di.xml index c39d5df8639..917aedad3d9 100644 --- a/app/code/Magento/Store/etc/frontend/di.xml +++ b/app/code/Magento/Store/etc/frontend/di.xml @@ -9,7 +9,7 @@ <type name="Magento\Framework\App\FrontController"> <plugin name="requestPreprocessor" type="Magento\Store\App\FrontController\Plugin\RequestPreprocessor" sortOrder="50"/> </type> - <type name="Magento\Framework\App\Action\Action"> + <type name="Magento\Framework\App\Action\AbstractAction"> <plugin name="contextPlugin" type="Magento\Store\App\Action\Plugin\Context" sortOrder="10"/> </type> <type name="Magento\Framework\App\RouterList" shared="true"> diff --git a/app/code/Magento/StoreGraphQl/etc/schema.graphqls b/app/code/Magento/StoreGraphQl/etc/schema.graphqls index af79d0e3e28..d9f7eaaaa29 100644 --- a/app/code/Magento/StoreGraphQl/etc/schema.graphqls +++ b/app/code/Magento/StoreGraphQl/etc/schema.graphqls @@ -6,10 +6,10 @@ type Query { type Website @doc(description: "The type contains information about a website") { id : Int @doc(description: "The ID number assigned to the website") - name : String @doc(description: "The website name. Websites use this name to identify it easyer.") + name : String @doc(description: "The website name. Websites use this name to identify it easier.") code : String @doc(description: "A code assigned to the website to identify it") sort_order : Int @doc(description: "The attribute to use for sorting websites") - default_group_id : String @doc(description: "The default group id that the website has") + default_group_id : String @doc(description: "The default group ID that the website has") is_default : Boolean @doc(description: "Specifies if this is the default website") } diff --git a/app/code/Magento/Swatches/Block/Product/Renderer/Listing/Configurable.php b/app/code/Magento/Swatches/Block/Product/Renderer/Listing/Configurable.php index e13373fb725..2e4980c2fbf 100644 --- a/app/code/Magento/Swatches/Block/Product/Renderer/Listing/Configurable.php +++ b/app/code/Magento/Swatches/Block/Product/Renderer/Listing/Configurable.php @@ -8,7 +8,8 @@ use Magento\Catalog\Block\Product\Context; use Magento\Catalog\Helper\Product as CatalogProduct; use Magento\Catalog\Model\Product; -use Magento\Catalog\Model\Product\Image\UrlBuilder; +use Magento\Catalog\Model\Layer\Resolver; +use Magento\Catalog\Model\Layer\Category as CategoryLayer; use Magento\ConfigurableProduct\Helper\Data; use Magento\ConfigurableProduct\Model\ConfigurableAttributeData; use Magento\Customer\Helper\Session\CurrentCustomer; @@ -39,6 +40,11 @@ class Configurable extends \Magento\Swatches\Block\Product\Renderer\Configurable */ private $variationPrices; + /** + * @var \Magento\Catalog\Model\Layer\Resolver + */ + private $layerResolver; + /** * @SuppressWarnings(PHPMD.ExcessiveParameterList) * @param Context $context @@ -55,6 +61,7 @@ class Configurable extends \Magento\Swatches\Block\Product\Renderer\Configurable * @param SwatchAttributesProvider|null $swatchAttributesProvider * @param \Magento\Framework\Locale\Format|null $localeFormat * @param \Magento\ConfigurableProduct\Model\Product\Type\Configurable\Variations\Prices|null $variationPrices + * @param Resolver $layerResolver */ public function __construct( Context $context, @@ -70,7 +77,8 @@ public function __construct( array $data = [], SwatchAttributesProvider $swatchAttributesProvider = null, \Magento\Framework\Locale\Format $localeFormat = null, - \Magento\ConfigurableProduct\Model\Product\Type\Configurable\Variations\Prices $variationPrices = null + \Magento\ConfigurableProduct\Model\Product\Type\Configurable\Variations\Prices $variationPrices = null, + Resolver $layerResolver = null ) { parent::__construct( $context, @@ -92,10 +100,11 @@ public function __construct( $this->variationPrices = $variationPrices ?: ObjectManager::getInstance()->get( \Magento\ConfigurableProduct\Model\Product\Type\Configurable\Variations\Prices::class ); + $this->layerResolver = $layerResolver ?: ObjectManager::getInstance()->get(Resolver::class); } /** - * @return string + * @inheritdoc */ protected function getRendererTemplate() { @@ -121,7 +130,7 @@ protected function _toHtml() } /** - * @return array + * @inheritdoc */ protected function getSwatchAttributesData() { @@ -183,6 +192,7 @@ protected function getOptionImages() * Add images to result json config in case of Layered Navigation is used * * @return array + * @SuppressWarnings(PHPMD.RequestAwareBlockMethod) * @since 100.2.0 */ protected function _getAdditionalConfig() @@ -247,4 +257,16 @@ private function getLayeredAttributesIfExists(Product $configurableProduct, arra return $layeredAttributes; } + + /** + * @inheritdoc + */ + public function getCacheKeyInfo() + { + $cacheKeyInfo = parent::getCacheKeyInfo(); + /** @var CategoryLayer $catalogLayer */ + $catalogLayer = $this->layerResolver->get(); + $cacheKeyInfo[] = $catalogLayer->getStateKey(); + return $cacheKeyInfo; + } } diff --git a/app/code/Magento/Swatches/Helper/Data.php b/app/code/Magento/Swatches/Helper/Data.php index 69217ea3777..f9a600925b2 100644 --- a/app/code/Magento/Swatches/Helper/Data.php +++ b/app/code/Magento/Swatches/Helper/Data.php @@ -254,18 +254,15 @@ public function loadVariationByFallback(Product $parentProduct, array $attribute $this->addFilterByParent($productCollection, $parentId); $configurableAttributes = $this->getAttributesFromConfigurable($parentProduct); - $allAttributesArray = []; + + $resultAttributesToFilter = []; foreach ($configurableAttributes as $attribute) { - if (!empty($attribute['default_value'])) { - $allAttributesArray[$attribute['attribute_code']] = $attribute['default_value']; + $attributeCode = $attribute->getData('attribute_code'); + if (array_key_exists($attributeCode, $attributes)) { + $resultAttributesToFilter[$attributeCode] = $attributes[$attributeCode]; } } - $resultAttributesToFilter = array_merge( - $attributes, - array_diff_key($allAttributesArray, $attributes) - ); - $this->addFilterByAttributes($productCollection, $resultAttributesToFilter); $variationProduct = $productCollection->getFirstItem(); diff --git a/app/code/Magento/Swatches/Model/ResourceModel/Swatch.php b/app/code/Magento/Swatches/Model/ResourceModel/Swatch.php index 9dc5b3a0c81..9ad62265be2 100644 --- a/app/code/Magento/Swatches/Model/ResourceModel/Swatch.php +++ b/app/code/Magento/Swatches/Model/ResourceModel/Swatch.php @@ -7,8 +7,9 @@ namespace Magento\Swatches\Model\ResourceModel; /** - * @codeCoverageIgnore * Swatch Resource Model + * + * @codeCoverageIgnore * @api * @since 100.0.2 */ @@ -25,8 +26,10 @@ protected function _construct() } /** - * @param string $defaultValue + * Update default swatch option value. + * * @param integer $id + * @param string $defaultValue * @return void */ public function saveDefaultSwatchOption($id, $defaultValue) @@ -49,7 +52,7 @@ public function clearSwatchOptionByOptionIdAndType($optionIDs, $type = null) { if (count($optionIDs)) { foreach ($optionIDs as $optionId) { - $where = ['option_id' => $optionId]; + $where = ['option_id = ?' => $optionId]; if ($type !== null) { $where['type = ?'] = $type; } diff --git a/app/code/Magento/Swatches/Test/Mftf/ActionGroup/AddSwatchToProductActionGroup.xml b/app/code/Magento/Swatches/Test/Mftf/ActionGroup/AddSwatchToProductActionGroup.xml index 60a8035dede..2c91bba75fe 100644 --- a/app/code/Magento/Swatches/Test/Mftf/ActionGroup/AddSwatchToProductActionGroup.xml +++ b/app/code/Magento/Swatches/Test/Mftf/ActionGroup/AddSwatchToProductActionGroup.xml @@ -62,4 +62,21 @@ <seeElement selector="{{AdminMessagesSection.success}}" stepKey="seeSaveProductMessage"/> </actionGroup> + <actionGroup name="AddVisualSwatchToProductWithStorefrontConfigActionGroup" extends="AddVisualSwatchToProductActionGroup"> + <arguments> + <argument name="attribute" defaultValue="visualSwatchAttribute"/> + <argument name="option1" defaultValue="visualSwatchOption1"/> + <argument name="option2" defaultValue="visualSwatchOption2"/> + </arguments> + + <!-- Go to Storefront Properties tab --> + <click selector="{{AdminNewAttributePanel.storefrontPropertiesTab}}" stepKey="goToStorefrontPropertiesTab" after="fillDefaultStoreLabel2"/> + <waitForElementVisible selector="{{AdminNewAttributePanel.storefrontPropertiesTitle}}" stepKey="waitTabLoad" after="goToStorefrontPropertiesTab"/> + <selectOption selector="{{AdminNewAttributePanel.useInSearch}}" stepKey="switchOnUsInSearch" userInput="Yes" after="waitTabLoad"/> + <selectOption selector="{{AdminNewAttributePanel.visibleInAdvancedSearch}}" stepKey="switchOnVisibleInAdvancedSearch" userInput="Yes" after="switchOnUsInSearch"/> + <selectOption selector="{{AdminNewAttributePanel.comparableOnStorefront}}" stepKey="switchOnComparableOnStorefront" userInput="Yes" after="switchOnVisibleInAdvancedSearch"/> + <selectOption selector="{{AdminNewAttributePanel.useInLayeredNavigation}}" stepKey="selectUseInLayer" userInput="Filterable (with results)" after="switchOnComparableOnStorefront"/> + <selectOption selector="{{AdminNewAttributePanel.visibleOnCatalogPagesOnStorefront}}" stepKey="switchOnVisibleOnCatalogPagesOnStorefront" userInput="Yes" after="selectUseInLayer"/> + <selectOption selector="{{AdminNewAttributePanel.useInProductListing}}" stepKey="switchOnUsedInProductListing" userInput="Yes" after="switchOnVisibleOnCatalogPagesOnStorefront"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/Swatches/Test/Mftf/Section/StorefrontProductInfoMainSection.xml b/app/code/Magento/Swatches/Test/Mftf/Section/StorefrontProductInfoMainSection.xml index 415ae88fceb..e40a0408028 100644 --- a/app/code/Magento/Swatches/Test/Mftf/Section/StorefrontProductInfoMainSection.xml +++ b/app/code/Magento/Swatches/Test/Mftf/Section/StorefrontProductInfoMainSection.xml @@ -14,5 +14,6 @@ <element name="selectedSwatchValue" type="text" selector="//div[contains(@class, 'swatch-attribute') and contains(., '{{attr}}')]//span[contains(@class, 'swatch-attribute-selected-option')]" parameterized="true"/> <element name="swatchAttributeOptions" type="text" selector="div.swatch-attribute-options"/> <element name="nthSwatchOptionText" type="button" selector="div.swatch-option.text:nth-of-type({{n}})" parameterized="true"/> + <element name="productSwatch" type="button" selector="//div[@class='swatch-option'][@aria-label='{{var1}}']" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontSwatchAttributesDisplayInWidgetCMSTest.xml b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontSwatchAttributesDisplayInWidgetCMSTest.xml new file mode 100644 index 00000000000..1ab2cd793f3 --- /dev/null +++ b/app/code/Magento/Swatches/Test/Mftf/Test/StorefrontSwatchAttributesDisplayInWidgetCMSTest.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="StorefrontSwatchAttributesDisplayInWidgetCMSTest"> + <annotations> + <features value="ConfigurableProduct"/> + <title value="Swatch Attribute is not displayed in the Widget CMS"/> + <description value="Swatch Attribute is not displayed in the Widget CMS"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-96469"/> + <useCaseId value="MAGETWO-96406"/> + <group value="ConfigurableProduct"/> + <skip> + <issueId value="MQE-1424" /> + </skip> + </annotations> + + <before> + <magentoCLI command="config:set cms/wysiwyg/enabled disabled" stepKey="disableWYSIWYG"/> + <createData entity="NewRootCategory" stepKey="createRootCategory"/> + </before> + + <after> + <!--delete created configurable product--> + <actionGroup ref="deleteProductUsingProductGrid" stepKey="deleteProduct"> + <argument name="product" value="BaseConfigurableProduct"/> + </actionGroup> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="visitAdminProductPage"/> + <waitForPageLoad stepKey="waitForAdminProductGridLoad"/> + <conditionalClick selector="{{AdminProductGridFilterSection.clearFilters}}" dependentSelector="{{AdminProductGridFilterSection.clearFilters}}" visible="true" stepKey="clickClearFiltersInitial"/> + <actionGroup ref="deleteProductAttributeByLabel" stepKey="deleteAttribute"> + <argument name="ProductAttribute" value="visualSwatchAttribute"/> + </actionGroup> + <!--delete root category--> + <amOnPage url="{{AdminCategoryPage.url}}" stepKey="navigateToCategoryPage"/> + <waitForPageLoad time="30" stepKey="waitForPageCategoryLoad"/> + <click selector="{{AdminCategorySidebarTreeSection.categoryInTree('$$createRootCategory.name$$')}}" stepKey="clickOnDefaultRootCategory"/> + <waitForPageLoad stepKey="waitForPageDefaultCategoryEditLoad" /> + <seeElement selector="{{AdminCategoryMainActionsSection.DeleteButton}}" stepKey="assertDeleteButtonIsPresent1"/> + <click selector="{{AdminCategoryMainActionsSection.DeleteButton}}" stepKey="DeleteDefaultRootCategory"/> + <waitForElementVisible selector="{{AdminCategoryModalSection.ok}}" stepKey="waitForModalDeleteDefaultRootCategory" /> + <click selector="{{AdminCategoryModalSection.ok}}" stepKey="acceptModal1"/> + <waitForElementVisible selector="{{AdminCategoryMessagesSection.SuccessMessage}}" stepKey="waitForPageReloadAfterDeleteDefaultCategory"/> + <magentoCLI command="config:set cms/wysiwyg/enabled enabled" stepKey="enableWYSIWYG"/> + <!--logout--> + <actionGroup ref="logout" stepKey="logoutFromAdmin"/> + </after> + <!--Login--> + <actionGroup ref="LoginAsAdmin" stepKey="loginToAdmin"/> + <!--Create a configurable swatch product via the UI --> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="goToProductIndex"/> + <waitForPageLoad stepKey="waitForProductPage"/> + <actionGroup ref="goToCreateProductPage" stepKey="goToCreateProductPage"> + <argument name="product" value="BaseConfigurableProduct"/> + </actionGroup> + <actionGroup ref="fillMainProductForm" stepKey="fillProductForm"> + <argument name="product" value="BaseConfigurableProduct"/> + </actionGroup> + <searchAndMultiSelectOption selector="{{AdminProductFormSection.categoriesDropdown}}" parameterArray="[$$createRootCategory.name$$]" stepKey="searchAndSelectCategory"/> + <!--Add swatch attribute to configurable product--> + <actionGroup ref="AddVisualSwatchToProductWithStorefrontConfigActionGroup" stepKey="addSwatchToProduct"/> + + <!--Create CMS page--> + <actionGroup ref="CreateNewPageWithWidget" stepKey="createCMSPageWithWidget"> + <argument name="category" value="$$createRootCategory.name$$"/> + <argument name="condition" value="Category"/> + <argument name="widgetType" value="Catalog Products List"/> + </actionGroup> + <click selector="{{CmsNewPagePageSeoSection.header}}" stepKey="clickToExpandSEOSection"/> + <scrollTo selector="{{CmsNewPagePageSeoSection.urlKey}}" stepKey="scrollToUrlKey"/> + <grabValueFrom selector="{{CmsNewPagePageSeoSection.urlKey}}" stepKey="grabTextFromUrlKey"/> + <actionGroup ref="logout" stepKey="logout"/> + + <!--Open Storefront page for the new created page--> + <amOnPage url="{{StorefrontHomePage.url}}$grabTextFromUrlKey" stepKey="gotToCreatedCmsPage"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <seeElement selector="{{StorefrontProductInfoMainSection.productSwatch(visualSwatchOption1.default_label)}}" stepKey="assertAddedWidgetS"/> + <seeElement selector="{{StorefrontProductInfoMainSection.productSwatch(visualSwatchOption2.default_label)}}" stepKey="assertAddedWidgetM"/> + + <!--Login to delete CMS page--> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <actionGroup ref="DeletePageByUrlKeyActionGroup" stepKey="deletePage"> + <argument name="UrlKey" value="$grabTextFromUrlKey"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Swatches/view/adminhtml/templates/catalog/product/attribute/text.phtml b/app/code/Magento/Swatches/view/adminhtml/templates/catalog/product/attribute/text.phtml index 8d4400b3d04..e00c41d371c 100644 --- a/app/code/Magento/Swatches/view/adminhtml/templates/catalog/product/attribute/text.phtml +++ b/app/code/Magento/Swatches/view/adminhtml/templates/catalog/product/attribute/text.phtml @@ -21,7 +21,7 @@ $stores = $block->getStoresSortedBySortOrder(); <th class="col-draggable"></th> <th class="col-default"><span><?= $block->escapeHtml(__('Is Default')) ?></span></th> <?php foreach ($stores as $_store): ?> - <th class="col-swatch col-<%- data.id %> + <th class="col-swatch col-swatch-min-width col-<%- data.id %> <?php if ($_store->getId() == \Magento\Store\Model\Store::DEFAULT_STORE_ID): ?> _required<?php endif; ?>" colspan="2"> <span><?= $block->escapeHtml($_store->getName()) ?></span> @@ -75,7 +75,7 @@ $stores = $block->getStoresSortedBySortOrder(); </td> <?php foreach ($stores as $_store): ?> <?php $storeId = (int)$_store->getId(); ?> - <td class="col-swatch col-<%- data.id %>"> + <td class="col-swatch col-swatch-min-width col-<%- data.id %>"> <input class="input-text swatch-text-field-<?= /* @noEscape */ $storeId ?> <?php if ($storeId == \Magento\Store\Model\Store::DEFAULT_STORE_ID): ?> required-option required-unique<?php endif; ?>" @@ -83,7 +83,7 @@ $stores = $block->getStoresSortedBySortOrder(); type="text" value="<%- data.swatch<?= /* @noEscape */ $storeId ?> %>" placeholder="<?= $block->escapeHtml(__("Swatch")) ?>"/> </td> - <td class="swatch-col-<%- data.id %>"> + <td class="col-swatch-min-width swatch-col-<%- data.id %>"> <input name="optiontext[value][<%- data.id %>][<?= /* @noEscape */ $storeId ?>]" value="<%- data.store<?= /* @noEscape */ $storeId ?> %>" class="input-text<?php if ($storeId == \Magento\Store\Model\Store::DEFAULT_STORE_ID): ?> required-option<?php endif; ?>" diff --git a/app/code/Magento/Swatches/view/adminhtml/web/css/swatches.css b/app/code/Magento/Swatches/view/adminhtml/web/css/swatches.css index d170ed0345a..ef635c48e34 100644 --- a/app/code/Magento/Swatches/view/adminhtml/web/css/swatches.css +++ b/app/code/Magento/Swatches/view/adminhtml/web/css/swatches.css @@ -149,6 +149,14 @@ width: 50px; } +.col-swatch-min-width { + min-width: 65px; +} + +.data-table .col-swatch-min-width input[type="text"] { + padding: inherit; +} + .swatches-visual-col.unavailable:after { content: ''; position: absolute; diff --git a/app/code/Magento/Swatches/view/frontend/layout/catalog_widget_product_list.xml b/app/code/Magento/Swatches/view/frontend/layout/catalog_widget_product_list.xml new file mode 100644 index 00000000000..91798cbd994 --- /dev/null +++ b/app/code/Magento/Swatches/view/frontend/layout/catalog_widget_product_list.xml @@ -0,0 +1,12 @@ +<!-- + ~ 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> + <referenceBlock name="category.product.type.widget.details.renderers"> + <block class="Magento\Swatches\Block\Product\Renderer\Listing\Configurable" name="category.product.type.details.renderers.configurable" as="configurable" template="Magento_Swatches::product/listing/renderer.phtml" ifconfig="catalog/frontend/show_swatches_in_product_list"/> + </referenceBlock> + </body> +</page> \ No newline at end of file diff --git a/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js b/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js index 938028f6250..7b69edf6b93 100644 --- a/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js +++ b/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js @@ -1029,14 +1029,10 @@ define([ _.each(allowedProducts, function (allowedProduct) { optionFinalPrice = parseFloat(optionPrices[allowedProduct].finalPrice.amount); - if (_.isEmpty(product)) { + if (_.isEmpty(product) || optionFinalPrice < optionMinPrice) { optionMinPrice = optionFinalPrice; product = allowedProduct; } - - if (optionFinalPrice < optionMinPrice) { - product = allowedProduct; - } }, this); return product; @@ -1233,7 +1229,10 @@ define([ } imagesToUpdate = this._setImageIndex(imagesToUpdate); - gallery.updateData(imagesToUpdate); + + if (!_.isUndefined(gallery)) { + gallery.updateData(imagesToUpdate); + } if (isInitial) { $(this.options.mediaGallerySelector).AddFotoramaVideoEvents(); diff --git a/app/code/Magento/Tax/Api/TaxClassRepositoryInterface.php b/app/code/Magento/Tax/Api/TaxClassRepositoryInterface.php index d8b251f3c8c..f841f9c047b 100644 --- a/app/code/Magento/Tax/Api/TaxClassRepositoryInterface.php +++ b/app/code/Magento/Tax/Api/TaxClassRepositoryInterface.php @@ -27,7 +27,7 @@ public function get($taxClassId); * Retrieve tax classes which match a specific criteria. * * This call returns an array of objects, but detailed information about each object’s attributes might not be - * included. See http://devdocs.magento.com/codelinks/attributes.html#TaxClassRepositoryInterface to + * included. See https://devdocs.magento.com/codelinks/attributes.html#TaxClassRepositoryInterface to * determine which call to use to get detailed information about all attributes for an object. * * @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria diff --git a/app/code/Magento/Tax/Api/TaxRateRepositoryInterface.php b/app/code/Magento/Tax/Api/TaxRateRepositoryInterface.php index 252bc0fc715..c0f5ccd95ba 100644 --- a/app/code/Magento/Tax/Api/TaxRateRepositoryInterface.php +++ b/app/code/Magento/Tax/Api/TaxRateRepositoryInterface.php @@ -47,7 +47,7 @@ public function deleteById($rateId); * Search TaxRates * * This call returns an array of objects, but detailed information about each object’s attributes might not be - * included. See http://devdocs.magento.com/codelinks/attributes.html#TaxRateRepositoryInterface to + * included. See https://devdocs.magento.com/codelinks/attributes.html#TaxRateRepositoryInterface to * determine which call to use to get detailed information about all attributes for an object. * * @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria diff --git a/app/code/Magento/Tax/Api/TaxRuleRepositoryInterface.php b/app/code/Magento/Tax/Api/TaxRuleRepositoryInterface.php index 1d69f932573..5e045d94de4 100644 --- a/app/code/Magento/Tax/Api/TaxRuleRepositoryInterface.php +++ b/app/code/Magento/Tax/Api/TaxRuleRepositoryInterface.php @@ -55,7 +55,7 @@ public function deleteById($ruleId); * Search TaxRules * * This call returns an array of objects, but detailed information about each object’s attributes might not be - * included. See http://devdocs.magento.com/codelinks/attributes.html#TaxRuleRepositoryInterface to + * included. See https://devdocs.magento.com/codelinks/attributes.html#TaxRuleRepositoryInterface to * determine which call to use to get detailed information about all attributes for an object. * * @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria diff --git a/app/code/Magento/Tax/Model/Calculation/AbstractAggregateCalculator.php b/app/code/Magento/Tax/Model/Calculation/AbstractAggregateCalculator.php index bad64260cf5..939facd02c0 100644 --- a/app/code/Magento/Tax/Model/Calculation/AbstractAggregateCalculator.php +++ b/app/code/Magento/Tax/Model/Calculation/AbstractAggregateCalculator.php @@ -7,6 +7,9 @@ use Magento\Tax\Api\Data\QuoteDetailsItemInterface; +/** + * Abstract aggregate calculator. + */ abstract class AbstractAggregateCalculator extends AbstractCalculator { /** @@ -106,11 +109,12 @@ protected function calculateWithTaxNotInPrice(QuoteDetailsItemInterface $item, $ $rowTaxes = []; $rowTaxesBeforeDiscount = []; $appliedTaxes = []; + $rowTotalForTaxCalculation = $this->getPriceForTaxCalculation($item, $price) * $quantity; //Apply each tax rate separately foreach ($appliedRates as $appliedRate) { $taxId = $appliedRate['id']; $taxRate = $appliedRate['percent']; - $rowTaxPerRate = $this->calculationTool->calcTaxAmount($rowTotal, $taxRate, false, false); + $rowTaxPerRate = $this->calculationTool->calcTaxAmount($rowTotalForTaxCalculation, $taxRate, false, false); $deltaRoundingType = self::KEY_REGULAR_DELTA_ROUNDING; if ($applyTaxAfterDiscount) { $deltaRoundingType = self::KEY_TAX_BEFORE_DISCOUNT_DELTA_ROUNDING; @@ -121,7 +125,10 @@ protected function calculateWithTaxNotInPrice(QuoteDetailsItemInterface $item, $ //Handle discount if ($applyTaxAfterDiscount) { //TODO: handle originalDiscountAmount - $taxableAmount = max($rowTotal - $discountAmount, 0); + $taxableAmount = max($rowTotalForTaxCalculation - $discountAmount, 0); + if ($taxableAmount && !$applyTaxAfterDiscount) { + $taxableAmount = $rowTotalForTaxCalculation; + } $rowTaxAfterDiscount = $this->calculationTool->calcTaxAmount( $taxableAmount, $taxRate, @@ -168,6 +175,26 @@ protected function calculateWithTaxNotInPrice(QuoteDetailsItemInterface $item, $ ->setAppliedTaxes($appliedTaxes); } + /** + * Get price for tax calculation. + * + * @param QuoteDetailsItemInterface $item + * @param float $price + * @return float + */ + private function getPriceForTaxCalculation(QuoteDetailsItemInterface $item, float $price) + { + if ($item->getExtensionAttributes() && $item->getExtensionAttributes()->getPriceForTaxCalculation()) { + $priceForTaxCalculation = $this->calculationTool->round( + $item->getExtensionAttributes()->getPriceForTaxCalculation() + ); + } else { + $priceForTaxCalculation = $price; + } + + return $priceForTaxCalculation; + } + /** * Round amount * diff --git a/app/code/Magento/Tax/Model/Sales/Total/Quote/CommonTaxCollector.php b/app/code/Magento/Tax/Model/Sales/Total/Quote/CommonTaxCollector.php index 0901e1b7bc7..bff489ee50c 100644 --- a/app/code/Magento/Tax/Model/Sales/Total/Quote/CommonTaxCollector.php +++ b/app/code/Magento/Tax/Model/Sales/Total/Quote/CommonTaxCollector.php @@ -15,12 +15,17 @@ use Magento\Quote\Model\Quote\Item\AbstractItem; use Magento\Store\Model\Store; use Magento\Tax\Api\Data\QuoteDetailsInterfaceFactory; +use Magento\Tax\Api\Data\QuoteDetailsItemInterface; use Magento\Tax\Api\Data\TaxClassKeyInterfaceFactory; use Magento\Tax\Api\Data\TaxClassKeyInterface; use Magento\Tax\Api\Data\TaxDetailsInterface; use Magento\Tax\Api\Data\TaxDetailsItemInterface; use Magento\Tax\Api\Data\QuoteDetailsInterface; use Magento\Quote\Api\Data\ShippingAssignmentInterface; +use Magento\Tax\Helper\Data as TaxHelper; +use Magento\Framework\App\ObjectManager; +use Magento\Tax\Api\Data\QuoteDetailsItemExtensionInterface; +use Magento\Tax\Api\Data\QuoteDetailsItemExtensionInterfaceFactory; /** * Tax totals calculation model @@ -129,6 +134,16 @@ class CommonTaxCollector extends AbstractTotal */ protected $quoteDetailsItemDataObjectFactory; + /** + * @var TaxHelper + */ + private $taxHelper; + + /** + * @var QuoteDetailsItemExtensionInterfaceFactory + */ + private $quoteDetailsItemExtensionFactory; + /** * Class constructor * @@ -139,6 +154,8 @@ class CommonTaxCollector extends AbstractTotal * @param \Magento\Tax\Api\Data\TaxClassKeyInterfaceFactory $taxClassKeyDataObjectFactory * @param CustomerAddressFactory $customerAddressFactory * @param CustomerAddressRegionFactory $customerAddressRegionFactory + * @param TaxHelper|null $taxHelper + * @param QuoteDetailsItemExtensionInterfaceFactory|null $quoteDetailsItemExtensionInterfaceFactory */ public function __construct( \Magento\Tax\Model\Config $taxConfig, @@ -147,7 +164,9 @@ public function __construct( \Magento\Tax\Api\Data\QuoteDetailsItemInterfaceFactory $quoteDetailsItemDataObjectFactory, \Magento\Tax\Api\Data\TaxClassKeyInterfaceFactory $taxClassKeyDataObjectFactory, CustomerAddressFactory $customerAddressFactory, - CustomerAddressRegionFactory $customerAddressRegionFactory + CustomerAddressRegionFactory $customerAddressRegionFactory, + TaxHelper $taxHelper = null, + QuoteDetailsItemExtensionInterfaceFactory $quoteDetailsItemExtensionInterfaceFactory = null ) { $this->taxCalculationService = $taxCalculationService; $this->quoteDetailsDataObjectFactory = $quoteDetailsDataObjectFactory; @@ -156,6 +175,9 @@ public function __construct( $this->quoteDetailsItemDataObjectFactory = $quoteDetailsItemDataObjectFactory; $this->customerAddressFactory = $customerAddressFactory; $this->customerAddressRegionFactory = $customerAddressRegionFactory; + $this->taxHelper = $taxHelper ?: ObjectManager::getInstance()->get(TaxHelper::class); + $this->quoteDetailsItemExtensionFactory = $quoteDetailsItemExtensionInterfaceFactory ?: + ObjectManager::getInstance()->get(QuoteDetailsItemExtensionInterfaceFactory::class); } /** @@ -186,7 +208,7 @@ public function mapAddress(QuoteAddress $address) * @param bool $priceIncludesTax * @param bool $useBaseCurrency * @param string $parentCode - * @return \Magento\Tax\Api\Data\QuoteDetailsItemInterface + * @return QuoteDetailsItemInterface */ public function mapItem( \Magento\Tax\Api\Data\QuoteDetailsItemInterfaceFactory $itemDataObjectFactory, @@ -199,7 +221,7 @@ public function mapItem( $sequence = 'sequence-' . $this->getNextIncrement(); $item->setTaxCalculationItemId($sequence); } - /** @var \Magento\Tax\Api\Data\QuoteDetailsItemInterface $itemDataObject */ + /** @var QuoteDetailsItemInterface $itemDataObject */ $itemDataObject = $itemDataObjectFactory->create(); $itemDataObject->setCode($item->getTaxCalculationItemId()) ->setQuantity($item->getQty()) @@ -215,12 +237,28 @@ public function mapItem( if (!$item->getBaseTaxCalculationPrice()) { $item->setBaseTaxCalculationPrice($item->getBaseCalculationPriceOriginal()); } + + if ($this->taxHelper->applyTaxOnOriginalPrice()) { + $baseTaxCalculationPrice = $item->getBaseOriginalPrice(); + } else { + $baseTaxCalculationPrice = $item->getBaseCalculationPriceOriginal(); + } + $this->setPriceForTaxCalculation($itemDataObject, (float)$baseTaxCalculationPrice); + $itemDataObject->setUnitPrice($item->getBaseTaxCalculationPrice()) ->setDiscountAmount($item->getBaseDiscountAmount()); } else { if (!$item->getTaxCalculationPrice()) { $item->setTaxCalculationPrice($item->getCalculationPriceOriginal()); } + + if ($this->taxHelper->applyTaxOnOriginalPrice()) { + $taxCalculationPrice = $item->getOriginalPrice(); + } else { + $taxCalculationPrice = $item->getCalculationPriceOriginal(); + } + $this->setPriceForTaxCalculation($itemDataObject, (float)$taxCalculationPrice); + $itemDataObject->setUnitPrice($item->getTaxCalculationPrice()) ->setDiscountAmount($item->getDiscountAmount()); } @@ -230,6 +268,23 @@ public function mapItem( return $itemDataObject; } + /** + * Set price for tax calculation. + * + * @param QuoteDetailsItemInterface $quoteDetailsItem + * @param float $taxCalculationPrice + * @return void + */ + private function setPriceForTaxCalculation(QuoteDetailsItemInterface $quoteDetailsItem, float $taxCalculationPrice) + { + $extensionAttributes = $quoteDetailsItem->getExtensionAttributes(); + if (!$extensionAttributes) { + $extensionAttributes = $this->quoteDetailsItemExtensionFactory->create(); + } + $extensionAttributes->setPriceForTaxCalculation($taxCalculationPrice); + $quoteDetailsItem->setExtensionAttributes($extensionAttributes); + } + /** * Map item extra taxables * @@ -237,7 +292,7 @@ public function mapItem( * @param AbstractItem $item * @param bool $priceIncludesTax * @param bool $useBaseCurrency - * @return \Magento\Tax\Api\Data\QuoteDetailsItemInterface[] + * @return QuoteDetailsItemInterface[] */ public function mapItemExtraTaxables( \Magento\Tax\Api\Data\QuoteDetailsItemInterfaceFactory $itemDataObjectFactory, @@ -260,7 +315,7 @@ public function mapItemExtraTaxables( } else { $unitPrice = $extraTaxable[self::KEY_ASSOCIATED_TAXABLE_UNIT_PRICE]; } - /** @var \Magento\Tax\Api\Data\QuoteDetailsItemInterface $itemDataObject */ + /** @var QuoteDetailsItemInterface $itemDataObject */ $itemDataObject = $itemDataObjectFactory->create(); $itemDataObject->setCode($extraTaxable[self::KEY_ASSOCIATED_TAXABLE_CODE]) ->setType($extraTaxable[self::KEY_ASSOCIATED_TAXABLE_TYPE]) @@ -283,9 +338,9 @@ public function mapItemExtraTaxables( * Add quote items * * @param ShippingAssignmentInterface $shippingAssignment - * @param bool $useBaseCurrency * @param bool $priceIncludesTax - * @return \Magento\Tax\Api\Data\QuoteDetailsItemInterface[] + * @param bool $useBaseCurrency + * @return QuoteDetailsItemInterface[] */ public function mapItems( ShippingAssignmentInterface $shippingAssignment, @@ -361,10 +416,12 @@ public function populateAddressData(QuoteDetailsInterface $quoteDetails, QuoteAd } /** + * Get shipping data object. + * * @param ShippingAssignmentInterface $shippingAssignment * @param QuoteAddress\Total $total * @param bool $useBaseCurrency - * @return \Magento\Tax\Api\Data\QuoteDetailsItemInterface + * @return QuoteDetailsItemInterface */ public function getShippingDataObject( ShippingAssignmentInterface $shippingAssignment, @@ -379,7 +436,7 @@ public function getShippingDataObject( $total->setBaseShippingTaxCalculationAmount($total->getBaseShippingAmount()); } if ($total->getShippingTaxCalculationAmount() !== null) { - /** @var \Magento\Tax\Api\Data\QuoteDetailsItemInterface $itemDataObject */ + /** @var QuoteDetailsItemInterface $itemDataObject */ $itemDataObject = $this->quoteDetailsItemDataObjectFactory->create() ->setType(self::ITEM_TYPE_SHIPPING) ->setCode(self::ITEM_CODE_SHIPPING) @@ -414,7 +471,7 @@ public function getShippingDataObject( * Populate QuoteDetails object from quote address object * * @param ShippingAssignmentInterface $shippingAssignment - * @param \Magento\Tax\Api\Data\QuoteDetailsItemInterface[] $itemDataObjects + * @param QuoteDetailsItemInterface[] $itemDataObjects * @return \Magento\Tax\Api\Data\QuoteDetailsInterface */ protected function prepareQuoteDetails(ShippingAssignmentInterface $shippingAssignment, $itemDataObjects) @@ -543,6 +600,7 @@ protected function processProductItems( * Process applied taxes for items and quote * * @param QuoteAddress\Total $total + * @param ShippingAssignmentInterface $shippingAssignment * @param array $itemsByType * @return $this */ @@ -846,8 +904,9 @@ protected function saveAppliedTaxes() } /** - * Increment and return counter. This function is intended to be used to generate temporary - * id for an item. + * Increment and return counter. + * + * This function is intended to be used to generate temporary id for an item. * * @return int */ diff --git a/app/code/Magento/Tax/Model/Sales/Total/Quote/Tax.php b/app/code/Magento/Tax/Model/Sales/Total/Quote/Tax.php index 4aea7ab4c5a..52061fd5d38 100755 --- a/app/code/Magento/Tax/Model/Sales/Total/Quote/Tax.php +++ b/app/code/Magento/Tax/Model/Sales/Total/Quote/Tax.php @@ -265,7 +265,7 @@ protected function processExtraTaxables(Address\Total $total, array $itemsByType { $extraTaxableDetails = []; foreach ($itemsByType as $itemType => $itemTaxDetails) { - if ($itemType != self::ITEM_TYPE_PRODUCT and $itemType != self::ITEM_TYPE_SHIPPING) { + if ($itemType != self::ITEM_TYPE_PRODUCT && $itemType != self::ITEM_TYPE_SHIPPING) { foreach ($itemTaxDetails as $itemCode => $itemTaxDetail) { /** @var \Magento\Tax\Api\Data\TaxDetailsInterface $taxDetails */ $taxDetails = $itemTaxDetail[self::KEY_ITEM]; @@ -408,6 +408,7 @@ protected function enhanceTotalData( /** * Process model configuration array. + * * This method can be used for changing totals collect sort order * * @param array $config diff --git a/app/code/Magento/Tax/Test/Mftf/ActionGroup/AdminTaxActionGroup.xml b/app/code/Magento/Tax/Test/Mftf/ActionGroup/AdminTaxActionGroup.xml index 596b72070bf..3986ede9acf 100644 --- a/app/code/Magento/Tax/Test/Mftf/ActionGroup/AdminTaxActionGroup.xml +++ b/app/code/Magento/Tax/Test/Mftf/ActionGroup/AdminTaxActionGroup.xml @@ -128,12 +128,12 @@ <!--Select Configuration menu from Store--> <click selector="{{AdminMenuSection.stores}}" stepKey="clickOnSTORES" /> <waitForPageLoad stepKey="waitForConfiguration"/> - <click selector="{{StoresSubmenuSection.configuration}}" stepKey="clickOnConfigurations"/> + <click selector="{{AdminMenuSection.configuration}}" stepKey="clickOnConfigurations"/> <waitForPageLoad stepKey="waitForSales"/> <!--Double click the same to fix flaky issue with redirection to Dashboard--> <click selector="{{AdminMenuSection.stores}}" stepKey="clickOnSTORES1" /> <waitForPageLoad stepKey="waitForConfiguration1"/> - <click selector="{{StoresSubmenuSection.configuration}}" stepKey="clickOnConfigurations1"/> + <click selector="{{AdminMenuSection.configuration}}" stepKey="clickOnConfigurations1"/> <waitForPageLoad stepKey="waitForSales1" time="5"/> <!--Change default tax class for Shipping on Taxable Goods--> <click selector="{{ConfigurationListSection.sales}}" stepKey="clickOnSales" /> @@ -156,12 +156,12 @@ <!--Select Configuration menu from Store--> <click selector="{{AdminMenuSection.stores}}" stepKey="clickOnSTORES" /> <waitForPageLoad stepKey="waitForConfiguration"/> - <click selector="{{StoresSubmenuSection.configuration}}" stepKey="clickOnConfigurations"/> + <click selector="{{AdminMenuSection.configuration}}" stepKey="clickOnConfigurations"/> <waitForPageLoad stepKey="waitForSales"/> <!--Double click the same to fix flaky issue with redirection to Dashboard--> <click selector="{{AdminMenuSection.stores}}" stepKey="clickOnSTORES1" /> <waitForPageLoad stepKey="waitForConfiguration1"/> - <click selector="{{StoresSubmenuSection.configuration}}" stepKey="clickOnConfigurations1"/> + <click selector="{{AdminMenuSection.configuration}}" stepKey="clickOnConfigurations1"/> <waitForPageLoad stepKey="waitForSales1"/> <!--Change default tax class for Shipping on Taxable Goods--> <click selector="{{ConfigurationListSection.sales}}" stepKey="clickOnSales" /> diff --git a/app/code/Magento/Tax/Test/Mftf/Data/TaxRateData.xml b/app/code/Magento/Tax/Test/Mftf/Data/TaxRateData.xml index 887203a76fd..4409ea0a21d 100644 --- a/app/code/Magento/Tax/Test/Mftf/Data/TaxRateData.xml +++ b/app/code/Magento/Tax/Test/Mftf/Data/TaxRateData.xml @@ -106,4 +106,8 @@ <data key="zip_is_range">0</data> <data key="rate">0.1</data> </entity> + <entity name="taxRateForPensylvannia" extends="defaultTaxRate"> + <data key="tax_region_id">51</data> + <data key="rate">6</data> + </entity> </entities> diff --git a/app/code/Magento/Tax/Test/Mftf/Page/AdminEditTaxRatePage.xml b/app/code/Magento/Tax/Test/Mftf/Page/AdminEditTaxRatePage.xml new file mode 100644 index 00000000000..26152d5497a --- /dev/null +++ b/app/code/Magento/Tax/Test/Mftf/Page/AdminEditTaxRatePage.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="AdminEditTaxRatePage" url="tax/rate/edit/rate/{{var1}}/" module="Magento_Tax" area="admin" parameterized="true"> + <section name="AdminTaxRateFormSection"/> + </page> +</pages> diff --git a/app/code/Magento/Tax/Test/Mftf/Page/AdminEditTaxRulePage.xml b/app/code/Magento/Tax/Test/Mftf/Page/AdminEditTaxRulePage.xml new file mode 100644 index 00000000000..c0e4958619c --- /dev/null +++ b/app/code/Magento/Tax/Test/Mftf/Page/AdminEditTaxRulePage.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="AdminEditTaxRulePage" url="tax/rule/edit/rule/{{var}}/" module="Magento_Tax" area="admin" parameterized="true"> + <section name="AdminTaxRulesSection"/> + </page> +</pages> diff --git a/app/code/Magento/Tax/Test/Mftf/Section/AdminConfigureTaxSection.xml b/app/code/Magento/Tax/Test/Mftf/Section/AdminConfigureTaxSection.xml index bfb082c145f..e69bfbaebbf 100644 --- a/app/code/Magento/Tax/Test/Mftf/Section/AdminConfigureTaxSection.xml +++ b/app/code/Magento/Tax/Test/Mftf/Section/AdminConfigureTaxSection.xml @@ -28,6 +28,8 @@ <element name="taxCalculationPrices" type="select" selector="#tax_calculation_price_includes_tax"/> <element name="taxCalculationPricesDisabled" type="select" selector="#tax_calculation_price_includes_tax[disabled='disabled']"/> <element name="taxCalculationPricesInherit" type="checkbox" selector="#tax_calculation_price_includes_tax_inherit"/> + <element name="taxCalculationApplyTaxOn" type="select" selector="#tax_calculation_apply_tax_on"/> + <element name="taxCalculationApplyTaxOnInherit" type="checkbox" selector="#tax_calculation_apply_tax_on_inherit"/> <element name="defaultDestination" type="block" selector="#tax_defaults-head" timeout="30"/> <element name="systemValueDefaultState" type="checkbox" selector="#row_tax_defaults_region input[type='checkbox']"/> diff --git a/app/code/Magento/Tax/Test/Mftf/Section/AdminTaxRulesSection.xml b/app/code/Magento/Tax/Test/Mftf/Section/AdminTaxRulesSection.xml index 29c53242b90..46d92e30395 100644 --- a/app/code/Magento/Tax/Test/Mftf/Section/AdminTaxRulesSection.xml +++ b/app/code/Magento/Tax/Test/Mftf/Section/AdminTaxRulesSection.xml @@ -33,5 +33,6 @@ <element name="deleteTaxClass" type="button" selector="//span[contains(text(),'{{var1}}')]/../..//*[@class='mselect-delete']" parameterized="true"/> <element name="popUpDialogOK" type="button" selector="//*[@class='modal-footer']//*[contains(text(),'OK')]"/> <element name="taxRateMultiSelectItems" type="block" selector=".mselect-list-item"/> + <element name="taxRateNumber" type="button" selector="//div[@data-ui-id='tax-rate-form-fieldset-element-form-field-tax-rate']//div[@class='mselect-items-wrapper']//label[{{var}}]" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRateZipCodeRangeTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRateZipCodeRangeTest.xml index 6d9717318ef..f75fa716e9d 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRateZipCodeRangeTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRateZipCodeRangeTest.xml @@ -11,7 +11,7 @@ <test name="AdminCreateTaxRateZipCodeRangeTest"> <annotations> <stories value="Create tax rate"/> - <title value="Create tax tate, zip code range"/> + <title value="Create tax rate, zip code range"/> <description value="Test log in to Create Tax Rate and Create Zip Code Range"/> <testCaseId value="MC-5319"/> <severity value="CRITICAL"/> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/AdminDeleteTaxRuleTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/AdminDeleteTaxRuleTest.xml index 658f524a4a5..72adf7b0dae 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/AdminDeleteTaxRuleTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/AdminDeleteTaxRuleTest.xml @@ -68,7 +68,9 @@ <argument name="address" value="US_Address_Utah" /> </actionGroup> <scrollTo selector="{{StorefrontProductPageSection.orderTotal}}" x="0" y="-80" stepKey="scrollToOrderTotal"/> + <waitForElementVisible selector="{{StorefrontProductPageSection.subTotal}}" time="30" stepKey="waitSubtotalAppears"/> <see selector="{{StorefrontProductPageSection.subTotal}}" userInput="$100.00" stepKey="seeSubTotal"/> + <waitForElementVisible selector="{{StorefrontProductPageSection.shipping}}" time="30" stepKey="waitShippingAppears"/> <see selector="{{StorefrontProductPageSection.shipping}}" userInput="$5.00" stepKey="seeShipping"/> <dontSee selector="{{StorefrontProductPageSection.tax}}" stepKey="dontSeeAssertTaxAmount" /> <see selector="{{StorefrontProductPageSection.orderTotal}}" userInput="$105.00" stepKey="seeAssertOrderTotal"/> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/AdminTaxCalcWithApplyTaxOnSettingTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/AdminTaxCalcWithApplyTaxOnSettingTest.xml new file mode 100644 index 00000000000..732470d2558 --- /dev/null +++ b/app/code/Magento/Tax/Test/Mftf/Test/AdminTaxCalcWithApplyTaxOnSettingTest.xml @@ -0,0 +1,79 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminTaxCalcWithApplyTaxOnSettingTest"> + <annotations> + <features value="AdminTaxCalcWithApplyTaxOnSettingTest"/> + <title value="Tax calculation process following 'Apply Tax On' setting"/> + <description value="Tax calculation process following 'Apply Tax On' setting"/> + <severity value="MAJOR"/> + <testCaseId value="MC-11026"/> + <useCaseId value="MC-4316"/> + <group value="Tax"/> + </annotations> + + <before> + <createData entity="taxRateForPensylvannia" stepKey="initialTaxRate"/> + <createData entity="defaultTaxRule" stepKey="createTaxRule"/> + <createData entity="ApiCategory" stepKey="createCategory"/> + <createData entity="ApiSimpleProductWithCustomPrice" stepKey="createProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <actionGroup ref="LoginActionGroup" stepKey="loginAsAdmin"/> + <actionGroup ref="SetTaxClassForShipping" stepKey="setTaxClass"/> + <actionGroup ref="SetTaxApplyOnSetting" stepKey="setApplyTaxOnSetting"> + <argument name="userInput" value="Original price only"/> + </actionGroup> + <amOnPage url="{{AdminEditTaxRulePage.url($$createTaxRule.id$$)}}" stepKey="navigateToEditTaxRulePage"/> + <waitForPageLoad stepKey="waitEditTaxRulePageToLoad"/> + <click selector="{{AdminTaxRulesSection.taxRateNumber('1')}}" stepKey="clickonTaxRate"/> + <click selector="{{AdminTaxRulesSection.deleteTaxClassName($$initialTaxRate.code$$)}}" stepKey="checkTaxRate"/> + <click selector="{{AdminTaxRulesSection.saveRule}}" stepKey="saveChanges"/> + <waitForPageLoad stepKey="waitTaxRulesToBeSaved"/> + <see selector="{{AdminMessagesSection.success}}" userInput="You saved the tax rule." stepKey="seeSuccessMessage2"/> + </before> + <after> + <deleteData createDataKey="createProduct" stepKey="deleteSimpleProduct"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createTaxRule" stepKey="deleteTaxRule"/> + <deleteData stepKey="deleteTaxRate" createDataKey="initialTaxRate" /> + <actionGroup ref="DisableTaxApplyOnOriginalPrice" stepKey="setApplyTaxOffSetting"> + <argument name="userInput" value="Custom price if available"/> + </actionGroup> + <actionGroup ref="ResetTaxClassForShipping" stepKey="resetTaxClassForShipping"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <actionGroup ref="navigateToNewOrderPageNewCustomerSingleStore" stepKey="gotoNewOrderCreationPage"/> + <actionGroup ref="addSimpleProductToOrder" stepKey="addSimpleProductToOrder"> + <argument name="product" value="$$createProduct$$"></argument> + </actionGroup> + <fillField selector="{{AdminOrderFormAccountSection.email}}" userInput="{{Simple_US_Customer.email}}" stepKey="fillEmailField"/> + <actionGroup ref="fillOrderCustomerInformation" stepKey="fillCustomerAddress"> + <argument name="customer" value="Simple_US_Customer"/> + <argument name="address" value="US_Address_CA"/> + </actionGroup> + <scrollTo selector="{{AdminOrderFormAccountSection.email}}" stepKey="scrollToEmailField"/> + <waitForElementVisible selector="{{AdminOrderFormAccountSection.email}}" stepKey="waitEmailFieldToBeVisible"/> + <click selector="{{AdminOrderFormShippingAddressSection.SameAsBilling}}" stepKey="uncheckSameAsBillingAddressCheckbox"/> + <waitForPageLoad stepKey="waitSectionToReload"/> + <selectOption selector="{{AdminOrderFormShippingAddressSection.State}}" stepKey="switchOnVisibleInAdvancedSearch" userInput="Pennsylvania"/> + <click selector="{{AdminOrderFormPaymentSection.getShippingMethods}}" stepKey="getShippingMethods"/> + <waitForPageLoad stepKey="waitForApplyingShippingMethods"/> + <grabTextFrom selector="{{AdminOrderFormTotalSection.subtotalRow('3')}}" stepKey="grabTaxCost"/> + <assertEquals expected='$6.00' expectedType="string" actual="($grabTaxCost)" stepKey="assertTax"/> + <scrollTo selector="{{AdminOrderFormItemsSection.addProducts}}" stepKey="scrollToSubmitButton"/> + <waitForElementVisible selector="{{AdminOrderFormItemsSection.addProducts}}" stepKey="waitElementToBeVisble"/> + <click selector="{{AdminOrderFormItemsSection.customPriceCheckbox}}" stepKey="clickOnCustomPriceCheckbox"/> + <fillField selector="{{AdminOrderFormItemsSection.customPriceField}}" userInput="{{SimpleProductNameWithDoubleQuote.price}}" stepKey="changePrice"/> + <click selector="{{AdminOrderFormItemsSection.updateItemsAndQuantities}}" stepKey="updateItemsAndQunatities"/> + <assertEquals expected='$6.00' expectedType="string" actual="($grabTaxCost)" stepKey="assertTaxAfterCustomPrice"/> + </test> +</tests> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/AdminUpdateTaxRuleWithFixedZipUtahTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/AdminUpdateTaxRuleWithFixedZipUtahTest.xml index 3ef279e4a76..a96a57cbfec 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/AdminUpdateTaxRuleWithFixedZipUtahTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/AdminUpdateTaxRuleWithFixedZipUtahTest.xml @@ -95,7 +95,11 @@ <argument name="address" value="US_Address_Utah" /> </actionGroup> <scrollTo selector="{{StorefrontProductPageSection.orderTotal}}" x="0" y="-80" stepKey="scrollToOrderTotal"/> + <waitForElementVisible selector="{{StorefrontProductPageSection.shipping}}" time="30" stepKey="waitForShipping"/> + <see selector="{{StorefrontProductPageSection.shipping}}" userInput="$5.00" stepKey="seeShipping"/> + <waitForElementVisible selector="{{StorefrontProductPageSection.tax}}" time="30" stepKey="waitForTax"/> <see selector="{{StorefrontProductPageSection.tax}}" userInput="$20.00" stepKey="seeAssertTaxAmount" /> + <waitForElementVisible selector="{{StorefrontProductPageSection.orderTotal}}" time="30" stepKey="waitForOrderTotal"/> <see selector="{{StorefrontProductPageSection.orderTotal}}" userInput="$125.00" stepKey="seeAssertGrandTotal"/> </test> </tests> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForCustomerPhysicalQuoteTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForCustomerPhysicalQuoteTest.xml index 1cb96b37cc7..aa44593400a 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForCustomerPhysicalQuoteTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForCustomerPhysicalQuoteTest.xml @@ -89,8 +89,8 @@ <actionGroup ref="clickViewAndEditCartFromMiniCart" stepKey="goToShoppingCartFromMinicart"/> <!-- Step 4: Open Estimate Shipping and Tax section --> <conditionalClick selector="{{CheckoutCartSummarySection.estimateShippingAndTax}}" dependentSelector="{{CheckoutCartSummarySection.country}}" visible="false" stepKey="expandEstimateShippingandTax" /> - <see selector="{{CheckoutCartSummarySection.country}}" userInput="{{US_Address_CA.country_id}}" stepKey="checkCustomerCountry" /> - <see selector="{{CheckoutCartSummarySection.stateProvince}}" userInput="{{US_Address_CA.state}}" stepKey="checkCustomerRegion" /> + <seeOptionIsSelected selector="{{CheckoutCartSummarySection.country}}" userInput="{{US_Address_CA.country}}" stepKey="checkCustomerCountry" /> + <seeOptionIsSelected selector="{{CheckoutCartSummarySection.stateProvince}}" userInput="{{US_Address_CA.state}}" stepKey="checkCustomerRegion" /> <grabValueFrom selector="{{CheckoutCartSummarySection.postcode}}" stepKey="grabTextPostCode"/> <assertEquals message="Customer postcode is invalid" stepKey="checkCustomerPostcode"> <expectedResult type="string">{{US_Address_CA.postcode}}</expectedResult> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForCustomerVirtualQuoteTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForCustomerVirtualQuoteTest.xml index 190263efb24..ac090fd4fe9 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForCustomerVirtualQuoteTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForCustomerVirtualQuoteTest.xml @@ -61,8 +61,8 @@ <actionGroup ref="clickViewAndEditCartFromMiniCart" stepKey="goToShoppingCartFromMinicart"/> <!-- Step 4: Open Estimate Shipping and Tax section --> <conditionalClick selector="{{CheckoutCartSummarySection.estimateShippingAndTax}}" dependentSelector="{{CheckoutCartSummarySection.country}}" visible="false" stepKey="expandEstimateShippingandTax" /> - <see selector="{{CheckoutCartSummarySection.country}}" userInput="{{US_Address_NY.country_id}}" stepKey="checkCustomerCountry" /> - <see selector="{{CheckoutCartSummarySection.stateProvince}}" userInput="{{US_Address_NY.state}}" stepKey="checkCustomerRegion" /> + <seeOptionIsSelected selector="{{CheckoutCartSummarySection.country}}" userInput="{{US_Address_NY.country}}" stepKey="checkCustomerCountry" /> + <seeOptionIsSelected selector="{{CheckoutCartSummarySection.stateProvince}}" userInput="{{US_Address_NY.state}}" stepKey="checkCustomerRegion" /> <grabValueFrom selector="{{CheckoutCartSummarySection.postcode}}" stepKey="grabTextPostCode"/> <assertEquals message="Customer postcode is invalid" stepKey="checkCustomerPostcode"> <expectedResult type="string">{{US_Address_NY.postcode}}</expectedResult> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/Update01TaxRateEntityTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/Update01TaxRateEntityTest.xml index 433e390d776..2ed31c2e204 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/Update01TaxRateEntityTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/Update01TaxRateEntityTest.xml @@ -37,6 +37,7 @@ <!-- Update 0.1 tax rate on the tax rate form page --> <fillField selector="{{AdminTaxRateFormSection.taxIdentifier}}" userInput="{{taxRateCustomRateFrance.code}}" stepKey="fillTaxIdentifierField2"/> <selectOption selector="{{AdminTaxRateFormSection.country}}" userInput="{{taxRateCustomRateFrance.tax_country_id}}" stepKey="selectCountry1"/> + <wait time="10" stepKey="waitForRegionsLoaded" /> <selectOption selector="{{AdminTaxRateFormSection.state}}" userInput="{{taxRateCustomRateFrance.tax_region_id}}" stepKey="selectState"/> <fillField selector="{{AdminTaxRateFormSection.zipCode}}" userInput="{{taxRateCustomRateFrance.tax_postcode}}" stepKey="fillPostCode"/> <fillField selector="{{AdminTaxRateFormSection.rate}}" userInput="{{taxRateCustomRateFrance.rate}}" stepKey="fillRate1"/> diff --git a/app/code/Magento/Tax/Test/Unit/Model/Calculation/RowBaseAndTotalBaseCalculatorTestCase.php b/app/code/Magento/Tax/Test/Unit/Model/Calculation/RowBaseAndTotalBaseCalculatorTestCase.php index cbd7ed46e38..2a7eeb27ee0 100644 --- a/app/code/Magento/Tax/Test/Unit/Model/Calculation/RowBaseAndTotalBaseCalculatorTestCase.php +++ b/app/code/Magento/Tax/Test/Unit/Model/Calculation/RowBaseAndTotalBaseCalculatorTestCase.php @@ -9,6 +9,7 @@ use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Tax\Model\Calculation\RowBaseCalculator; use Magento\Tax\Model\Calculation\TotalBaseCalculator; +use Magento\Tax\Api\Data\QuoteDetailsItemExtensionInterface; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -66,6 +67,11 @@ class RowBaseAndTotalBaseCalculatorTestCase extends \PHPUnit\Framework\TestCase */ protected $taxDetailsItem; + /** + * @var \Magento\Tax\Api\Data\QuoteDetailsItemExtensionInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $quoteDetailsItemExtension; + /** * initialize all mocks * @@ -101,7 +107,14 @@ protected function setUp() ->disableOriginalConstructor() ->getMock(); - $this->mockItem = $this->getMockBuilder(\Magento\Tax\Api\Data\QuoteDetailsItemInterface::class)->getMock(); + $this->mockItem = $this->getMockBuilder(\Magento\Tax\Api\Data\QuoteDetailsItemInterface::class) + ->disableOriginalConstructor()->setMethods(['getExtensionAttributes', 'getUnitPrice']) + ->getMockForAbstractClass(); + $this->quoteDetailsItemExtension = $this->getMockBuilder(QuoteDetailsItemExtensionInterface::class) + ->disableOriginalConstructor()->setMethods(['getPriceForTaxCalculation']) + ->getMockForAbstractClass(); + $this->mockItem->expects($this->any())->method('getExtensionAttributes') + ->willReturn($this->quoteDetailsItemExtension); $this->appliedTaxDataObjectFactory = $this->createPartialMock( \Magento\Tax\Api\Data\AppliedTaxInterfaceFactory::class, diff --git a/app/code/Magento/Tax/etc/extension_attributes.xml b/app/code/Magento/Tax/etc/extension_attributes.xml index 90a5e6d2ece..41af1df836d 100644 --- a/app/code/Magento/Tax/etc/extension_attributes.xml +++ b/app/code/Magento/Tax/etc/extension_attributes.xml @@ -20,4 +20,7 @@ <extension_attributes for="Magento\Catalog\Api\Data\ProductRender\PriceInfoInterface"> <attribute code="tax_adjustments" type="Magento\Catalog\Api\Data\ProductRender\PriceInfoInterface" /> </extension_attributes> + <extension_attributes for="Magento\Tax\Api\Data\QuoteDetailsItemInterface"> + <attribute code="price_for_tax_calculation" type="float" /> + </extension_attributes> </config> diff --git a/app/code/Magento/Tax/etc/sales.xml b/app/code/Magento/Tax/etc/sales.xml index 64d29ece898..15afd499bce 100644 --- a/app/code/Magento/Tax/etc/sales.xml +++ b/app/code/Magento/Tax/etc/sales.xml @@ -9,7 +9,7 @@ <section name="quote"> <group name="totals"> <item name="tax_subtotal" instance="Magento\Tax\Model\Sales\Total\Quote\Subtotal" sort_order="200"/> - <item name="tax_shipping" instance="Magento\Tax\Model\Sales\Total\Quote\Shipping" sort_order="300"/> + <item name="tax_shipping" instance="Magento\Tax\Model\Sales\Total\Quote\Shipping" sort_order="375"/> <item name="tax" instance="Magento\Tax\Model\Sales\Total\Quote\Tax" sort_order="450"> <renderer name="adminhtml" instance="Magento\Sales\Block\Adminhtml\Order\Create\Totals\Tax"/> <renderer name="frontend" instance="Magento\Tax\Block\Checkout\Tax"/> diff --git a/app/code/Magento/Theme/Model/PageLayout/Config/Builder.php b/app/code/Magento/Theme/Model/PageLayout/Config/Builder.php index 98fa12ab987..13b8aa23073 100644 --- a/app/code/Magento/Theme/Model/PageLayout/Config/Builder.php +++ b/app/code/Magento/Theme/Model/PageLayout/Config/Builder.php @@ -27,6 +27,11 @@ class Builder implements \Magento\Framework\View\Model\PageLayout\Config\Builder */ protected $themeCollection; + /** + * @var array + */ + private $configFiles = []; + /** * @param \Magento\Framework\View\PageLayout\ConfigFactory $configFactory * @param \Magento\Framework\View\PageLayout\File\Collector\Aggregated $fileCollector @@ -44,7 +49,7 @@ public function __construct( } /** - * @return \Magento\Framework\View\PageLayout\Config + * @inheritdoc */ public function getPageLayoutsConfig() { @@ -52,15 +57,20 @@ public function getPageLayoutsConfig() } /** + * Retrieve configuration files. + * * @return array */ protected function getConfigFiles() { - $configFiles = []; - foreach ($this->themeCollection->loadRegisteredThemes() as $theme) { - $configFiles = array_merge($configFiles, $this->fileCollector->getFilesContent($theme, 'layouts.xml')); + if (!$this->configFiles) { + $configFiles = []; + foreach ($this->themeCollection->loadRegisteredThemes() as $theme) { + $configFiles[] = $this->fileCollector->getFilesContent($theme, 'layouts.xml'); + } + $this->configFiles = array_merge(...$configFiles); } - return $configFiles; + return $this->configFiles; } } diff --git a/app/code/Magento/Theme/Test/Mftf/Section/AdminDesignConfigSection.xml b/app/code/Magento/Theme/Test/Mftf/Section/AdminDesignConfigSection.xml index 0aa2f7f3521..e90548a7c94 100644 --- a/app/code/Magento/Theme/Test/Mftf/Section/AdminDesignConfigSection.xml +++ b/app/code/Magento/Theme/Test/Mftf/Section/AdminDesignConfigSection.xml @@ -14,5 +14,10 @@ <element name="watermarkSection" type="text" selector="[data-index='watermark'] .admin__fieldset-wrapper-content"/> <element name="imageUploadInputByFieldsetName" type="input" selector="//*[contains(@class,'fieldset-wrapper')][child::*[contains(@class,'fieldset-wrapper-title')]//*[contains(text(),'{{arg1}}')]]//*[contains(@class,'file-uploader')]//input" parameterized="true"/> <element name="imageUploadPreviewByFieldsetName" type="input" selector="//*[contains(@class,'fieldset-wrapper')][child::*[contains(@class,'fieldset-wrapper-title')]//*[contains(text(),'{{arg1}}')]]//*[contains(@class,'file-uploader-preview')]//img" parameterized="true"/> + <element name="logoSectionHeader" type="text" selector="[data-index='email']"/> + <element name="logoSection" type="text" selector="[data-index='email'] .admin__fieldset-wrapper-content"/> + <element name="logoUpload" type ="input" selector="[name='email_logo']" /> + <element name="logoWrapperOpen" type ="text" selector="[data-index='email'] [data-state-collapsible ='closed']"/> + <element name="logoPreview" type ="text" selector="[alt ='magento-logo.png']"/> </section> </sections> diff --git a/app/code/Magento/Cms/Test/Mftf/Section/StorefrontHeaderSection.xml b/app/code/Magento/Theme/Test/Mftf/Section/StorefrontHeaderSection.xml similarity index 82% rename from app/code/Magento/Cms/Test/Mftf/Section/StorefrontHeaderSection.xml rename to app/code/Magento/Theme/Test/Mftf/Section/StorefrontHeaderSection.xml index d26f7d83616..a4088c7a4a0 100644 --- a/app/code/Magento/Cms/Test/Mftf/Section/StorefrontHeaderSection.xml +++ b/app/code/Magento/Theme/Test/Mftf/Section/StorefrontHeaderSection.xml @@ -5,9 +5,9 @@ * See COPYING.txt for license details. */ --> - <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontHeaderSection"> + <element name="welcomeMessage" type="text" selector=".greet.welcome"/> </section> </sections> diff --git a/app/code/Magento/Theme/Test/Unit/Model/PageLayout/Config/BuilderTest.php b/app/code/Magento/Theme/Test/Unit/Model/PageLayout/Config/BuilderTest.php index e5d69cbc820..8429be84cae 100644 --- a/app/code/Magento/Theme/Test/Unit/Model/PageLayout/Config/BuilderTest.php +++ b/app/code/Magento/Theme/Test/Unit/Model/PageLayout/Config/BuilderTest.php @@ -83,7 +83,7 @@ public function testGetPageLayoutsConfig() ->disableOriginalConstructor() ->getMock(); - $this->themeCollection->expects($this->any()) + $this->themeCollection->expects($this->once()) ->method('loadRegisteredThemes') ->willReturn([$theme1, $theme2]); diff --git a/app/code/Magento/Theme/etc/di.xml b/app/code/Magento/Theme/etc/di.xml index 148267feeaa..62f51e74b60 100644 --- a/app/code/Magento/Theme/etc/di.xml +++ b/app/code/Magento/Theme/etc/di.xml @@ -273,7 +273,7 @@ <type name="Magento\Config\App\Config\Source\DumpConfigSourceAggregated"> <plugin name="designConfigTheme" type="Magento\Theme\Model\Design\Config\Plugin\Dump" sortOrder="50"/> </type> - <type name="\Magento\Theme\Model\Design\Config\Plugin\Dump"> + <type name="Magento\Theme\Model\Design\Config\Plugin\Dump"> <arguments> <argument name="themeList" xsi:type="object">Magento\Theme\Model\ResourceModel\Theme\Collection</argument> </arguments> diff --git a/app/code/Magento/Theme/view/frontend/templates/html/header.phtml b/app/code/Magento/Theme/view/frontend/templates/html/header.phtml index 4395cab651d..1103ae28741 100644 --- a/app/code/Magento/Theme/view/frontend/templates/html/header.phtml +++ b/app/code/Magento/Theme/view/frontend/templates/html/header.phtml @@ -15,11 +15,11 @@ $welcomeMessage = $block->getWelcome(); case 'welcome': ?> <li class="greet welcome" data-bind="scope: 'customer'"> <!-- ko if: customer().fullname --> - <span data-bind="text: new String('<?= $block->escapeHtml(__('Welcome, %1!', '%1')) ?>').replace('%1', customer().fullname)"> + <span class="logged-in" data-bind="text: new String('<?= $block->escapeHtml(__('Welcome, %1!', '%1')) ?>').replace('%1', customer().fullname)"> </span> <!-- /ko --> <!-- ko ifnot: customer().fullname --> - <span data-bind='html:"<?= $block->escapeHtml($welcomeMessage) ?>"'></span> + <span class="not-logged-in" data-bind='html:"<?= $block->escapeHtml($welcomeMessage) ?>"'></span> <?= $block->getBlockHtml('header.additional') ?> <!-- /ko --> </li> diff --git a/app/code/Magento/Theme/view/frontend/templates/html/header/logo.phtml b/app/code/Magento/Theme/view/frontend/templates/html/header/logo.phtml index 79b891b7e55..f719f5dd783 100644 --- a/app/code/Magento/Theme/view/frontend/templates/html/header/logo.phtml +++ b/app/code/Magento/Theme/view/frontend/templates/html/header/logo.phtml @@ -14,8 +14,8 @@ <span data-action="toggle-nav" class="action nav-toggle"><span><?= /* @escapeNotVerified */ __('Toggle Nav') ?></span></span> <a class="logo" href="<?= $block->getUrl('') ?>" title="<?= /* @escapeNotVerified */ $storeName ?>"> <img src="<?= /* @escapeNotVerified */ $block->getLogoSrc() ?>" - title="<?= /* @escapeNotVerified */ $block->getLogoAlt() ?>" - alt="<?= /* @escapeNotVerified */ $block->getLogoAlt() ?>" + title="<?= $block->escapeHtmlAttr($block->getLogoAlt()) ?>" + alt="<?= $block->escapeHtmlAttr($block->getLogoAlt()) ?>" <?= $block->getLogoWidth() ? 'width="' . $block->getLogoWidth() . '"' : '' ?> <?= $block->getLogoHeight() ? 'height="' . $block->getLogoHeight() . '"' : '' ?> /> diff --git a/app/code/Magento/ThemeGraphQl/etc/graphql/di.xml b/app/code/Magento/ThemeGraphQl/etc/graphql/di.xml new file mode 100644 index 00000000000..9f55e522bf5 --- /dev/null +++ b/app/code/Magento/ThemeGraphQl/etc/graphql/di.xml @@ -0,0 +1,30 @@ +<?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\StoreGraphQl\Model\Resolver\Store\StoreConfigDataProvider"> + <arguments> + <argument name="extendedConfigData" xsi:type="array"> + <item name="head_shortcut_icon" xsi:type="string">design/head/shortcut_icon</item> + <item name="default_title" xsi:type="string">design/head/default_title</item> + <item name="title_prefix" xsi:type="string">design/head/title_prefix</item> + <item name="title_suffix" xsi:type="string">design/head/title_suffix</item> + <item name="default_description" xsi:type="string">design/head/default_description</item> + <item name="default_keywords" xsi:type="string">design/head/default_keywords</item> + <item name="head_includes" xsi:type="string">design/head/includes</item> + <item name="demonotice" xsi:type="string">design/head/demonotice</item> + <item name="header_logo_src" xsi:type="string">design/header/logo_src</item> + <item name="logo_width" xsi:type="string">design/header/logo_width</item> + <item name="logo_height" xsi:type="string">design/header/logo_height</item> + <item name="logo_alt" xsi:type="string">design/header/logo_alt</item> + <item name="welcome" xsi:type="string">design/header/welcome</item> + <item name="absolute_footer" xsi:type="string">design/footer/absolute_footer</item> + <item name="copyright" xsi:type="string">design/footer/copyright</item> + </argument> + </arguments> + </type> +</config> diff --git a/app/code/Magento/Translation/Block/Js.php b/app/code/Magento/Translation/Block/Js.php index 86bb416524d..db26feb8067 100644 --- a/app/code/Magento/Translation/Block/Js.php +++ b/app/code/Magento/Translation/Block/Js.php @@ -8,9 +8,10 @@ use Magento\Framework\View\Element\Template; use Magento\Translation\Model\Js\Config; -use Magento\Framework\Escaper; /** + * JS translation block + * * @api * @since 100.0.2 */ @@ -54,7 +55,7 @@ public function dictionaryEnabled() } /** - * gets current js-translation.json timestamp + * Gets current js-translation.json timestamp * * @return string */ @@ -64,6 +65,8 @@ public function getTranslationFileTimestamp() } /** + * Get translation file path + * * @return string */ public function getTranslationFilePath() diff --git a/app/code/Magento/Translation/Model/Json/PreProcessor.php b/app/code/Magento/Translation/Model/Json/PreProcessor.php index 5d46c3c8b06..c178a324cb4 100644 --- a/app/code/Magento/Translation/Model/Json/PreProcessor.php +++ b/app/code/Magento/Translation/Model/Json/PreProcessor.php @@ -6,6 +6,7 @@ namespace Magento\Translation\Model\Json; +use Magento\Framework\App\Area; use Magento\Framework\App\AreaList; use Magento\Framework\App\ObjectManager; use Magento\Framework\TranslateInterface; @@ -13,6 +14,7 @@ use Magento\Framework\View\Asset\PreProcessor\Chain; use Magento\Framework\View\Asset\PreProcessorInterface; use Magento\Framework\View\DesignInterface; +use Magento\Backend\App\Area\FrontNameResolver; use Magento\Translation\Model\Js\Config; use Magento\Translation\Model\Js\DataProviderInterface; @@ -83,7 +85,7 @@ public function process(Chain $chain) $context = $chain->getAsset()->getContext(); $themePath = '*/*'; - $areaCode = \Magento\Backend\App\Area\FrontNameResolver::AREA_CODE; + $areaCode = FrontNameResolver::AREA_CODE; if ($context instanceof FallbackContext) { $themePath = $context->getThemePath(); @@ -92,8 +94,10 @@ public function process(Chain $chain) $this->viewDesign->setDesignTheme($themePath, $areaCode); } - $area = $this->areaList->getArea($areaCode); - $area->load(\Magento\Framework\App\Area::PART_TRANSLATE); + if ($areaCode !== FrontNameResolver::AREA_CODE) { + $area = $this->areaList->getArea($areaCode); + $area->load(Area::PART_TRANSLATE); + } $this->translate->setLocale($context->getLocale())->loadData($areaCode, true); diff --git a/app/code/Magento/Translation/Test/Unit/Model/Json/PreProcessorTest.php b/app/code/Magento/Translation/Test/Unit/Model/Json/PreProcessorTest.php index d9340e03dc9..cbeeefed6be 100644 --- a/app/code/Magento/Translation/Test/Unit/Model/Json/PreProcessorTest.php +++ b/app/code/Magento/Translation/Test/Unit/Model/Json/PreProcessorTest.php @@ -8,39 +8,43 @@ use Magento\Translation\Model\Js\Config; use Magento\Translation\Model\Js\DataProvider; use Magento\Translation\Model\Json\PreProcessor; +use Magento\Backend\App\Area\FrontNameResolver; class PreProcessorTest extends \PHPUnit\Framework\TestCase { /** * @var PreProcessor */ - protected $model; + private $model; /** * @var Config|\PHPUnit_Framework_MockObject_MockObject */ - protected $configMock; + private $configMock; /** * @var DataProvider|\PHPUnit_Framework_MockObject_MockObject */ - protected $dataProviderMock; + private $dataProviderMock; /** * @var \Magento\Framework\App\AreaList|\PHPUnit_Framework_MockObject_MockObject */ - protected $areaListMock; + private $areaListMock; /** * @var \Magento\Framework\TranslateInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $translateMock; + private $translateMock; /** * @var \Magento\Framework\View\DesignInterface|\PHPUnit_Framework_MockObject_MockObject */ private $designMock; + /** + * @inheritdoc + */ protected function setUp() { $this->configMock = $this->createMock(\Magento\Translation\Model\Js\Config::class); @@ -57,7 +61,14 @@ protected function setUp() ); } - public function testGetData() + /** + * Test 'process' method. + * + * @param array $data + * @param array $expects + * @dataProvider processDataProvider + */ + public function testProcess(array $data, array $expects) { $chain = $this->createMock(\Magento\Framework\View\Asset\PreProcessor\Chain::class); $asset = $this->createMock(\Magento\Framework\View\Asset\File::class); @@ -66,8 +77,10 @@ public function testGetData() $targetPath = 'path/js-translation.json'; $themePath = '*/*'; $dictionary = ['hello' => 'bonjour']; - $areaCode = 'adminhtml'; + $areaCode = $data['area_code']; + $area = $this->createMock(\Magento\Framework\App\Area::class); + $area->expects($expects['area_load'])->method('load')->willReturnSelf(); $chain->expects($this->once()) ->method('getTargetAssetPath') @@ -93,7 +106,7 @@ public function testGetData() $this->designMock->expects($this->once())->method('setDesignTheme')->with($themePath, $areaCode); - $this->areaListMock->expects($this->once()) + $this->areaListMock->expects($expects['areaList_getArea']) ->method('getArea') ->with($areaCode) ->willReturn($area); @@ -114,4 +127,33 @@ public function testGetData() $this->model->process($chain); } + + /** + * Data provider for 'process' method test. + * + * @return array + */ + public function processDataProvider() + { + return [ + [ + [ + 'area_code' => FrontNameResolver::AREA_CODE + ], + [ + 'areaList_getArea' => $this->never(), + 'area_load' => $this->never(), + ] + ], + [ + [ + 'area_code' => 'frontend' + ], + [ + 'areaList_getArea' => $this->once(), + 'area_load' => $this->once(), + ] + ], + ]; + } } diff --git a/app/code/Magento/Translation/view/base/templates/translate.phtml b/app/code/Magento/Translation/view/base/templates/translate.phtml index c8366037e22..ec88b1d0920 100644 --- a/app/code/Magento/Translation/view/base/templates/translate.phtml +++ b/app/code/Magento/Translation/view/base/templates/translate.phtml @@ -9,19 +9,50 @@ /** @var \Magento\Translation\Block\Js $block */ ?> <?php if ($block->dictionaryEnabled()): ?> + <script> + require.config({ + deps: [ + 'jquery', + 'mage/translate', + 'jquery/jquery-storageapi' + ], + callback: function ($) { + 'use strict'; + + var dependencies = [], + versionObj; + + $.initNamespaceStorage('mage-translation-storage'); + $.initNamespaceStorage('mage-translation-file-version'); + versionObj = $.localStorage.get('mage-translation-file-version'); + + <?php $version = $block->getTranslationFileVersion(); ?> + + if (versionObj.version !== '<?= /* @escapeNotVerified */ $block->escapeJsQuote($version) ?>') { + dependencies.push( + 'text!<?= /* @noEscape */ Magento\Translation\Model\Js\Config::DICTIONARY_FILE_NAME ?>' + ); -<?php - $version = $block->getTranslationFileVersion(); - $fileName = Magento\Translation\Model\Js\Config::DICTIONARY_FILE_NAME; -?> - <script type="text/x-magento-init"> - { - "*": { - "mage/translate-init": { - "dictionaryFile": "text!<?= $block->escapeJs($fileName); ?>", - "version": "<?= $block->escapeJs($version) ?>" } + + require.config({ + deps: dependencies, + callback: function (string) { + if (typeof string === 'string') { + $.mage.translate.add(JSON.parse(string)); + $.localStorage.set('mage-translation-storage', string); + $.localStorage.set( + 'mage-translation-file-version', + { + version: '<?= /* @escapeNotVerified */ $block->escapeJsQuote($version) ?>' + } + ); + } else { + $.mage.translate.add($.localStorage.get('mage-translation-storage')); + } + } + }); } - } + }); </script> <?php endif; ?> diff --git a/app/code/Magento/Ui/Component/MassAction/Filter.php b/app/code/Magento/Ui/Component/MassAction/Filter.php index a8ed5d901d8..c512c82d694 100644 --- a/app/code/Magento/Ui/Component/MassAction/Filter.php +++ b/app/code/Magento/Ui/Component/MassAction/Filter.php @@ -7,14 +7,16 @@ namespace Magento\Ui\Component\MassAction; use Magento\Framework\Api\FilterBuilder; -use Magento\Framework\Exception\LocalizedException; -use Magento\Framework\View\Element\UiComponentFactory; use Magento\Framework\App\RequestInterface; -use Magento\Framework\View\Element\UiComponentInterface; use Magento\Framework\Data\Collection\AbstractDb; +use Magento\Framework\Exception\LocalizedException; use Magento\Framework\View\Element\UiComponent\DataProvider\DataProviderInterface; +use Magento\Framework\View\Element\UiComponentFactory; +use Magento\Framework\View\Element\UiComponentInterface; /** + * Filter component. + * * @api * @since 100.0.2 */ @@ -100,9 +102,13 @@ public function getCollection(AbstractDb $collection) } } + $filterIds = $this->getFilterIds(); + if (\is_array($selected)) { + $filterIds = array_unique(array_merge($filterIds, $selected)); + } $collection->addFieldToFilter( $collection->getIdFieldName(), - ['in' => $this->getFilterIds()] + ['in' => $filterIds] ); return $collection; diff --git a/app/code/Magento/Ui/TemplateEngine/Xhtml/Result.php b/app/code/Magento/Ui/TemplateEngine/Xhtml/Result.php index e249a64861d..9304d25cc8a 100644 --- a/app/code/Magento/Ui/TemplateEngine/Xhtml/Result.php +++ b/app/code/Magento/Ui/TemplateEngine/Xhtml/Result.php @@ -5,6 +5,8 @@ */ namespace Magento\Ui\TemplateEngine\Xhtml; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Serialize\Serializer\JsonHexTag; use Magento\Framework\View\Layout\Generator\Structure; use Magento\Framework\View\Element\UiComponentInterface; use Magento\Framework\View\TemplateEngine\Xhtml\Template; @@ -42,25 +44,33 @@ class Result implements ResultInterface */ protected $logger; + /** + * @var JsonHexTag + */ + private $jsonSerializer; + /** * @param Template $template * @param CompilerInterface $compiler * @param UiComponentInterface $component * @param Structure $structure * @param LoggerInterface $logger + * @param JsonHexTag $jsonSerializer */ public function __construct( Template $template, CompilerInterface $compiler, UiComponentInterface $component, Structure $structure, - LoggerInterface $logger + LoggerInterface $logger, + JsonHexTag $jsonSerializer = null ) { $this->template = $template; $this->compiler = $compiler; $this->component = $component; $this->structure = $structure; $this->logger = $logger; + $this->jsonSerializer = $jsonSerializer ?? ObjectManager::getInstance()->get(JsonHexTag::class); } /** @@ -81,7 +91,7 @@ public function getDocumentElement() public function appendLayoutConfiguration() { $layoutConfiguration = $this->wrapContent( - json_encode($this->structure->generate($this->component), JSON_HEX_TAG) + $this->jsonSerializer->serialize($this->structure->generate($this->component)) ); $this->template->append($layoutConfiguration); } diff --git a/app/code/Magento/Ui/Test/Mftf/Section/AdminDataGridHeaderSection.xml b/app/code/Magento/Ui/Test/Mftf/Section/AdminDataGridHeaderSection.xml index 3e917a5944f..4ee38e30f98 100644 --- a/app/code/Magento/Ui/Test/Mftf/Section/AdminDataGridHeaderSection.xml +++ b/app/code/Magento/Ui/Test/Mftf/Section/AdminDataGridHeaderSection.xml @@ -25,5 +25,7 @@ <!--Visible columns management--> <element name="columnsToggle" type="button" selector="div.admin__data-grid-action-columns button[data-bind='toggleCollapsible']" timeout="30"/> <element name="columnCheckbox" type="checkbox" selector="//div[contains(@class,'admin__data-grid-action-columns')]//div[contains(@class, 'admin__field-option')]//label[text() = '{{column}}']/preceding-sibling::input" parameterized="true"/> + <element name="perPage" type="select" selector="#product_attributes_listing.product_attributes_listing.listing_top.listing_paging_sizes"/> + <element name="attributeName" type="input" selector="//div[text()='{{arg}}']/../preceding-sibling::td//input" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/Ui/Test/Mftf/Section/AdminDataGridTableSection.xml b/app/code/Magento/Ui/Test/Mftf/Section/AdminDataGridTableSection.xml index edcc70a8239..a2b7ba8c1ff 100644 --- a/app/code/Magento/Ui/Test/Mftf/Section/AdminDataGridTableSection.xml +++ b/app/code/Magento/Ui/Test/Mftf/Section/AdminDataGridTableSection.xml @@ -18,5 +18,6 @@ <!--Specific cell e.g. {{Section.gridCell('1', 'Name')}}--> <element name="gridCell" type="text" selector="//tr[{{row}}]//td[count(//div[@data-role='grid-wrapper']//tr//th[contains(., '{{column}}')]/preceding-sibling::th) +1 ]" parameterized="true"/> <element name="rowViewAction" type="button" selector=".data-grid tbody > tr:nth-of-type({{row}}) .action-menu-item" parameterized="true" timeout="30"/> + <element name="dataGridEmpty" type="block" selector=".data-grid-tr-no-data td"/> </section> </sections> diff --git a/app/code/Magento/Ui/Test/Unit/Component/Form/Element/ActionDeleteTest.php b/app/code/Magento/Ui/Test/Unit/Component/Form/Element/ActionDeleteTest.php index 6f45c192d6c..2cb35c7b85d 100644 --- a/app/code/Magento/Ui/Test/Unit/Component/Form/Element/ActionDeleteTest.php +++ b/app/code/Magento/Ui/Test/Unit/Component/Form/Element/ActionDeleteTest.php @@ -13,13 +13,16 @@ class ActionDeleteTest extends AbstractElementTest { /** - * {@inheritdoc} + * @inheritdoc */ protected function getModelName() { return ActionDelete::class; } + /** + * @inheritdoc + */ public function testGetComponentName() { $this->assertSame(ActionDelete::NAME, $this->getModel()->getComponentName()); diff --git a/app/code/Magento/Ui/Test/Unit/Component/Form/Element/CheckboxSetTest.php b/app/code/Magento/Ui/Test/Unit/Component/Form/Element/CheckboxSetTest.php index 025f4a15824..3f00fa6c7ff 100644 --- a/app/code/Magento/Ui/Test/Unit/Component/Form/Element/CheckboxSetTest.php +++ b/app/code/Magento/Ui/Test/Unit/Component/Form/Element/CheckboxSetTest.php @@ -15,13 +15,16 @@ class CheckboxSetTest extends AbstractElementTest { /** - * {@inheritdoc} + * @inheritdoc */ protected function getModelName() { return CheckboxSet::class; } + /** + * @inheritdoc + */ public function testGetComponentName() { $this->assertSame(CheckboxSet::NAME, $this->getModel()->getComponentName()); diff --git a/app/code/Magento/Ui/Test/Unit/Component/Form/Element/MultiSelectTest.php b/app/code/Magento/Ui/Test/Unit/Component/Form/Element/MultiSelectTest.php index cb91fbb945b..f37ca38a8d9 100644 --- a/app/code/Magento/Ui/Test/Unit/Component/Form/Element/MultiSelectTest.php +++ b/app/code/Magento/Ui/Test/Unit/Component/Form/Element/MultiSelectTest.php @@ -15,13 +15,16 @@ class MultiSelectTest extends AbstractElementTest { /** - * {@inheritdoc} + * @inheritdoc */ protected function getModelName() { return MultiSelect::class; } + /** + * @inheritdoc + */ public function testGetComponentName() { $this->contextMock->expects($this->never())->method('getProcessor'); diff --git a/app/code/Magento/Ui/Test/Unit/Component/Form/Element/RadioSetTest.php b/app/code/Magento/Ui/Test/Unit/Component/Form/Element/RadioSetTest.php index 0e0fef60df6..67150e3c8fd 100644 --- a/app/code/Magento/Ui/Test/Unit/Component/Form/Element/RadioSetTest.php +++ b/app/code/Magento/Ui/Test/Unit/Component/Form/Element/RadioSetTest.php @@ -15,13 +15,16 @@ class RadioSetTest extends AbstractElementTest { /** - * {@inheritdoc} + * @inheritdoc */ protected function getModelName() { return RadioSet::class; } + /** + * @inheritdoc + */ public function testGetComponentName() { $this->assertSame(RadioSet::NAME, $this->getModel()->getComponentName()); diff --git a/app/code/Magento/Ui/Test/Unit/Component/Form/Element/SelectTest.php b/app/code/Magento/Ui/Test/Unit/Component/Form/Element/SelectTest.php index c6952626810..d4677192cc0 100644 --- a/app/code/Magento/Ui/Test/Unit/Component/Form/Element/SelectTest.php +++ b/app/code/Magento/Ui/Test/Unit/Component/Form/Element/SelectTest.php @@ -15,13 +15,16 @@ class SelectTest extends AbstractElementTest { /** - * {@inheritdoc} + * @inheritdoc */ protected function getModelName() { return Select::class; } + /** + * @inheritdoc + */ public function testGetComponentName() { $this->assertSame(Select::NAME, $this->getModel()->getComponentName()); diff --git a/app/code/Magento/Ui/Test/Unit/Component/Form/Element/WysiwygTest.php b/app/code/Magento/Ui/Test/Unit/Component/Form/Element/WysiwygTest.php index b345989ba05..4bfd952a6c5 100644 --- a/app/code/Magento/Ui/Test/Unit/Component/Form/Element/WysiwygTest.php +++ b/app/code/Magento/Ui/Test/Unit/Component/Form/Element/WysiwygTest.php @@ -85,13 +85,16 @@ protected function getModel() } /** - * {@inheritdoc} + * @inheritdoc */ protected function getModelName() { return Wysiwyg::class; } + /** + * @inheritdoc + */ public function testGetComponentName() { $this->assertSame(Wysiwyg::NAME, $this->getModel()->getComponentName()); diff --git a/app/code/Magento/Ui/view/base/ui_component/etc/definition/ui_settings.xsd b/app/code/Magento/Ui/view/base/ui_component/etc/definition/ui_settings.xsd index cbf69e60469..ff4d530b5bf 100644 --- a/app/code/Magento/Ui/view/base/ui_component/etc/definition/ui_settings.xsd +++ b/app/code/Magento/Ui/view/base/ui_component/etc/definition/ui_settings.xsd @@ -476,6 +476,13 @@ </xs:documentation> </xs:annotation> </xs:element> + <xs:element name="aclResource" type="xs:string" minOccurs="0" maxOccurs="1"> + <xs:annotation> + <xs:documentation> + ACL Resource used to validate access to UI Component data + </xs:documentation> + </xs:annotation> + </xs:element> <xs:element ref="param"/> </xs:choice> <xs:attribute name="name" type="xs:string" use="required"> diff --git a/app/code/Magento/Ui/view/base/web/js/dynamic-rows/dnd.js b/app/code/Magento/Ui/view/base/web/js/dynamic-rows/dnd.js index dee9ba7acc1..583e97b7e94 100644 --- a/app/code/Magento/Ui/view/base/web/js/dynamic-rows/dnd.js +++ b/app/code/Magento/Ui/view/base/web/js/dynamic-rows/dnd.js @@ -15,8 +15,7 @@ define([ ], function (ko, $, _, Element) { 'use strict'; - var transformProp, - isTouchDevice = typeof document.ontouchstart !== 'undefined'; + var transformProp; /** * Get element context @@ -110,11 +109,7 @@ define([ * @param {Object} data - element data */ initListeners: function (elem, data) { - if (isTouchDevice) { - $(elem).on('touchstart', this.mousedownHandler.bind(this, data, elem)); - } else { - $(elem).on('mousedown', this.mousedownHandler.bind(this, data, elem)); - } + $(elem).on('mousedown touchstart', this.mousedownHandler.bind(this, data, elem)); }, /** @@ -131,26 +126,20 @@ define([ $table = $(elem).parents('table').eq(0), $tableWrapper = $table.parent(); + this.disableScroll(); $(recordNode).addClass(this.draggableElementClass); $(originRecord).addClass(this.draggableElementClass); this.step = this.step === 'auto' ? originRecord.height() / 2 : this.step; drEl.originRow = originRecord; drEl.instance = recordNode = this.processingStyles(recordNode, elem); drEl.instanceCtx = this.getRecord(originRecord[0]); - drEl.eventMousedownY = isTouchDevice ? event.originalEvent.touches[0].pageY : event.pageY; + drEl.eventMousedownY = this.getPageY(event); drEl.minYpos = $table.offset().top - originRecord.offset().top + $table.children('thead').outerHeight(); drEl.maxYpos = drEl.minYpos + $table.children('tbody').outerHeight() - originRecord.outerHeight(); $tableWrapper.append(recordNode); - - if (isTouchDevice) { - this.body.bind('touchmove', this.mousemoveHandler); - this.body.bind('touchend', this.mouseupHandler); - } else { - this.body.bind('mousemove', this.mousemoveHandler); - this.body.bind('mouseup', this.mouseupHandler); - } - + this.body.bind('mousemove touchmove', this.mousemoveHandler); + this.body.bind('mouseup touchend', this.mouseupHandler); }, /** @@ -160,16 +149,13 @@ define([ */ mousemoveHandler: function (event) { var depEl = this.draggableElement, - pageY = isTouchDevice ? event.originalEvent.touches[0].pageY : event.pageY, + pageY = this.getPageY(event), positionY = pageY - depEl.eventMousedownY, processingPositionY = positionY + 'px', processingMaxYpos = depEl.maxYpos + 'px', processingMinYpos = depEl.minYpos + 'px', depElement = this.getDepElement(depEl.instance, positionY, depEl.originRow); - event.stopPropagation(); - event.preventDefault(); - if (depElement) { depEl.depElement ? depEl.depElement.elem.removeClass(depEl.depElement.className) : false; depEl.depElement = depElement; @@ -194,9 +180,10 @@ define([ mouseupHandler: function (event) { var depElementCtx, drEl = this.draggableElement, - pageY = isTouchDevice ? event.originalEvent.touches[0].pageY : event.pageY, + pageY = this.getPageY(event), positionY = pageY - drEl.eventMousedownY; + this.enableScroll(); drEl.depElement = this.getDepElement(drEl.instance, positionY, this.draggableElement.originRow); drEl.instance.remove(); @@ -212,13 +199,8 @@ define([ drEl.originRow.removeClass(this.draggableElementClass); - if (isTouchDevice) { - this.body.unbind('touchmove', this.mousemoveHandler); - this.body.unbind('touchend', this.mouseupHandler); - } else { - this.body.unbind('mousemove', this.mousemoveHandler); - this.body.unbind('mouseup', this.mouseupHandler); - } + this.body.unbind('mousemove touchmove', this.mousemoveHandler); + this.body.unbind('mouseup touchend', this.mouseupHandler); this.draggableElement = {}; }, @@ -402,6 +384,55 @@ define([ index = _.isFunction(ctx.$index) ? ctx.$index() : ctx.$index; return this.recordsCache()[index]; + }, + + /** + * Get correct page Y + * + * @param {Object} event - current event + * @returns {integer} + */ + getPageY: function (event) { + var pageY; + + if (event.type.indexOf('touch') >= 0) { + if (event.originalEvent.touches[0]) { + pageY = event.originalEvent.touches[0].pageY; + } else { + pageY = event.originalEvent.changedTouches[0].pageY; + } + } else { + pageY = event.pageY; + } + + return pageY; + }, + + /** + * Disable page scrolling + */ + disableScroll: function () { + document.body.addEventListener('touchmove', this.preventDefault, { + passive: false + }); + }, + + /** + * Enable page scrolling + */ + enableScroll: function () { + document.body.removeEventListener('touchmove', this.preventDefault, { + passive: false + }); + }, + + /** + * Prevent default function + * + * @param {Object} event - event object + */ + preventDefault: function (event) { + event.preventDefault(); } }); diff --git a/app/code/Magento/Ui/view/base/web/js/form/components/fieldset.js b/app/code/Magento/Ui/view/base/web/js/form/components/fieldset.js index 1ddc4dc247b..9ee387e0e6a 100644 --- a/app/code/Magento/Ui/view/base/web/js/form/components/fieldset.js +++ b/app/code/Magento/Ui/view/base/web/js/form/components/fieldset.js @@ -162,6 +162,10 @@ define([ } this.error(hasErrors || message); + + if (hasErrors || message) { + this.open(); + } }, /** diff --git a/app/code/Magento/Ui/view/base/web/js/form/components/insert.js b/app/code/Magento/Ui/view/base/web/js/form/components/insert.js index 26ad9fbfec0..d8cbcc9cc17 100644 --- a/app/code/Magento/Ui/view/base/web/js/form/components/insert.js +++ b/app/code/Magento/Ui/view/base/web/js/form/components/insert.js @@ -237,10 +237,21 @@ define([ * @param {*} data */ onRender: function (data) { + var resp; + this.loading(false); - this.set('content', data); - this.isRendered = true; - this.startRender = false; + + try { + resp = JSON.parse(data); + + if (resp.ajaxExpired) { + window.location.href = resp.ajaxRedirect; + } + } catch (e) { + this.set('content', data); + this.isRendered = true; + this.startRender = false; + } }, /** diff --git a/app/code/Magento/Ui/view/base/web/js/form/element/abstract.js b/app/code/Magento/Ui/view/base/web/js/form/element/abstract.js index 3b98d2c93c7..ca3d383accc 100755 --- a/app/code/Magento/Ui/view/base/web/js/form/element/abstract.js +++ b/app/code/Magento/Ui/view/base/web/js/form/element/abstract.js @@ -408,6 +408,7 @@ define([ isValid = this.disabled() || !this.visible() || result.passed; this.error(message); + this.error.valueHasMutated(); this.bubble('error', message); //TODO: Implement proper result propagation for form diff --git a/app/code/Magento/Ui/view/base/web/js/form/element/post-code.js b/app/code/Magento/Ui/view/base/web/js/form/element/post-code.js index 911574a0fb4..1b6dd9f1c57 100644 --- a/app/code/Magento/Ui/view/base/web/js/form/element/post-code.js +++ b/app/code/Magento/Ui/view/base/web/js/form/element/post-code.js @@ -26,7 +26,7 @@ define([ update: function (value) { var country = registry.get(this.parentName + '.' + 'country_id'), options = country.indexedOptions, - option; + option = null; if (!value) { return; @@ -34,6 +34,10 @@ define([ option = options[value]; + if (!option) { + return; + } + if (option['is_zipcode_optional']) { this.error(false); this.validation = _.omit(this.validation, 'required-entry'); diff --git a/app/code/Magento/Ui/view/base/web/js/form/element/wysiwyg.js b/app/code/Magento/Ui/view/base/web/js/form/element/wysiwyg.js index 3d02afcc40a..ce19899cd12 100644 --- a/app/code/Magento/Ui/view/base/web/js/form/element/wysiwyg.js +++ b/app/code/Magento/Ui/view/base/web/js/form/element/wysiwyg.js @@ -18,6 +18,7 @@ define([ 'use strict'; return Abstract.extend({ + currentWysiwyg: undefined, defaults: { elementSelector: 'textarea', suffixRegExpPattern: '${ $.wysiwygUniqueSuffix }', @@ -53,6 +54,10 @@ define([ // disable editor completely after initialization is field is disabled varienGlobalEvents.attachEventHandler('wysiwygEditorInitialized', function () { + if (!_.isUndefined(window.tinyMceEditors)) { + this.currentWysiwyg = window.tinyMceEditors[this.wysiwygId]; + } + if (this.disabled()) { this.setDisabled(true); } @@ -136,14 +141,9 @@ define([ } /* eslint-disable no-undef */ - if (typeof wysiwyg !== 'undefined' && wysiwyg.activeEditor()) { - if (wysiwyg && disabled) { - wysiwyg.setEnabledStatus(false); - wysiwyg.getPluginButtons().prop('disabled', 'disabled'); - } else if (wysiwyg) { - wysiwyg.setEnabledStatus(true); - wysiwyg.getPluginButtons().removeProp('disabled'); - } + if (!_.isUndefined(this.currentWysiwyg) && this.currentWysiwyg.activeEditor()) { + this.currentWysiwyg.setEnabledStatus(!disabled); + this.currentWysiwyg.getPluginButtons().prop('disabled', disabled); } } }); diff --git a/app/code/Magento/Ui/view/base/web/js/grid/controls/bookmarks/bookmarks.js b/app/code/Magento/Ui/view/base/web/js/grid/controls/bookmarks/bookmarks.js index 076465351cd..cfcd37a65b8 100644 --- a/app/code/Magento/Ui/view/base/web/js/grid/controls/bookmarks/bookmarks.js +++ b/app/code/Magento/Ui/view/base/web/js/grid/controls/bookmarks/bookmarks.js @@ -382,7 +382,7 @@ define([ * Checks if specified view is in editing state. * * @param {String} index - Index of a view to be checked. - * @returns {Bollean} + * @returns {Boolean} */ isEditing: function (index) { return this.editing === index; diff --git a/app/code/Magento/Ui/view/base/web/js/grid/editing/client.js b/app/code/Magento/Ui/view/base/web/js/grid/editing/client.js index f68a6f97d96..ca82ff81d3b 100644 --- a/app/code/Magento/Ui/view/base/web/js/grid/editing/client.js +++ b/app/code/Magento/Ui/view/base/web/js/grid/editing/client.js @@ -54,7 +54,7 @@ define([ /** * Proxy save method which might invoke - * data valiation prior to its' saving. + * data validation prior to its' saving. * * @param {Object} data - Data to be processed. * @returns {jQueryPromise} @@ -128,7 +128,7 @@ define([ /** * Handles ajax success callback. * - * @param {jQueryPromise} promise - Promise to be resoloved. + * @param {jQueryPromise} promise - Promise to be resolved. * @param {*} data - See 'jquery' ajax success callback. */ onSuccess: function (promise, data) { diff --git a/app/code/Magento/Ui/view/base/web/js/grid/editing/editor.js b/app/code/Magento/Ui/view/base/web/js/grid/editing/editor.js index a4785aea037..ece49cc8fe2 100644 --- a/app/code/Magento/Ui/view/base/web/js/grid/editing/editor.js +++ b/app/code/Magento/Ui/view/base/web/js/grid/editing/editor.js @@ -357,7 +357,7 @@ define([ /** * Resets specific records' data - * to the data present in asscotiated row. + * to the data present in associated row. * * @param {(Number|String)} id - See 'getId' method. * @param {Boolean} [isIndex=false] - See 'getId' method. @@ -403,7 +403,7 @@ define([ /** * Disables editing of specified fields. * - * @param {Array} fields - An array of fields indeces to be disabled. + * @param {Array} fields - An array of fields indexes to be disabled. * @returns {Editor} Chainable. */ disableFields: function (fields) { diff --git a/app/code/Magento/Ui/view/base/web/js/grid/editing/record.js b/app/code/Magento/Ui/view/base/web/js/grid/editing/record.js index aa83083cac3..9b8998368c5 100644 --- a/app/code/Magento/Ui/view/base/web/js/grid/editing/record.js +++ b/app/code/Magento/Ui/view/base/web/js/grid/editing/record.js @@ -264,7 +264,7 @@ define([ /** * Validates all of the available fields. * - * @returns {Array} An array with validatation results. + * @returns {Array} An array with validation results. */ validate: function () { return this.elems.map('validate'); @@ -280,7 +280,7 @@ define([ }, /** - * Counts total errors ammount accros all fields. + * Counts total errors amount across all fields. * * @returns {Number} */ @@ -306,7 +306,7 @@ define([ }, /** - * Updates 'fields' array filling it with available edtiors + * Updates 'fields' array filling it with available editors * or with column instances if associated field is not present. * * @returns {Record} Chainable. diff --git a/app/code/Magento/Ui/view/base/web/js/lib/core/collection.js b/app/code/Magento/Ui/view/base/web/js/lib/core/collection.js index eb4f2b39128..0491390d2b6 100644 --- a/app/code/Magento/Ui/view/base/web/js/lib/core/collection.js +++ b/app/code/Magento/Ui/view/base/web/js/lib/core/collection.js @@ -310,7 +310,7 @@ define([ * @private * * @param {Array} args - An array of arguments to pass to the next delegation call. - * @returns {Array} An array of delegation resutls. + * @returns {Array} An array of delegation results. */ _delegate: function (args) { var result; diff --git a/app/code/Magento/Ui/view/base/web/templates/grid/filters/elements/ui-select.html b/app/code/Magento/Ui/view/base/web/templates/grid/filters/elements/ui-select.html index bf3e2df8a82..82205de4156 100644 --- a/app/code/Magento/Ui/view/base/web/templates/grid/filters/elements/ui-select.html +++ b/app/code/Magento/Ui/view/base/web/templates/grid/filters/elements/ui-select.html @@ -36,7 +36,7 @@ class="action-select admin__action-multiselect" data-role="advanced-select" data-bind=" - css: {_active: multiselectFocus}, + css: {_active: listVisible}, click: function(data, event) { toggleListVisible(data, event) } @@ -73,7 +73,7 @@ class="action-select admin__action-multiselect" data-role="advanced-select" data-bind=" - css: {_active: multiselectFocus}, + css: {_active: listVisible}, click: function(data, event) { toggleListVisible(data, event) } diff --git a/app/code/Magento/Ups/Model/Carrier.php b/app/code/Magento/Ups/Model/Carrier.php index 06f68db0539..8c60f5a53a2 100644 --- a/app/code/Magento/Ups/Model/Carrier.php +++ b/app/code/Magento/Ups/Model/Carrier.php @@ -332,6 +332,14 @@ public function setRequest(RateRequest $request) $destCountry = self::GUAM_COUNTRY_ID; } + // For UPS, Las Palmas and Santa Cruz de Tenerife will be represented by Canary Islands country + if ($destCountry === 'ES' && + ($request->getDestRegionCode() === 'Las Palmas' + || $request->getDestRegionCode() === 'Santa Cruz de Tenerife') + ) { + $destCountry = 'IC'; + } + $country = $this->_countryFactory->create()->load($destCountry); $rowRequest->setDestCountry($country->getData('iso2_code') ?: $destCountry); @@ -1700,6 +1708,7 @@ public function getCustomizableContainerTypes() /** * Get delivery confirmation level based on origin/destination + * * Return null if delivery confirmation is not acceptable * * @param string|null $countyDestination diff --git a/app/code/Magento/UrlRewrite/Model/Storage/DbStorage.php b/app/code/Magento/UrlRewrite/Model/Storage/DbStorage.php index f4aa61b145f..d8ceb16d71f 100644 --- a/app/code/Magento/UrlRewrite/Model/Storage/DbStorage.php +++ b/app/code/Magento/UrlRewrite/Model/Storage/DbStorage.php @@ -87,7 +87,7 @@ protected function prepareSelect(array $data) } /** - * {@inheritdoc} + * @inheritdoc */ protected function doFindAllByData(array $data) { @@ -95,7 +95,7 @@ protected function doFindAllByData(array $data) } /** - * {@inheritdoc} + * @inheritdoc */ protected function doFindOneByData(array $data) { @@ -161,26 +161,22 @@ private function deleteOldUrls(array $urls): void $oldUrlsSelect->from( $this->resource->getTableName(self::TABLE_NAME) ); - /** @var UrlRewrite $url */ - foreach ($urls as $url) { - $oldUrlsSelect->orWhere( - $this->connection->quoteIdentifier( - UrlRewrite::ENTITY_TYPE - ) . ' = ?', - $url->getEntityType() - ); - $oldUrlsSelect->where( - $this->connection->quoteIdentifier( - UrlRewrite::ENTITY_ID - ) . ' = ?', - $url->getEntityId() - ); - $oldUrlsSelect->where( - $this->connection->quoteIdentifier( - UrlRewrite::STORE_ID - ) . ' = ?', - $url->getStoreId() - ); + + $uniqueEntities = $this->prepareUniqueEntities($urls); + foreach ($uniqueEntities as $storeId => $entityTypes) { + foreach ($entityTypes as $entityType => $entities) { + $oldUrlsSelect->orWhere( + $this->connection->quoteIdentifier( + UrlRewrite::STORE_ID + ) . ' = ' . $this->connection->quote($storeId, 'INTEGER') . + ' AND ' . $this->connection->quoteIdentifier( + UrlRewrite::ENTITY_ID + ) . ' IN (' . $this->connection->quote($entities, 'INTEGER') . ')' . + ' AND ' . $this->connection->quoteIdentifier( + UrlRewrite::ENTITY_TYPE + ) . ' = ' . $this->connection->quote($entityType) + ); + } } // prevent query locking in a case when nothing to delete @@ -198,6 +194,28 @@ private function deleteOldUrls(array $urls): void } } + /** + * Prepare array with unique entities + * + * @param UrlRewrite[] $urls + * @return array + */ + private function prepareUniqueEntities(array $urls): array + { + $uniqueEntities = []; + /** @var UrlRewrite $url */ + foreach ($urls as $url) { + $entityIds = (!empty($uniqueEntities[$url->getStoreId()][$url->getEntityType()])) ? + $uniqueEntities[$url->getStoreId()][$url->getEntityType()] : []; + + if (!\in_array($url->getEntityId(), $entityIds)) { + $entityIds[] = $url->getEntityId(); + } + $uniqueEntities[$url->getStoreId()][$url->getEntityType()] = $entityIds; + } + return $uniqueEntities; + } + /** * @inheritDoc */ @@ -289,7 +307,7 @@ protected function createFilterDataBasedOnUrls($urls) } /** - * {@inheritdoc} + * @inheritdoc */ public function deleteByData(array $data) { diff --git a/app/code/Magento/UrlRewrite/Model/StoreSwitcher/RewriteUrl.php b/app/code/Magento/UrlRewrite/Model/StoreSwitcher/RewriteUrl.php index 2ce00d53588..2ec573b6459 100644 --- a/app/code/Magento/UrlRewrite/Model/StoreSwitcher/RewriteUrl.php +++ b/app/code/Magento/UrlRewrite/Model/StoreSwitcher/RewriteUrl.php @@ -40,6 +40,8 @@ public function __construct( } /** + * Switch to another store. + * * @param StoreInterface $fromStore * @param StoreInterface $targetStore * @param string $redirectUrl @@ -66,17 +68,24 @@ public function switch(StoreInterface $fromStore, StoreInterface $targetStore, s UrlRewrite::STORE_ID => $oldStoreId, ]); if ($oldRewrite) { + $targetUrl = $targetStore->getBaseUrl(); // look for url rewrite match on the target store $currentRewrite = $this->urlFinder->findOneByData([ - UrlRewrite::REQUEST_PATH => $urlPath, + UrlRewrite::TARGET_PATH => $oldRewrite->getTargetPath(), UrlRewrite::STORE_ID => $targetStore->getId(), ]); - if (null === $currentRewrite) { + if ($currentRewrite) { + $targetUrl .= $currentRewrite->getRequestPath(); + } + } else { + $existingRewrite = $this->urlFinder->findOneByData([ + UrlRewrite::REQUEST_PATH => $urlPath + ]); + if ($existingRewrite) { /** @var \Magento\Framework\App\Response\Http $response */ $targetUrl = $targetStore->getBaseUrl(); } } - return $targetUrl; } } diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Section/AdminUrlRewriteIndexSection.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Section/AdminUrlRewriteIndexSection.xml index 0880b50950e..7c21acdf943 100644 --- a/app/code/Magento/UrlRewrite/Test/Mftf/Section/AdminUrlRewriteIndexSection.xml +++ b/app/code/Magento/UrlRewrite/Test/Mftf/Section/AdminUrlRewriteIndexSection.xml @@ -11,5 +11,13 @@ <section name="AdminUrlRewriteIndexSection"> <element name="requestPathFilter" type="input" selector="#urlrewriteGrid_filter_request_path"/> <element name="requestPathColumnValue" type="text" selector="//*[@id='urlrewriteGrid']//tbody//td[@data-column='request_path' and normalize-space(.)='{{columnValue}}']" parameterized="true"/> + <element name="targetPathColumnValue" type="text" selector="//*[@id='urlrewriteGrid']//tbody//td[@data-column='target_path' and normalize-space(.)='{{columnValue}}']" parameterized="true"/> + <element name="searchButton" type="button" selector="//button[@data-ui-id='widget-button-1']" timeout="30"/> + <element name="resetButton" type="button" selector="button[data-ui-id='widget-button-0']" timeout="30"/> + <element name="emptyRecordMessage" type="text" selector="//*[@class='empty-text']"/> + <element name="targetPathColumn" type="text" selector="//tr[@data-role='row'][{{var1}}]/td[@data-column='target_path']" parameterized="true"/> + <element name="redirectTypeColumn" type="text" selector="//tr[@data-role='row'][{{var1}}]/td[@data-column='redirect_type']" parameterized="true"/> + <element name="requestPathColumn" type="text" selector="//tr[@data-role='row'][{{var1}}]/td[@data-column='request_path']" parameterized="true"/> + <element name="emptyRecords" type="text" selector="//td[@class='empty-text']"/> </section> </sections> diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCheckUrlRewritesCorrectlyGeneratedForMultipleStoreviewsDuringProductImportTest.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCheckUrlRewritesCorrectlyGeneratedForMultipleStoreviewsDuringProductImportTest.xml new file mode 100644 index 00000000000..2c2dd48caea --- /dev/null +++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCheckUrlRewritesCorrectlyGeneratedForMultipleStoreviewsDuringProductImportTest.xml @@ -0,0 +1,117 @@ +<?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="AdminCheckUrlRewritesCorrectlyGeneratedForMultipleStoreviewsDuringProductImportTest"> + <annotations> + <features value="Url Rewrite"/> + <stories value="Url Rewrites for Multiple Storeviews"/> + <title value="Url Rewrites Correctly Generated for Multiple Storeviews During Product Import"/> + <description value="Check Url Rewrites Correctly Generated for Multiple Storeviews During Product Import."/> + <severity value="CRITICAL"/> + <testCaseId value="MAGETWO-68980"/> + <group value="urlRewrite"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <!-- Create Store View EN --> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createStoreViewEn"> + <argument name="customStore" value="customStoreENNotUnique"/> + </actionGroup> + <!-- Create Store View NL --> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createStoreViewNl"> + <argument name="customStore" value="customStoreNLNotUnique"/> + </actionGroup> + <createData entity="ApiCategory" stepKey="createCategory"> + <field key="name">category-admin</field> + </createData> + </before> + <after> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <actionGroup ref="deleteProductByName" stepKey="deleteImportedProduct"> + <argument name="sku" value="productformagetwo68980"/> + <argument name="name" value="productformagetwo68980"/> + </actionGroup> + <actionGroup ref="clearFiltersAdminDataGrid" stepKey="clearFiltersIfSet"/> + <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteStoreViewEn"> + <argument name="customStore" value="customStoreENNotUnique"/> + </actionGroup> + <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteStoreViewNl"> + <argument name="customStore" value="customStoreNLNotUnique"/> + </actionGroup> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <actionGroup ref="switchCategoryStoreView" stepKey="switchToStoreViewEn"> + <argument name="Store" value="customStoreENNotUnique.name"/> + <argument name="CatName" value="$$createCategory.name$$"/> + </actionGroup> + <uncheckOption selector="{{AdminCategoryBasicFieldSection.categoryNameUseDefault}}" stepKey="uncheckUseDefaultValueENStoreView"/> + <fillField selector="{{AdminCategoryBasicFieldSection.CategoryNameInput}}" userInput="category-english" stepKey="changeNameField"/> + <click selector="{{AdminCategorySEOSection.SectionHeader}}" stepKey="clickOnSectionHeader"/> + <actionGroup ref="ChangeSeoUrlKeyForSubCategory" stepKey="changeSeoUrlKeyENStoreView"> + <argument name="value" value="category-english"/> + </actionGroup> + <actionGroup ref="switchCategoryStoreView" stepKey="switchToStoreViewNl"> + <argument name="Store" value="customStoreNLNotUnique.name"/> + <argument name="CatName" value="$$createCategory.name$$"/> + </actionGroup> + <uncheckOption selector="{{AdminCategoryBasicFieldSection.categoryNameUseDefault}}" stepKey="uncheckUseDefaultValue1"/> + <fillField selector="{{AdminCategoryBasicFieldSection.CategoryNameInput}}" userInput="category-dutch" stepKey="changeNameFieldNLStoreView"/> + <click selector="{{AdminCategorySEOSection.SectionHeader}}" stepKey="clickOnSectionHeader2"/> + <actionGroup ref="ChangeSeoUrlKeyForSubCategory" stepKey="changeSeoUrlKeyNLStoreView"> + <argument name="value" value="category-dutch"/> + </actionGroup> + <amOnPage url="{{AdminImportIndexPage.url}}" stepKey="navigateToSystemImport"/> + <selectOption selector="{{AdminImportMainSection.entityType}}" userInput="Products" stepKey="selectProductsOption"/> + <waitForElementVisible selector="{{AdminImportMainSection.importBehavior}}" stepKey="waitForImportBehaviorElementVisible"/> + <selectOption selector="{{AdminImportMainSection.importBehavior}}" userInput="Add/Update" stepKey="selectAddUpdateOption"/> + <attachFile selector="{{AdminImportMainSection.selectFileToImport}}" userInput="import_updated.csv" stepKey="attachFileForImport"/> + <click selector="{{AdminImportHeaderSection.checkDataButton}}" stepKey="clickCheckDataButton"/> + <see selector="{{AdminMessagesSection.notice}}" userInput="Checked rows: 3, checked entities: 1, invalid rows: 0, total errors: 0" stepKey="assertNotice"/> + <see selector="{{AdminMessagesSection.successMessage}}" userInput="File is valid! To start import process press "Import" button" stepKey="assertSuccessMessage"/> + <click selector="{{AdminImportMainSection.importButton}}" stepKey="clickImportButton"/> + <see selector="{{AdminMessagesSection.successMessage}}" userInput="Import successfully done" stepKey="assertSuccessMessage1"/> + <see selector="{{AdminMessagesSection.notice}}" userInput="Created: 1, Updated: 0, Deleted: 0" stepKey="assertNotice1"/> + <actionGroup ref="SearchForProductOnBackendByNameActionGroup" stepKey="searchForProductOnBackend"> + <argument name="productName" value="productformagetwo68980"/> + </actionGroup> + <click selector="{{AdminProductGridSection.productRowBySku('productformagetwo68980')}}" stepKey="clickOnProductRow"/> + <grabFromCurrentUrl regex="~/id/(\d+)/~" stepKey="grabProductIdFromUrl"/> + <amOnPage url="{{AdminUrlRewriteIndexPage.url}}" stepKey="goToUrlRewritesIndexPage"/> + + <fillField selector="{{AdminUrlRewriteIndexSection.requestPathFilter}}" userInput="category-english.html" stepKey="inputCategoryUrlForENStoreView"/> + <click selector="{{AdminDataGridHeaderSection.applyFilters}}" stepKey="clickSearchButton"/> + <seeElement selector="{{AdminUrlRewriteIndexSection.requestPathColumnValue('category-english.html')}}" stepKey="seeUrlInRequestPathColumn"/> + <seeElement selector="{{AdminUrlRewriteIndexSection.targetPathColumnValue(catalog/category/view/id/$$createCategory.id$$)}}" stepKey="seeUrlInTargetPathColumn"/> + + <fillField selector="{{AdminUrlRewriteIndexSection.requestPathFilter}}" userInput="category-dutch.html" stepKey="inputCategoryUrlForNLStoreView"/> + <click selector="{{AdminDataGridHeaderSection.applyFilters}}" stepKey="clickSearchButton1"/> + <seeElement selector="{{AdminUrlRewriteIndexSection.requestPathColumnValue('category-dutch.html')}}" stepKey="seeUrlInRequestPathColumn1"/> + <seeElement selector="{{AdminUrlRewriteIndexSection.targetPathColumnValue(catalog/category/view/id/$$createCategory.id$$)}}" stepKey="seeUrlInTargetPathColumn1"/> + + <fillField selector="{{AdminUrlRewriteIndexSection.requestPathFilter}}" userInput="productformagetwo68980-english.html" stepKey="inputProductUrlForENStoreView"/> + <click selector="{{AdminDataGridHeaderSection.applyFilters}}" stepKey="clickSearchButton2"/> + <seeElement selector="{{AdminUrlRewriteIndexSection.requestPathColumnValue('productformagetwo68980-english.html')}}" stepKey="seeUrlInRequestPathColumn2"/> + <seeElement selector="{{AdminUrlRewriteIndexSection.targetPathColumnValue('catalog/product/view/id/$grabProductIdFromUrl')}}" stepKey="seeUrlInTargetPathColumn2"/> + + <fillField selector="{{AdminUrlRewriteIndexSection.requestPathFilter}}" userInput="productformagetwo68980-dutch.html" stepKey="inputProductUrlForENStoreView1"/> + <click selector="{{AdminDataGridHeaderSection.applyFilters}}" stepKey="clickSearchButton3"/> + <seeElement selector="{{AdminUrlRewriteIndexSection.requestPathColumnValue('productformagetwo68980-dutch.html')}}" stepKey="seeUrlInRequestPathColumn3"/> + <seeElement selector="{{AdminUrlRewriteIndexSection.targetPathColumnValue('catalog/product/view/id/$grabProductIdFromUrl')}}" stepKey="seeUrlInTargetPathColumn3"/> + + <fillField selector="{{AdminUrlRewriteIndexSection.requestPathFilter}}" userInput="category-english/productformagetwo68980-english.html" stepKey="inputProductUrlForENStoreView2"/> + <click selector="{{AdminDataGridHeaderSection.applyFilters}}" stepKey="clickSearchButton4"/> + <seeElement selector="{{AdminUrlRewriteIndexSection.requestPathColumnValue('category-english/productformagetwo68980-english.html')}}" stepKey="seeUrlInRequestPathColumn4"/> + <seeElement selector="{{AdminUrlRewriteIndexSection.targetPathColumnValue(catalog/product/view/id/$grabProductIdFromUrl/category/$$createCategory.id$$)}}" stepKey="seeUrlInTargetPathColumn4"/> + + <fillField selector="{{AdminUrlRewriteIndexSection.requestPathFilter}}" userInput="category-dutch/productformagetwo68980-dutch.html" stepKey="inputProductUrlForENStoreView3"/> + <click selector="{{AdminDataGridHeaderSection.applyFilters}}" stepKey="clickSearchButton5"/> + <seeElement selector="{{AdminUrlRewriteIndexSection.requestPathColumnValue('category-dutch/productformagetwo68980-dutch.html')}}" stepKey="seeUrlInRequestPathColumn5"/> + <seeElement selector="{{AdminUrlRewriteIndexSection.targetPathColumnValue(catalog/product/view/id/$grabProductIdFromUrl/category/$$createCategory.id$$)}}" stepKey="seeUrlInTargetPathColumn5"/> + </test> +</tests> diff --git a/app/code/Magento/UrlRewrite/Test/Unit/Model/Storage/DbStorageTest.php b/app/code/Magento/UrlRewrite/Test/Unit/Model/Storage/DbStorageTest.php index b96a8ef6374..697ce33be0f 100644 --- a/app/code/Magento/UrlRewrite/Test/Unit/Model/Storage/DbStorageTest.php +++ b/app/code/Magento/UrlRewrite/Test/Unit/Model/Storage/DbStorageTest.php @@ -445,7 +445,6 @@ public function testReplace() $urlSecond = $this->createMock(\Magento\UrlRewrite\Service\V1\Data\UrlRewrite::class); // delete - $urlFirst->expects($this->any()) ->method('getEntityType') ->willReturn('product'); @@ -479,10 +478,6 @@ public function testReplace() ->with(DbStorage::TABLE_NAME) ->will($this->returnValue('table_name')); - $this->connectionMock->expects($this->any()) - ->method('query') - ->with('sql delete query'); - // insert $urlFirst->expects($this->any()) @@ -497,10 +492,6 @@ public function testReplace() ->with(DbStorage::TABLE_NAME) ->will($this->returnValue('table_name')); - $this->connectionMock->expects($this->once()) - ->method('insertMultiple') - ->with('table_name', [['row1'], ['row2']]); - $this->storage->replace([$urlFirst, $urlSecond]); } diff --git a/app/code/Magento/User/Test/Mftf/ActionGroup/AdminCreateRoleActionGroup.xml b/app/code/Magento/User/Test/Mftf/ActionGroup/AdminCreateRoleActionGroup.xml new file mode 100644 index 00000000000..da08ac469b7 --- /dev/null +++ b/app/code/Magento/User/Test/Mftf/ActionGroup/AdminCreateRoleActionGroup.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"> + <actionGroup name="AdminCreateRoleActionGroup"> + <arguments> + <argument name="restrictedRole"/> + <argument name="User"/> + </arguments> + <amOnPage url="{{AdminEditRolePage.url}}" stepKey="navigateToNewRole"/> + <waitForPageLoad stepKey="waitForPageLoad1"/> + <fillField selector="{{AdminEditRoleInfoSection.roleName}}" userInput="{{User.name}}" stepKey="fillRoleName" /> + <fillField selector="{{AdminEditRoleInfoSection.password}}" userInput="{{_ENV.MAGENTO_ADMIN_PASSWORD}}" stepKey="enterPassword" /> + <click selector="{{AdminEditRoleInfoSection.roleResourcesTab}}" stepKey="clickRoleResourcesTab" /> + <waitForElementVisible selector="{{AdminEditRoleResourcesSection.roleScopes}}" stepKey="waitForScopeSelection" /> + <selectOption selector="{{AdminEditRoleResourcesSection.resourceAccess}}" userInput="0" stepKey="selectResourceAccessCustom"/> + <waitForElementVisible stepKey="waitForElementVisible" selector="{{AdminEditRoleInfoSection.blockName('restrictedRole')}}" time="30"/> + <click stepKey="clickContentBlockCheckbox" selector="{{AdminEditRoleInfoSection.blockName('restrictedRole')}}"/> + <click selector="{{AdminEditRoleInfoSection.saveButton}}" stepKey="clickSaveRoleButton" /> + <waitForPageLoad stepKey="waitForPageLoad2" /> + </actionGroup> + <!--Create new role--> + <actionGroup name="AdminCreateRole"> + <arguments> + <argument name="role" type="string" defaultValue=""/> + <argument name="resource" type="string" defaultValue="All"/> + <argument name="scope" type="string" defaultValue="Custom"/> + <argument name="websites" type="string" defaultValue="Main Website"/> + </arguments> + <click selector="{{AdminCreateRoleSection.create}}" stepKey="clickToAddNewRole"/> + <fillField selector="{{AdminCreateRoleSection.name}}" userInput="{{role.name}}" stepKey="setRoleName"/> + <fillField stepKey="setPassword" selector="{{AdminCreateRoleSection.password}}" userInput="{{_ENV.MAGENTO_ADMIN_PASSWORD}}"/> + <click selector="{{AdminCreateRoleSection.roleResources}}" stepKey="clickToOpenRoleResources"/> + <waitForPageLoad stepKey="waitForRoleResourcePage" time="5"/> + <click stepKey="checkSales" selector="//a[text()='Sales']"/> + <click selector="{{AdminCreateRoleSection.save}}" stepKey="clickToSaveRole"/> + <waitForPageLoad stepKey="waitForPageLoad" time="10"/> + <see userInput="You saved the role." stepKey="seeSuccessMessage" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/User/Test/Mftf/ActionGroup/AdminCreateUserActionGroup.xml b/app/code/Magento/User/Test/Mftf/ActionGroup/AdminCreateUserActionGroup.xml index 9a0fa4a2057..303713132d2 100644 --- a/app/code/Magento/User/Test/Mftf/ActionGroup/AdminCreateUserActionGroup.xml +++ b/app/code/Magento/User/Test/Mftf/ActionGroup/AdminCreateUserActionGroup.xml @@ -10,23 +10,24 @@ <actionGroup name="AdminCreateUserActionGroup"> <arguments> <argument name="role"/> - <argument name="User" defaultValue="admin2"/> + <argument name="User" defaultValue="newAdmin"/> </arguments> - <amOnPage url="{{AdminEditUserPage.url}}" stepKey="navigateToNewUser"/> - <waitForPageLoad stepKey="waitForPageLoad1" /> - <fillField selector="{{AdminEditUserSection.usernameTextField}}" userInput="{{admin2.username}}" stepKey="enterUserName" /> - <fillField selector="{{AdminEditUserSection.firstNameTextField}}" userInput="{{admin2.firstName}}" stepKey="enterFirstName" /> - <fillField selector="{{AdminEditUserSection.lastNameTextField}}" userInput="{{admin2.lastName}}" stepKey="enterLastName" /> - <fillField selector="{{AdminEditUserSection.emailTextField}}" userInput="{{admin2.username}}@magento.com" stepKey="enterEmail" /> - <fillField selector="{{AdminEditUserSection.passwordTextField}}" userInput="{{admin2.password}}" stepKey="enterPassword" /> - <fillField selector="{{AdminEditUserSection.pwConfirmationTextField}}" userInput="{{admin2.password}}" stepKey="confirmPassword" /> + <amOnPage url="{{AdminUsersPage.url}}" stepKey="amOnAdminUsersPage"/> + <waitForPageLoad stepKey="waitForAdminUserPageLoad"/> + <click selector="{{AdminCreateUserSection.create}}" stepKey="clickToCreateNewUser"/> + <fillField selector="{{AdminEditUserSection.usernameTextField}}" userInput="{{newAdmin.username}}" stepKey="enterUserName" /> + <fillField selector="{{AdminEditUserSection.firstNameTextField}}" userInput="{{newAdmin.firstName}}" stepKey="enterFirstName" /> + <fillField selector="{{AdminEditUserSection.lastNameTextField}}" userInput="{{newAdmin.lastName}}" stepKey="enterLastName" /> + <fillField selector="{{AdminEditUserSection.emailTextField}}" userInput="{{newAdmin.username}}@magento.com" stepKey="enterEmail" /> + <fillField selector="{{AdminEditUserSection.passwordTextField}}" userInput="{{newAdmin.password}}" stepKey="enterPassword" /> + <fillField selector="{{AdminEditUserSection.pwConfirmationTextField}}" userInput="{{newAdmin.password}}" stepKey="confirmPassword" /> <fillField selector="{{AdminEditUserSection.currentPasswordField}}" userInput="{{_ENV.MAGENTO_ADMIN_PASSWORD}}" stepKey="enterCurrentPassword" /> <scrollToTopOfPage stepKey="scrollToTopOfPage" /> <click selector="{{AdminEditUserSection.userRoleTab}}" stepKey="clickUserRole" /> - <fillField selector="{{AdminEditUserRoleSection.roleNameFilterTextField}}" userInput="{{role.name}}" stepKey="filterRole" /> - <click selector="{{AdminEditUserRoleSection.searchButton}}" stepKey="clickSearch" /> + <fillField selector="{{AdminEditUserSection.roleNameFilterTextField}}" userInput="{{role.name}}" stepKey="filterRole" /> + <click selector="{{AdminEditUserSection.searchButton}}" stepKey="clickSearch" /> <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskToDisappear1"/> - <click selector="{{AdminEditUserRoleSection.searchResultFirstRow}}" stepKey="selectRole" /> + <click selector="{{AdminEditUserSection.searchResultFirstRow}}" stepKey="selectRole" /> <click selector="{{AdminEditUserSection.saveButton}}" stepKey="clickSaveUser" /> <waitForPageLoad stepKey="waitForPageLoad2" /> <see userInput="You saved the user." stepKey="seeSuccessMessage" /> diff --git a/app/code/Magento/User/Test/Mftf/ActionGroup/AdminDeleteCreatedRoleActionGroup.xml b/app/code/Magento/User/Test/Mftf/ActionGroup/AdminDeleteCreatedRoleActionGroup.xml new file mode 100644 index 00000000000..813e22df227 --- /dev/null +++ b/app/code/Magento/User/Test/Mftf/ActionGroup/AdminDeleteCreatedRoleActionGroup.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="AdminDeleteCreatedRoleActionGroup"> + <arguments> + <argument name="role" defaultValue=""/> + </arguments> + <amOnPage url="{{AdminRolesPage.url}}" stepKey="amOnAdminUsersPage"/> + <waitForPageLoad stepKey="waitForUserRolePageLoad"/> + <click stepKey="clickToAddNewRole" selector="{{AdminDeleteRoleSection.role(role.name)}}"/> + <fillField stepKey="TypeCurrentPassword" selector="{{AdminDeleteRoleSection.current_pass}}" userInput="{{_ENV.MAGENTO_ADMIN_PASSWORD}}"/> + <click stepKey="clickToDeleteRole" selector="{{AdminDeleteRoleSection.delete}}"/> + <waitForElementVisible stepKey="wait" selector="{{AdminDeleteRoleSection.confirm}}" time="30"/> + <click stepKey="clickToConfirm" selector="{{AdminDeleteRoleSection.confirm}}"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <see stepKey="seeSuccessMessage" userInput="You deleted the role."/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/User/Test/Mftf/ActionGroup/AdminDeleteCreatedUserActionGroup.xml b/app/code/Magento/User/Test/Mftf/ActionGroup/AdminDeleteCreatedUserActionGroup.xml index 7f1ed3be1ca..74124f366a5 100644 --- a/app/code/Magento/User/Test/Mftf/ActionGroup/AdminDeleteCreatedUserActionGroup.xml +++ b/app/code/Magento/User/Test/Mftf/ActionGroup/AdminDeleteCreatedUserActionGroup.xml @@ -13,6 +13,7 @@ </arguments> <amOnPage stepKey="amOnAdminUsersPage" url="{{AdminUsersPage.url}}"/> <click stepKey="openTheUser" selector="{{AdminDeleteUserSection.role(user.username)}}"/> + <waitForPageLoad stepKey="waitForSingleUserPageToLoad" /> <fillField stepKey="TypeCurrentPassword" selector="{{AdminDeleteUserSection.password}}" userInput="{{_ENV.MAGENTO_ADMIN_PASSWORD}}"/> <scrollToTopOfPage stepKey="scrollToTop"/> <click stepKey="clickToDeleteUser" selector="{{AdminDeleteUserSection.delete}}"/> diff --git a/app/code/Magento/User/Test/Mftf/ActionGroup/AdminDeleteUserActionGroup.xml b/app/code/Magento/User/Test/Mftf/ActionGroup/AdminDeleteUserActionGroup.xml index 70c0a772ec3..9b7342e531b 100644 --- a/app/code/Magento/User/Test/Mftf/ActionGroup/AdminDeleteUserActionGroup.xml +++ b/app/code/Magento/User/Test/Mftf/ActionGroup/AdminDeleteUserActionGroup.xml @@ -7,6 +7,21 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminDeleteUserActionGroup"> + <arguments> + <argument name="user"/> + </arguments> + <amOnPage stepKey="amOnAdminUsersPage" url="{{AdminUsersPage.url}}"/> + <waitForPageLoad stepKey="waitForAdminUserPageLoad"/> + <click stepKey="openTheUser" selector="{{AdminDeleteUserSection.role(user.name)}}"/> + <fillField stepKey="TypeCurrentPassword" selector="{{AdminDeleteUserSection.password}}" userInput="{{_ENV.MAGENTO_ADMIN_PASSWORD}}"/> + <scrollToTopOfPage stepKey="scrollToTop"/> + <click stepKey="clickToDeleteRole" selector="{{AdminDeleteUserSection.delete}}"/> + <waitForElementVisible stepKey="wait" selector="{{AdminDeleteRoleSection.confirm}}" time="30"/> + <click stepKey="clickToConfirm" selector="{{AdminDeleteUserSection.confirm}}"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <see stepKey="seeDeleteMessageForUser" userInput="You deleted the user."/> + </actionGroup> <actionGroup name="AdminDeleteCustomUserActionGroup"> <arguments> <argument name="user"/> diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/AdminCreateRoleSection.xml b/app/code/Magento/User/Test/Mftf/Section/AdminCreateRoleSection.xml similarity index 83% rename from app/code/Magento/Braintree/Test/Mftf/Section/AdminCreateRoleSection.xml rename to app/code/Magento/User/Test/Mftf/Section/AdminCreateRoleSection.xml index 1158f471d51..7dd313a2ba8 100644 --- a/app/code/Magento/Braintree/Test/Mftf/Section/AdminCreateRoleSection.xml +++ b/app/code/Magento/User/Test/Mftf/Section/AdminCreateRoleSection.xml @@ -5,7 +5,9 @@ * See COPYING.txt for license details. */ --> -<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminCreateRoleSection"> <element name="create" type="button" selector="#add"/> <element name="name" type="button" selector="#role_name"/> @@ -21,4 +23,4 @@ <element name="searchButton" type="button" selector=".admin__data-grid-header button[title=Search]"/> <element name="searchResultFirstRow" type="text" selector=".data-grid>tbody>tr"/> </section> -</sections> \ No newline at end of file +</sections> diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/AdminDeleteRoleSection.xml b/app/code/Magento/User/Test/Mftf/Section/AdminDeleteRoleSection.xml similarity index 68% rename from app/code/Magento/Braintree/Test/Mftf/Section/AdminDeleteRoleSection.xml rename to app/code/Magento/User/Test/Mftf/Section/AdminDeleteRoleSection.xml index 220c9a444b0..1b55d09d059 100644 --- a/app/code/Magento/Braintree/Test/Mftf/Section/AdminDeleteRoleSection.xml +++ b/app/code/Magento/User/Test/Mftf/Section/AdminDeleteRoleSection.xml @@ -5,11 +5,13 @@ * See COPYING.txt for license details. */ --> -<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminDeleteRoleSection"> <element name="theRole" selector="//td[contains(text(), 'Role')]" type="button"/> <element name="current_pass" type="button" selector="#current_password"/> <element name="delete" selector="//button/span[contains(text(), 'Delete Role')]" type="button"/> <element name="confirm" selector="//*[@class='action-primary action-accept']" type="button"/> </section> -</sections> \ No newline at end of file +</sections> diff --git a/app/code/Magento/User/Test/Mftf/Section/AdminEditRoleInfoSection.xml b/app/code/Magento/User/Test/Mftf/Section/AdminEditRoleInfoSection.xml index e30a545649d..57659e1aff0 100644 --- a/app/code/Magento/User/Test/Mftf/Section/AdminEditRoleInfoSection.xml +++ b/app/code/Magento/User/Test/Mftf/Section/AdminEditRoleInfoSection.xml @@ -17,5 +17,6 @@ <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"/> + <element name="blockName" type="checkbox" selector="//*[text()='{{var}}']//*[@class='jstree-checkbox']" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/User/Test/Mftf/Section/AdminEditUserSection.xml b/app/code/Magento/User/Test/Mftf/Section/AdminEditUserSection.xml index 5b866b45e2f..64068a0a5ef 100644 --- a/app/code/Magento/User/Test/Mftf/Section/AdminEditUserSection.xml +++ b/app/code/Magento/User/Test/Mftf/Section/AdminEditUserSection.xml @@ -5,8 +5,12 @@ * 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"> +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminEditUserSection"> + <element name="system" type="input" selector="#menu-magento-backend-system"/> + <element name="allUsers" type="input" selector="//span[contains(text(), 'All Users')]"/> + <element name="create" type="input" selector="#add"/> <element name="usernameTextField" type="input" selector="#user_username"/> <element name="firstNameTextField" type="input" selector="#user_firstname"/> <element name="lastNameTextField" type="input" selector="#user_lastname"/> diff --git a/app/code/Magento/User/Test/Mftf/Section/AdminRoleGridSection.xml b/app/code/Magento/User/Test/Mftf/Section/AdminRoleGridSection.xml index 6db68585003..8413081237f 100644 --- a/app/code/Magento/User/Test/Mftf/Section/AdminRoleGridSection.xml +++ b/app/code/Magento/User/Test/Mftf/Section/AdminRoleGridSection.xml @@ -14,4 +14,11 @@ <element name="roleNameInFirstRow" type="text" selector=".col-role_name"/> <element name="searchResultFirstRow" type="text" selector=".data-grid>tbody>tr"/> </section> + + <section name="AdminDeleteRoleSection"> + <element name="theRole" selector="//td[contains(text(), 'Role')]" type="button"/> + <element name="current_pass" type="button" selector="#current_password"/> + <element name="delete" selector="//button/span[contains(text(), 'Delete Role')]" type="button"/> + <element name="confirm" selector="//*[@class='action-primary action-accept']" type="button"/> + </section> </sections> diff --git a/app/code/Magento/User/Test/Mftf/Section/AdminUserGridSection.xml b/app/code/Magento/User/Test/Mftf/Section/AdminUserGridSection.xml index f429c390efe..c21a8b875e9 100644 --- a/app/code/Magento/User/Test/Mftf/Section/AdminUserGridSection.xml +++ b/app/code/Magento/User/Test/Mftf/Section/AdminUserGridSection.xml @@ -14,4 +14,11 @@ <element name="searchResultFirstRow" type="text" selector=".data-grid>tbody>tr"/> <element name="successMessage" type="text" selector=".message-success"/> </section> + + <section name="AdminDeleteUserSection"> + <element name="theUser" selector="//td[contains(text(), 'John')]" type="button"/> + <element name="password" selector="#user_current_password" type="input"/> + <element name="delete" selector="//button/span[contains(text(), 'Delete User')]" type="button"/> + <element name="confirm" selector="//*[@class='action-primary action-accept']" type="button"/> + </section> </sections> diff --git a/app/code/Magento/Usps/Model/Carrier.php b/app/code/Magento/Usps/Model/Carrier.php index 1e8faf3cc96..6e69b9c3179 100644 --- a/app/code/Magento/Usps/Model/Carrier.php +++ b/app/code/Magento/Usps/Model/Carrier.php @@ -366,6 +366,7 @@ public function getResult() /** * @inheritdoc + * * Starting from 23.02.2018 USPS doesn't allow to create free shipping labels via their API. */ public function isShippingLabelsAvailable() @@ -448,7 +449,7 @@ protected function _getXmlQuotes() $package->addChild('FirstClassMailType', 'PARCEL'); } $package->addChild('ZipOrigination', $r->getOrigPostal()); - //only 5 chars avaialble + //only 5 chars available $package->addChild('ZipDestination', substr($r->getDestPostal(), 0, 5)); $package->addChild('Pounds', $r->getWeightPounds()); $package->addChild('Ounces', $r->getWeightOunces()); @@ -1167,6 +1168,7 @@ public function getAllowedMethods() /** * Return USPS county name by country ISO 3166-1-alpha-2 code + * * Return false for unknown countries * * @param string $countryId @@ -1246,7 +1248,7 @@ protected function _getCountryName($countryId) 'FO' => 'Faroe Islands', 'FR' => 'France', 'GA' => 'Gabon', - 'GB' => 'Great Britain and Northern Ireland', + 'GB' => 'United Kingdom of Great Britain and Northern Ireland', 'GD' => 'Grenada', 'GE' => 'Georgia, Republic of', 'GF' => 'French Guiana', @@ -1364,7 +1366,7 @@ protected function _getCountryName($countryId) 'ST' => 'Sao Tome and Principe', 'SV' => 'El Salvador', 'SY' => 'Syrian Arab Republic', - 'SZ' => 'Swaziland', + 'SZ' => 'Eswatini', 'TC' => 'Turks and Caicos Islands', 'TD' => 'Chad', 'TG' => 'Togo', diff --git a/app/code/Magento/Variable/etc/di.xml b/app/code/Magento/Variable/etc/di.xml index f0a24e89ef8..41759e1f158 100644 --- a/app/code/Magento/Variable/etc/di.xml +++ b/app/code/Magento/Variable/etc/di.xml @@ -42,6 +42,7 @@ <item name="general/store_information/merchant_vat_number" xsi:type="string">1</item> </item> </argument> + <argument name="configStructure" xsi:type="object">Magento\Config\Model\Config\Structure\Proxy</argument> </arguments> </type> -</config> \ No newline at end of file +</config> diff --git a/app/code/Magento/WebapiAsync/Code/Generator/Config/RemoteServiceReader/Communication.php b/app/code/Magento/WebapiAsync/Code/Generator/Config/RemoteServiceReader/Communication.php index 39e7fa418a3..2a6cac09c22 100644 --- a/app/code/Magento/WebapiAsync/Code/Generator/Config/RemoteServiceReader/Communication.php +++ b/app/code/Magento/WebapiAsync/Code/Generator/Config/RemoteServiceReader/Communication.php @@ -67,7 +67,8 @@ public function read($scope = null) CommunicationConfig::HANDLER_TYPE => $serviceClass, CommunicationConfig::HANDLER_METHOD => $serviceMethod, ], - ] + ], + false ); $rewriteTopicParams = [ CommunicationConfig::TOPIC_IS_SYNCHRONOUS => false, diff --git a/app/code/Magento/WebapiAsync/Model/Config.php b/app/code/Magento/WebapiAsync/Model/Config.php index 92343027adc..16c24643ba3 100644 --- a/app/code/Magento/WebapiAsync/Model/Config.php +++ b/app/code/Magento/WebapiAsync/Model/Config.php @@ -15,6 +15,9 @@ use Magento\Framework\Exception\LocalizedException; use Magento\Webapi\Model\Config\Converter; +/** + * Class for accessing to Webapi_Async configuration. + */ class Config implements \Magento\AsynchronousOperations\Model\ConfigInterface { /** @@ -55,7 +58,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function getServices() { @@ -73,26 +76,30 @@ public function getServices() } /** - * {@inheritdoc} + * @inheritdoc */ public function getTopicName($routeUrl, $httpMethod) { $services = $this->getServices(); - $topicName = $this->generateTopicNameByRouteData( + $lookupKey = $this->generateLookupKeyByRouteData( $routeUrl, $httpMethod ); - if (array_key_exists($topicName, $services) === false) { + if (array_key_exists($lookupKey, $services) === false) { throw new LocalizedException( - __('WebapiAsync config for "%topicName" does not exist.', ['topicName' => $topicName]) + __('WebapiAsync config for "%lookupKey" does not exist.', ['lookupKey' => $lookupKey]) ); } - return $services[$topicName][self::SERVICE_PARAM_KEY_TOPIC]; + return $services[$lookupKey][self::SERVICE_PARAM_KEY_TOPIC]; } /** + * Generate topic data for all defined services + * + * Topic data is indexed by a lookup key that is derived from route data + * * @return array */ private function generateTopicsDataFromWebapiConfig() @@ -105,11 +112,18 @@ private function generateTopicsDataFromWebapiConfig() $serviceInterface = $httpMethodData[Converter::KEY_SERVICE][Converter::KEY_SERVICE_CLASS]; $serviceMethod = $httpMethodData[Converter::KEY_SERVICE][Converter::KEY_SERVICE_METHOD]; - $topicName = $this->generateTopicNameByRouteData( + $lookupKey = $this->generateLookupKeyByRouteData( $routeUrl, $httpMethod ); - $services[$topicName] = [ + + $topicName = $this->generateTopicNameFromService( + $serviceInterface, + $serviceMethod, + $httpMethod + ); + + $services[$lookupKey] = [ self::SERVICE_PARAM_KEY_INTERFACE => $serviceInterface, self::SERVICE_PARAM_KEY_METHOD => $serviceMethod, self::SERVICE_PARAM_KEY_TOPIC => $topicName, @@ -122,7 +136,7 @@ private function generateTopicsDataFromWebapiConfig() } /** - * Generate topic name based on service type and method name. + * Generate lookup key name based on route and method * * Perform the following conversion: * self::TOPIC_PREFIX + /V1/products + POST => async.V1.products.POST @@ -131,19 +145,39 @@ private function generateTopicsDataFromWebapiConfig() * @param string $httpMethod * @return string */ - private function generateTopicNameByRouteData($routeUrl, $httpMethod) + private function generateLookupKeyByRouteData($routeUrl, $httpMethod) { - return self::TOPIC_PREFIX . $this->generateTopicName($routeUrl, $httpMethod, '/', false); + return self::TOPIC_PREFIX . $this->generateKey($routeUrl, $httpMethod, '/', false); } /** + * Generate topic name based on service type and method name. + * + * Perform the following conversion: + * self::TOPIC_PREFIX + Magento\Catalog\Api\ProductRepositoryInterface + save + POST + * => async.magento.catalog.api.productrepositoryinterface.save.POST + * + * @param string $serviceInterface + * @param string $serviceMethod + * @param string $httpMethod + * @return string + */ + private function generateTopicNameFromService($serviceInterface, $serviceMethod, $httpMethod) + { + $typeName = strtolower(sprintf('%s.%s', $serviceInterface, $serviceMethod)); + return strtolower(self::TOPIC_PREFIX . $this->generateKey($typeName, $httpMethod, '\\', false)); + } + + /** + * Join and simplify input type and method into a string that can be used as an array key + * * @param string $typeName * @param string $methodName * @param string $delimiter * @param bool $lcfirst * @return string */ - private function generateTopicName($typeName, $methodName, $delimiter = '\\', $lcfirst = true) + private function generateKey($typeName, $methodName, $delimiter = '\\', $lcfirst = true) { $parts = explode($delimiter, ltrim($typeName, $delimiter)); foreach ($parts as &$part) { diff --git a/app/code/Magento/WebapiAsync/Plugin/Cache/Webapi.php b/app/code/Magento/WebapiAsync/Plugin/Cache/Webapi.php new file mode 100644 index 00000000000..ecc929b2048 --- /dev/null +++ b/app/code/Magento/WebapiAsync/Plugin/Cache/Webapi.php @@ -0,0 +1,106 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\WebapiAsync\Plugin\Cache; + +use Magento\WebapiAsync\Controller\Rest\AsynchronousSchemaRequestProcessor; +use Magento\Framework\Webapi\Rest\Request; + +/** + * Class Webapi + */ +class Webapi +{ + /** + * Cache key for Async Routes + */ + const ASYNC_ROUTES_CONFIG_CACHE_ID = 'async-routes-services-config'; + + /** + * @var AsynchronousSchemaRequestProcessor + */ + private $asynchronousSchemaRequestProcessor; + + /** + * @var \Magento\Framework\Webapi\Rest\Request + */ + private $request; + + /** + * ServiceMetadata constructor. + * + * @param Request $request + * @param AsynchronousSchemaRequestProcessor $asynchronousSchemaRequestProcessor + */ + public function __construct( + \Magento\Framework\Webapi\Rest\Request $request, + AsynchronousSchemaRequestProcessor $asynchronousSchemaRequestProcessor + ) { + $this->request = $request; + $this->asynchronousSchemaRequestProcessor = $asynchronousSchemaRequestProcessor; + } + + /** + * Change identifier in case if Async request before cache load + * + * @param \Magento\Webapi\Model\Cache\Type\Webapi $subject + * @param string $identifier + * @return null|string + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function beforeLoad(\Magento\Webapi\Model\Cache\Type\Webapi $subject, $identifier) + { + if ($this->asynchronousSchemaRequestProcessor->canProcess($this->request) + && $identifier === \Magento\Webapi\Model\ServiceMetadata::ROUTES_CONFIG_CACHE_ID) { + return self::ASYNC_ROUTES_CONFIG_CACHE_ID; + } + return null; + } + + /** + * Change identifier in case if Async request before cache save + * + * @param \Magento\Webapi\Model\Cache\Type\Webapi $subject + * @param string $data + * @param string $identifier + * @param array $tags + * @param int|bool|null $lifeTime + * @return array|null + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function beforeSave( + \Magento\Webapi\Model\Cache\Type\Webapi $subject, + $data, + $identifier, + array $tags = [], + $lifeTime = null + ) { + if ($this->asynchronousSchemaRequestProcessor->canProcess($this->request) + && $identifier === \Magento\Webapi\Model\ServiceMetadata::ROUTES_CONFIG_CACHE_ID) { + return [$data, self::ASYNC_ROUTES_CONFIG_CACHE_ID, $tags, $lifeTime]; + } + return null; + } + + /** + * Change identifier in case if Async request before remove cache + * + * @param \Magento\Webapi\Model\Cache\Type\Webapi $subject + * @param string $identifier + * @return null|string + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function beforeRemove(\Magento\Webapi\Model\Cache\Type\Webapi $subject, $identifier) + { + if ($this->asynchronousSchemaRequestProcessor->canProcess($this->request) + && $identifier === \Magento\Webapi\Model\ServiceMetadata::ROUTES_CONFIG_CACHE_ID) { + return self::ASYNC_ROUTES_CONFIG_CACHE_ID; + } + return null; + } +} diff --git a/app/code/Magento/WebapiAsync/Test/Unit/Model/ConfigTest.php b/app/code/Magento/WebapiAsync/Test/Unit/Model/ConfigTest.php new file mode 100644 index 00000000000..47b75b20573 --- /dev/null +++ b/app/code/Magento/WebapiAsync/Test/Unit/Model/ConfigTest.php @@ -0,0 +1,91 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\WebapiAsync\Test\Unit\Model; + +use Magento\Framework\Serialize\SerializerInterface; +use Magento\Webapi\Model\Cache\Type\Webapi; +use Magento\Webapi\Model\Config as WebapiConfig; +use Magento\WebapiAsync\Model\Config; +use Magento\Webapi\Model\Config\Converter; + +class ConfigTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var Config + */ + private $config; + + /** + * @var Webapi|\PHPUnit_Framework_MockObject_MockObject + */ + private $webapiCacheMock; + + /** + * @var WebapiConfig|\PHPUnit_Framework_MockObject_MockObject + */ + private $configMock; + + /** + * @var SerializerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $serializerMock; + + protected function setUp() + { + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $this->webapiCacheMock = $this->createMock(\Magento\Webapi\Model\Cache\Type\Webapi::class); + $this->configMock = $this->createMock(WebapiConfig::class); + $this->serializerMock = $this->createMock(SerializerInterface::class); + + $this->config = $objectManager->getObject( + Config::class, + [ + 'cache' => $this->webapiCacheMock, + 'webApiConfig' => $this->configMock, + 'serializer' => $this->serializerMock + ] + ); + } + + public function testGetServicesSetsTopicFromServiceContractName() + { + $services = [ + Converter::KEY_ROUTES => [ + '/V1/products' => [ + 'POST' => [ + 'service' => [ + 'class' => \Magento\Catalog\Api\ProductRepositoryInterface::class, + 'method' => 'save', + ] + ] + ] + ] + ]; + $this->configMock->expects($this->once()) + ->method('getServices') + ->willReturn($services); + + /* example of what $this->config->getServices() returns + $result = [ + 'async.V1.products.POST' => [ + 'interface' => 'Magento\Catalog\Api\ProductRepositoryInterface', + 'method' => 'save', + 'topic' => 'async.magento.catalog.api.productrepositoryinterface.save.post', + ] + ]; + */ + $result = $this->config->getServices(); + + $expectedTopic = 'async.magento.catalog.api.productrepositoryinterface.save.post'; + $lookupKey = 'async.V1.products.POST'; + $this->assertArrayHasKey($lookupKey, $result); + $this->assertEquals($result[$lookupKey]['topic'], $expectedTopic); + } +} diff --git a/app/code/Magento/WebapiAsync/etc/di.xml b/app/code/Magento/WebapiAsync/etc/di.xml index 83f1d6a78f2..7411ec0561d 100755 --- a/app/code/Magento/WebapiAsync/etc/di.xml +++ b/app/code/Magento/WebapiAsync/etc/di.xml @@ -10,6 +10,9 @@ <type name="Magento\Webapi\Model\ServiceMetadata"> <plugin name="webapiServiceMetadataAsync" type="Magento\WebapiAsync\Plugin\ServiceMetadata" /> </type> + <type name="Magento\Webapi\Model\Cache\Type\Webapi"> + <plugin name="webapiCacheAsync" type="Magento\WebapiAsync\Plugin\Cache\Webapi" /> + </type> <virtualType name="Magento\WebapiAsync\Model\VirtualType\Rest\Config" type="Magento\Webapi\Model\Rest\Config"> <arguments> <argument name="config" xsi:type="object">Magento\WebapiAsync\Model\BulkServiceConfig</argument> diff --git a/app/code/Magento/Weee/Test/Mftf/Test/AdminRemoveProductWeeeAttributeOptionTest.xml b/app/code/Magento/Weee/Test/Mftf/Test/AdminRemoveProductWeeeAttributeOptionTest.xml index 2e3467fe2c7..3aeed3095dc 100644 --- a/app/code/Magento/Weee/Test/Mftf/Test/AdminRemoveProductWeeeAttributeOptionTest.xml +++ b/app/code/Magento/Weee/Test/Mftf/Test/AdminRemoveProductWeeeAttributeOptionTest.xml @@ -5,11 +5,12 @@ * See COPYING.txt for license details. */ --> + <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminRemoveProductWeeeAttributeOptionTest"> <annotations> - <features value="Weee attribute options can be removed in product page"/> + <stories value="Weee attribute options can be removed in product page"/> <title value="Weee attribute options can be removed in product page"/> <description value="Weee attribute options can be removed in product page"/> <severity value="CRITICAL"/> @@ -44,6 +45,7 @@ <deleteData createDataKey="createProductFPTAttribute" stepKey="deleteProductFPTAttribute"/> <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> </after> + <!-- Test Steps --> <!-- Step 1: Open created product edit page --> <actionGroup ref="SearchForProductOnBackendActionGroup" stepKey="searchForSimpleProduct"> diff --git a/app/code/Magento/Widget/Block/Adminhtml/Widget.php b/app/code/Magento/Widget/Block/Adminhtml/Widget.php index 33e6109b769..dad318f163b 100644 --- a/app/code/Magento/Widget/Block/Adminhtml/Widget.php +++ b/app/code/Magento/Widget/Block/Adminhtml/Widget.php @@ -16,8 +16,6 @@ class Widget extends \Magento\Backend\Block\Widget\Form\Container { /** * @inheritdoc - * - * @SuppressWarnings(PHPMD.RequestAwareBlockMethod) */ protected function _construct() { diff --git a/app/code/Magento/Widget/Model/Template/Filter.php b/app/code/Magento/Widget/Model/Template/Filter.php index 7c3e8e467e0..c79334f67a9 100644 --- a/app/code/Magento/Widget/Model/Template/Filter.php +++ b/app/code/Magento/Widget/Model/Template/Filter.php @@ -91,6 +91,10 @@ public function generateWidget($construction) $name = $params['name']; } + if (isset($this->_storeId) && !isset($params['store_id'])) { + $params['store_id'] = $this->_storeId; + } + // validate required parameter type or id if (!empty($params['type'])) { $type = $params['type']; diff --git a/app/code/Magento/Widget/Model/Widget.php b/app/code/Magento/Widget/Model/Widget.php index 5ba03d008de..52dc8e7837a 100644 --- a/app/code/Magento/Widget/Model/Widget.php +++ b/app/code/Magento/Widget/Model/Widget.php @@ -151,8 +151,8 @@ public function getConfigAsObject($type) $widget = $this->getAsCanonicalArray($widget); // Save all nodes to object data - $object->setType($type); $object->setData($widget); + $object->setType($type); // Correct widget parameters and convert its data to objects $newParams = $this->prepareWidgetParameters($object); diff --git a/app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminCreateWidgetActionGroup.xml b/app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminCreateWidgetActionGroup.xml index 7955f4ec29e..642ad6a2682 100644 --- a/app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminCreateWidgetActionGroup.xml +++ b/app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminCreateWidgetActionGroup.xml @@ -7,55 +7,62 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> - <actionGroup name="AdminCreateProductsListWidgetActionGroup"> - <arguments> - <argument name="widget"/> - </arguments> - <amOnPage url="{{AdminDashboardPage.url}}" stepKey="amOnAdminDashboard"/> - <click selector="{{AdminMenuSection.content}}" stepKey="clickContent"/> - <waitForLoadingMaskToDisappear stepKey="waitForWidgets" /> - <click selector="{{AdminMenuSection.widgets}}" stepKey="clickWidgets"/> - <waitForPageLoad stepKey="waitForWidgetsLoad"/> - <click selector="{{AdminGridMainControls.add}}" stepKey="addNewWidget"/> - <selectOption selector="{{AdminNewWidgetSection.widgetType}}" userInput="{{widget.type}}" stepKey="setWidgetType"/> - <selectOption selector="{{AdminNewWidgetSection.widgetDesignTheme}}" userInput="{{widget.design_theme}}" stepKey="setWidgetDesignTheme"/> - <click selector="{{AdminNewWidgetSection.continue}}" stepKey="clickContinue"/> - <fillField selector="{{AdminNewWidgetSection.widgetTitle}}" userInput="{{widget.name}}" stepKey="fillTitle"/> - <selectOption selector="{{AdminNewWidgetSection.widgetStoreIds}}" userInput="{{widget.store_ids[0]}}" stepKey="setWidgetStoreIds"/> - <click selector="{{AdminNewWidgetSection.addLayoutUpdate}}" stepKey="clickAddLayoutUpdate"/> - <selectOption selector="{{AdminNewWidgetSection.selectDisplayOn}}" userInput="{{widget.display_on}}" stepKey="setDisplayOn"/> - <waitForAjaxLoad stepKey="waitForLoad"/> - <selectOption selector="{{AdminNewWidgetSection.selectContainer}}" userInput="{{widget.container}}" stepKey="setContainer"/> - <waitForAjaxLoad stepKey="waitForPageLoad"/> - <scrollToTopOfPage stepKey="scrollToTopOfPage"/> - <click selector="{{AdminNewWidgetSection.widgetOptions}}" stepKey="clickWidgetOptions"/> - <click selector="{{AdminNewWidgetSection.addNewCondition}}" stepKey="clickAddNewCondition"/> - <selectOption selector="{{AdminNewWidgetSection.selectCondition}}" userInput="{{widget.condition}}" stepKey="selectCondition"/> - <waitForElement selector="{{AdminNewWidgetSection.ruleParameter}}" stepKey="waitRuleParameter"/> - <click selector="{{AdminNewWidgetSection.ruleParameter}}" stepKey="clickRuleParameter"/> - <click selector="{{AdminNewWidgetSection.openChooser}}" stepKey="clickChooser"/> - <waitForAjaxLoad stepKey="waitForAjaxLoad"/> - <click selector="{{AdminNewWidgetSection.selectAll}}" stepKey="clickSelectAll"/> - <click selector="{{AdminNewWidgetSection.applyParameter}}" stepKey="clickApplyRuleParameter"/> - <click selector="{{AdminMainActionsSection.save}}" stepKey="clickSaveWidget"/> - <waitForPageLoad stepKey="waitForSaveLoad"/> - <see selector="{{AdminMessagesSection.successMessage}}" userInput="The widget instance has been saved" stepKey="seeSuccess"/> - </actionGroup> - <actionGroup name="AdminDeleteWidgetActionGroup"> - <arguments> - <argument name="widget"/> - </arguments> - <amOnPage url="{{AdminWidgetsPage.url}}" stepKey="amOnAdmin"/> - <waitForPageLoad stepKey="waitWidgetsLoad"/> - <fillField selector="{{AdminWidgetsSection.widgetTitleSearch}}" userInput="{{widget.name}}" stepKey="fillTitle"/> - <click selector="{{AdminWidgetsSection.searchButton}}" stepKey="clickContinue"/> - <click selector="{{AdminWidgetsSection.searchResult}}" stepKey="clickSearchResult"/> - <waitForPageLoad stepKey="waitForResultLoad"/> - <click selector="{{AdminMainActionsSection.delete}}" stepKey="clickDelete"/> - <waitForAjaxLoad stepKey="waitForAjaxLoad"/> - <click selector="{{AdminConfirmationModalSection.ok}}" stepKey="confirmDelete"/> - <waitForPageLoad stepKey="waitForDeleteLoad"/> - <see selector="{{AdminMessagesSection.successMessage}}" userInput="The widget instance has been deleted" stepKey="seeSuccess"/> - </actionGroup> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminCreateWidgetActionGroup"> + <arguments> + <argument name="widget"/> + </arguments> + <amOnPage url="{{AdminNewWidgetPage.url}}" stepKey="amOnAdminNewWidgetPage"/> + <selectOption selector="{{AdminNewWidgetSection.widgetType}}" userInput="{{widget.type}}" stepKey="setWidgetType"/> + <selectOption selector="{{AdminNewWidgetSection.widgetDesignTheme}}" userInput="{{widget.design_theme}}" stepKey="setWidgetDesignTheme"/> + <click selector="{{AdminNewWidgetSection.continue}}" stepKey="clickContinue"/> + <fillField selector="{{AdminNewWidgetSection.widgetTitle}}" userInput="{{widget.name}}" stepKey="fillTitle"/> + <selectOption selector="{{AdminNewWidgetSection.widgetStoreIds}}" userInput="{{widget.store_ids[0]}}" stepKey="setWidgetStoreIds"/> + <click selector="{{AdminNewWidgetSection.addLayoutUpdate}}" stepKey="clickAddLayoutUpdate"/> + <selectOption selector="{{AdminNewWidgetSection.selectDisplayOn}}" userInput="{{widget.display_on}}" stepKey="setDisplayOn"/> + <waitForAjaxLoad stepKey="waitForLoad"/> + <selectOption selector="{{AdminNewWidgetSection.selectContainer}}" userInput="{{widget.container}}" stepKey="setContainer"/> + <waitForAjaxLoad stepKey="waitForPageLoad"/> + <scrollToTopOfPage stepKey="scrollToTopOfPage"/> + <click selector="{{AdminNewWidgetSection.widgetOptions}}" stepKey="clickWidgetOptions"/> + </actionGroup> + + <!--Create Product List Widget--> + <actionGroup name="AdminCreateProductsListWidgetActionGroup" extends="AdminCreateWidgetActionGroup"> + <click selector="{{AdminNewWidgetSection.addNewCondition}}" stepKey="clickAddNewCondition"/> + <selectOption selector="{{AdminNewWidgetSection.selectCondition}}" userInput="{{widget.condition}}" stepKey="selectCondition"/> + <waitForElement selector="{{AdminNewWidgetSection.ruleParameter}}" stepKey="waitRuleParameter"/> + <click selector="{{AdminNewWidgetSection.ruleParameter}}" stepKey="clickRuleParameter"/> + <click selector="{{AdminNewWidgetSection.openChooser}}" stepKey="clickChooser"/> + <waitForAjaxLoad stepKey="waitForAjaxLoad"/> + <click selector="{{AdminNewWidgetSection.selectAll}}" stepKey="clickSelectAll"/> + <click selector="{{AdminNewWidgetSection.applyParameter}}" stepKey="clickApplyRuleParameter"/> + <click selector="{{AdminMainActionsSection.save}}" stepKey="clickSaveWidget"/> + <see selector="{{AdminMessagesSection.successMessage}}" userInput="The widget instance has been saved" stepKey="seeSuccess"/> + </actionGroup> + + <!--Create Dynamic Block Rotate Widget--> + <actionGroup name="AdminCreateDynamicBlocksRotatorWidgetActionGroup" extends="AdminCreateWidgetActionGroup"> + <selectOption selector="{{AdminNewWidgetSection.displayMode}}" userInput="{{widget.display_mode}}" stepKey="selectDisplayMode"/> + <selectOption selector="{{AdminNewWidgetSection.restrictTypes}}" userInput="{{widget.restrict_type}}" stepKey="selectRestrictType"/> + <click selector="{{AdminNewWidgetSection.saveAndContinue}}" stepKey="clickSaveWidget"/> + <see selector="{{AdminMessagesSection.successMessage}}" userInput="The widget instance has been saved" stepKey="seeSuccess"/> + </actionGroup> + + <actionGroup name="AdminDeleteWidgetActionGroup"> + <arguments> + <argument name="widget"/> + </arguments> + <amOnPage url="{{AdminWidgetsPage.url}}" stepKey="amOnAdmin"/> + <waitForPageLoad stepKey="waitWidgetsLoad"/> + <fillField selector="{{AdminWidgetsSection.widgetTitleSearch}}" userInput="{{widget.name}}" stepKey="fillTitle"/> + <click selector="{{AdminWidgetsSection.searchButton}}" stepKey="clickContinue"/> + <click selector="{{AdminWidgetsSection.searchResult}}" stepKey="clickSearchResult"/> + <waitForPageLoad stepKey="waitForResultLoad"/> + <click selector="{{AdminMainActionsSection.delete}}" stepKey="clickDelete"/> + <waitForAjaxLoad stepKey="waitForAjaxLoad"/> + <click selector="{{AdminConfirmationModalSection.ok}}" stepKey="confirmDelete"/> + <waitForPageLoad stepKey="waitForDeleteLoad"/> + <see selector="{{AdminMessagesSection.successMessage}}" userInput="The widget instance has been deleted" stepKey="seeSuccess"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminWidgetActionGroup.xml b/app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminWidgetActionGroup.xml new file mode 100644 index 00000000000..c303b7cc8e9 --- /dev/null +++ b/app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminWidgetActionGroup.xml @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminCreateWidgetWithBlockActionGroup"> + <arguments> + <argument name="widget"/> + <argument name="block" type="string"/> + </arguments> + <amOnPage url="{{AdminNewWidgetPage.url}}" stepKey="createWidgetPage"/> + <selectOption selector="{{AdminNewWidgetSection.widgetType}}" userInput="{{widget.type}}" stepKey="selectWidgetType"/> + <selectOption selector="{{AdminNewWidgetSection.widgetDesignTheme}}" userInput="{{widget.designTheme}}" stepKey="selectWidgetDesignTheme"/> + <click selector="{{AdminNewWidgetSection.continue}}" stepKey="continue"/> + <waitForElement selector="{{AdminNewWidgetSection.widgetTitle}}" time="30" stepKey="waitForElement"/> + <fillField selector="{{AdminNewWidgetSection.widgetTitle}}" userInput="{{widget.name}}" stepKey="fillWidgetTitle"/> + <selectOption selector="{{AdminNewWidgetSection.widgetStoreIds}}" userInput="{{widget.store_id}}" stepKey="selectWidgetStoreView"/> + <click selector="{{AdminNewWidgetSection.addLayoutUpdate}}" stepKey="clickAddLayoutUpdate"/> + <waitForPageLoad stepKey="waitForLoad1"/> + <scrollTo selector="{{AdminNewWidgetSection.selectDisplayOn}}" stepKey="scrollToElement" /> + <selectOption selector="{{AdminNewWidgetSection.selectDisplayOn}}" userInput="{{widget.display}}" stepKey="selectWidgetDisplayOn"/> + <waitForElement selector="{{AdminNewWidgetSection.selectContainer}}" time="30" stepKey="waitForContainer"/> + <selectOption selector="{{AdminNewWidgetSection.selectContainer}}" userInput="{{widget.container}}" stepKey="selectWidgetContainer"/> + <scrollToTopOfPage stepKey="scrollToAddresses"/> + <waitForAjaxLoad stepKey="waitForAjaxLoad1"/> + <click selector="{{AdminNewWidgetSection.widgetOptions}}" stepKey="goToWidgetOptions"/> + <waitForElement selector="{{AdminNewWidgetSection.widgetSelectBlock}}" time="60" stepKey="waitForSelectBlock"/> + <click selector="{{AdminNewWidgetSection.widgetSelectBlock}}" stepKey="openSelectBlock"/> + <waitForPageLoad stepKey="waitForLoadBlocks"/> + <selectOption selector="{{AdminNewWidgetSection.blockStatus}}" userInput="Disable" stepKey="chooseStatus"/> + <fillField selector="{{AdminNewWidgetSection.selectBlockTitle}}" userInput="{{block}}" stepKey="fillBlockTitle"/> + <click selector="{{AdminNewWidgetSection.searchBlock}}" stepKey="searchBlock"/> + <waitForAjaxLoad stepKey="waitForAjaxLoad"/> + <click selector="{{AdminNewWidgetSection.searchedBlock}}" stepKey="clickSearchedBlock"/> + <waitForPageLoad stepKey="wait"/> + <click selector="{{AdminNewWidgetSection.saveWidget}}" stepKey="saveWidget"/> + <waitForPageLoad stepKey="waitForSaving"/> + <see userInput="The widget instance has been saved." stepKey="seeSuccessMessage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Widget/Test/Mftf/Data/WidgetData.xml b/app/code/Magento/Widget/Test/Mftf/Data/WidgetData.xml new file mode 100644 index 00000000000..4c6e98aafd7 --- /dev/null +++ b/app/code/Magento/Widget/Test/Mftf/Data/WidgetData.xml @@ -0,0 +1,19 @@ +<?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="WidgetWithBlock" type="widget"> + <data key="type">CMS Static Block</data> + <data key="designTheme">Magento Luma</data> + <data key="name" unique="suffix">testName</data> + <data key="store_id">All Store Views</data> + <data key="display">All Pages</data> + <data key="container">Page Top</data> + </entity> +</entities> diff --git a/app/code/Magento/Widget/Test/Mftf/Data/WidgetsData.xml b/app/code/Magento/Widget/Test/Mftf/Data/WidgetsData.xml index 26864c60b64..27222298408 100644 --- a/app/code/Magento/Widget/Test/Mftf/Data/WidgetsData.xml +++ b/app/code/Magento/Widget/Test/Mftf/Data/WidgetsData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="ProductsListWidget" type="widget"> <data key="type">Catalog Products List</data> <data key="design_theme">Magento Luma</data> @@ -19,4 +19,17 @@ <data key="display_on">All Pages</data> <data key="container">Main Content Area</data> </entity> + <entity name="DynamicBlocksRotatorWidget" type="widget"> + <data key="type">Dynamic Blocks Rotator</data> + <data key="design_theme">Magento Luma</data> + <data key="name" unique="suffix">TestBannerWidget</data> + <array key="store_ids"> + <item>All Store Views</item> + </array> + <data key="condition">SKU</data> + <data key="display_on">All Pages</data> + <data key="container">Main Content Area</data> + <data key="display_mode">Cart Price Rule Related</data> + <data key="restrict_type">Header</data> + </entity> </entities> diff --git a/app/code/Magento/Widget/Test/Mftf/Page/AdminNewWidgetPage.xml b/app/code/Magento/Widget/Test/Mftf/Page/AdminNewWidgetPage.xml index 8eb0a5f6531..d495a36f68d 100644 --- a/app/code/Magento/Widget/Test/Mftf/Page/AdminNewWidgetPage.xml +++ b/app/code/Magento/Widget/Test/Mftf/Page/AdminNewWidgetPage.xml @@ -7,8 +7,8 @@ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> - <page name="AdminNewWidgetPage" url="admin/admin/widget_instance/new/" area="admin" module="Magento_Widget"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="AdminNewWidgetPage" url="admin/widget_instance/new/" area="admin" module="Magento_Widget"> <section name="AdminNewWidgetSection"/> </page> </pages> diff --git a/app/code/Magento/Widget/Test/Mftf/Page/AdminWidgetsPage.xml b/app/code/Magento/Widget/Test/Mftf/Page/AdminWidgetsPage.xml index 421899ad216..46209f9e5f0 100644 --- a/app/code/Magento/Widget/Test/Mftf/Page/AdminWidgetsPage.xml +++ b/app/code/Magento/Widget/Test/Mftf/Page/AdminWidgetsPage.xml @@ -7,7 +7,7 @@ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="AdminWidgetsPage" url="admin/widget_instance/" area="admin" module="Magento_Widget"> <section name="AdminWidgetsSection"/> </page> diff --git a/app/code/Magento/Widget/Test/Mftf/Section/AdminNewWidgetSection.xml b/app/code/Magento/Widget/Test/Mftf/Section/AdminNewWidgetSection.xml index 38b4df335ea..003b398d565 100644 --- a/app/code/Magento/Widget/Test/Mftf/Section/AdminNewWidgetSection.xml +++ b/app/code/Magento/Widget/Test/Mftf/Section/AdminNewWidgetSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminNewWidgetSection"> <element name="widgetType" type="select" selector="#code"/> <element name="widgetDesignTheme" type="select" selector="#theme_id"/> @@ -25,5 +25,14 @@ <element name="applyParameter" type="button" selector=".rule-param-apply"/> <element name="openChooser" type="button" selector=".rule-chooser-trigger"/> <element name="selectAll" type="checkbox" selector=".admin__control-checkbox"/> + <element name="widgetSelectBlock" type="button" selector="//button[@class='action-default scalable btn-chooser']"/> + <element name="selectBlockTitle" type="input" selector="//input[@name='chooser_title']"/> + <element name="searchBlock" type="button" selector="//div[@class='admin__filter-actions']/button[@title='Search']"/> + <element name="blockStatus" type="select" selector="//select[@name='chooser_is_active']"/> + <element name="searchedBlock" type="button" selector="//*[@class='magento-message']//tbody/tr/td[1]"/> + <element name="saveWidget" type="select" selector="#save"/> + <element name="displayMode" type="select" selector="select[id*='display_mode']"/> + <element name="restrictTypes" type="select" selector="select[id*='types']"/> + <element name="saveAndContinue" type="button" selector="#save_and_edit_button" timeout="30"/> </section> </sections> diff --git a/app/code/Magento/Widget/Test/Mftf/Section/AdminWidgetsSection.xml b/app/code/Magento/Widget/Test/Mftf/Section/AdminWidgetsSection.xml index 5a0515d35ad..f3282362d9a 100644 --- a/app/code/Magento/Widget/Test/Mftf/Section/AdminWidgetsSection.xml +++ b/app/code/Magento/Widget/Test/Mftf/Section/AdminWidgetsSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminWidgetsSection"> <element name="widgetTitleSearch" type="input" selector="#widgetInstanceGrid_filter_title"/> <element name="searchButton" type="button" selector=".action-default.scalable.action-secondary"/> diff --git a/app/code/Magento/Widget/Test/Mftf/Section/StorefrontWidgetsSection.xml b/app/code/Magento/Widget/Test/Mftf/Section/StorefrontWidgetsSection.xml index 23908626389..0e2f6cec73a 100644 --- a/app/code/Magento/Widget/Test/Mftf/Section/StorefrontWidgetsSection.xml +++ b/app/code/Magento/Widget/Test/Mftf/Section/StorefrontWidgetsSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontWidgetsSection"> <element name="widgetProductsGrid" type="block" selector=".block.widget.block-products-list.grid"/> <element name="widgetProductName" type="text" selector=".product-item-name"/> diff --git a/app/code/Magento/Wishlist/Block/Customer/Wishlist.php b/app/code/Magento/Wishlist/Block/Customer/Wishlist.php index 2b1b6d44b42..d02f2229401 100644 --- a/app/code/Magento/Wishlist/Block/Customer/Wishlist.php +++ b/app/code/Magento/Wishlist/Block/Customer/Wishlist.php @@ -5,13 +5,13 @@ */ /** - * Wishlist block customer items - * * @author Magento Core Team <core@magentocommerce.com> */ namespace Magento\Wishlist\Block\Customer; /** + * Wishlist block customer items. + * * @api * @since 100.0.2 */ @@ -29,6 +29,11 @@ class Wishlist extends \Magento\Wishlist\Block\AbstractBlock */ protected $_helperPool; + /** + * @var \Magento\Wishlist\Model\ResourceModel\Item\Collection + */ + protected $_collection; + /** * @var \Magento\Customer\Helper\Session\CurrentCustomer */ @@ -78,14 +83,64 @@ protected function _prepareCollection($collection) } /** - * Preparing global layout + * Paginate Wishlist Product Items collection * * @return void + * @SuppressWarnings(PHPMD.RequestAwareBlockMethod) + */ + private function paginateCollection() + { + $page = $this->getRequest()->getParam("p", 1); + $limit = $this->getRequest()->getParam("limit", 10); + $this->_collection + ->setPageSize($limit) + ->setCurPage($page); + } + + /** + * Retrieve Wishlist Product Items collection + * + * @return \Magento\Wishlist\Model\ResourceModel\Item\Collection + */ + public function getWishlistItems() + { + if ($this->_collection === null) { + $this->_collection = $this->_createWishlistItemCollection(); + $this->_prepareCollection($this->_collection); + $this->paginateCollection(); + } + return $this->_collection; + } + + /** + * Preparing global layout + * + * @return $this */ protected function _prepareLayout() { parent::_prepareLayout(); $this->pageConfig->getTitle()->set(__('My Wish List')); + $this->getChildBlock('wishlist_item_pager') + ->setUseContainer( + true + )->setShowAmounts( + true + )->setFrameLength( + $this->_scopeConfig->getValue( + 'design/pagination/pagination_frame', + \Magento\Store\Model\ScopeInterface::SCOPE_STORE + ) + )->setJump( + $this->_scopeConfig->getValue( + 'design/pagination/pagination_frame_skip', + \Magento\Store\Model\ScopeInterface::SCOPE_STORE + ) + )->setLimit( + $this->getLimit() + ) + ->setCollection($this->getWishlistItems()); + return $this; } /** @@ -198,6 +253,7 @@ public function getAddToCartQty(\Magento\Wishlist\Model\Item $item) /** * Get add all to cart params for POST request + * * @return string */ public function getAddAllToCartParams() @@ -209,7 +265,7 @@ public function getAddAllToCartParams() } /** - * @return string + * @inheritdoc */ protected function _toHtml() { diff --git a/app/code/Magento/Wishlist/Block/Customer/Wishlist/Item/Column/Cart.php b/app/code/Magento/Wishlist/Block/Customer/Wishlist/Item/Column/Cart.php index fe0683a52fe..b043a8d4b68 100644 --- a/app/code/Magento/Wishlist/Block/Customer/Wishlist/Item/Column/Cart.php +++ b/app/code/Magento/Wishlist/Block/Customer/Wishlist/Item/Column/Cart.php @@ -6,8 +6,6 @@ namespace Magento\Wishlist\Block\Customer\Wishlist\Item\Column; -use Magento\Catalog\Controller\Adminhtml\Product\Initialization\StockDataFilter; - /** * Wishlist block customer item cart column * @@ -37,28 +35,4 @@ public function getProductItem() { return $this->getItem()->getProduct(); } - - /** - * Get min and max qty for wishlist form. - * - * @return array - */ - public function getMinMaxQty() - { - $stockItem = $this->stockRegistry->getStockItem( - $this->getItem()->getProduct()->getId(), - $this->getItem()->getProduct()->getStore()->getWebsiteId() - ); - - $params = []; - - $params['minAllowed'] = (float)$stockItem->getMinSaleQty(); - if ($stockItem->getMaxSaleQty()) { - $params['maxAllowed'] = (float)$stockItem->getMaxSaleQty(); - } else { - $params['maxAllowed'] = (float)StockDataFilter::MAX_QTY_VALUE; - } - - return $params; - } } diff --git a/app/code/Magento/Wishlist/Controller/Index/Update.php b/app/code/Magento/Wishlist/Controller/Index/Update.php index 056d58b4c70..b56aa4b5b3c 100755 --- a/app/code/Magento/Wishlist/Controller/Index/Update.php +++ b/app/code/Magento/Wishlist/Controller/Index/Update.php @@ -6,10 +6,14 @@ namespace Magento\Wishlist\Controller\Index; use Magento\Framework\App\Action; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Framework\Exception\NotFoundException; use Magento\Framework\Controller\ResultFactory; -class Update extends \Magento\Wishlist\Controller\AbstractIndex +/** + * Class Update + */ +class Update extends \Magento\Wishlist\Controller\AbstractIndex implements HttpPostActionInterface { /** * @var \Magento\Wishlist\Controller\WishlistProviderInterface @@ -83,8 +87,6 @@ public function execute() )->defaultCommentString() ) { $description = ''; - } elseif (!strlen($description)) { - $description = $item->getDescription(); } $qty = null; diff --git a/app/code/Magento/Wishlist/Test/Mftf/Test/ConfProdAddToCartWishListWithUnselectedAttrTest.xml b/app/code/Magento/Wishlist/Test/Mftf/Test/ConfProdAddToCartWishListWithUnselectedAttrTest.xml new file mode 100644 index 00000000000..4e6a062c799 --- /dev/null +++ b/app/code/Magento/Wishlist/Test/Mftf/Test/ConfProdAddToCartWishListWithUnselectedAttrTest.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="ConfProdAddToCartWishListWithUnselectedAttrTest"> + <annotations> + <stories value="Wishlist"/> + <group value="wishlist"/> + <title value="Adding configurable product to Cart from Wish List with unselected attributes"/> + <description value="Verify adding configurable product to Cart from Wish List when attributes is unselected"/> + <severity value="AVERAGE"/> + <testCaseId value="MAGETWO-95897"/> + <useCaseId value="MAGETWO-95837"/> + </annotations> + + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin1"/> + <createData entity="ApiCategory" stepKey="createCategory"/> + <!--Create Configurable product--> + <actionGroup ref="createConfigurableProduct" stepKey="createProduct"> + <argument name="product" value="_defaultProduct"/> + <argument name="category" value="$$createCategory$$"/> + </actionGroup> + <createData entity="Simple_US_Customer" stepKey="createCustomer"/> + </before> + <after> + <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <!-- Delete the first simple product --> + <actionGroup stepKey="deleteProduct1" ref="deleteProductBySku"> + <argument name="sku" value="{{_defaultProduct.sku}}"/> + </actionGroup> + <conditionalClick selector="{{AdminProductGridFilterSection.clearFilters}}" + dependentSelector="{{AdminProductGridFilterSection.clearFilters}}" visible="true" stepKey="clickClearFilters"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Login as customer --> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginToStorefrontAccount"> + <argument name="Customer" value="$$createCustomer$$"/> + </actionGroup> + <waitForPageLoad stepKey="waitForLogin"/> + + <!--Go To Created Product Page--> + <amOnPage stepKey="goToCreatedProductPage" url="{{_defaultProduct.urlKey}}.html"/> + <waitForPageLoad stepKey="waitForProductPageLoad2"/> + + <seeElement selector="{{StorefrontProductInfoMainSection.productAttributeOptions1}}" stepKey="checkDropDownProductOption"/> + <selectOption userInput="{{colorProductAttribute1.name}}" selector="{{StorefrontProductInfoMainSection.productAttributeOptionsSelectButton}}" stepKey="selectOption1"/> + <selectOption userInput="{{colorProductAttribute2.name}}" selector="{{StorefrontProductInfoMainSection.productAttributeOptionsSelectButton}}" stepKey="selectOption2"/> + <click selector="{{StorefrontProductInfoMainSection.productAttributeOptions1}}" stepKey="clickDropDownProductOption"/> + + <!--Click Add to Wish List link--> + <click selector="{{StorefrontProductPageSection.addToWishlist}}" stepKey="addFirstPnroductToWishlist"/> + + <waitForPageLoad stepKey="waitForLoading"/> + + <!--Click "Add All to Cart" button--> + <click selector="{{StorefrontCustomerWishlistProductSection.ProductAddAllToCart}}" stepKey="addAllToCart"/> + <waitForElementVisible stepKey="waitForErrorAppears" selector="{{StorefrontMessagesSection.error}}"/> + + <!--Assert Correct Error Message--> + <see userInput="You need to choose options for your item for" stepKey="assertCorrectErrorMessage"/> + <dontSee userInput="1 product(s) have been added to shopping cart" stepKey="dontSeeSuccessMessage"/> + </test> +</tests> diff --git a/app/code/Magento/Wishlist/Test/Mftf/Test/ConfigurableProductChildImageShouldBeShownOnWishListTest.xml b/app/code/Magento/Wishlist/Test/Mftf/Test/ConfigurableProductChildImageShouldBeShownOnWishListTest.xml index 42d4203999a..6b951c89208 100644 --- a/app/code/Magento/Wishlist/Test/Mftf/Test/ConfigurableProductChildImageShouldBeShownOnWishListTest.xml +++ b/app/code/Magento/Wishlist/Test/Mftf/Test/ConfigurableProductChildImageShouldBeShownOnWishListTest.xml @@ -6,7 +6,8 @@ */ --> -<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="ConfigurableProductChildImageShouldBeShownOnWishListTest"> <annotations> <features value="Wishlist"/> diff --git a/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontAddMultipleStoreProductsToWishlistTest.xml b/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontAddMultipleStoreProductsToWishlistTest.xml index b91f796e6a1..ede63322235 100644 --- a/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontAddMultipleStoreProductsToWishlistTest.xml +++ b/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontAddMultipleStoreProductsToWishlistTest.xml @@ -16,6 +16,9 @@ <group value="wishlist"/> <severity value="AVERAGE"/> <testCaseId value="MAGETWO-95678"/> + <skip> + <issueId value="MC-13867"/> + </skip> </annotations> <before> <createData entity="customStoreGroup" stepKey="storeGroup"/> diff --git a/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontUpdateWishlistTest.xml b/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontUpdateWishlistTest.xml index 9f11de49adc..e482449f623 100644 --- a/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontUpdateWishlistTest.xml +++ b/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontUpdateWishlistTest.xml @@ -1,59 +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="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> - <test name="StorefrontUpdateWishlistTest"> - <annotations> - <title value="Displaying of message after Wish List update"/> - <stories value="MAGETWO-91666: Wishlist update does not return a success message"/> - <description value="Displaying of message after Wish List update"/> - <features value="Wishlist"/> - <severity value="MAJOR"/> - <testCaseId value="MAGETWO-94296"/> - <group value="Wishlist"/> - </annotations> - - <before> - <createData entity="SimpleSubCategory" stepKey="category"/> - <createData entity="SimpleProduct" stepKey="product"> - <requiredEntity createDataKey="category"/> - </createData> - <createData entity="Simple_US_Customer" stepKey="customer"/> - </before> - - <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginToStorefrontAccount"> - <argument name="Customer" value="$$customer$$"/> - </actionGroup> - - <actionGroup ref="OpenProductFromCategoryPageActionGroup" stepKey="openProductFromCategory"> - <argument name="category" value="$$category$$"/> - <argument name="product" value="$$product$$"/> - </actionGroup> - - <actionGroup ref="StorefrontCustomerAddProductToWishlistActionGroup" stepKey="addProductToWishlist"> - <argument name="productVar" value="$$product$$"/> - </actionGroup> - - <actionGroup ref="StorefrontCustomerCheckProductInWishlist" stepKey="checkProductInWishlist"> - <argument name="productVar" value="$$product$$"/> - </actionGroup> - - <actionGroup ref="StorefrontCustomerEditProductInWishlist" stepKey="updateProductInWishlist"> - <argument name="product" value="$$product$$"/> - <argument name="description" value="some text"/> - <argument name="quantity" value="2"/> - </actionGroup> - - <after> - <deleteData createDataKey="category" stepKey="deleteCategory"/> - <deleteData createDataKey="product" stepKey="deleteProduct"/> - <deleteData createDataKey="customer" stepKey="deleteCustomer"/> - </after> - - </test> -</tests> \ No newline at end of file +<?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="StorefrontUpdateWishlistTest"> + <annotations> + <title value="Displaying of message after Wish List update"/> + <stories value="MAGETWO-91666: Wishlist update does not return a success message"/> + <description value="Displaying of message after Wish List update"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-94296"/> + <group value="Wishlist"/> + </annotations> + + <before> + <createData entity="SimpleSubCategory" stepKey="category"/> + <createData entity="SimpleProduct" stepKey="product"> + <requiredEntity createDataKey="category"/> + </createData> + <createData entity="Simple_US_Customer" stepKey="customer"/> + </before> + + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginToStorefrontAccount"> + <argument name="Customer" value="$$customer$$"/> + </actionGroup> + + <actionGroup ref="OpenProductFromCategoryPageActionGroup" stepKey="openProductFromCategory"> + <argument name="category" value="$$category$$"/> + <argument name="product" value="$$product$$"/> + </actionGroup> + + <actionGroup ref="StorefrontCustomerAddProductToWishlistActionGroup" stepKey="addProductToWishlist"> + <argument name="productVar" value="$$product$$"/> + </actionGroup> + + <actionGroup ref="StorefrontCustomerCheckProductInWishlist" stepKey="checkProductInWishlist"> + <argument name="productVar" value="$$product$$"/> + </actionGroup> + + <actionGroup ref="StorefrontCustomerEditProductInWishlist" stepKey="updateProductInWishlist"> + <argument name="product" value="$$product$$"/> + <argument name="description" value="some text"/> + <argument name="quantity" value="2"/> + </actionGroup> + + <after> + <deleteData createDataKey="category" stepKey="deleteCategory"/> + <deleteData createDataKey="product" stepKey="deleteProduct"/> + <deleteData createDataKey="customer" stepKey="deleteCustomer"/> + </after> + </test> +</tests> diff --git a/app/code/Magento/Wishlist/composer.json b/app/code/Magento/Wishlist/composer.json index ce43f6faae2..ad2fe8e2b04 100644 --- a/app/code/Magento/Wishlist/composer.json +++ b/app/code/Magento/Wishlist/composer.json @@ -15,6 +15,7 @@ "magento/module-rss": "*", "magento/module-sales": "*", "magento/module-store": "*", + "magento/module-theme": "*", "magento/module-ui": "*" }, "suggest": { diff --git a/app/code/Magento/Wishlist/view/frontend/layout/wishlist_index_index.xml b/app/code/Magento/Wishlist/view/frontend/layout/wishlist_index_index.xml index 243a0606242..d3f21dda9cc 100644 --- a/app/code/Magento/Wishlist/view/frontend/layout/wishlist_index_index.xml +++ b/app/code/Magento/Wishlist/view/frontend/layout/wishlist_index_index.xml @@ -13,6 +13,7 @@ </referenceBlock> <referenceContainer name="content"> <block class="Magento\Wishlist\Block\Customer\Wishlist" name="customer.wishlist" template="Magento_Wishlist::view.phtml" cacheable="false"> + <block class="Magento\Theme\Block\Html\Pager" name="wishlist_item_pager"/> <block class="Magento\Wishlist\Block\Rss\Link" name="wishlist.rss.link" template="Magento_Wishlist::rss/wishlist.phtml"/> <block class="Magento\Wishlist\Block\Customer\Wishlist\Items" name="customer.wishlist.items" as="items" template="Magento_Wishlist::item/list.phtml" cacheable="false"> <block class="Magento\Wishlist\Block\Customer\Wishlist\Item\Column\Image" name="customer.wishlist.item.image" template="Magento_Wishlist::item/column/image.phtml" cacheable="false"/> diff --git a/app/code/Magento/Wishlist/view/frontend/templates/item/column/cart.phtml b/app/code/Magento/Wishlist/view/frontend/templates/item/column/cart.phtml index 848c6a76393..9ea0d1a8232 100644 --- a/app/code/Magento/Wishlist/view/frontend/templates/item/column/cart.phtml +++ b/app/code/Magento/Wishlist/view/frontend/templates/item/column/cart.phtml @@ -11,7 +11,6 @@ /** @var \Magento\Wishlist\Model\Item $item */ $item = $block->getItem(); $product = $item->getProduct(); -$allowedQty = $block->getMinMaxQty(); ?> <?php foreach ($block->getChildNames() as $childName): ?> <?= /* @noEscape */ $block->getLayout()->renderElement($childName, false) ?> @@ -22,7 +21,7 @@ $allowedQty = $block->getMinMaxQty(); <div class="field qty"> <label class="label" for="qty[<?= $block->escapeHtmlAttr($item->getId()) ?>]"><span><?= $block->escapeHtml(__('Qty')) ?></span></label> <div class="control"> - <input type="number" data-role="qty" id="qty[<?= /* @noEscape */ $block->escapeHtmlAttr($item->getId()) ?>]" class="input-text qty" data-validate="{'required-number':true,'validate-greater-than-zero':true, 'validate-item-quantity':{'minAllowed':<?= /* @noEscape */ $allowedQty['minAllowed'] ?>,'maxAllowed':<?= /* @noEscape */ $allowedQty['maxAllowed'] ?>}}" + <input type="number" data-role="qty" id="qty[<?= $block->escapeHtmlAttr($item->getId()) ?>]" class="input-text qty" data-validate="{'required-number':true,'validate-greater-than-zero':true}" name="qty[<?= $block->escapeHtmlAttr($item->getId()) ?>]" value="<?= /* @noEscape */ (int)($block->getAddToCartQty($item) * 1) ?>"> </div> </div> diff --git a/app/code/Magento/Wishlist/view/frontend/templates/view.phtml b/app/code/Magento/Wishlist/view/frontend/templates/view.phtml index 8b2e1b1c9d8..4f4a1d302c1 100644 --- a/app/code/Magento/Wishlist/view/frontend/templates/view.phtml +++ b/app/code/Magento/Wishlist/view/frontend/templates/view.phtml @@ -10,6 +10,7 @@ ?> <?php if ($this->helper('Magento\Wishlist\Helper\Data')->isAllow()) : ?> + <div class="toolbar wishlist-toolbar"><?= $block->getChildHtml('wishlist_item_pager'); ?></div> <?= ($block->getChildHtml('wishlist.rss.link')) ?> <form class="form-wishlist-items" id="wishlist-view-form" data-mage-init='{"wishlist":{ @@ -51,5 +52,6 @@ <input name="entity" value="<%- data.entity %>"> <% } %> </form> - </script> + </script> + <div class="toolbar wishlist-toolbar"><br><?= $block->getChildHtml('wishlist_item_pager'); ?></div> <?php endif ?> diff --git a/app/code/Magento/Wishlist/view/frontend/web/js/add-to-wishlist.js b/app/code/Magento/Wishlist/view/frontend/web/js/add-to-wishlist.js index cab130f7c21..b38c5c2cda3 100644 --- a/app/code/Magento/Wishlist/view/frontend/web/js/add-to-wishlist.js +++ b/app/code/Magento/Wishlist/view/frontend/web/js/add-to-wishlist.js @@ -63,6 +63,12 @@ define([ isFileUploaded = false, self = this; + if (event.handleObj.selector == this.options.qtyInfo) { //eslint-disable-line eqeqeq + this._updateAddToWishlistButton({}); + event.stopPropagation(); + + return; + } $(event.handleObj.selector).each(function (index, element) { if ($(element).is('input[type=text]') || $(element).is('input[type=email]') || @@ -83,7 +89,9 @@ define([ } }); - this.bindFormSubmit(isFileUploaded); + if (isFileUploaded) { + this.bindFormSubmit(); + } this._updateAddToWishlistButton(dataToAdd); event.stopPropagation(); }, @@ -154,18 +162,12 @@ define([ $.each(elementValue, function (key, option) { data[elementName + '[' + option + ']'] = option; }); + } else if (elementName.substr(elementName.length - 2) == '[]') { //eslint-disable-line eqeqeq, max-depth + elementName = elementName.substring(0, elementName.length - 2); + + data[elementName + '[' + elementValue + ']'] = elementValue; } else { - if (elementValue) { //eslint-disable-line no-lonely-if - if (elementName.substr(elementName.length - 2) == '[]') { //eslint-disable-line eqeqeq, max-depth - elementName = elementName.substring(0, elementName.length - 2); - - if (elementValue) { //eslint-disable-line max-depth - data[elementName + '[' + elementValue + ']'] = elementValue; - } - } else { - data[elementName] = elementValue; - } - } + data[elementName] = elementValue; } return data; @@ -187,45 +189,34 @@ define([ /** * Bind form submit. - * - * @param {Boolean} isFileUploaded */ - bindFormSubmit: function (isFileUploaded) { + bindFormSubmit: function () { var self = this; $('[data-action="add-to-wishlist"]').on('click', function (event) { var element, params, form, action; - if (!$($(self.options.qtyInfo).closest('form')).valid()) { - event.stopPropagation(); - event.preventDefault(); - - return; - } - - if (isFileUploaded) { + event.stopPropagation(); + event.preventDefault(); - element = $('input[type=file]' + self.options.customOptionsInfo); - params = $(event.currentTarget).data('post'); - form = $(element).closest('form'); - action = params.action; + element = $('input[type=file]' + self.options.customOptionsInfo); + params = $(event.currentTarget).data('post'); + form = $(element).closest('form'); + action = params.action; - if (params.data.id) { - $('<input>', { - type: 'hidden', - name: 'id', - value: params.data.id - }).appendTo(form); - } - - if (params.data.uenc) { - action += 'uenc/' + params.data.uenc; - } + if (params.data.id) { + $('<input>', { + type: 'hidden', + name: 'id', + value: params.data.id + }).appendTo(form); + } - $(form).attr('action', action).submit(); - event.stopPropagation(); - event.preventDefault(); + if (params.data.uenc) { + action += 'uenc/' + params.data.uenc; } + + $(form).attr('action', action).submit(); }); } }); diff --git a/app/code/Magento/WishlistAnalytics/README.md b/app/code/Magento/WishlistAnalytics/README.md index 999fc835626..1ad88959829 100644 --- a/app/code/Magento/WishlistAnalytics/README.md +++ b/app/code/Magento/WishlistAnalytics/README.md @@ -1,3 +1,3 @@ # Magento_WishlistAnalytics module -The Magento_WishlistAnalytics module configures data definitions for a data collection related to the Wishlist module entities to be used in [Advanced Reporting](http://devdocs.magento.com/guides/v2.2/advanced-reporting/modules.html). +The Magento_WishlistAnalytics module configures data definitions for a data collection related to the Wishlist module entities to be used in [Advanced Reporting](https://devdocs.magento.com/guides/v2.2/advanced-reporting/modules.html). diff --git a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/_menu.less b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/_menu.less index ba281083260..d0b17b3439d 100644 --- a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/_menu.less +++ b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/_menu.less @@ -15,9 +15,9 @@ @menu__background-color: @color-very-dark-grayish-orange; -@menu-logo__padding-bottom: 2.2rem; +@menu-logo__padding-bottom: 1.7rem; @menu-logo__outer-size: @menu-logo__padding-top + @menu-logo-img__height + @menu-logo__padding-bottom; -@menu-logo__padding-top: 1.2rem; +@menu-logo__padding-top: 1.7rem; @menu-logo-img__height: 4.1rem; @menu-logo-img__width: 3.5rem; diff --git a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/main/_collapsible-blocks.less b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/main/_collapsible-blocks.less index e8e2746717e..dec35d13648 100644 --- a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/main/_collapsible-blocks.less +++ b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/main/_collapsible-blocks.less @@ -162,9 +162,12 @@ &.collapsible-block-wrapper-last { border-bottom: 0; } + .admin__dynamic-rows.admin__control-collapsible { - .admin__collapsible-block-wrapper { - border-bottom: none; + td { + &.admin__collapsible-block-wrapper { + border-bottom: none; + } } } } @@ -342,7 +345,7 @@ } .value { - padding-right: 4rem; + padding-right: 2rem; } } @@ -492,6 +495,8 @@ width: 44%; &.with-tooltip { + font-size: 0; + .tooltip { bottom: 0; float: right; diff --git a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/main/_page-nav.less b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/main/_page-nav.less index 42b3ecfb711..070ee634750 100644 --- a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/main/_page-nav.less +++ b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/main/_page-nav.less @@ -203,7 +203,7 @@ font-weight: @font-weight__heavier; line-height: @line-height__s; margin: 0 0 -1px; - padding: @admin__page-nav-link__padding; + padding: 2rem 0 2rem 1rem; transition: @admin__page-nav-transition; word-wrap: break-word; } diff --git a/app/design/adminhtml/Magento/backend/Magento_Catalog/web/css/source/_module.less b/app/design/adminhtml/Magento/backend/Magento_Catalog/web/css/source/_module.less index 33559502540..ffbbaeb0841 100644 --- a/app/design/adminhtml/Magento/backend/Magento_Catalog/web/css/source/_module.less +++ b/app/design/adminhtml/Magento/backend/Magento_Catalog/web/css/source/_module.less @@ -15,6 +15,14 @@ } } +.catalog-category-edit { + .admin__grid-control { + .admin__grid-control-value { + display: none; + } + } +} + .product-composite-configure-inner { .admin__control-text { &.qty { diff --git a/app/design/adminhtml/Magento/backend/Magento_Review/web/css/source/_module.less b/app/design/adminhtml/Magento/backend/Magento_Review/web/css/source/_module.less index 17be2ca7060..08606402f7a 100644 --- a/app/design/adminhtml/Magento/backend/Magento_Review/web/css/source/_module.less +++ b/app/design/adminhtml/Magento/backend/Magento_Review/web/css/source/_module.less @@ -34,7 +34,7 @@ .admin__field-control { direction: rtl; display: inline-block; - margin: -4px 0 0; + margin: -1px 0 0; unicode-bidi: bidi-override; vertical-align: top; width: 125px; diff --git a/app/design/adminhtml/Magento/backend/Magento_Sales/web/css/source/module/_order.less b/app/design/adminhtml/Magento/backend/Magento_Sales/web/css/source/module/_order.less index 1e76679f594..fa1ae256289 100644 --- a/app/design/adminhtml/Magento/backend/Magento_Sales/web/css/source/module/_order.less +++ b/app/design/adminhtml/Magento/backend/Magento_Sales/web/css/source/module/_order.less @@ -92,6 +92,14 @@ margin: 0; padding: 0; } + .admin__data-grid-pager-wrap{ + .selectmenu { + margin-bottom: 10px; + } + } + .data-grid-search-control-wrap { + margin-bottom: 10px; + } } // diff --git a/app/design/adminhtml/Magento/backend/Magento_Sales/web/css/source/module/order/_order-comments.less b/app/design/adminhtml/Magento/backend/Magento_Sales/web/css/source/module/order/_order-comments.less index 2f6aec0315e..5bcf4d4953c 100644 --- a/app/design/adminhtml/Magento/backend/Magento_Sales/web/css/source/module/order/_order-comments.less +++ b/app/design/adminhtml/Magento/backend/Magento_Sales/web/css/source/module/order/_order-comments.less @@ -49,7 +49,7 @@ margin: 0 0 @order-create-sidebar__margin; .lib-typography( @_font-size: 1.9rem, - @_color: @color-brown-darkie, + @_color: @color-brown-darker, @_font-weight: @font-weight__semibold, @_line-height: @line-height__s, @_font-family: false, diff --git a/app/design/adminhtml/Magento/backend/Magento_Staging/web/css/source/module/_staging-preview.less b/app/design/adminhtml/Magento/backend/Magento_Staging/web/css/source/module/_staging-preview.less index 3e1f4e75031..ae13c479cea 100644 --- a/app/design/adminhtml/Magento/backend/Magento_Staging/web/css/source/module/_staging-preview.less +++ b/app/design/adminhtml/Magento/backend/Magento_Staging/web/css/source/module/_staging-preview.less @@ -16,7 +16,7 @@ @staging-preview-header__font-size: 1.3rem; @staging-preview-header-item__active__background-color: @color-brownie-almost; -@staging-preview-header-item-actions__border-color: @color-darkie-gray; +@staging-preview-header-item-actions__border-color: @color-darker-gray; @staging-preview-form-element__background-color: @color-very-dark-brownie; @staging-preview-form-element__border-color: @color-lighter-grayish-almost; diff --git a/app/design/adminhtml/Magento/backend/Magento_VisualMerchandiser/web/css/source/_module.less b/app/design/adminhtml/Magento/backend/Magento_VisualMerchandiser/web/css/source/_module.less index 1cd867efdd1..554b6394a10 100644 --- a/app/design/adminhtml/Magento/backend/Magento_VisualMerchandiser/web/css/source/_module.less +++ b/app/design/adminhtml/Magento/backend/Magento_VisualMerchandiser/web/css/source/_module.less @@ -68,12 +68,14 @@ a { color: @color-gray85; + cursor: move; display: block; float: left; text-decoration: none; } a:last-child { + cursor: pointer; float: right; } } diff --git a/app/design/adminhtml/Magento/backend/etc/view.xml b/app/design/adminhtml/Magento/backend/etc/view.xml index f10f7789b08..18c2d8f1b17 100644 --- a/app/design/adminhtml/Magento/backend/etc/view.xml +++ b/app/design/adminhtml/Magento/backend/etc/view.xml @@ -23,6 +23,8 @@ </images> </media> <exclude> + <item type="file">Lib::mage/captcha.js</item> + <item type="file">Lib::mage/captcha.min.js</item> <item type="file">Lib::mage/common.js</item> <item type="file">Lib::mage/cookies.js</item> <item type="file">Lib::mage/dataPost.js</item> diff --git a/app/design/adminhtml/Magento/backend/web/app/setup/styles/less/components/tooltips/_tooltips.less b/app/design/adminhtml/Magento/backend/web/app/setup/styles/less/components/tooltips/_tooltips.less index 00490222046..8b26177a05c 100644 --- a/app/design/adminhtml/Magento/backend/web/app/setup/styles/less/components/tooltips/_tooltips.less +++ b/app/design/adminhtml/Magento/backend/web/app/setup/styles/less/components/tooltips/_tooltips.less @@ -10,7 +10,7 @@ @tooltip__background-color: @color-white; @tooltip__border-color: @color-gray68; @tooltip__border-radius: 0; -@tooltip__color: @color-brown-darkie; +@tooltip__color: @color-brown-darker; @tooltip__max-width: 31rem; @tooltip__opacity: .9; @tooltip__shadow-color: @color-gray80; diff --git a/app/design/adminhtml/Magento/backend/web/app/setup/styles/less/lib/_variables.less b/app/design/adminhtml/Magento/backend/web/app/setup/styles/less/lib/_variables.less index 39d7be029f8..be137863818 100644 --- a/app/design/adminhtml/Magento/backend/web/app/setup/styles/less/lib/_variables.less +++ b/app/design/adminhtml/Magento/backend/web/app/setup/styles/less/lib/_variables.less @@ -28,7 +28,7 @@ @color-green-apple: #79a22e; @color-green-islamic: #090; @color-dark-brownie: #41362f; -@color-brown-darkie: #41362f; +@color-brown-darker: #41362f; @color-phoenix-down: #e04f00; @color-phoenix: #eb5202; @color-phoenix-almost-rise: #ef672f; diff --git a/app/design/adminhtml/Magento/backend/web/app/updater/styles/less/pages/_extension-manager.less b/app/design/adminhtml/Magento/backend/web/app/updater/styles/less/pages/_extension-manager.less index 911ef55f3f2..30500569c82 100644 --- a/app/design/adminhtml/Magento/backend/web/app/updater/styles/less/pages/_extension-manager.less +++ b/app/design/adminhtml/Magento/backend/web/app/updater/styles/less/pages/_extension-manager.less @@ -15,7 +15,7 @@ @extension-manager-title__background-color: @color-white-fog; @extension-manager-title__border-color: @color-gray89; -@extension-manager-title__color: @color-brown-darkie; +@extension-manager-title__color: @color-brown-darker; @extension-manager-button__border-color: @color-gray68; diff --git a/app/design/adminhtml/Magento/backend/web/css/source/_tabs.less b/app/design/adminhtml/Magento/backend/web/css/source/_tabs.less index 475d3914a5f..5658214a769 100644 --- a/app/design/adminhtml/Magento/backend/web/css/source/_tabs.less +++ b/app/design/adminhtml/Magento/backend/web/css/source/_tabs.less @@ -45,13 +45,13 @@ } .ui-tabs-anchor { - color: @color-brown-darkie; + color: @color-brown-darker; display: block; padding: 1.5rem 1.8rem 1.3rem; text-decoration: none; &:hover { // ToDo UI: should be deleted with old styles - color: @color-brown-darkie; + color: @color-brown-darker; text-decoration: none; } } diff --git a/app/design/adminhtml/Magento/backend/web/css/source/_typography.less b/app/design/adminhtml/Magento/backend/web/css/source/_typography.less index 54726d2d34b..1f7d7f879c4 100644 --- a/app/design/adminhtml/Magento/backend/web/css/source/_typography.less +++ b/app/design/adminhtml/Magento/backend/web/css/source/_typography.less @@ -71,7 +71,7 @@ h1 { .lib-typography( @_font-size: 2.8rem, - @_color: @color-brown-darkie, + @_color: @color-brown-darker, @_font-weight: @font-weight__regular, @_line-height: @line-height__s, @_font-family: false, @@ -84,7 +84,7 @@ h2 { .lib-typography( @_font-size: 2rem, - @_color: @color-brown-darkie, + @_color: @color-brown-darker, @_font-weight: @font-weight__regular, @_line-height: @line-height__s, @_font-family: false, @@ -97,7 +97,7 @@ h3 { .lib-typography( @_font-size: 1.7rem, - @_color: @color-brown-darkie, + @_color: @color-brown-darker, @_font-weight: @font-weight__semibold, @_line-height: @line-height__s, @_font-family: false, diff --git a/app/design/adminhtml/Magento/backend/web/css/source/actions/_actions-multiselect.less b/app/design/adminhtml/Magento/backend/web/css/source/actions/_actions-multiselect.less index 7b99d4f136d..4c364bed688 100644 --- a/app/design/adminhtml/Magento/backend/web/css/source/actions/_actions-multiselect.less +++ b/app/design/adminhtml/Magento/backend/web/css/source/actions/_actions-multiselect.less @@ -179,7 +179,7 @@ .admin__action-multiselect-search-label { display: block; font-size: 1.5rem; - height: 1em; + height: 1.3em; overflow: hidden; position: absolute; right: 2.2rem; @@ -199,8 +199,8 @@ .admin__action-multiselect-empty-area { color: @color-gray65-almost; - padding-top: 20px; padding-bottom: 20px; + padding-top: 20px; text-align: center; vertical-align: middle; } diff --git a/app/design/adminhtml/Magento/backend/web/css/source/components/_file-insertion.less b/app/design/adminhtml/Magento/backend/web/css/source/components/_file-insertion.less index 88962a1019a..84d9cb15308 100644 --- a/app/design/adminhtml/Magento/backend/web/css/source/components/_file-insertion.less +++ b/app/design/adminhtml/Magento/backend/web/css/source/components/_file-insertion.less @@ -44,6 +44,7 @@ margin: 0 @indent__xs 15px 0; overflow: hidden; padding: 3px; + text-overflow: ellipsis; width: 100px; &.selected { diff --git a/app/design/adminhtml/Magento/backend/web/css/source/components/_file-uploader.less b/app/design/adminhtml/Magento/backend/web/css/source/components/_file-uploader.less index 9a88d5e3593..ec276449263 100644 --- a/app/design/adminhtml/Magento/backend/web/css/source/components/_file-uploader.less +++ b/app/design/adminhtml/Magento/backend/web/css/source/components/_file-uploader.less @@ -48,7 +48,7 @@ @data-grid-file-uploader-menu-button__width: 2rem; -@data-grid-file-uploader-upload-icon__color: @color-darkie-gray; +@data-grid-file-uploader-upload-icon__color: @color-darker-gray; @data-grid-file-uploader-upload-icon__hover__color: @color-very-dark-gray; @data-grid-file-uploader-upload-icon__line-height: 48px; diff --git a/app/design/adminhtml/Magento/backend/web/css/source/components/_messages.less b/app/design/adminhtml/Magento/backend/web/css/source/components/_messages.less index de24bf89620..03caa1c543b 100644 --- a/app/design/adminhtml/Magento/backend/web/css/source/components/_messages.less +++ b/app/design/adminhtml/Magento/backend/web/css/source/components/_messages.less @@ -76,7 +76,8 @@ position: absolute; speak: none; text-shadow: none; - top: 1.3rem; + top: 50%; + margin-top: -1.25rem; width: auto; } } @@ -110,7 +111,7 @@ content: @alert-icon__error__content; font-size: @alert-icon__error__font-size; left: 2.2rem; - margin-top: 0.5rem; + margin-top: .5rem; } } diff --git a/app/design/adminhtml/Magento/backend/web/css/source/components/_modals_extend.less b/app/design/adminhtml/Magento/backend/web/css/source/components/_modals_extend.less index 95d7f8f65fd..efc747e4d71 100644 --- a/app/design/adminhtml/Magento/backend/web/css/source/components/_modals_extend.less +++ b/app/design/adminhtml/Magento/backend/web/css/source/components/_modals_extend.less @@ -146,13 +146,13 @@ } .action-close { - padding: @modal-popup__padding; + padding: @modal-popup__padding - 2; &:active, &:focus { background: transparent; - padding-right: @modal-popup__padding + (@modal-action-close__font-size - @modal-action-close__active__font-size) / 2; - padding-top: @modal-popup__padding + (@modal-action-close__font-size - @modal-action-close__active__font-size) / 2; + padding-right: @modal-popup__padding - 2; + padding-top: @modal-popup__padding - 2; } } } diff --git a/app/design/adminhtml/Magento/backend/web/css/source/components/_popups.less b/app/design/adminhtml/Magento/backend/web/css/source/components/_popups.less index d1d1ff98916..bcdc96b6c17 100644 --- a/app/design/adminhtml/Magento/backend/web/css/source/components/_popups.less +++ b/app/design/adminhtml/Magento/backend/web/css/source/components/_popups.less @@ -264,7 +264,7 @@ } } - #contents-uploader { + .contents-uploader { margin: 0 0 @indent__base; } @@ -299,6 +299,7 @@ margin: 0 @indent__xs 15px 0; overflow: hidden; padding: 3px; + text-overflow: ellipsis; width: 100px; &.selected { @@ -310,7 +311,7 @@ } } - #contents-uploader { + .contents-uploader { &:extend(.abs-clearfix all); } diff --git a/app/design/adminhtml/Magento/backend/web/css/source/forms/_controls.less b/app/design/adminhtml/Magento/backend/web/css/source/forms/_controls.less index f971246ab46..6c3756370d9 100644 --- a/app/design/adminhtml/Magento/backend/web/css/source/forms/_controls.less +++ b/app/design/adminhtml/Magento/backend/web/css/source/forms/_controls.less @@ -85,7 +85,7 @@ cursor: pointer; } - &:focus { + &:active { background-image+: url('../images/arrows-bg.svg'); background-position+: ~'calc(100% - 12px)' 13px; diff --git a/app/design/adminhtml/Magento/backend/web/css/source/forms/_fields.less b/app/design/adminhtml/Magento/backend/web/css/source/forms/_fields.less index 02925881253..4b77c518d3d 100644 --- a/app/design/adminhtml/Magento/backend/web/css/source/forms/_fields.less +++ b/app/design/adminhtml/Magento/backend/web/css/source/forms/_fields.less @@ -121,6 +121,9 @@ > .admin__field-control { #mix-grid .column(@field-control-grid__column, @field-grid__columns); + input[type="checkbox"] { + margin-top: 1.2rem; + } } > .admin__field-label { @@ -156,6 +159,14 @@ } } } + &.composite-bundle { + .admin__field-control { + padding-top: 7px; + } + .admin__field-option { + padding-top: 0; + } + } } .admin__fieldset-product-websites { @@ -649,10 +660,11 @@ &.admin__field { > .admin__field-control { &:extend(.abs-field-size-small all); - float: left; position: relative; + display: inline-block; } } + + .admin__field:last-child { width: auto; diff --git a/app/design/adminhtml/Magento/backend/web/css/source/forms/_temp.less b/app/design/adminhtml/Magento/backend/web/css/source/forms/_temp.less index df031bebeb2..9cc38532cf3 100644 --- a/app/design/adminhtml/Magento/backend/web/css/source/forms/_temp.less +++ b/app/design/adminhtml/Magento/backend/web/css/source/forms/_temp.less @@ -11,7 +11,7 @@ .admin__fieldset-wrapper-title { &:extend(.abs-clearfix all); border-bottom: 1px solid @color-gray80; - line-height: 1.2; + line-height: 1.4; margin-bottom: 0; padding: 14px 0 16px; @@ -162,7 +162,7 @@ @_icon-font-line-height: 16px, @_icon-font-text-hide: true, @_icon-font-position: after, - @_icon-font-color: @color-brown-darkie + @_icon-font-color: @color-brown-darker ); span { @@ -175,7 +175,7 @@ z-index: 2; &:after { - color: darken(@color-brown-darkie, 20%); + color: darken(@color-brown-darker, 20%); } // @Todo ui - testing solution to show action hint without title attribute @@ -552,7 +552,7 @@ label.mage-error { } .admin__control-select-placeholder { - color: @color-darkie-gray; + color: @color-darker-gray; font-weight: @font-weight__bold; } } diff --git a/app/design/adminhtml/Magento/backend/web/css/source/variables/_colors.less b/app/design/adminhtml/Magento/backend/web/css/source/variables/_colors.less index b477384096b..ad57d7b4711 100644 --- a/app/design/adminhtml/Magento/backend/web/css/source/variables/_colors.less +++ b/app/design/adminhtml/Magento/backend/web/css/source/variables/_colors.less @@ -8,7 +8,7 @@ // _____________________________________________ @color-brown-dark: #4a3f39; -@color-brown-darkie: #41362f; +@color-brown-darker: #41362f; @color-very-dark-gray-black: #303030; @color-very-dark-gray-black2: #35302c; @color-very-dark-grayish-orange: #373330; @@ -23,7 +23,7 @@ @color-brownie-vanilla: #736963; @color-dark-gray0: #7f7c7a; @color-dark-gray: #808080; -@color-darkie-gray: #8a837f; +@color-darker-gray: #8a837f; @color-gray65: #a6a6a6; @color-gray65-almost: #a79d95; @color-gray65-lighten: #aaa6a0; @@ -73,5 +73,5 @@ @primary__color: @color-phoenix; @success__color: @color-green-apple; -@text__color: @color-brown-darkie; +@text__color: @color-brown-darker; @border__color: @color-gray89; diff --git a/app/design/adminhtml/Magento/backend/web/css/source/variables/_data-grid.less b/app/design/adminhtml/Magento/backend/web/css/source/variables/_data-grid.less index 69393a62200..40831684adc 100644 --- a/app/design/adminhtml/Magento/backend/web/css/source/variables/_data-grid.less +++ b/app/design/adminhtml/Magento/backend/web/css/source/variables/_data-grid.less @@ -30,7 +30,7 @@ @data-grid-td__odd__update__active__background-color: darken(@data-grid-td__update__active__background-color, 10%); @data-grid-td__odd__update__upcoming__background-color: darken(@data-grid-td__update__upcoming__background-color, 10%); -@data-grid-th__border-color: @color-darkie-gray; +@data-grid-th__border-color: @color-darker-gray; @data-grid-th__border-style: solid; @data-grid-th__background-color: @color-brownie; @data-grid-th__color: @color-white; diff --git a/app/design/adminhtml/Magento/backend/web/css/styles-old.less b/app/design/adminhtml/Magento/backend/web/css/styles-old.less index 26381367c72..2dbe68ef96e 100644 --- a/app/design/adminhtml/Magento/backend/web/css/styles-old.less +++ b/app/design/adminhtml/Magento/backend/web/css/styles-old.less @@ -2738,7 +2738,8 @@ // --------------------------------------------- #widget_instace_tabs_properties_section_content .widget-option-label { - margin-top: 6px; + margin-top: 7px; + display: inline-block; } // @@ -3845,6 +3846,26 @@ .rule-param-edit .element { display: inline; + position: relative; + } + + .rule-param-edit .element input.input-date, + .rule-param-edit .element input.input-date[readonly] { + background-color: @color-white; + min-width: 140px; + width: 140px !important; + cursor: pointer; + text-align: center; + opacity: 1; + margin-right: 10px; + padding-right: 40px; + + + .ui-datepicker-trigger { + position: absolute; + width: 140px; + text-align: right; + left: 0; + } } .rule-param-edit .element .addafter { diff --git a/app/design/frontend/Magento/blank/Magento_Braintree/web/css/source/_module.less b/app/design/frontend/Magento/blank/Magento_Braintree/web/css/source/_module.less index b70bab6f320..8c1ec09afec 100644 --- a/app/design/frontend/Magento/blank/Magento_Braintree/web/css/source/_module.less +++ b/app/design/frontend/Magento/blank/Magento_Braintree/web/css/source/_module.less @@ -9,9 +9,9 @@ @braintree-input-border__color: @color-gray76; -@braintree-error__color: @color-red10; -@braintree-focus__color: @color-blue2; -@braintree-success__color: @color-dark-green1; +@braintree-error__color: @message-error__color; +@braintree-focus__color: @theme__color__primary-alt; +@braintree-success__color: @message-success__color; @braintree-paypal-icon__height: 16px; @braintree-paypal-icon__width: 16px; diff --git a/app/design/frontend/Magento/blank/Magento_Catalog/web/css/source/_module.less b/app/design/frontend/Magento/blank/Magento_Catalog/web/css/source/_module.less index 08a9b619779..d3b314836ae 100644 --- a/app/design/frontend/Magento/blank/Magento_Catalog/web/css/source/_module.less +++ b/app/design/frontend/Magento/blank/Magento_Catalog/web/css/source/_module.less @@ -488,6 +488,7 @@ .product-items-names { .product-item { + display: flex; margin-bottom: @indent__s; } diff --git a/app/design/frontend/Magento/blank/Magento_Catalog/web/css/source/_widgets.less b/app/design/frontend/Magento/blank/Magento_Catalog/web/css/source/_widgets.less index 42b1bf2d0cc..7181606090c 100644 --- a/app/design/frontend/Magento/blank/Magento_Catalog/web/css/source/_widgets.less +++ b/app/design/frontend/Magento/blank/Magento_Catalog/web/css/source/_widgets.less @@ -23,6 +23,15 @@ } .block.widget { + .products-grid .product-item { + margin-left: 2%; + width: calc(~'(100% - 2%)/2'); + + &:nth-child(2n + 1) { + margin-left: 0; + } + } + .product-item-info { width: auto; } @@ -60,6 +69,15 @@ .page-layout-3columns .block.widget .products-grid .product-item { width: 100%/3; } + + .page-layout-1column .block.widget .products-grid .product-item { + margin-left: 2%; + width: calc(~'(100% - 4%)/3'); + + &:nth-child(3n + 1) { + margin-left: 0; + } + } } // @@ -82,7 +100,16 @@ } .page-layout-1column .block.widget .products-grid .product-item { - width: 100%/4; + margin-left: 2%; + width: calc(~'(100% - 6%)/4'); + + &:nth-child(3n + 1) { + margin-left: 2%; + } + + &:nth-child(4n + 1) { + margin-left: 0; + } } .page-layout-3columns .block.widget .products-grid .product-item { @@ -96,11 +123,11 @@ } .page-layout-1column .block.widget .products-grid .product-item { - margin-left: calc(~'(100% - 5 * (100%/6)) / 4'); - width: 100%/6; + margin-left: 2%; + width: calc(~'(100% - 8%)/5'); &:nth-child(4n + 1) { - margin-left: calc(~'(100% - 5 * (100%/6)) / 4'); + margin-left: 2%; } &:nth-child(5n + 1) { diff --git a/app/design/frontend/Magento/blank/Magento_Catalog/web/css/source/module/_listings.less b/app/design/frontend/Magento/blank/Magento_Catalog/web/css/source/module/_listings.less index 951ca89a079..b7af69fd5ca 100644 --- a/app/design/frontend/Magento/blank/Magento_Catalog/web/css/source/module/_listings.less +++ b/app/design/frontend/Magento/blank/Magento_Catalog/web/css/source/module/_listings.less @@ -29,15 +29,23 @@ .product { &-items { + font-size: 0; &:extend(.abs-reset-list all); } &-item { + font-size: 1.4rem; vertical-align: top; .products-grid & { display: inline-block; - width: 100%/2; + margin-left: 2%; + padding: 0; + width: calc(~'(100% - 2%) / 2'); + } + + &:nth-child(2n + 1) { + margin-left: 0; } &:extend(.abs-add-box-sizing all); @@ -63,13 +71,26 @@ } &-actions { + font-size: 0; + + > * { + font-size: 1.4rem; + } .actions-secondary { + display: inline-block; + font-size: 1.4rem; + vertical-align: middle; + white-space: nowrap; > button.action { .lib-button-reset(); } > .action { + line-height: 35px; + text-align: center; + width: 35px; + &:extend(.abs-actions-addto-gridlist all); &:before { margin: 0; @@ -80,6 +101,10 @@ } } } + + .actions-primary { + display: inline-block; + } } &-description { @@ -191,19 +216,6 @@ } } - .column.main { - .product { - &-items { - margin-left: -@indent__base; - } - - &-item { - padding-left: @indent__base; - } - } - - } - .price-container { .price { .lib-font-size(14); @@ -302,18 +314,10 @@ } .actions-primary + .actions-secondary { - display: table-cell; - padding-left: 5px; - white-space: nowrap; - width: 50%; > * { white-space: normal; } } - - .actions-primary { - display: table-cell; - } } } } @@ -329,7 +333,13 @@ .page-products.page-layout-3columns { .products-grid { .product-item { - width: 100%/3; + margin-left: 2%; + padding: 0; + width: calc(~'(100% - 4%) / 3'); + + &:nth-child(3n + 1) { + margin-left: 0; + } } } } @@ -343,7 +353,13 @@ .page-products { .products-grid { .product-item { - width: 100%/3; + margin-left: 2%; + padding: 0; + width: calc(~'(100% - 4%) / 3'); + + &:nth-child(3n + 1) { + margin-left: 0; + } } } } @@ -394,9 +410,13 @@ } .product-item { - margin-left: calc(~'(100% - 4 * 23.233%) / 3'); + margin-left: 2%; padding: 0; - width: 23.233%; + width: calc(~'(100% - 6%) / 4'); + + &:nth-child(3n + 1) { + margin-left: 2%; + } &:nth-child(4n + 1) { margin-left: 0; diff --git a/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/_minicart.less b/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/_minicart.less index 67313156341..65f3eeef63b 100644 --- a/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/_minicart.less +++ b/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/_minicart.less @@ -342,7 +342,7 @@ .item-qty { margin-right: @indent__s; text-align: center; - width: 40px; + width: 45px; } .update-cart-item { diff --git a/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_order-summary.less b/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_order-summary.less index 43c2ad50c7a..3394e8a4b50 100644 --- a/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_order-summary.less +++ b/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_order-summary.less @@ -118,6 +118,10 @@ .product { position: relative; + .item-options { + &:extend(.abs-product-options-list all); + &:extend(.abs-add-clearfix all); + } } } diff --git a/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_progress-bar.less b/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_progress-bar.less index 301517c6d0c..4792cb7b179 100644 --- a/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_progress-bar.less +++ b/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_progress-bar.less @@ -16,7 +16,7 @@ @checkout-progress-bar-item__color: @primary__color; @checkout-progress-bar-item__margin: @indent__s; @checkout-progress-bar-item__width: 185px; -@checkout-progress-bar-item__active__background-color: @color-orange-red1; +@checkout-progress-bar-item__active__background-color: @active__color; @checkout-progress-bar-item__complete__color: @link__color; @checkout-progress-bar-item-element__height: @checkout-progress-bar-item-element__width; diff --git a/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_shipping.less b/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_shipping.less index 158cb0ebc0e..af6127fd2ca 100644 --- a/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_shipping.less +++ b/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_shipping.less @@ -22,7 +22,7 @@ @checkout-shipping-item__width: 100%/3; @checkout-shipping-item-tablet__width: 100%/2; @checkout-shipping-item-mobile__width: 100%; -@checkout-shipping-item__active__border-color: @color-orange-red1; +@checkout-shipping-item__active__border-color: @active__color; @checkout-shipping-item-icon__selected__height: 27px; @checkout-shipping-item-icon__selected__width: 29px; diff --git a/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_sidebar-shipping-information.less b/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_sidebar-shipping-information.less index b54c0a264a0..0f2a7abcbaa 100644 --- a/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_sidebar-shipping-information.less +++ b/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_sidebar-shipping-information.less @@ -67,3 +67,11 @@ } } } + +.media-width(@extremum, @break) when (@extremum = 'max') and (@break = @screen__s) { + .opc-block-shipping-information { + .shipping-information-title { + font-size: 2.3rem; + } + } +} diff --git a/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_tooltip.less b/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_tooltip.less index bf264a98f33..39b9a051e65 100644 --- a/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_tooltip.less +++ b/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_tooltip.less @@ -147,3 +147,32 @@ } } } + +// +// Tablet +// _____________________________________________ + +@media only screen and (max-width: @screen__m) { + .field-tooltip .field-tooltip-content { + left: auto; + right: -10px; + top: 40px; + } + .field-tooltip .field-tooltip-content::before, + .field-tooltip .field-tooltip-content::after { + border: 10px solid transparent; + height: 0; + left: auto; + margin-top: -21px; + right: 10px; + top: 0; + width: 0; + } + .field-tooltip .field-tooltip-content::before { + border-bottom-color: @color-gray40; + } + .field-tooltip .field-tooltip-content::after { + border-bottom-color: @color-gray-light01; + top: 1px; + } +} diff --git a/app/design/frontend/Magento/blank/Magento_Customer/web/css/source/_module.less b/app/design/frontend/Magento/blank/Magento_Customer/web/css/source/_module.less index e2fd226a54f..4d9cb41b3ad 100644 --- a/app/design/frontend/Magento/blank/Magento_Customer/web/css/source/_module.less +++ b/app/design/frontend/Magento/blank/Magento_Customer/web/css/source/_module.less @@ -11,7 +11,7 @@ @account-nav-color: false; @account-nav-current-border: 3px solid transparent; -@account-nav-current-border-color: @color-orange-red1; +@account-nav-current-border-color: @active__color; @account-nav-current-color: false; @account-nav-current-font-weight: @font-weight__semibold; @@ -367,8 +367,8 @@ } .account { - .page.messages { - margin-bottom: @indent__base; + .messages { + margin-bottom: 0; } .toolbar { diff --git a/app/design/frontend/Magento/blank/Magento_MultipleWishlist/web/css/source/_module.less b/app/design/frontend/Magento/blank/Magento_MultipleWishlist/web/css/source/_module.less index 2761a2f74f9..c572c983d80 100644 --- a/app/design/frontend/Magento/blank/Magento_MultipleWishlist/web/css/source/_module.less +++ b/app/design/frontend/Magento/blank/Magento_MultipleWishlist/web/css/source/_module.less @@ -350,7 +350,7 @@ .product { &-item { &-checkbox { - left: 20px; + left: 0; position: absolute; top: 20px; } diff --git a/app/design/frontend/Magento/blank/Magento_Multishipping/web/css/source/_module.less b/app/design/frontend/Magento/blank/Magento_Multishipping/web/css/source/_module.less index 96648f504af..46f9661d828 100644 --- a/app/design/frontend/Magento/blank/Magento_Multishipping/web/css/source/_module.less +++ b/app/design/frontend/Magento/blank/Magento_Multishipping/web/css/source/_module.less @@ -47,7 +47,7 @@ } .error-block { - color: @color-red10; + color: @error__color; .error-label { font-weight: @font-weight__bold; @@ -222,7 +222,7 @@ } .error-description { - color: @color-red10; + color: @error__color; font-weight: @font-weight__regular; margin-bottom: @indent__s; margin-top: -@indent__s; diff --git a/app/design/frontend/Magento/blank/Magento_Swatches/web/css/source/_module.less b/app/design/frontend/Magento/blank/Magento_Swatches/web/css/source/_module.less index f2083cabce9..28aa3f187e9 100644 --- a/app/design/frontend/Magento/blank/Magento_Swatches/web/css/source/_module.less +++ b/app/design/frontend/Magento/blank/Magento_Swatches/web/css/source/_module.less @@ -18,7 +18,7 @@ @swatch-option__selected__border: @swatch-option__hover__border; @swatch-option__selected__color: @swatch-option__hover__color; -@swatch-option__selected__outline: 2px solid @color-orange-red1; +@swatch-option__selected__outline: 2px solid @active__color; @swatch-option__disabled__background: @color-red10; @@ -38,7 +38,7 @@ // Image and Color swatch @img-color-swatch-option__hover__border: @swatch-option__hover__border; -@img-color-swatch-option__hover__outline: 2px solid #e00; +@img-color-swatch-option__hover__outline: 2px solid darken(@active__color, 12%); // Tooltip @swatch-option-tooltip__background: @color-white; @@ -54,7 +54,7 @@ @swatch-option-tooltip-layered-title__color: @swatch-option-tooltip-title__color; // Layered Features -@swatch-option-link-layered__focus__box-shadow: 0 0 3px 1px @color-sky-blue1; +@swatch-option-link-layered__focus__box-shadow: 0 0 3px 1px @focus__color; // // Common diff --git a/app/design/frontend/Magento/blank/Magento_Wishlist/web/css/source/_module.less b/app/design/frontend/Magento/blank/Magento_Wishlist/web/css/source/_module.less index 0e8350261e0..9cd0439c139 100644 --- a/app/design/frontend/Magento/blank/Magento_Wishlist/web/css/source/_module.less +++ b/app/design/frontend/Magento/blank/Magento_Wishlist/web/css/source/_module.less @@ -8,6 +8,24 @@ // _____________________________________________ & when (@media-common = true) { + .toolbar { + &.wishlist-toolbar { + .limiter { + float: right; + } + .main .pages { + display: inline-block; + position: relative; + z-index: 0; + } + .toolbar-amount, + .limiter { + display: inline-block; + z-index: 1; + } + } + } + .form.wishlist.items { .actions-toolbar { &:extend(.abs-reset-left-margin all); @@ -177,10 +195,10 @@ .media-width(@extremum, @break) when (@extremum = 'max') and (@break = @screen__m) { .products-grid.wishlist { margin-bottom: @indent__l; - margin-right: -@indent__s; + margin-right: 0; .product { &-item { - padding: @indent__base @indent__s @indent__base @indent__base; + padding: @indent__base 0 @indent__base 0; position: relative; &-photo { @@ -194,6 +212,7 @@ &-actions { display: block; + float: left; .action { margin-right: 15px; diff --git a/app/design/frontend/Magento/blank/etc/view.xml b/app/design/frontend/Magento/blank/etc/view.xml index 572632b6683..e742ce0a21c 100644 --- a/app/design/frontend/Magento/blank/etc/view.xml +++ b/app/design/frontend/Magento/blank/etc/view.xml @@ -250,7 +250,7 @@ <var name="product_list_image_size">166</var> <!-- New Product image size used in product list --> <var name="product_zoom_image_size">370</var> <!-- New Product image size used for zooming --> - <var name="product_image_white_borders">0</var> + <var name="product_image_white_borders">1</var> </vars> <vars module="Magento_Bundle"> <var name="product_summary_image_size">58</var> <!-- New Product image size used for summary block--> diff --git a/app/design/frontend/Magento/blank/web/css/source/_forms.less b/app/design/frontend/Magento/blank/web/css/source/_forms.less index 94b993b53b5..c9f3c3d72ef 100644 --- a/app/design/frontend/Magento/blank/web/css/source/_forms.less +++ b/app/design/frontend/Magento/blank/web/css/source/_forms.less @@ -18,7 +18,7 @@ .fieldset { .lib-form-fieldset(); &:last-child { - margin-bottom: 0; + margin-bottom: @indent__base; } > .field, diff --git a/app/design/frontend/Magento/blank/web/css/source/_navigation.less b/app/design/frontend/Magento/blank/web/css/source/_navigation.less index 4499886ef0f..21b73157797 100644 --- a/app/design/frontend/Magento/blank/web/css/source/_navigation.less +++ b/app/design/frontend/Magento/blank/web/css/source/_navigation.less @@ -131,12 +131,18 @@ ); } } - .switcher-dropdown { .lib-list-reset-styles(); + display: none; padding: @indent__s 0; } - + .switcher-options { + &.active { + .switcher-dropdown { + display: block; + } + } + } .header.links { .lib-list-reset-styles(); border-bottom: 1px solid @color-gray82; @@ -207,7 +213,7 @@ } .nav-toggle { - &:after{ + &:after { background: rgba(0, 0, 0, @overlay__opacity); content: ''; display: block; diff --git a/app/design/frontend/Magento/blank/web/css/source/_sections.less b/app/design/frontend/Magento/blank/web/css/source/_sections.less index f0a3518c92a..1eee47bda81 100644 --- a/app/design/frontend/Magento/blank/web/css/source/_sections.less +++ b/app/design/frontend/Magento/blank/web/css/source/_sections.less @@ -31,8 +31,19 @@ .media-width(@extremum, @break) when (@extremum = 'max') and (@break = @screen__m) { .product.data.items { .lib-data-accordion(); + .data.item { display: block; } + + .item.title { + > .switch { + padding: 1px 15px 1px; + } + } + + > .item.content { + padding: 10px 15px 30px; + } } } diff --git a/app/design/frontend/Magento/luma/Magento_Bundle/web/css/source/_module.less b/app/design/frontend/Magento/luma/Magento_Bundle/web/css/source/_module.less index 43ae23bab78..45a01269bef 100644 --- a/app/design/frontend/Magento/luma/Magento_Bundle/web/css/source/_module.less +++ b/app/design/frontend/Magento/luma/Magento_Bundle/web/css/source/_module.less @@ -58,6 +58,7 @@ .field.choice { input { float: left; + margin-top: 4px; } .label { @@ -253,7 +254,7 @@ .box-tocart { .action.primary { margin-right: 1%; - width: 49%; + width: auto; } } diff --git a/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/_module.less b/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/_module.less index 501a1d2918d..f15509ceb63 100644 --- a/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/_module.less +++ b/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/_module.less @@ -563,6 +563,7 @@ .product-items-names { .product-item { + display: flex; margin-bottom: @indent__s; } @@ -975,6 +976,15 @@ [class*='block-compare'] { display: none; } + .catalog-product_compare-index { + .columns { + .column { + &.main { + flex-basis: inherit; + } + } + } + } } // diff --git a/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/module/_listings.less b/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/module/_listings.less index 6bf766b7400..d477c08fc95 100644 --- a/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/module/_listings.less +++ b/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/module/_listings.less @@ -18,7 +18,7 @@ @product-name-link__text-decoration__visited: @link__hover__text-decoration; @product-item__hover__background-color: @color-white; -@product-item__hover__box-shadow: 3px 3px 4px 0 rgba(0, 0, 0, .3); +@product-item__hover__box-shadow: 3px 4px 4px 0 rgba(0, 0, 0, .3); @product-price__muted__color: @color-gray40; @@ -34,15 +34,22 @@ .product { &-items { + font-size: 0; &:extend(.abs-reset-list all); } &-item { + font-size: 1.4rem; vertical-align: top; .products-grid & { display: inline-block; - width: 100%/2; + margin-left: 2%; + width: calc(~'(100% - 2%)/2'); + } + + &:nth-child(2n + 1) { + margin-left: 0; } &:extend(.abs-add-box-sizing all); @@ -68,8 +75,17 @@ } &-actions { + font-size: 0; + + > * { + font-size: 1.4rem; + } .actions-secondary { + display: inline-block; + font-size: 1.4rem; + vertical-align: middle; + > button.action { .lib-button-reset(); } @@ -79,12 +95,19 @@ &:before { margin: 0; } + line-height: 35px; + text-align: center; + width: 35px; span { &:extend(.abs-visually-hidden all); } } } + + .actions-primary { + display: inline-block; + } } &-description { @@ -291,7 +314,7 @@ border: 1px solid @color-gray-light2; border-top: none; left: 0; - margin: 9px 0 0 -1px; + margin: 10px 0 0 -1px; padding: 0 9px 9px; position: absolute; right: -1px; @@ -307,13 +330,13 @@ } .actions-primary + .actions-secondary { - display: table-cell; - padding-left: 10px; + display: inline-block; vertical-align: middle; - width: 50%; > .action { - margin-right: 10px; + line-height: 35px; + text-align: center; + width: 35px; &:last-child { margin-right: 0; @@ -322,7 +345,7 @@ } .actions-primary { - display: table-cell; + display: inline-block; } } @@ -374,10 +397,24 @@ .page-products.page-layout-3columns { .products-grid { .product-item { - width: 100%/3; + margin-left: 2%; + width: calc(~'(100% - 4%) / 3'); + + &:nth-child(3n + 1) { + margin-left: 0; + } } } } + + .block.widget .products-grid .product-item, + .page-layout-1column .block.widget .products-grid .product-item, + .page-layout-3columns .block.widget .products-grid .product-item { + .product-item-inner { + box-shadow: 3px 6px 4px 0 rgba(0, 0, 0, .3); + margin: 9px 0 0 -1px; + } + } } // @@ -388,7 +425,12 @@ .page-products { .products-grid { .product-item { - width: 100%/3; + margin-left: 2%; + width: calc(~'(100% - 4%) / 3'); + + &:nth-child(3n + 1) { + margin-left: 0; + } } } } @@ -441,9 +483,13 @@ } .product-item { - margin-left: calc(~'(100% - 4 * 24.439%) / 3'); + margin-left: 2%; padding: 5px; - width: 24.439%; + width: calc(~'(100% - 6%)/4'); + + &:nth-child(3n + 1) { + margin-left: 2%; + } &:nth-child(4n + 1) { margin-left: 0; diff --git a/app/design/frontend/Magento/luma/Magento_CatalogSearch/web/css/source/_module.less b/app/design/frontend/Magento/luma/Magento_CatalogSearch/web/css/source/_module.less index f785dd74d90..0f91f857a71 100644 --- a/app/design/frontend/Magento/luma/Magento_CatalogSearch/web/css/source/_module.less +++ b/app/design/frontend/Magento/luma/Magento_CatalogSearch/web/css/source/_module.less @@ -18,6 +18,20 @@ // _____________________________________________ & when (@media-common = true) { + + .search { + .fieldset { + .control { + .addon { + input { + flex-basis: auto; + width: 100%; + } + } + } + } + } + .block-search { margin-bottom: 0; diff --git a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/_cart.less b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/_cart.less index 58582d344e7..6be6010fd2d 100644 --- a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/_cart.less +++ b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/_cart.less @@ -97,6 +97,7 @@ .field { .radio { float: left; + margin-top: 4px; } .radio { @@ -145,12 +146,6 @@ } &:extend(.abs-adjustment-incl-excl-tax all); - - .action { - &.multicheckout { - color: @color-blue2; - } - } } // Totals block @@ -497,6 +492,17 @@ } } } + + .cart.table-wrapper, + .order-items.table-wrapper { + .col.price, + .col.qty, + .col.subtotal, + .col.msrp { + text-align: left; + } + } + } // @@ -694,6 +700,9 @@ position: static; } } + &.discount { + width: auto; + } } } diff --git a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/_minicart.less b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/_minicart.less index b9b223f4402..7265d7bd61c 100644 --- a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/_minicart.less +++ b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/_minicart.less @@ -63,6 +63,13 @@ } } + dl { + &.product.options.list { + display: inline-block; + vertical-align: top; + } + } + .text { &.empty { text-align: center; @@ -288,6 +295,15 @@ .details-qty { margin-top: @indent__s; } + + .product { + .options { + &.list { + &:extend(.abs-product-options-list all); + &:extend(.abs-add-clearfix all); + } + } + } } .product { @@ -355,7 +371,7 @@ .item-qty { margin-right: @indent__s; text-align: center; - width: 40px; + width: 45px; } .update-cart-item { diff --git a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_checkout.less b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_checkout.less index 0df0cace338..3ea1f5b7f68 100644 --- a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_checkout.less +++ b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_checkout.less @@ -48,6 +48,7 @@ .step-title { &:extend(.abs-checkout-title all); .lib-css(border-bottom, @checkout-step-title__border); + margin-bottom: 15px; } .step-content { diff --git a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_order-summary.less b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_order-summary.less index 9bad9518f57..308b034026e 100644 --- a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_order-summary.less +++ b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_order-summary.less @@ -118,6 +118,10 @@ .product { position: relative; + .item-options { + &:extend(.abs-product-options-list all); + &:extend(.abs-add-clearfix all); + } } } diff --git a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_payment-options.less b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_payment-options.less index 0b27454b206..3b584bc26fe 100644 --- a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_payment-options.less +++ b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_payment-options.less @@ -69,6 +69,13 @@ .payment-option-content { .lib-css(padding, 0 0 @indent__base @checkout-payment-option-content__padding__xl); + .primary { + .action { + &.action-apply { + margin-right: 0; + } + } + } } .payment-option-inner { diff --git a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_progress-bar.less b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_progress-bar.less index e2378eeeef0..ad1ac36237c 100644 --- a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_progress-bar.less +++ b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_progress-bar.less @@ -18,7 +18,7 @@ @checkout-progress-bar-item__transition: background .3s; @checkout-progress-bar-item__width: 185px; -@checkout-progress-bar-item__active__background-color: @color-orange-red1; +@checkout-progress-bar-item__active__background-color: @active__color; @checkout-progress-bar-item__active__color: @primary__color; @checkout-progress-bar-item__active__font-weight: @font-weight__semibold; @checkout-progress-bar-item__complete__color: @link__color; @@ -32,7 +32,7 @@ @checkout-progress-bar-item-element-inner__color: @primary__color; @checkout-progress-bar-item-element-inner__height: @checkout-progress-bar-item-element-inner__width; @checkout-progress-bar-item-element-inner__width: @checkout-progress-bar-item-element__width - ( @checkout-progress-bar-item-element-outer-radius__width*2 ); -@checkout-progress-bar-item-element-inner__active__border-color: @color-orange-red1; +@checkout-progress-bar-item-element-inner__active__border-color: @active__color; @checkout-progress-bar-item-element-inner__active__content: @icon-checkmark; @checkout-progress-bar-item-element-outer-radius__width: 6px; diff --git a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_shipping.less b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_shipping.less index d0ce87beb6a..d0edf245dc5 100644 --- a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_shipping.less +++ b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_shipping.less @@ -22,7 +22,7 @@ @checkout-shipping-item__width: 100%/3; @checkout-shipping-item-tablet__width: 100%/2; @checkout-shipping-item-mobile__width: 100%; -@checkout-shipping-item__active__border-color: @color-orange-red1; +@checkout-shipping-item__active__border-color: @active__color; @checkout-shipping-item-icon__selected__height: 27px; @checkout-shipping-item-icon__selected__width: 29px; diff --git a/app/design/frontend/Magento/luma/Magento_Customer/web/css/source/_module.less b/app/design/frontend/Magento/luma/Magento_Customer/web/css/source/_module.less index 5418d836fc2..749a388ac23 100755 --- a/app/design/frontend/Magento/luma/Magento_Customer/web/css/source/_module.less +++ b/app/design/frontend/Magento/luma/Magento_Customer/web/css/source/_module.less @@ -334,13 +334,17 @@ } } - .order-products-toolbar { + .order-products-toolbar, + .customer-addresses-toolbar { position: relative; .toolbar-amount { position: relative; text-align: center; } + .pages { + position: relative; + } } } @@ -417,6 +421,12 @@ .column.main { width: 77.7%; } + + .sidebar-main { + .block { + margin-bottom: 0; + } + } } .account { @@ -528,11 +538,18 @@ .column.main, .sidebar-additional { margin: 0; + padding: 0; } .data.table { &:extend(.abs-table-striped-mobile all); } + + .sidebar-main { + .account-nav { + margin-bottom: 0; + } + } } } @@ -550,8 +567,8 @@ } .account { - .page.messages { - margin-bottom: @indent__base; + .messages { + margin-bottom: 0; } .column.main { diff --git a/app/design/frontend/Magento/luma/Magento_GiftMessage/web/css/source/_module.less b/app/design/frontend/Magento/luma/Magento_GiftMessage/web/css/source/_module.less index 388ec32b4fc..ff377a4b88a 100644 --- a/app/design/frontend/Magento/luma/Magento_GiftMessage/web/css/source/_module.less +++ b/app/design/frontend/Magento/luma/Magento_GiftMessage/web/css/source/_module.less @@ -12,7 +12,7 @@ @gift-item-block__border-color: @color-gray-light5; @gift-item-block__border-width: @border-width__base; -@gift-item-block-title__color: @color-blue1; +@gift-item-block-title__color: @link__color; @gift-item-block-title-icon__content: @icon-down; @gift-item-block-title-icon__active__content: @icon-up; @gift-item-block-title-icon__color: @color-gray52; @@ -60,8 +60,8 @@ } .gift-summary { - position: relative; margin-top: @indent__s; + position: relative; .actions-toolbar { > .secondary { @@ -246,6 +246,9 @@ .gift-messages-order { margin-bottom: @indent__m; } + .gift-message-summary { + padding-right: 7rem; + } } // @@ -282,10 +285,6 @@ } } - .gift-message-summary { - padding-right: 7rem; - } - // // In-table block // --------------------------------------------- diff --git a/app/design/frontend/Magento/luma/Magento_LayeredNavigation/web/css/source/_module.less b/app/design/frontend/Magento/luma/Magento_LayeredNavigation/web/css/source/_module.less index 48461892fa6..76baec87e00 100644 --- a/app/design/frontend/Magento/luma/Magento_LayeredNavigation/web/css/source/_module.less +++ b/app/design/frontend/Magento/luma/Magento_LayeredNavigation/web/css/source/_module.less @@ -32,7 +32,7 @@ &[data-count]:after { .lib-css(color, @color-white); - background: @color-orange-red1; + background: @theme__color__secondary; border-radius: 2px; content: attr(data-count); display: inline-block; diff --git a/app/design/frontend/Magento/luma/Magento_MultipleWishlist/web/css/source/_module.less b/app/design/frontend/Magento/luma/Magento_MultipleWishlist/web/css/source/_module.less index 0b01c54a643..7ed4a9e64e9 100644 --- a/app/design/frontend/Magento/luma/Magento_MultipleWishlist/web/css/source/_module.less +++ b/app/design/frontend/Magento/luma/Magento_MultipleWishlist/web/css/source/_module.less @@ -429,7 +429,7 @@ .product { &-item { &-checkbox { - left: 20px; + left: 0; position: absolute; top: 20px; } diff --git a/app/design/frontend/Magento/luma/Magento_Multishipping/web/css/source/_module.less b/app/design/frontend/Magento/luma/Magento_Multishipping/web/css/source/_module.less index 7662c60734a..38f7c4ca873 100644 --- a/app/design/frontend/Magento/luma/Magento_Multishipping/web/css/source/_module.less +++ b/app/design/frontend/Magento/luma/Magento_Multishipping/web/css/source/_module.less @@ -48,7 +48,7 @@ } .error-block { - color: @color-red10; + color: @error__color; .error-label { font-weight: @font-weight__bold; @@ -230,7 +230,7 @@ } .error-description { - color: @color-red10; + color: @error__color; font-weight: @font-weight__regular; margin-bottom: @indent__s; margin-top: -@indent__s; @@ -374,7 +374,7 @@ text-align: right; .action { - margin-left: @indent__s; + margin-left: 0; &.back { display: block; @@ -496,4 +496,12 @@ margin-left: @indent__xl; } } + + .multicheckout { + .actions-toolbar { + > .primary { + margin-right: 0; + } + } + } } diff --git a/app/design/frontend/Magento/luma/Magento_Review/web/css/source/_module.less b/app/design/frontend/Magento/luma/Magento_Review/web/css/source/_module.less index 2c66420f65f..4324e5f1d6a 100644 --- a/app/design/frontend/Magento/luma/Magento_Review/web/css/source/_module.less +++ b/app/design/frontend/Magento/luma/Magento_Review/web/css/source/_module.less @@ -297,6 +297,9 @@ a:not(:last-child) { margin-right: 30px; } + .action.add { + white-space: nowrap; + } } } diff --git a/app/design/frontend/Magento/luma/Magento_Sales/email/creditmemo_update.html b/app/design/frontend/Magento/luma/Magento_Sales/email/creditmemo_update.html index a7b9b330ab9..269e46d7520 100644 --- a/app/design/frontend/Magento/luma/Magento_Sales/email/creditmemo_update.html +++ b/app/design/frontend/Magento/luma/Magento_Sales/email/creditmemo_update.html @@ -11,7 +11,7 @@ "var this.getUrl($store, 'customer/account/')":"Customer Account URL", "var order.getCustomerName()":"Customer Name", "var order.increment_id":"Order Id", -"var order.getStatusLabel()":"Order Status" +"var order.getFrontendStatusLabel()":"Order Status" } @--> {{template config_path="design/email/header_template"}} @@ -24,7 +24,7 @@ "Your order #%increment_id has been updated with a status of <strong>%order_status</strong>." increment_id=$order.increment_id - order_status=$order.getStatusLabel() + order_status=$order.getFrontendStatusLabel() |raw}} {{trans 'You can check the status of your order by <a href="%account_url">logging into your account</a>.' account_url=$this.getUrl($store,'customer/account/',[_nosid:1]) |raw}} </p> diff --git a/app/design/frontend/Magento/luma/Magento_Sales/email/creditmemo_update_guest.html b/app/design/frontend/Magento/luma/Magento_Sales/email/creditmemo_update_guest.html index 36279eb2600..c8bdae7b08f 100644 --- a/app/design/frontend/Magento/luma/Magento_Sales/email/creditmemo_update_guest.html +++ b/app/design/frontend/Magento/luma/Magento_Sales/email/creditmemo_update_guest.html @@ -10,7 +10,7 @@ "var creditmemo.increment_id":"Credit Memo Id", "var billing.getName()":"Guest Customer Name", "var order.increment_id":"Order Id", -"var order.getStatusLabel()":"Order Status" +"var order.getFrontendStatusLabel()":"Order Status" } @--> {{template config_path="design/email/header_template"}} @@ -23,7 +23,7 @@ "Your order #%increment_id has been updated with a status of <strong>%order_status</strong>." increment_id=$order.increment_id - order_status=$order.getStatusLabel() + order_status=$order.getFrontendStatusLabel() |raw}} </p> <p> diff --git a/app/design/frontend/Magento/luma/Magento_Sales/email/invoice_update.html b/app/design/frontend/Magento/luma/Magento_Sales/email/invoice_update.html index a739c9f54b0..8ec54f1e64d 100644 --- a/app/design/frontend/Magento/luma/Magento_Sales/email/invoice_update.html +++ b/app/design/frontend/Magento/luma/Magento_Sales/email/invoice_update.html @@ -11,7 +11,7 @@ "var comment":"Invoice Comment", "var invoice.increment_id":"Invoice Id", "var order.increment_id":"Order Id", -"var order.getStatusLabel()":"Order Status" +"var order.getFrontendStatusLabel()":"Order Status" } @--> {{template config_path="design/email/header_template"}} @@ -24,7 +24,7 @@ "Your order #%increment_id has been updated with a status of <strong>%order_status</strong>." increment_id=$order.increment_id - order_status=$order.getStatusLabel() + order_status=$order.getFrontendStatusLabel() |raw}} {{trans 'You can check the status of your order by <a href="%account_url">logging into your account</a>.' account_url=$this.getUrl($store,'customer/account/',[_nosid:1]) |raw}} </p> diff --git a/app/design/frontend/Magento/luma/Magento_Sales/email/invoice_update_guest.html b/app/design/frontend/Magento/luma/Magento_Sales/email/invoice_update_guest.html index a56ee6da9fa..6028db7b977 100644 --- a/app/design/frontend/Magento/luma/Magento_Sales/email/invoice_update_guest.html +++ b/app/design/frontend/Magento/luma/Magento_Sales/email/invoice_update_guest.html @@ -10,7 +10,7 @@ "var comment":"Invoice Comment", "var invoice.increment_id":"Invoice Id", "var order.increment_id":"Order Id", -"var order.getStatusLabel()":"Order Status" +"var order.getFrontendStatusLabel()":"Order Status" } @--> {{template config_path="design/email/header_template"}} @@ -23,7 +23,7 @@ "Your order #%increment_id has been updated with a status of <strong>%order_status</strong>." increment_id=$order.increment_id - order_status=$order.getStatusLabel() + order_status=$order.getFrontendStatusLabel() |raw}} </p> <p> diff --git a/app/design/frontend/Magento/luma/Magento_Sales/email/order_update.html b/app/design/frontend/Magento/luma/Magento_Sales/email/order_update.html index 3e4bf8df2f1..fa16ac2196b 100644 --- a/app/design/frontend/Magento/luma/Magento_Sales/email/order_update.html +++ b/app/design/frontend/Magento/luma/Magento_Sales/email/order_update.html @@ -10,7 +10,7 @@ "var order.getCustomerName()":"Customer Name", "var comment":"Order Comment", "var order.increment_id":"Order Id", -"var order.getStatusLabel()":"Order Status" +"var order.getFrontendStatusLabel()":"Order Status" } @--> {{template config_path="design/email/header_template"}} @@ -23,7 +23,7 @@ "Your order #%increment_id has been updated with a status of <strong>%order_status</strong>." increment_id=$order.increment_id - order_status=$order.getStatusLabel() + order_status=$order.getFrontendStatusLabel() |raw}} {{trans 'You can check the status of your order by <a href="%account_url">logging into your account</a>.' account_url=$this.getUrl($store,'customer/account/',[_nosid:1]) |raw}} </p> diff --git a/app/design/frontend/Magento/luma/Magento_Sales/email/order_update_guest.html b/app/design/frontend/Magento/luma/Magento_Sales/email/order_update_guest.html index 1075608db43..8ead615fe01 100644 --- a/app/design/frontend/Magento/luma/Magento_Sales/email/order_update_guest.html +++ b/app/design/frontend/Magento/luma/Magento_Sales/email/order_update_guest.html @@ -9,7 +9,7 @@ "var billing.getName()":"Guest Customer Name", "var comment":"Order Comment", "var order.increment_id":"Order Id", -"var order.getStatusLabel()":"Order Status" +"var order.getFrontendStatusLabel()":"Order Status" } @--> {{template config_path="design/email/header_template"}} @@ -22,7 +22,7 @@ "Your order #%increment_id has been updated with a status of <strong>%order_status</strong>." increment_id=$order.increment_id - order_status=$order.getStatusLabel() + order_status=$order.getFrontendStatusLabel() |raw}} </p> <p> diff --git a/app/design/frontend/Magento/luma/Magento_Sales/email/shipment_update.html b/app/design/frontend/Magento/luma/Magento_Sales/email/shipment_update.html index 37bf92b866c..4f9b7286f3a 100644 --- a/app/design/frontend/Magento/luma/Magento_Sales/email/shipment_update.html +++ b/app/design/frontend/Magento/luma/Magento_Sales/email/shipment_update.html @@ -10,7 +10,7 @@ "var order.getCustomerName()":"Customer Name", "var comment":"Order Comment", "var order.increment_id":"Order Id", -"var order.getStatusLabel()":"Order Status", +"var order.getFrontendStatusLabel()":"Order Status", "var shipment.increment_id":"Shipment Id" } @--> {{template config_path="design/email/header_template"}} @@ -24,7 +24,7 @@ "Your order #%increment_id has been updated with a status of <strong>%order_status</strong>." increment_id=$order.increment_id - order_status=$order.getStatusLabel() + order_status=$order.getFrontendStatusLabel() |raw}} {{trans 'You can check the status of your order by <a href="%account_url">logging into your account</a>.' account_url=$this.getUrl($store,'customer/account/',[_nosid:1]) |raw}} </p> diff --git a/app/design/frontend/Magento/luma/Magento_Sales/email/shipment_update_guest.html b/app/design/frontend/Magento/luma/Magento_Sales/email/shipment_update_guest.html index 95481994986..3ef26463ea7 100644 --- a/app/design/frontend/Magento/luma/Magento_Sales/email/shipment_update_guest.html +++ b/app/design/frontend/Magento/luma/Magento_Sales/email/shipment_update_guest.html @@ -9,7 +9,7 @@ "var billing.getName()":"Guest Customer Name", "var comment":"Order Comment", "var order.increment_id":"Order Id", -"var order.getStatusLabel()":"Order Status", +"var order.getFrontendStatusLabel()":"Order Status", "var shipment.increment_id":"Shipment Id" } @--> {{template config_path="design/email/header_template"}} @@ -23,7 +23,7 @@ "Your order #%increment_id has been updated with a status of <strong>%order_status</strong>." increment_id=$order.increment_id - order_status=$order.getStatusLabel() + order_status=$order.getFrontendStatusLabel() |raw}} </p> <p> diff --git a/app/design/frontend/Magento/luma/Magento_Theme/web/css/source/_module.less b/app/design/frontend/Magento/luma/Magento_Theme/web/css/source/_module.less index cadf575b95f..99da7716f92 100644 --- a/app/design/frontend/Magento/luma/Magento_Theme/web/css/source/_module.less +++ b/app/design/frontend/Magento/luma/Magento_Theme/web/css/source/_module.less @@ -144,6 +144,12 @@ } } + .page-print { + .nav-toggle { + display: none; + } + } + .page-main { > .page-title-wrapper { .page-title + .action { @@ -312,6 +318,23 @@ } } } + .page-header { + .switcher { + .options { + ul.dropdown { + right: 0; + &:before { + left: auto; + right: 10px; + } + &:after { + left: auto; + right: 9px; + } + } + } + } + } // // Widgets @@ -435,7 +458,7 @@ .media-width(@extremum, @break) when (@extremum = 'max') and (@break = @screen__m) { .cms-page-view .page-main { - padding-top: 41px; + padding-top: 0; position: relative; } } diff --git a/app/design/frontend/Magento/luma/Magento_Wishlist/web/css/source/_module.less b/app/design/frontend/Magento/luma/Magento_Wishlist/web/css/source/_module.less index 584eefb9bc6..2db14c05cca 100644 --- a/app/design/frontend/Magento/luma/Magento_Wishlist/web/css/source/_module.less +++ b/app/design/frontend/Magento/luma/Magento_Wishlist/web/css/source/_module.less @@ -8,6 +8,24 @@ // _____________________________________________ & when (@media-common = true) { + .toolbar { + &.wishlist-toolbar { + .limiter { + float: right; + } + .main .pages { + display: inline-block; + position: relative; + z-index: 0; + } + .toolbar-amount, + .limiter { + display: inline-block; + z-index: 1; + } + } + } + .form.wishlist.items { .actions-toolbar { &:extend(.abs-reset-left-margin all); @@ -164,6 +182,30 @@ } } } + .products-grid.wishlist { + .product-item-actions { + .action { + &.edit, + &.delete { + .lib-icon-font( + @icon-edit, + @_icon-font-size: 18px, + @_icon-font-line-height: 20px, + @_icon-font-text-hide: true, + @_icon-font-color: @minicart-icons-color, + @_icon-font-color-hover: @primary__color, + @_icon-font-color-active: @minicart-icons-color + ); + } + + &.delete { + .lib-icon-font-symbol( + @_icon-font-content: @icon-trash + ); + } + } + } + } } // @@ -185,11 +227,11 @@ .media-width(@extremum, @break) when (@extremum = 'max') and (@break = @screen__m) { .products-grid.wishlist { margin-bottom: @indent__l; - margin-right: -@indent__s; + margin-right: 0; .product { &-item { - padding: @indent__base @indent__s @indent__base @indent__base; + padding: @indent__base 0 @indent__base 0; position: relative; &-photo { @@ -203,6 +245,7 @@ &-actions { display: block; + float: left; .action { margin-right: 15px; @@ -210,15 +253,7 @@ &:last-child { margin-right: 0; } - - &.edit { - float: left; - } - - &.delete { - float: right; - } - + &.edit, &.delete { margin-top: 7px; diff --git a/app/design/frontend/Magento/luma/etc/view.xml b/app/design/frontend/Magento/luma/etc/view.xml index 55d43272caa..7aa2e51481b 100644 --- a/app/design/frontend/Magento/luma/etc/view.xml +++ b/app/design/frontend/Magento/luma/etc/view.xml @@ -256,7 +256,7 @@ <var name="product_list_image_size">166</var> <!-- New Product image size used in product list --> <var name="product_zoom_image_size">370</var> <!-- New Product image size used for zooming --> - <var name="product_image_white_borders">0</var> + <var name="product_image_white_borders">1</var> </vars> <vars module="Magento_Bundle"> <var name="product_summary_image_size">58</var> <!-- New Product image size used for summary block--> diff --git a/app/design/frontend/Magento/luma/web/css/source/_extends.less b/app/design/frontend/Magento/luma/web/css/source/_extends.less index 3eb189468a9..a88ecbf5057 100644 --- a/app/design/frontend/Magento/luma/web/css/source/_extends.less +++ b/app/design/frontend/Magento/luma/web/css/source/_extends.less @@ -1863,7 +1863,7 @@ > .title { strong { - color: @color-blue1; + color: @link__color; font-weight: @font-weight__regular; } } diff --git a/app/design/frontend/Magento/luma/web/css/source/_forms.less b/app/design/frontend/Magento/luma/web/css/source/_forms.less index 0c7150c1855..98dd57dead7 100644 --- a/app/design/frontend/Magento/luma/web/css/source/_forms.less +++ b/app/design/frontend/Magento/luma/web/css/source/_forms.less @@ -20,7 +20,7 @@ .lib-form-fieldset(); &:last-child { - margin-bottom: 0; + margin-bottom: @indent__base; } > .field, diff --git a/app/design/frontend/Magento/luma/web/css/source/_sections.less b/app/design/frontend/Magento/luma/web/css/source/_sections.less index 73665fd22da..95769c4f4b6 100644 --- a/app/design/frontend/Magento/luma/web/css/source/_sections.less +++ b/app/design/frontend/Magento/luma/web/css/source/_sections.less @@ -19,16 +19,16 @@ a { position: relative; .lib-icon-font( - @_icon-font-content: @icon-down, - @_icon-font-size: @font-size__base, - @_icon-font-line-height: @icon-font__line-height, - @_icon-font-color: @icon-font__color, - @_icon-font-color-hover: @icon-font__color-hover, - @_icon-font-color-active: @icon-font__color-active, - @_icon-font-margin: @icon-font__margin, - @_icon-font-vertical-align: @icon-font__vertical-align, - @_icon-font-position: after, - @_icon-font-display: false + @_icon-font-content: @icon-down, + @_icon-font-size: @font-size__base, + @_icon-font-line-height: @icon-font__line-height, + @_icon-font-color: @icon-font__color, + @_icon-font-color-hover: @icon-font__color-hover, + @_icon-font-color-active: @icon-font__color-active, + @_icon-font-margin: @icon-font__margin, + @_icon-font-vertical-align: @icon-font__vertical-align, + @_icon-font-position: after, + @_icon-font-display: false ); &:after { @@ -75,3 +75,17 @@ } } } + +.media-width(@extremum, @break) when (@extremum = 'max') and (@break = @screen__m) { + .product.data.items { + .item.title { + > .switch { + padding: 1px 15px 1px; + } + } + + > .item.content { + padding: 10px 15px 30px; + } + } +} diff --git a/app/design/frontend/Magento/luma/web/css/source/_theme.less b/app/design/frontend/Magento/luma/web/css/source/_theme.less index 957e622d6a0..114fb7b8172 100644 --- a/app/design/frontend/Magento/luma/web/css/source/_theme.less +++ b/app/design/frontend/Magento/luma/web/css/source/_theme.less @@ -28,7 +28,7 @@ @h3__margin-top: @indent__base; // Links -@link__color: @color-blue2; +@link__color: @theme__color__primary-alt; // Focus @focus__color: @color-blue3; @@ -60,14 +60,9 @@ @navigation__background: @color-gray94; @navigation-level0-item__active__color: @primary__color; -@navigation-level0-item__active__border-color: @active__color; -@navigation-desktop-level0-item__active__border-color: @active__color; -@submenu-desktop-item__active__border-color: @active__color; -@submenu-item__active__border-color: @active__color; @submenu-item__active__color: @navigation-level0-item__active__color; - // Desktop navigation @navigation-desktop-level0-item__line-height: 47px; @submenu-desktop__font-weight: @font-weight__regular; @@ -225,7 +220,6 @@ @rating-icon__font-size: 16px; @rating-icon__letter-spacing: 2px; -@rating-icon__active__color: @active__color; // // Dropdowns @@ -252,7 +246,7 @@ @breadcrumbs-icon__font-margin: 0 @indent__s; @breadcrumbs-current__color: @color-gray-middle5; -@breadcrumbs-link__color: @color-blue2; +@breadcrumbs-link__color: @link__color; @breadcrumbs-link__visited__color: @breadcrumbs-link__color; @breadcrumbs-link__hover__color: @breadcrumbs-link__color; @breadcrumbs-link__active__color: @breadcrumbs-link__color; diff --git a/app/design/frontend/Magento/luma/web/css/source/components/_modals_extend.less b/app/design/frontend/Magento/luma/web/css/source/components/_modals_extend.less index ae68fc81acd..7e3ee14ca5f 100644 --- a/app/design/frontend/Magento/luma/web/css/source/components/_modals_extend.less +++ b/app/design/frontend/Magento/luma/web/css/source/components/_modals_extend.less @@ -58,7 +58,7 @@ .modal-custom { .action-close { - .lib-css(margin, @indent__m); + .lib-css(margin, 15px); } } @@ -82,7 +82,8 @@ .modal-slide { .action-close { - padding: @modal-slide-action-close__padding; + margin: 15px; + padding: 0; } .page-main-actions { diff --git a/app/etc/di.xml b/app/etc/di.xml index ceccee04a8b..6cf169c1d22 100755 --- a/app/etc/di.xml +++ b/app/etc/di.xml @@ -1749,4 +1749,11 @@ </argument> </arguments> </type> + <type name="Magento\Framework\App\ScopeResolverPool"> + <arguments> + <argument name="scopeResolvers" xsi:type="array"> + <item name="default" xsi:type="object">Magento\Framework\App\ScopeResolver</item> + </argument> + </arguments> + </type> </config> diff --git a/auth.json.sample b/auth.json.sample index 81c8fd220ea..48b13ee8b69 100644 --- a/auth.json.sample +++ b/auth.json.sample @@ -1,8 +1,11 @@ { - "http-basic": { - "repo.magento.com": { - "username": "<public-key>", - "password": "<private-key>" - } - } + "github-oauth": { + "github.com": "<github-personal-access-token>" + }, + "http-basic": { + "repo.magento.com": { + "username": "<public-key>", + "password": "<private-key>" + } + } } diff --git a/composer.json b/composer.json index 1822fc90e82..1a4c9e957a0 100644 --- a/composer.json +++ b/composer.json @@ -84,7 +84,7 @@ "require-dev": { "friendsofphp/php-cs-fixer": "~2.13.0", "lusitanian/oauth": "~0.8.10", - "magento/magento2-functional-testing-framework": "~2.3.12", + "magento/magento2-functional-testing-framework": "~2.3.13", "pdepend/pdepend": "2.5.2", "phpmd/phpmd": "@stable", "phpunit/phpunit": "~6.5.0", @@ -104,6 +104,7 @@ "magento/module-asynchronous-operations": "*", "magento/module-authorization": "*", "magento/module-authorizenet": "*", + "magento/module-authorizenet-acceptjs": "*", "magento/module-advanced-search": "*", "magento/module-backend": "*", "magento/module-backup": "*", @@ -142,6 +143,7 @@ "magento/module-developer": "*", "magento/module-dhl": "*", "magento/module-directory": "*", + "magento/module-directory-graph-ql": "*", "magento/module-downloadable": "*", "magento/module-downloadable-graph-ql": "*", "magento/module-downloadable-import-export": "*", diff --git a/composer.lock b/composer.lock index c628658b191..48aef8b603d 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "d73dacf383a73ef9e405261921896e12", + "content-hash": "f22c780b1ed27ba951c0562e20078a70", "packages": [ { "name": "braintree/braintree_php", @@ -201,16 +201,16 @@ }, { "name": "composer/ca-bundle", - "version": "1.1.3", + "version": "1.1.4", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "8afa52cd417f4ec417b4bfe86b68106538a87660" + "reference": "558f321c52faeb4828c03e7dc0cfe39a09e09a2d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/8afa52cd417f4ec417b4bfe86b68106538a87660", - "reference": "8afa52cd417f4ec417b4bfe86b68106538a87660", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/558f321c52faeb4828c03e7dc0cfe39a09e09a2d", + "reference": "558f321c52faeb4828c03e7dc0cfe39a09e09a2d", "shasum": "" }, "require": { @@ -253,20 +253,20 @@ "ssl", "tls" ], - "time": "2018-10-18T06:09:13+00:00" + "time": "2019-01-28T09:30:10+00:00" }, { "name": "composer/composer", - "version": "1.8.0", + "version": "1.8.3", "source": { "type": "git", "url": "https://github.com/composer/composer.git", - "reference": "d8aef3af866b28786ce9b8647e52c42496436669" + "reference": "a6a3b44581398b7135c7baa0557b7c5b10808b47" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/composer/zipball/d8aef3af866b28786ce9b8647e52c42496436669", - "reference": "d8aef3af866b28786ce9b8647e52c42496436669", + "url": "https://api.github.com/repos/composer/composer/zipball/a6a3b44581398b7135c7baa0557b7c5b10808b47", + "reference": "a6a3b44581398b7135c7baa0557b7c5b10808b47", "shasum": "" }, "require": { @@ -333,7 +333,7 @@ "dependency", "package" ], - "time": "2018-12-03T09:31:16+00:00" + "time": "2019-01-30T07:31:34+00:00" }, { "name": "composer/semver", @@ -460,16 +460,16 @@ }, { "name": "composer/xdebug-handler", - "version": "1.3.1", + "version": "1.3.2", "source": { "type": "git", "url": "https://github.com/composer/xdebug-handler.git", - "reference": "dc523135366eb68f22268d069ea7749486458562" + "reference": "d17708133b6c276d6e42ef887a877866b909d892" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/dc523135366eb68f22268d069ea7749486458562", - "reference": "dc523135366eb68f22268d069ea7749486458562", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/d17708133b6c276d6e42ef887a877866b909d892", + "reference": "d17708133b6c276d6e42ef887a877866b909d892", "shasum": "" }, "require": { @@ -500,7 +500,7 @@ "Xdebug", "performance" ], - "time": "2018-11-29T10:59:02+00:00" + "time": "2019-01-28T20:25:53+00:00" }, { "name": "container-interop/container-interop", @@ -535,16 +535,16 @@ }, { "name": "elasticsearch/elasticsearch", - "version": "v5.3.2", + "version": "v5.4.0", "source": { "type": "git", "url": "https://github.com/elastic/elasticsearch-php.git", - "reference": "4b29a4121e790bbfe690d5ee77da348b62d48eb8" + "reference": "d3c5b55ad94f5053ca76c48585b4cde2cdc6bc59" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/elastic/elasticsearch-php/zipball/4b29a4121e790bbfe690d5ee77da348b62d48eb8", - "reference": "4b29a4121e790bbfe690d5ee77da348b62d48eb8", + "url": "https://api.github.com/repos/elastic/elasticsearch-php/zipball/d3c5b55ad94f5053ca76c48585b4cde2cdc6bc59", + "reference": "d3c5b55ad94f5053ca76c48585b4cde2cdc6bc59", "shasum": "" }, "require": { @@ -586,7 +586,7 @@ "elasticsearch", "search" ], - "time": "2017-11-08T17:04:47+00:00" + "time": "2019-01-08T18:57:00+00:00" }, { "name": "guzzlehttp/ringphp", @@ -691,23 +691,23 @@ }, { "name": "justinrainbow/json-schema", - "version": "5.2.7", + "version": "5.2.8", "source": { "type": "git", "url": "https://github.com/justinrainbow/json-schema.git", - "reference": "8560d4314577199ba51bf2032f02cd1315587c23" + "reference": "dcb6e1006bb5fd1e392b4daa68932880f37550d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/8560d4314577199ba51bf2032f02cd1315587c23", - "reference": "8560d4314577199ba51bf2032f02cd1315587c23", + "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/dcb6e1006bb5fd1e392b4daa68932880f37550d4", + "reference": "dcb6e1006bb5fd1e392b4daa68932880f37550d4", "shasum": "" }, "require": { "php": ">=5.3.3" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^2.1", + "friendsofphp/php-cs-fixer": "~2.2.20", "json-schema/json-schema-test-suite": "1.2.0", "phpunit/phpunit": "^4.8.35" }, @@ -753,7 +753,7 @@ "json", "schema" ], - "time": "2018-02-14T22:26:30+00:00" + "time": "2019-01-14T23:55:14+00:00" }, { "name": "magento/composer", @@ -1380,16 +1380,16 @@ }, { "name": "phpseclib/phpseclib", - "version": "2.0.13", + "version": "2.0.14", "source": { "type": "git", "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "42603ce3f42a27f7e14e54feab95db7b680ad473" + "reference": "8ebfcadbf30524aeb75b2c446bc2519d5b321478" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/42603ce3f42a27f7e14e54feab95db7b680ad473", - "reference": "42603ce3f42a27f7e14e54feab95db7b680ad473", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/8ebfcadbf30524aeb75b2c446bc2519d5b321478", + "reference": "8ebfcadbf30524aeb75b2c446bc2519d5b321478", "shasum": "" }, "require": { @@ -1468,7 +1468,7 @@ "x.509", "x509" ], - "time": "2018-12-16T17:45:25+00:00" + "time": "2019-01-27T19:37:29+00:00" }, { "name": "psr/container", @@ -1839,16 +1839,16 @@ }, { "name": "symfony/console", - "version": "v4.1.10", + "version": "v4.1.11", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "e798b40183701b297c43bb45f81fb2fa8d177b0a" + "reference": "9e87c798f67dc9fceeb4f3d57847b52d945d1a02" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/e798b40183701b297c43bb45f81fb2fa8d177b0a", - "reference": "e798b40183701b297c43bb45f81fb2fa8d177b0a", + "url": "https://api.github.com/repos/symfony/console/zipball/9e87c798f67dc9fceeb4f3d57847b52d945d1a02", + "reference": "9e87c798f67dc9fceeb4f3d57847b52d945d1a02", "shasum": "" }, "require": { @@ -1859,6 +1859,9 @@ "symfony/dependency-injection": "<3.4", "symfony/process": "<3.3" }, + "provide": { + "psr/log-implementation": "1.0" + }, "require-dev": { "psr/log": "~1.0", "symfony/config": "~3.4|~4.0", @@ -1868,7 +1871,7 @@ "symfony/process": "~3.4|~4.0" }, "suggest": { - "psr/log-implementation": "For using the console logger", + "psr/log": "For using the console logger", "symfony/event-dispatcher": "", "symfony/lock": "", "symfony/process": "" @@ -1903,20 +1906,20 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2019-01-04T15:13:30+00:00" + "time": "2019-01-25T14:34:37+00:00" }, { "name": "symfony/css-selector", - "version": "v4.2.2", + "version": "v4.2.3", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "76dac1dbe2830213e95892c7c2ec1edd74113ea4" + "reference": "48eddf66950fa57996e1be4a55916d65c10c604a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/76dac1dbe2830213e95892c7c2ec1edd74113ea4", - "reference": "76dac1dbe2830213e95892c7c2ec1edd74113ea4", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/48eddf66950fa57996e1be4a55916d65c10c604a", + "reference": "48eddf66950fa57996e1be4a55916d65c10c604a", "shasum": "" }, "require": { @@ -1956,20 +1959,20 @@ ], "description": "Symfony CssSelector Component", "homepage": "https://symfony.com", - "time": "2019-01-03T09:07:35+00:00" + "time": "2019-01-16T20:31:39+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v4.1.10", + "version": "v4.1.11", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "9da78639776ee15285a1505f1dc405a6e6844a87" + "reference": "51be1b61dfe04d64a260223f2b81475fa8066b97" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/9da78639776ee15285a1505f1dc405a6e6844a87", - "reference": "9da78639776ee15285a1505f1dc405a6e6844a87", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/51be1b61dfe04d64a260223f2b81475fa8066b97", + "reference": "51be1b61dfe04d64a260223f2b81475fa8066b97", "shasum": "" }, "require": { @@ -2019,20 +2022,20 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2019-01-04T15:09:47+00:00" + "time": "2019-01-16T18:35:49+00:00" }, { "name": "symfony/filesystem", - "version": "v4.2.2", + "version": "v4.2.3", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "c2ffd9a93f2d6c5be2f68a0aa7953cc229f871f8" + "reference": "7c16ebc2629827d4ec915a52ac809768d060a4ee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/c2ffd9a93f2d6c5be2f68a0aa7953cc229f871f8", - "reference": "c2ffd9a93f2d6c5be2f68a0aa7953cc229f871f8", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/7c16ebc2629827d4ec915a52ac809768d060a4ee", + "reference": "7c16ebc2629827d4ec915a52ac809768d060a4ee", "shasum": "" }, "require": { @@ -2069,20 +2072,20 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2019-01-03T09:07:35+00:00" + "time": "2019-01-16T20:35:37+00:00" }, { "name": "symfony/finder", - "version": "v4.2.2", + "version": "v4.2.3", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "9094d69e8c6ee3fe186a0ec5a4f1401e506071ce" + "reference": "ef71816cbb264988bb57fe6a73f610888b9aa70c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/9094d69e8c6ee3fe186a0ec5a4f1401e506071ce", - "reference": "9094d69e8c6ee3fe186a0ec5a4f1401e506071ce", + "url": "https://api.github.com/repos/symfony/finder/zipball/ef71816cbb264988bb57fe6a73f610888b9aa70c", + "reference": "ef71816cbb264988bb57fe6a73f610888b9aa70c", "shasum": "" }, "require": { @@ -2118,7 +2121,7 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2019-01-03T09:07:35+00:00" + "time": "2019-01-16T20:35:37+00:00" }, { "name": "symfony/polyfill-ctype", @@ -2239,16 +2242,16 @@ }, { "name": "symfony/process", - "version": "v4.1.10", + "version": "v4.1.11", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "5bcfa3a267431d95ab723101d2de3f48db1a0800" + "reference": "72d838aafaa7c790330fe362b9cecec362c64629" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/5bcfa3a267431d95ab723101d2de3f48db1a0800", - "reference": "5bcfa3a267431d95ab723101d2de3f48db1a0800", + "url": "https://api.github.com/repos/symfony/process/zipball/72d838aafaa7c790330fe362b9cecec362c64629", + "reference": "72d838aafaa7c790330fe362b9cecec362c64629", "shasum": "" }, "require": { @@ -2284,7 +2287,7 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2019-01-03T09:05:57+00:00" + "time": "2019-01-16T19:07:26+00:00" }, { "name": "tedivm/jshrink", @@ -3213,16 +3216,16 @@ }, { "name": "zendframework/zend-http", - "version": "2.8.3", + "version": "2.8.4", "source": { "type": "git", "url": "https://github.com/zendframework/zend-http.git", - "reference": "e70ed2a51f37b848c8c3829739d1b1f803854ffa" + "reference": "d160aedc096be230af0fe9c31151b2b33ad4e807" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-http/zipball/e70ed2a51f37b848c8c3829739d1b1f803854ffa", - "reference": "e70ed2a51f37b848c8c3829739d1b1f803854ffa", + "url": "https://api.github.com/repos/zendframework/zend-http/zipball/d160aedc096be230af0fe9c31151b2b33ad4e807", + "reference": "d160aedc096be230af0fe9c31151b2b33ad4e807", "shasum": "" }, "require": { @@ -3264,7 +3267,7 @@ "zend", "zf" ], - "time": "2019-01-08T15:55:38+00:00" + "time": "2019-02-07T17:47:08+00:00" }, { "name": "zendframework/zend-hydrator", @@ -3394,16 +3397,16 @@ }, { "name": "zendframework/zend-inputfilter", - "version": "2.9.1", + "version": "2.10.0", "source": { "type": "git", "url": "https://github.com/zendframework/zend-inputfilter.git", - "reference": "3dc2ff5a474bce824e2429564daa56b4c6b0d547" + "reference": "4f52b71ec9cef3a06e3bba8f5c2124e94055ec0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-inputfilter/zipball/3dc2ff5a474bce824e2429564daa56b4c6b0d547", - "reference": "3dc2ff5a474bce824e2429564daa56b4c6b0d547", + "url": "https://api.github.com/repos/zendframework/zend-inputfilter/zipball/4f52b71ec9cef3a06e3bba8f5c2124e94055ec0c", + "reference": "4f52b71ec9cef3a06e3bba8f5c2124e94055ec0c", "shasum": "" }, "require": { @@ -3424,8 +3427,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.9.x-dev", - "dev-develop": "2.10.x-dev" + "dev-master": "2.10.x-dev", + "dev-develop": "2.11.x-dev" }, "zf": { "component": "Zend\\InputFilter", @@ -3447,7 +3450,7 @@ "inputfilter", "zf" ], - "time": "2019-01-07T17:52:18+00:00" + "time": "2019-01-30T16:58:51+00:00" }, { "name": "zendframework/zend-json", @@ -4419,16 +4422,16 @@ }, { "name": "zendframework/zend-validator", - "version": "2.11.0", + "version": "2.11.1", "source": { "type": "git", "url": "https://github.com/zendframework/zend-validator.git", - "reference": "f0789b4c4c099afdd2ecc58cc209a26c64bd4f17" + "reference": "3c28dfe4e5951ba38059cea895244d9d206190b3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-validator/zipball/f0789b4c4c099afdd2ecc58cc209a26c64bd4f17", - "reference": "f0789b4c4c099afdd2ecc58cc209a26c64bd4f17", + "url": "https://api.github.com/repos/zendframework/zend-validator/zipball/3c28dfe4e5951ba38059cea895244d9d206190b3", + "reference": "3c28dfe4e5951ba38059cea895244d9d206190b3", "shasum": "" }, "require": { @@ -4488,7 +4491,7 @@ "validator", "zf2" ], - "time": "2018-12-13T21:23:15+00:00" + "time": "2019-01-29T22:26:39+00:00" }, { "name": "zendframework/zend-view", @@ -4734,16 +4737,16 @@ }, { "name": "behat/gherkin", - "version": "v4.4.5", + "version": "v4.6.0", "source": { "type": "git", "url": "https://github.com/Behat/Gherkin.git", - "reference": "5c14cff4f955b17d20d088dec1bde61c0539ec74" + "reference": "ab0a02ea14893860bca00f225f5621d351a3ad07" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Behat/Gherkin/zipball/5c14cff4f955b17d20d088dec1bde61c0539ec74", - "reference": "5c14cff4f955b17d20d088dec1bde61c0539ec74", + "url": "https://api.github.com/repos/Behat/Gherkin/zipball/ab0a02ea14893860bca00f225f5621d351a3ad07", + "reference": "ab0a02ea14893860bca00f225f5621d351a3ad07", "shasum": "" }, "require": { @@ -4751,8 +4754,8 @@ }, "require-dev": { "phpunit/phpunit": "~4.5|~5", - "symfony/phpunit-bridge": "~2.7|~3", - "symfony/yaml": "~2.3|~3" + "symfony/phpunit-bridge": "~2.7|~3|~4", + "symfony/yaml": "~2.3|~3|~4" }, "suggest": { "symfony/yaml": "If you want to parse features, represented in YAML files" @@ -4789,35 +4792,32 @@ "gherkin", "parser" ], - "time": "2016-10-30T11:50:56+00:00" + "time": "2019-01-16T14:22:17+00:00" }, { "name": "codeception/codeception", - "version": "2.3.9", + "version": "2.4.5", "source": { "type": "git", "url": "https://github.com/Codeception/Codeception.git", - "reference": "104f46fa0bde339f1bcc3a375aac21eb36e65a1e" + "reference": "5fee32d5c82791548931cbc34806b4de6aa1abfc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Codeception/Codeception/zipball/104f46fa0bde339f1bcc3a375aac21eb36e65a1e", - "reference": "104f46fa0bde339f1bcc3a375aac21eb36e65a1e", + "url": "https://api.github.com/repos/Codeception/Codeception/zipball/5fee32d5c82791548931cbc34806b4de6aa1abfc", + "reference": "5fee32d5c82791548931cbc34806b4de6aa1abfc", "shasum": "" }, "require": { - "behat/gherkin": "~4.4.0", - "codeception/stub": "^1.0", + "behat/gherkin": "^4.4.0", + "codeception/phpunit-wrapper": "^6.0.9|^7.0.6", + "codeception/stub": "^2.0", "ext-json": "*", "ext-mbstring": "*", "facebook/webdriver": ">=1.1.3 <2.0", "guzzlehttp/guzzle": ">=4.1.4 <7.0", "guzzlehttp/psr7": "~1.0", - "php": ">=5.4.0 <8.0", - "phpunit/php-code-coverage": ">=2.2.4 <6.0", - "phpunit/phpunit": ">=4.8.28 <5.0.0 || >=5.6.3 <7.0", - "sebastian/comparator": ">1.1 <3.0", - "sebastian/diff": ">=1.4 <3.0", + "php": ">=5.6.0 <8.0", "symfony/browser-kit": ">=2.7 <5.0", "symfony/console": ">=2.7 <5.0", "symfony/css-selector": ">=2.7 <5.0", @@ -4883,26 +4883,69 @@ "functional testing", "unit testing" ], - "time": "2018-02-26T23:29:41+00:00" + "time": "2018-08-01T07:21:49+00:00" }, { - "name": "codeception/stub", - "version": "1.0.4", + "name": "codeception/phpunit-wrapper", + "version": "6.5.1", "source": { "type": "git", - "url": "https://github.com/Codeception/Stub.git", - "reference": "681b62348837a5ef07d10d8a226f5bc358cc8805" + "url": "https://github.com/Codeception/phpunit-wrapper.git", + "reference": "d78f9eb9c4300a5924cc27dee03e8c1a96fcf5f3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Codeception/Stub/zipball/681b62348837a5ef07d10d8a226f5bc358cc8805", - "reference": "681b62348837a5ef07d10d8a226f5bc358cc8805", + "url": "https://api.github.com/repos/Codeception/phpunit-wrapper/zipball/d78f9eb9c4300a5924cc27dee03e8c1a96fcf5f3", + "reference": "d78f9eb9c4300a5924cc27dee03e8c1a96fcf5f3", "shasum": "" }, "require": { - "phpunit/phpunit-mock-objects": ">2.3 <7.0" + "phpunit/php-code-coverage": ">=4.0.4 <6.0", + "phpunit/phpunit": ">=6.5.13 <7.0", + "sebastian/comparator": ">=1.2.4 <3.0", + "sebastian/diff": ">=1.4 <4.0" + }, + "replace": { + "codeception/phpunit-wrapper": "*" }, "require-dev": { + "codeception/specify": "*", + "vlucas/phpdotenv": "^2.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Codeception\\PHPUnit\\": "src\\" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Davert", + "email": "davert.php@resend.cc" + } + ], + "description": "PHPUnit classes used by Codeception", + "time": "2019-01-13T10:34:55+00:00" + }, + { + "name": "codeception/stub", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/Codeception/Stub.git", + "reference": "f50bc271f392a2836ff80690ce0c058efe1ae03e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Codeception/Stub/zipball/f50bc271f392a2836ff80690ce0c058efe1ae03e", + "reference": "f50bc271f392a2836ff80690ce0c058efe1ae03e", + "shasum": "" + }, + "require": { "phpunit/phpunit": ">=4.8 <8.0" }, "type": "library", @@ -4916,25 +4959,25 @@ "MIT" ], "description": "Flexible Stub wrapper for PHPUnit's Mock Builder", - "time": "2018-05-17T09:31:08+00:00" + "time": "2018-07-26T11:55:37+00:00" }, { "name": "consolidation/annotated-command", - "version": "2.11.0", + "version": "2.11.2", "source": { "type": "git", "url": "https://github.com/consolidation/annotated-command.git", - "reference": "edea407f57104ed518cc3c3b47d5b84403ee267a" + "reference": "004af26391cd7d1cd04b0ac736dc1324d1b4f572" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consolidation/annotated-command/zipball/edea407f57104ed518cc3c3b47d5b84403ee267a", - "reference": "edea407f57104ed518cc3c3b47d5b84403ee267a", + "url": "https://api.github.com/repos/consolidation/annotated-command/zipball/004af26391cd7d1cd04b0ac736dc1324d1b4f572", + "reference": "004af26391cd7d1cd04b0ac736dc1324d1b4f572", "shasum": "" }, "require": { "consolidation/output-formatters": "^3.4", - "php": ">=5.4.0", + "php": ">=5.4.5", "psr/log": "^1", "symfony/console": "^2.8|^3|^4", "symfony/event-dispatcher": "^2.5|^3|^4", @@ -5012,7 +5055,7 @@ } ], "description": "Initialize Symfony Console commands from annotated command class methods.", - "time": "2018-12-29T04:43:17+00:00" + "time": "2019-02-02T02:29:53+00:00" }, { "name": "consolidation/config", @@ -5216,16 +5259,16 @@ }, { "name": "consolidation/robo", - "version": "1.4.3", + "version": "1.4.4", "source": { "type": "git", "url": "https://github.com/consolidation/Robo.git", - "reference": "d0b6f516ec940add7abed4f1432d30cca5f8ae0c" + "reference": "8bec6a6ea54a7d03d56552a4250c49dec3b3083d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consolidation/Robo/zipball/d0b6f516ec940add7abed4f1432d30cca5f8ae0c", - "reference": "d0b6f516ec940add7abed4f1432d30cca5f8ae0c", + "url": "https://api.github.com/repos/consolidation/Robo/zipball/8bec6a6ea54a7d03d56552a4250c49dec3b3083d", + "reference": "8bec6a6ea54a7d03d56552a4250c49dec3b3083d", "shasum": "" }, "require": { @@ -5256,7 +5299,7 @@ "natxet/cssmin": "3.0.4", "nikic/php-parser": "^3.1.5", "patchwork/jsqueeze": "~2", - "pear/archive_tar": "^1.4.2", + "pear/archive_tar": "^1.4.4", "php-coveralls/php-coveralls": "^1", "phpunit/php-code-coverage": "~2|~4", "squizlabs/php_codesniffer": "^2.8" @@ -5320,7 +5363,7 @@ } ], "description": "Modern task runner", - "time": "2019-01-02T21:33:28+00:00" + "time": "2019-02-08T20:59:23+00:00" }, { "name": "consolidation/self-update", @@ -6548,23 +6591,24 @@ }, { "name": "magento/magento2-functional-testing-framework", - "version": "2.3.12", + "version": "2.3.13", "source": { "type": "git", "url": "https://github.com/magento/magento2-functional-testing-framework.git", - "reference": "599004be3e14ebbe6fac77de2edbab934d70f19c" + "reference": "2c8a4c3557c9a8412eb2ea50ce3f69abc2f47ba1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/magento/magento2-functional-testing-framework/zipball/599004be3e14ebbe6fac77de2edbab934d70f19c", - "reference": "599004be3e14ebbe6fac77de2edbab934d70f19c", + "url": "https://api.github.com/repos/magento/magento2-functional-testing-framework/zipball/2c8a4c3557c9a8412eb2ea50ce3f69abc2f47ba1", + "reference": "2c8a4c3557c9a8412eb2ea50ce3f69abc2f47ba1", "shasum": "" }, "require": { "allure-framework/allure-codeception": "~1.3.0", - "codeception/codeception": "~2.3.4", + "codeception/codeception": "~2.3.4 || ~2.4.0 ", "consolidation/robo": "^1.0.0", "epfremme/swagger-php": "^2.0", + "ext-curl": "*", "flow/jsonpath": ">0.2", "fzaninotto/faker": "^1.6", "monolog/monolog": "^1.0", @@ -6581,6 +6625,7 @@ "goaop/framework": "2.2.0", "php-coveralls/php-coveralls": "^1.0", "phpmd/phpmd": "^2.6.0", + "phpunit/phpunit": "~6.5.0 || ~7.0.0", "rregeer/phpunit-coverage-check": "^0.1.4", "sebastian/phpcpd": "~3.0 || ~4.0", "squizlabs/php_codesniffer": "~3.2", @@ -6615,7 +6660,7 @@ "magento", "testing" ], - "time": "2018-12-19T17:04:11+00:00" + "time": "2019-01-29T15:31:14+00:00" }, { "name": "mikey179/vfsStream", @@ -7629,16 +7674,16 @@ }, { "name": "phpunit/phpunit", - "version": "6.5.13", + "version": "6.5.14", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "0973426fb012359b2f18d3bd1e90ef1172839693" + "reference": "bac23fe7ff13dbdb461481f706f0e9fe746334b7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0973426fb012359b2f18d3bd1e90ef1172839693", - "reference": "0973426fb012359b2f18d3bd1e90ef1172839693", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/bac23fe7ff13dbdb461481f706f0e9fe746334b7", + "reference": "bac23fe7ff13dbdb461481f706f0e9fe746334b7", "shasum": "" }, "require": { @@ -7709,7 +7754,7 @@ "testing", "xunit" ], - "time": "2018-09-08T15:10:43+00:00" + "time": "2019-02-01T05:22:47+00:00" }, { "name": "phpunit/phpunit-mock-objects", @@ -8511,16 +8556,16 @@ }, { "name": "symfony/browser-kit", - "version": "v4.2.2", + "version": "v4.2.3", "source": { "type": "git", "url": "https://github.com/symfony/browser-kit.git", - "reference": "313512c878805971aebddb5d1707bcf3f4e25df7" + "reference": "ee4462581eb54bf34b746e4a5d522a4f21620160" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/browser-kit/zipball/313512c878805971aebddb5d1707bcf3f4e25df7", - "reference": "313512c878805971aebddb5d1707bcf3f4e25df7", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/ee4462581eb54bf34b746e4a5d522a4f21620160", + "reference": "ee4462581eb54bf34b746e4a5d522a4f21620160", "shasum": "" }, "require": { @@ -8564,20 +8609,20 @@ ], "description": "Symfony BrowserKit Component", "homepage": "https://symfony.com", - "time": "2019-01-03T09:07:35+00:00" + "time": "2019-01-16T21:31:25+00:00" }, { "name": "symfony/config", - "version": "v4.2.2", + "version": "v4.2.3", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "a7a7d0a0244cfc82f040729ccf769e6cf55a78fb" + "reference": "25a2e7abe0d97e70282537292e3df45cf6da7b98" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/a7a7d0a0244cfc82f040729ccf769e6cf55a78fb", - "reference": "a7a7d0a0244cfc82f040729ccf769e6cf55a78fb", + "url": "https://api.github.com/repos/symfony/config/zipball/25a2e7abe0d97e70282537292e3df45cf6da7b98", + "reference": "25a2e7abe0d97e70282537292e3df45cf6da7b98", "shasum": "" }, "require": { @@ -8627,7 +8672,7 @@ ], "description": "Symfony Config Component", "homepage": "https://symfony.com", - "time": "2019-01-03T09:07:35+00:00" + "time": "2019-01-30T11:44:30+00:00" }, { "name": "symfony/contracts", @@ -8699,16 +8744,16 @@ }, { "name": "symfony/dependency-injection", - "version": "v4.2.2", + "version": "v4.2.3", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "a28dda9df1d5494367454cad91e44751ac53921c" + "reference": "72c14cbc0c27706b9b4c33b9cd7a280972ff4806" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/a28dda9df1d5494367454cad91e44751ac53921c", - "reference": "a28dda9df1d5494367454cad91e44751ac53921c", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/72c14cbc0c27706b9b4c33b9cd7a280972ff4806", + "reference": "72c14cbc0c27706b9b4c33b9cd7a280972ff4806", "shasum": "" }, "require": { @@ -8768,20 +8813,20 @@ ], "description": "Symfony DependencyInjection Component", "homepage": "https://symfony.com", - "time": "2019-01-05T16:37:49+00:00" + "time": "2019-01-30T17:51:38+00:00" }, { "name": "symfony/dom-crawler", - "version": "v4.2.2", + "version": "v4.2.3", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "8dc06251d5ad98d8494e1f742bec9cfdb9e42044" + "reference": "d8476760b04cdf7b499c8718aa437c20a9155103" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/8dc06251d5ad98d8494e1f742bec9cfdb9e42044", - "reference": "8dc06251d5ad98d8494e1f742bec9cfdb9e42044", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/d8476760b04cdf7b499c8718aa437c20a9155103", + "reference": "d8476760b04cdf7b499c8718aa437c20a9155103", "shasum": "" }, "require": { @@ -8825,20 +8870,20 @@ ], "description": "Symfony DomCrawler Component", "homepage": "https://symfony.com", - "time": "2019-01-03T09:07:35+00:00" + "time": "2019-01-16T20:35:37+00:00" }, { "name": "symfony/http-foundation", - "version": "v4.2.2", + "version": "v4.2.3", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "a633d422a09242064ba24e44a6e1494c5126de86" + "reference": "8d2318b73e0a1bc75baa699d00ebe2ae8b595a39" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/a633d422a09242064ba24e44a6e1494c5126de86", - "reference": "a633d422a09242064ba24e44a6e1494c5126de86", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/8d2318b73e0a1bc75baa699d00ebe2ae8b595a39", + "reference": "8d2318b73e0a1bc75baa699d00ebe2ae8b595a39", "shasum": "" }, "require": { @@ -8879,20 +8924,20 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2019-01-05T16:37:49+00:00" + "time": "2019-01-29T09:49:29+00:00" }, { "name": "symfony/options-resolver", - "version": "v4.2.2", + "version": "v4.2.3", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "fbcb106aeee72f3450298bf73324d2cc00d083d1" + "reference": "831b272963a8aa5a0613a1a7f013322d8161bbbb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/fbcb106aeee72f3450298bf73324d2cc00d083d1", - "reference": "fbcb106aeee72f3450298bf73324d2cc00d083d1", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/831b272963a8aa5a0613a1a7f013322d8161bbbb", + "reference": "831b272963a8aa5a0613a1a7f013322d8161bbbb", "shasum": "" }, "require": { @@ -8933,7 +8978,7 @@ "configuration", "options" ], - "time": "2019-01-03T09:07:35+00:00" + "time": "2019-01-16T21:31:25+00:00" }, { "name": "symfony/polyfill-php70", @@ -9051,16 +9096,16 @@ }, { "name": "symfony/stopwatch", - "version": "v4.2.2", + "version": "v4.2.3", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "af62b35760fc92c8dbdce659b4eebdfe0e6a0472" + "reference": "b1a5f646d56a3290230dbc8edf2a0d62cda23f67" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/af62b35760fc92c8dbdce659b4eebdfe0e6a0472", - "reference": "af62b35760fc92c8dbdce659b4eebdfe0e6a0472", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/b1a5f646d56a3290230dbc8edf2a0d62cda23f67", + "reference": "b1a5f646d56a3290230dbc8edf2a0d62cda23f67", "shasum": "" }, "require": { @@ -9097,20 +9142,20 @@ ], "description": "Symfony Stopwatch Component", "homepage": "https://symfony.com", - "time": "2019-01-03T09:07:35+00:00" + "time": "2019-01-16T20:31:39+00:00" }, { "name": "symfony/yaml", - "version": "v3.4.21", + "version": "v3.4.22", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "554a59a1ccbaac238a89b19c8e551a556fd0e2ea" + "reference": "ba11776e9e6c15ad5759a07bffb15899bac75c2d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/554a59a1ccbaac238a89b19c8e551a556fd0e2ea", - "reference": "554a59a1ccbaac238a89b19c8e551a556fd0e2ea", + "url": "https://api.github.com/repos/symfony/yaml/zipball/ba11776e9e6c15ad5759a07bffb15899bac75c2d", + "reference": "ba11776e9e6c15ad5759a07bffb15899bac75c2d", "shasum": "" }, "require": { @@ -9156,7 +9201,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2019-01-01T13:45:19+00:00" + "time": "2019-01-16T10:59:17+00:00" }, { "name": "theseer/fdomdocument", @@ -9240,20 +9285,21 @@ }, { "name": "vlucas/phpdotenv", - "version": "v2.5.2", + "version": "v2.6.1", "source": { "type": "git", "url": "https://github.com/vlucas/phpdotenv.git", - "reference": "cfd5dc225767ca154853752abc93aeec040fcf36" + "reference": "2a7dcf7e3e02dc5e701004e51a6f304b713107d5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/cfd5dc225767ca154853752abc93aeec040fcf36", - "reference": "cfd5dc225767ca154853752abc93aeec040fcf36", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/2a7dcf7e3e02dc5e701004e51a6f304b713107d5", + "reference": "2a7dcf7e3e02dc5e701004e51a6f304b713107d5", "shasum": "" }, "require": { - "php": ">=5.3.9" + "php": ">=5.3.9", + "symfony/polyfill-ctype": "^1.9" }, "require-dev": { "phpunit/phpunit": "^4.8.35 || ^5.0" @@ -9261,7 +9307,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.5-dev" + "dev-master": "2.6-dev" } }, "autoload": { @@ -9286,7 +9332,7 @@ "env", "environment" ], - "time": "2018-10-30T17:29:25+00:00" + "time": "2019-01-29T11:11:52+00:00" }, { "name": "webmozart/assert", diff --git a/dev/tests/acceptance/composer.lock b/dev/tests/acceptance/composer.lock index 8542402a98f..5d2c2c5eb17 100644 --- a/dev/tests/acceptance/composer.lock +++ b/dev/tests/acceptance/composer.lock @@ -1,23 +1,23 @@ { "_readme": [ "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "46ca2d50566f5069daef753664080c5a", + "content-hash": "b93d599d375af66b29edfd8a35875e69", "packages": [ { "name": "behat/gherkin", - "version": "v4.4.5", + "version": "v4.6.0", "source": { "type": "git", "url": "https://github.com/Behat/Gherkin.git", - "reference": "5c14cff4f955b17d20d088dec1bde61c0539ec74" + "reference": "ab0a02ea14893860bca00f225f5621d351a3ad07" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Behat/Gherkin/zipball/5c14cff4f955b17d20d088dec1bde61c0539ec74", - "reference": "5c14cff4f955b17d20d088dec1bde61c0539ec74", + "url": "https://api.github.com/repos/Behat/Gherkin/zipball/ab0a02ea14893860bca00f225f5621d351a3ad07", + "reference": "ab0a02ea14893860bca00f225f5621d351a3ad07", "shasum": "" }, "require": { @@ -25,8 +25,8 @@ }, "require-dev": { "phpunit/phpunit": "~4.5|~5", - "symfony/phpunit-bridge": "~2.7|~3", - "symfony/yaml": "~2.3|~3" + "symfony/phpunit-bridge": "~2.7|~3|~4", + "symfony/yaml": "~2.3|~3|~4" }, "suggest": { "symfony/yaml": "If you want to parse features, represented in YAML files" @@ -63,35 +63,32 @@ "gherkin", "parser" ], - "time": "2016-10-30T11:50:56+00:00" + "time": "2019-01-16T14:22:17+00:00" }, { "name": "codeception/codeception", - "version": "2.3.9", + "version": "2.4.5", "source": { "type": "git", "url": "https://github.com/Codeception/Codeception.git", - "reference": "104f46fa0bde339f1bcc3a375aac21eb36e65a1e" + "reference": "5fee32d5c82791548931cbc34806b4de6aa1abfc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Codeception/Codeception/zipball/104f46fa0bde339f1bcc3a375aac21eb36e65a1e", - "reference": "104f46fa0bde339f1bcc3a375aac21eb36e65a1e", + "url": "https://api.github.com/repos/Codeception/Codeception/zipball/5fee32d5c82791548931cbc34806b4de6aa1abfc", + "reference": "5fee32d5c82791548931cbc34806b4de6aa1abfc", "shasum": "" }, "require": { - "behat/gherkin": "~4.4.0", - "codeception/stub": "^1.0", + "behat/gherkin": "^4.4.0", + "codeception/phpunit-wrapper": "^6.0.9|^7.0.6", + "codeception/stub": "^2.0", "ext-json": "*", "ext-mbstring": "*", "facebook/webdriver": ">=1.1.3 <2.0", "guzzlehttp/guzzle": ">=4.1.4 <7.0", "guzzlehttp/psr7": "~1.0", - "php": ">=5.4.0 <8.0", - "phpunit/php-code-coverage": ">=2.2.4 <6.0", - "phpunit/phpunit": ">=4.8.28 <5.0.0 || >=5.6.3 <7.0", - "sebastian/comparator": ">1.1 <3.0", - "sebastian/diff": ">=1.4 <3.0", + "php": ">=5.6.0 <8.0", "symfony/browser-kit": ">=2.7 <5.0", "symfony/console": ">=2.7 <5.0", "symfony/css-selector": ">=2.7 <5.0", @@ -157,26 +154,66 @@ "functional testing", "unit testing" ], - "time": "2018-02-26T23:29:41+00:00" + "time": "2018-08-01T07:21:49+00:00" }, { - "name": "codeception/stub", - "version": "1.0.4", + "name": "codeception/phpunit-wrapper", + "version": "7.6.1", "source": { "type": "git", - "url": "https://github.com/Codeception/Stub.git", - "reference": "681b62348837a5ef07d10d8a226f5bc358cc8805" + "url": "https://github.com/Codeception/phpunit-wrapper.git", + "reference": "ed4b12beb167dc2ecea293b4f6df6c20ce8d280f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Codeception/Stub/zipball/681b62348837a5ef07d10d8a226f5bc358cc8805", - "reference": "681b62348837a5ef07d10d8a226f5bc358cc8805", + "url": "https://api.github.com/repos/Codeception/phpunit-wrapper/zipball/ed4b12beb167dc2ecea293b4f6df6c20ce8d280f", + "reference": "ed4b12beb167dc2ecea293b4f6df6c20ce8d280f", "shasum": "" }, "require": { - "phpunit/phpunit-mock-objects": ">2.3 <7.0" + "phpunit/php-code-coverage": "^6.0", + "phpunit/phpunit": ">=7.1 <7.6", + "sebastian/comparator": "^3.0", + "sebastian/diff": "^3.0" }, "require-dev": { + "codeception/specify": "*", + "vlucas/phpdotenv": "^2.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Codeception\\PHPUnit\\": "src\\" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Davert", + "email": "davert.php@resend.cc" + } + ], + "description": "PHPUnit classes used by Codeception", + "time": "2019-01-13T10:34:39+00:00" + }, + { + "name": "codeception/stub", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/Codeception/Stub.git", + "reference": "f50bc271f392a2836ff80690ce0c058efe1ae03e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Codeception/Stub/zipball/f50bc271f392a2836ff80690ce0c058efe1ae03e", + "reference": "f50bc271f392a2836ff80690ce0c058efe1ae03e", + "shasum": "" + }, + "require": { "phpunit/phpunit": ">=4.8 <8.0" }, "type": "library", @@ -190,38 +227,82 @@ "MIT" ], "description": "Flexible Stub wrapper for PHPUnit's Mock Builder", - "time": "2018-05-17T09:31:08+00:00" + "time": "2018-07-26T11:55:37+00:00" }, { "name": "consolidation/annotated-command", - "version": "2.8.4", + "version": "2.11.2", "source": { "type": "git", "url": "https://github.com/consolidation/annotated-command.git", - "reference": "651541a0b68318a2a202bda558a676e5ad92223c" + "reference": "004af26391cd7d1cd04b0ac736dc1324d1b4f572" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consolidation/annotated-command/zipball/651541a0b68318a2a202bda558a676e5ad92223c", - "reference": "651541a0b68318a2a202bda558a676e5ad92223c", + "url": "https://api.github.com/repos/consolidation/annotated-command/zipball/004af26391cd7d1cd04b0ac736dc1324d1b4f572", + "reference": "004af26391cd7d1cd04b0ac736dc1324d1b4f572", "shasum": "" }, "require": { - "consolidation/output-formatters": "^3.1.12", - "php": ">=5.4.0", + "consolidation/output-formatters": "^3.4", + "php": ">=5.4.5", "psr/log": "^1", "symfony/console": "^2.8|^3|^4", "symfony/event-dispatcher": "^2.5|^3|^4", "symfony/finder": "^2.5|^3|^4" }, "require-dev": { - "g1a/composer-test-scenarios": "^2", + "g1a/composer-test-scenarios": "^3", + "php-coveralls/php-coveralls": "^1", "phpunit/phpunit": "^6", - "satooshi/php-coveralls": "^2", "squizlabs/php_codesniffer": "^2.7" }, "type": "library", "extra": { + "scenarios": { + "symfony4": { + "require": { + "symfony/console": "^4.0" + }, + "config": { + "platform": { + "php": "7.1.3" + } + } + }, + "symfony2": { + "require": { + "symfony/console": "^2.8" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36" + }, + "remove": [ + "php-coveralls/php-coveralls" + ], + "config": { + "platform": { + "php": "5.4.8" + } + }, + "scenario-options": { + "create-lockfile": "false" + } + }, + "phpunit4": { + "require-dev": { + "phpunit/phpunit": "^4.8.36" + }, + "remove": [ + "php-coveralls/php-coveralls" + ], + "config": { + "platform": { + "php": "5.4.8" + } + } + } + }, "branch-alias": { "dev-master": "2.x-dev" } @@ -242,20 +323,20 @@ } ], "description": "Initialize Symfony Console commands from annotated command class methods.", - "time": "2018-05-25T18:04:25+00:00" + "time": "2019-02-02T02:29:53+00:00" }, { "name": "consolidation/config", - "version": "1.0.11", + "version": "1.1.1", "source": { "type": "git", "url": "https://github.com/consolidation/config.git", - "reference": "ede41d946078e97e7a9513aadc3352f1c26817af" + "reference": "925231dfff32f05b787e1fddb265e789b939cf4c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consolidation/config/zipball/ede41d946078e97e7a9513aadc3352f1c26817af", - "reference": "ede41d946078e97e7a9513aadc3352f1c26817af", + "url": "https://api.github.com/repos/consolidation/config/zipball/925231dfff32f05b787e1fddb265e789b939cf4c", + "reference": "925231dfff32f05b787e1fddb265e789b939cf4c", "shasum": "" }, "require": { @@ -265,7 +346,7 @@ }, "require-dev": { "g1a/composer-test-scenarios": "^1", - "phpunit/phpunit": "^4", + "phpunit/phpunit": "^5", "satooshi/php-coveralls": "^1.0", "squizlabs/php_codesniffer": "2.*", "symfony/console": "^2.5|^3|^4", @@ -296,35 +377,76 @@ } ], "description": "Provide configuration services for a commandline tool.", - "time": "2018-05-27T01:17:02+00:00" + "time": "2018-10-24T17:55:35+00:00" }, { "name": "consolidation/log", - "version": "1.0.6", + "version": "1.1.1", "source": { "type": "git", "url": "https://github.com/consolidation/log.git", - "reference": "dfd8189a771fe047bf3cd669111b2de5f1c79395" + "reference": "b2e887325ee90abc96b0a8b7b474cd9e7c896e3a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consolidation/log/zipball/dfd8189a771fe047bf3cd669111b2de5f1c79395", - "reference": "dfd8189a771fe047bf3cd669111b2de5f1c79395", + "url": "https://api.github.com/repos/consolidation/log/zipball/b2e887325ee90abc96b0a8b7b474cd9e7c896e3a", + "reference": "b2e887325ee90abc96b0a8b7b474cd9e7c896e3a", "shasum": "" }, "require": { - "php": ">=5.5.0", - "psr/log": "~1.0", + "php": ">=5.4.5", + "psr/log": "^1.0", "symfony/console": "^2.8|^3|^4" }, "require-dev": { - "g1a/composer-test-scenarios": "^1", - "phpunit/phpunit": "4.*", - "satooshi/php-coveralls": "^2", - "squizlabs/php_codesniffer": "2.*" + "g1a/composer-test-scenarios": "^3", + "php-coveralls/php-coveralls": "^1", + "phpunit/phpunit": "^6", + "squizlabs/php_codesniffer": "^2" }, "type": "library", "extra": { + "scenarios": { + "symfony4": { + "require": { + "symfony/console": "^4.0" + }, + "config": { + "platform": { + "php": "7.1.3" + } + } + }, + "symfony2": { + "require": { + "symfony/console": "^2.8" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36" + }, + "remove": [ + "php-coveralls/php-coveralls" + ], + "config": { + "platform": { + "php": "5.4.8" + } + } + }, + "phpunit4": { + "require-dev": { + "phpunit/phpunit": "^4.8.36" + }, + "remove": [ + "php-coveralls/php-coveralls" + ], + "config": { + "platform": { + "php": "5.4.8" + } + } + } + }, "branch-alias": { "dev-master": "1.x-dev" } @@ -345,23 +467,24 @@ } ], "description": "Improved Psr-3 / Psr\\Log logger based on Symfony Console components.", - "time": "2018-05-25T18:14:39+00:00" + "time": "2019-01-01T17:30:51+00:00" }, { "name": "consolidation/output-formatters", - "version": "3.2.1", + "version": "3.4.0", "source": { "type": "git", "url": "https://github.com/consolidation/output-formatters.git", - "reference": "d78ef59aea19d3e2e5a23f90a055155ee78a0ad5" + "reference": "a942680232094c4a5b21c0b7e54c20cce623ae19" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consolidation/output-formatters/zipball/d78ef59aea19d3e2e5a23f90a055155ee78a0ad5", - "reference": "d78ef59aea19d3e2e5a23f90a055155ee78a0ad5", + "url": "https://api.github.com/repos/consolidation/output-formatters/zipball/a942680232094c4a5b21c0b7e54c20cce623ae19", + "reference": "a942680232094c4a5b21c0b7e54c20cce623ae19", "shasum": "" }, "require": { + "dflydev/dot-access-data": "^1.1.0", "php": ">=5.4.0", "symfony/console": "^2.8|^3|^4", "symfony/finder": "^2.5|^3|^4" @@ -400,27 +523,28 @@ } ], "description": "Format text by applying transformations provided by plug-in formatters.", - "time": "2018-05-25T18:02:34+00:00" + "time": "2018-10-19T22:35:38+00:00" }, { "name": "consolidation/robo", - "version": "1.3.0", + "version": "1.4.4", "source": { "type": "git", "url": "https://github.com/consolidation/Robo.git", - "reference": "ac563abfadf7cb7314b4e152f2b5033a6c255f6f" + "reference": "8bec6a6ea54a7d03d56552a4250c49dec3b3083d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consolidation/Robo/zipball/ac563abfadf7cb7314b4e152f2b5033a6c255f6f", - "reference": "ac563abfadf7cb7314b4e152f2b5033a6c255f6f", + "url": "https://api.github.com/repos/consolidation/Robo/zipball/8bec6a6ea54a7d03d56552a4250c49dec3b3083d", + "reference": "8bec6a6ea54a7d03d56552a4250c49dec3b3083d", "shasum": "" }, "require": { - "consolidation/annotated-command": "^2.8.2", + "consolidation/annotated-command": "^2.10.2", "consolidation/config": "^1.0.10", "consolidation/log": "~1", "consolidation/output-formatters": "^3.1.13", + "consolidation/self-update": "^1", "grasmash/yaml-expander": "^1.3", "league/container": "^2.2", "php": ">=5.5.0", @@ -437,15 +561,15 @@ "codeception/aspect-mock": "^1|^2.1.1", "codeception/base": "^2.3.7", "codeception/verify": "^0.3.2", - "g1a/composer-test-scenarios": "^2", + "g1a/composer-test-scenarios": "^3", "goaop/framework": "~2.1.2", "goaop/parser-reflection": "^1.1.0", "natxet/cssmin": "3.0.4", "nikic/php-parser": "^3.1.5", "patchwork/jsqueeze": "~2", - "pear/archive_tar": "^1.4.2", + "pear/archive_tar": "^1.4.4", + "php-coveralls/php-coveralls": "^1", "phpunit/php-code-coverage": "~2|~4", - "satooshi/php-coveralls": "^2", "squizlabs/php_codesniffer": "^2.8" }, "suggest": { @@ -459,9 +583,36 @@ ], "type": "library", "extra": { + "scenarios": { + "symfony4": { + "require": { + "symfony/console": "^4" + }, + "config": { + "platform": { + "php": "7.1.3" + } + } + }, + "symfony2": { + "require": { + "symfony/console": "^2.8" + }, + "remove": [ + "goaop/framework" + ], + "config": { + "platform": { + "php": "5.5.9" + } + }, + "scenario-options": { + "create-lockfile": "false" + } + } + }, "branch-alias": { - "dev-master": "1.x-dev", - "dev-state": "1.x-dev" + "dev-master": "2.x-dev" } }, "autoload": { @@ -480,7 +631,57 @@ } ], "description": "Modern task runner", - "time": "2018-05-27T01:42:53+00:00" + "time": "2019-02-08T20:59:23+00:00" + }, + { + "name": "consolidation/self-update", + "version": "1.1.5", + "source": { + "type": "git", + "url": "https://github.com/consolidation/self-update.git", + "reference": "a1c273b14ce334789825a09d06d4c87c0a02ad54" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/consolidation/self-update/zipball/a1c273b14ce334789825a09d06d4c87c0a02ad54", + "reference": "a1c273b14ce334789825a09d06d4c87c0a02ad54", + "shasum": "" + }, + "require": { + "php": ">=5.5.0", + "symfony/console": "^2.8|^3|^4", + "symfony/filesystem": "^2.5|^3|^4" + }, + "bin": [ + "scripts/release" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "SelfUpdate\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + }, + { + "name": "Alexander Menk", + "email": "menk@mestrona.net" + } + ], + "description": "Provides a self:update command for Symfony Console applications.", + "time": "2018-10-28T01:52:03+00:00" }, { "name": "container-interop/container-interop", @@ -899,32 +1100,33 @@ }, { "name": "guzzlehttp/psr7", - "version": "1.4.2", + "version": "1.5.2", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c" + "reference": "9f83dded91781a01c63574e387eaa769be769115" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/f5b8a8512e2b58b0071a7280e39f14f72e05d87c", - "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/9f83dded91781a01c63574e387eaa769be769115", + "reference": "9f83dded91781a01c63574e387eaa769be769115", "shasum": "" }, "require": { "php": ">=5.4.0", - "psr/http-message": "~1.0" + "psr/http-message": "~1.0", + "ralouphie/getallheaders": "^2.0.5" }, "provide": { "psr/http-message-implementation": "1.0" }, "require-dev": { - "phpunit/phpunit": "~4.0" + "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4-dev" + "dev-master": "1.5-dev" } }, "autoload": { @@ -954,13 +1156,14 @@ "keywords": [ "http", "message", + "psr-7", "request", "response", "stream", "uri", "url" ], - "time": "2017-03-20T17:10:46+00:00" + "time": "2018-12-04T20:46:45+00:00" }, { "name": "league/container", @@ -1077,22 +1280,22 @@ }, { "name": "phar-io/manifest", - "version": "1.0.1", + "version": "1.0.3", "source": { "type": "git", "url": "https://github.com/phar-io/manifest.git", - "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0" + "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/2df402786ab5368a0169091f61a7c1e0eb6852d0", - "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", + "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", "shasum": "" }, "require": { "ext-dom": "*", "ext-phar": "*", - "phar-io/version": "^1.0.1", + "phar-io/version": "^2.0", "php": "^5.6 || ^7.0" }, "type": "library", @@ -1128,20 +1331,20 @@ } ], "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", - "time": "2017-03-05T18:14:27+00:00" + "time": "2018-07-08T19:23:20+00:00" }, { "name": "phar-io/version", - "version": "1.0.1", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/phar-io/version.git", - "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df" + "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/version/zipball/a70c0ced4be299a63d32fa96d9281d03e94041df", - "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df", + "url": "https://api.github.com/repos/phar-io/version/zipball/45a2ec53a73c70ce41d55cedef9063630abaf1b6", + "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6", "shasum": "" }, "require": { @@ -1175,7 +1378,7 @@ } ], "description": "Library for handling version information and constraints", - "time": "2017-03-05T17:38:23+00:00" + "time": "2018-07-08T19:19:57+00:00" }, { "name": "phpdocumentor/reflection-common", @@ -1331,16 +1534,16 @@ }, { "name": "phpspec/prophecy", - "version": "1.7.6", + "version": "1.8.0", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "33a7e3c4fda54e912ff6338c48823bd5c0f0b712" + "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/33a7e3c4fda54e912ff6338c48823bd5c0f0b712", - "reference": "33a7e3c4fda54e912ff6338c48823bd5c0f0b712", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4ba436b55987b4bf311cb7c6ba82aa528aac0a06", + "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06", "shasum": "" }, "require": { @@ -1352,12 +1555,12 @@ }, "require-dev": { "phpspec/phpspec": "^2.5|^3.2", - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5" + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.7.x-dev" + "dev-master": "1.8.x-dev" } }, "autoload": { @@ -1390,44 +1593,44 @@ "spy", "stub" ], - "time": "2018-04-18T13:57:24+00:00" + "time": "2018-08-05T17:53:17+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "5.3.2", + "version": "6.1.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "c89677919c5dd6d3b3852f230a663118762218ac" + "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/c89677919c5dd6d3b3852f230a663118762218ac", - "reference": "c89677919c5dd6d3b3852f230a663118762218ac", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/807e6013b00af69b6c5d9ceb4282d0393dbb9d8d", + "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d", "shasum": "" }, "require": { "ext-dom": "*", "ext-xmlwriter": "*", - "php": "^7.0", - "phpunit/php-file-iterator": "^1.4.2", + "php": "^7.1", + "phpunit/php-file-iterator": "^2.0", "phpunit/php-text-template": "^1.2.1", - "phpunit/php-token-stream": "^2.0.1", + "phpunit/php-token-stream": "^3.0", "sebastian/code-unit-reverse-lookup": "^1.0.1", - "sebastian/environment": "^3.0", + "sebastian/environment": "^3.1 || ^4.0", "sebastian/version": "^2.0.1", "theseer/tokenizer": "^1.1" }, "require-dev": { - "phpunit/phpunit": "^6.0" + "phpunit/phpunit": "^7.0" }, "suggest": { - "ext-xdebug": "^2.5.5" + "ext-xdebug": "^2.6.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.3.x-dev" + "dev-master": "6.1-dev" } }, "autoload": { @@ -1453,29 +1656,32 @@ "testing", "xunit" ], - "time": "2018-04-06T15:36:58+00:00" + "time": "2018-10-31T16:06:48+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "1.4.5", + "version": "2.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4" + "reference": "050bedf145a257b1ff02746c31894800e5122946" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4", - "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/050bedf145a257b1ff02746c31894800e5122946", + "reference": "050bedf145a257b1ff02746c31894800e5122946", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": "^7.1" + }, + "require-dev": { + "phpunit/phpunit": "^7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { @@ -1490,7 +1696,7 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", + "email": "sebastian@phpunit.de", "role": "lead" } ], @@ -1500,7 +1706,7 @@ "filesystem", "iterator" ], - "time": "2017-11-27T13:52:08+00:00" + "time": "2018-09-13T20:33:42+00:00" }, { "name": "phpunit/php-text-template", @@ -1545,28 +1751,28 @@ }, { "name": "phpunit/php-timer", - "version": "1.0.9", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" + "reference": "8b8454ea6958c3dee38453d3bd571e023108c91f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", - "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/8b8454ea6958c3dee38453d3bd571e023108c91f", + "reference": "8b8454ea6958c3dee38453d3bd571e023108c91f", "shasum": "" }, "require": { - "php": "^5.3.3 || ^7.0" + "php": "^7.1" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + "phpunit/phpunit": "^7.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -1581,7 +1787,7 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", + "email": "sebastian@phpunit.de", "role": "lead" } ], @@ -1590,33 +1796,33 @@ "keywords": [ "timer" ], - "time": "2017-02-26T11:10:40+00:00" + "time": "2018-02-01T13:07:23+00:00" }, { "name": "phpunit/php-token-stream", - "version": "2.0.2", + "version": "3.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "791198a2c6254db10131eecfe8c06670700904db" + "reference": "c99e3be9d3e85f60646f152f9002d46ed7770d18" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/791198a2c6254db10131eecfe8c06670700904db", - "reference": "791198a2c6254db10131eecfe8c06670700904db", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/c99e3be9d3e85f60646f152f9002d46ed7770d18", + "reference": "c99e3be9d3e85f60646f152f9002d46ed7770d18", "shasum": "" }, "require": { "ext-tokenizer": "*", - "php": "^7.0" + "php": "^7.1" }, "require-dev": { - "phpunit/phpunit": "^6.2.4" + "phpunit/phpunit": "^7.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -1639,57 +1845,57 @@ "keywords": [ "tokenizer" ], - "time": "2017-11-27T05:48:46+00:00" + "time": "2018-10-30T05:52:18+00:00" }, { "name": "phpunit/phpunit", - "version": "6.5.9", + "version": "7.5.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "093ca5508174cd8ab8efe44fd1dde447adfdec8f" + "reference": "2896657da5fb237bc316bdfc18c2650efeee0dc0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/093ca5508174cd8ab8efe44fd1dde447adfdec8f", - "reference": "093ca5508174cd8ab8efe44fd1dde447adfdec8f", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/2896657da5fb237bc316bdfc18c2650efeee0dc0", + "reference": "2896657da5fb237bc316bdfc18c2650efeee0dc0", "shasum": "" }, "require": { + "doctrine/instantiator": "^1.1", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", "ext-mbstring": "*", "ext-xml": "*", - "myclabs/deep-copy": "^1.6.1", - "phar-io/manifest": "^1.0.1", - "phar-io/version": "^1.0", - "php": "^7.0", + "myclabs/deep-copy": "^1.7", + "phar-io/manifest": "^1.0.2", + "phar-io/version": "^2.0", + "php": "^7.1", "phpspec/prophecy": "^1.7", - "phpunit/php-code-coverage": "^5.3", - "phpunit/php-file-iterator": "^1.4.3", + "phpunit/php-code-coverage": "^6.0.7", + "phpunit/php-file-iterator": "^2.0.1", "phpunit/php-text-template": "^1.2.1", - "phpunit/php-timer": "^1.0.9", - "phpunit/phpunit-mock-objects": "^5.0.5", - "sebastian/comparator": "^2.1", - "sebastian/diff": "^2.0", - "sebastian/environment": "^3.1", + "phpunit/php-timer": "^2.0", + "sebastian/comparator": "^3.0", + "sebastian/diff": "^3.0", + "sebastian/environment": "^4.0", "sebastian/exporter": "^3.1", "sebastian/global-state": "^2.0", "sebastian/object-enumerator": "^3.0.3", - "sebastian/resource-operations": "^1.0", + "sebastian/resource-operations": "^2.0", "sebastian/version": "^2.0.1" }, "conflict": { - "phpdocumentor/reflection-docblock": "3.0.2", - "phpunit/dbunit": "<3.0" + "phpunit/phpunit-mock-objects": "*" }, "require-dev": { "ext-pdo": "*" }, "suggest": { + "ext-soap": "*", "ext-xdebug": "*", - "phpunit/php-invoker": "^1.1" + "phpunit/php-invoker": "^2.0" }, "bin": [ "phpunit" @@ -1697,7 +1903,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "6.5.x-dev" + "dev-master": "7.5-dev" } }, "autoload": { @@ -1723,66 +1929,7 @@ "testing", "xunit" ], - "time": "2018-07-03T06:40:40+00:00" - }, - { - "name": "phpunit/phpunit-mock-objects", - "version": "5.0.8", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", - "reference": "6f9a3c8bf34188a2b53ce2ae7a126089c53e0a9f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/6f9a3c8bf34188a2b53ce2ae7a126089c53e0a9f", - "reference": "6f9a3c8bf34188a2b53ce2ae7a126089c53e0a9f", - "shasum": "" - }, - "require": { - "doctrine/instantiator": "^1.0.5", - "php": "^7.0", - "phpunit/php-text-template": "^1.2.1", - "sebastian/exporter": "^3.1" - }, - "conflict": { - "phpunit/phpunit": "<6.0" - }, - "require-dev": { - "phpunit/phpunit": "^6.5" - }, - "suggest": { - "ext-soap": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Mock Object library for PHPUnit", - "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", - "keywords": [ - "mock", - "xunit" - ], - "time": "2018-07-13T03:27:23+00:00" + "time": "2019-02-07T14:15:04+00:00" }, { "name": "psr/container", @@ -1885,16 +2032,16 @@ }, { "name": "psr/log", - "version": "1.0.2", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" + "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", - "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "url": "https://api.github.com/repos/php-fig/log/zipball/6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", + "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", "shasum": "" }, "require": { @@ -1928,7 +2075,47 @@ "psr", "psr-3" ], - "time": "2016-10-10T12:19:37+00:00" + "time": "2018-11-20T15:27:04+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "2.0.5", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "5601c8a83fbba7ef674a7369456d12f1e0d0eafa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/5601c8a83fbba7ef674a7369456d12f1e0d0eafa", + "reference": "5601c8a83fbba7ef674a7369456d12f1e0d0eafa", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "require-dev": { + "phpunit/phpunit": "~3.7.0", + "satooshi/php-coveralls": ">=1.0" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "time": "2016-02-11T07:05:27+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", @@ -1977,30 +2164,30 @@ }, { "name": "sebastian/comparator", - "version": "2.1.3", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9" + "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/34369daee48eafb2651bea869b4b15d75ccc35f9", - "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/5de4fc177adf9bce8df98d8d141a7559d7ccf6da", + "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da", "shasum": "" }, "require": { - "php": "^7.0", - "sebastian/diff": "^2.0 || ^3.0", + "php": "^7.1", + "sebastian/diff": "^3.0", "sebastian/exporter": "^3.1" }, "require-dev": { - "phpunit/phpunit": "^6.4" + "phpunit/phpunit": "^7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.1.x-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -2037,32 +2224,33 @@ "compare", "equality" ], - "time": "2018-02-01T13:46:46+00:00" + "time": "2018-07-12T15:12:46+00:00" }, { "name": "sebastian/diff", - "version": "2.0.1", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd" + "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/347c1d8b49c5c3ee30c7040ea6fc446790e6bddd", - "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/720fcc7e9b5cf384ea68d9d930d480907a0c1a29", + "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29", "shasum": "" }, "require": { - "php": "^7.0" + "php": "^7.1" }, "require-dev": { - "phpunit/phpunit": "^6.2" + "phpunit/phpunit": "^7.5 || ^8.0", + "symfony/process": "^2 || ^3.3 || ^4" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "3.0-dev" } }, "autoload": { @@ -2087,34 +2275,40 @@ "description": "Diff implementation", "homepage": "https://github.com/sebastianbergmann/diff", "keywords": [ - "diff" + "diff", + "udiff", + "unidiff", + "unified diff" ], - "time": "2017-08-03T08:09:46+00:00" + "time": "2019-02-04T06:01:07+00:00" }, { "name": "sebastian/environment", - "version": "3.1.0", + "version": "4.1.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5" + "reference": "6fda8ce1974b62b14935adc02a9ed38252eca656" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/cd0871b3975fb7fc44d11314fd1ee20925fce4f5", - "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/6fda8ce1974b62b14935adc02a9ed38252eca656", + "reference": "6fda8ce1974b62b14935adc02a9ed38252eca656", "shasum": "" }, "require": { - "php": "^7.0" + "php": "^7.1" }, "require-dev": { - "phpunit/phpunit": "^6.1" + "phpunit/phpunit": "^7.5" + }, + "suggest": { + "ext-posix": "*" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1.x-dev" + "dev-master": "4.1-dev" } }, "autoload": { @@ -2139,7 +2333,7 @@ "environment", "hhvm" ], - "time": "2017-07-01T08:51:00+00:00" + "time": "2019-02-01T05:27:49+00:00" }, { "name": "sebastian/exporter", @@ -2406,25 +2600,25 @@ }, { "name": "sebastian/resource-operations", - "version": "1.0.0", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" + "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", - "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/4d7a795d35b889bf80a0cc04e08d77cedfa917a9", + "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9", "shasum": "" }, "require": { - "php": ">=5.6.0" + "php": "^7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "2.0-dev" } }, "autoload": { @@ -2444,7 +2638,7 @@ ], "description": "Provides a list of PHP built-in functions that operate on resources", "homepage": "https://www.github.com/sebastianbergmann/resource-operations", - "time": "2015-07-28T20:34:47+00:00" + "time": "2018-10-04T04:07:39+00:00" }, { "name": "sebastian/version", @@ -2491,16 +2685,16 @@ }, { "name": "symfony/browser-kit", - "version": "v4.1.2", + "version": "v4.2.3", "source": { "type": "git", "url": "https://github.com/symfony/browser-kit.git", - "reference": "ff9ac5d5808a530b2e7f6abcf3a2412d4f9bcd62" + "reference": "ee4462581eb54bf34b746e4a5d522a4f21620160" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/browser-kit/zipball/ff9ac5d5808a530b2e7f6abcf3a2412d4f9bcd62", - "reference": "ff9ac5d5808a530b2e7f6abcf3a2412d4f9bcd62", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/ee4462581eb54bf34b746e4a5d522a4f21620160", + "reference": "ee4462581eb54bf34b746e4a5d522a4f21620160", "shasum": "" }, "require": { @@ -2517,7 +2711,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } }, "autoload": { @@ -2544,30 +2738,34 @@ ], "description": "Symfony BrowserKit Component", "homepage": "https://symfony.com", - "time": "2018-06-04T17:31:56+00:00" + "time": "2019-01-16T21:31:25+00:00" }, { "name": "symfony/console", - "version": "v4.1.2", + "version": "v4.2.3", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "5c31f6a97c1c240707f6d786e7e59bfacdbc0219" + "reference": "1f0ad51dfde4da8a6070f06adc58b4e37cbb37a4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/5c31f6a97c1c240707f6d786e7e59bfacdbc0219", - "reference": "5c31f6a97c1c240707f6d786e7e59bfacdbc0219", + "url": "https://api.github.com/repos/symfony/console/zipball/1f0ad51dfde4da8a6070f06adc58b4e37cbb37a4", + "reference": "1f0ad51dfde4da8a6070f06adc58b4e37cbb37a4", "shasum": "" }, "require": { "php": "^7.1.3", + "symfony/contracts": "^1.0", "symfony/polyfill-mbstring": "~1.0" }, "conflict": { "symfony/dependency-injection": "<3.4", "symfony/process": "<3.3" }, + "provide": { + "psr/log-implementation": "1.0" + }, "require-dev": { "psr/log": "~1.0", "symfony/config": "~3.4|~4.0", @@ -2577,7 +2775,7 @@ "symfony/process": "~3.4|~4.0" }, "suggest": { - "psr/log-implementation": "For using the console logger", + "psr/log": "For using the console logger", "symfony/event-dispatcher": "", "symfony/lock": "", "symfony/process": "" @@ -2585,7 +2783,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } }, "autoload": { @@ -2612,20 +2810,88 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2018-07-16T14:05:40+00:00" + "time": "2019-01-25T14:35:16+00:00" + }, + { + "name": "symfony/contracts", + "version": "v1.0.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/contracts.git", + "reference": "1aa7ab2429c3d594dd70689604b5cf7421254cdf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/contracts/zipball/1aa7ab2429c3d594dd70689604b5cf7421254cdf", + "reference": "1aa7ab2429c3d594dd70689604b5cf7421254cdf", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "require-dev": { + "psr/cache": "^1.0", + "psr/container": "^1.0" + }, + "suggest": { + "psr/cache": "When using the Cache contracts", + "psr/container": "When using the Service contracts", + "symfony/cache-contracts-implementation": "", + "symfony/service-contracts-implementation": "", + "symfony/translation-contracts-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\": "" + }, + "exclude-from-classmap": [ + "**/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A set of abstractions extracted out of the Symfony components", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "time": "2018-12-05T08:06:11+00:00" }, { "name": "symfony/css-selector", - "version": "v4.1.2", + "version": "v4.2.3", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "03ac71606ecb0b0ce792faa17d74cc32c2949ef4" + "reference": "48eddf66950fa57996e1be4a55916d65c10c604a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/03ac71606ecb0b0ce792faa17d74cc32c2949ef4", - "reference": "03ac71606ecb0b0ce792faa17d74cc32c2949ef4", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/48eddf66950fa57996e1be4a55916d65c10c604a", + "reference": "48eddf66950fa57996e1be4a55916d65c10c604a", "shasum": "" }, "require": { @@ -2634,7 +2900,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } }, "autoload": { @@ -2665,20 +2931,20 @@ ], "description": "Symfony CssSelector Component", "homepage": "https://symfony.com", - "time": "2018-05-30T07:26:09+00:00" + "time": "2019-01-16T20:31:39+00:00" }, { "name": "symfony/dom-crawler", - "version": "v4.1.2", + "version": "v4.2.3", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "eb501fa8aab8c8e2db790d8d0f945697769f6c41" + "reference": "d8476760b04cdf7b499c8718aa437c20a9155103" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/eb501fa8aab8c8e2db790d8d0f945697769f6c41", - "reference": "eb501fa8aab8c8e2db790d8d0f945697769f6c41", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/d8476760b04cdf7b499c8718aa437c20a9155103", + "reference": "d8476760b04cdf7b499c8718aa437c20a9155103", "shasum": "" }, "require": { @@ -2695,7 +2961,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } }, "autoload": { @@ -2722,24 +2988,25 @@ ], "description": "Symfony DomCrawler Component", "homepage": "https://symfony.com", - "time": "2018-07-05T11:54:23+00:00" + "time": "2019-01-16T20:35:37+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v4.1.2", + "version": "v4.2.3", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "00d64638e4f0703a00ab7fc2c8ae5f75f3b4020f" + "reference": "bd09ad265cd50b2b9d09d65ce6aba2d29bc81fe1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/00d64638e4f0703a00ab7fc2c8ae5f75f3b4020f", - "reference": "00d64638e4f0703a00ab7fc2c8ae5f75f3b4020f", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/bd09ad265cd50b2b9d09d65ce6aba2d29bc81fe1", + "reference": "bd09ad265cd50b2b9d09d65ce6aba2d29bc81fe1", "shasum": "" }, "require": { - "php": "^7.1.3" + "php": "^7.1.3", + "symfony/contracts": "^1.0" }, "conflict": { "symfony/dependency-injection": "<3.4" @@ -2758,7 +3025,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } }, "autoload": { @@ -2785,20 +3052,20 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2018-07-10T11:02:47+00:00" + "time": "2019-01-16T20:35:37+00:00" }, { "name": "symfony/filesystem", - "version": "v4.1.2", + "version": "v4.2.3", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "562bf7005b55fd80d26b582d28e3e10f2dd5ae9c" + "reference": "7c16ebc2629827d4ec915a52ac809768d060a4ee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/562bf7005b55fd80d26b582d28e3e10f2dd5ae9c", - "reference": "562bf7005b55fd80d26b582d28e3e10f2dd5ae9c", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/7c16ebc2629827d4ec915a52ac809768d060a4ee", + "reference": "7c16ebc2629827d4ec915a52ac809768d060a4ee", "shasum": "" }, "require": { @@ -2808,7 +3075,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } }, "autoload": { @@ -2835,20 +3102,20 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2018-05-30T07:26:09+00:00" + "time": "2019-01-16T20:35:37+00:00" }, { "name": "symfony/finder", - "version": "v4.1.2", + "version": "v4.2.3", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "84714b8417d19e4ba02ea78a41a975b3efaafddb" + "reference": "ef71816cbb264988bb57fe6a73f610888b9aa70c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/84714b8417d19e4ba02ea78a41a975b3efaafddb", - "reference": "84714b8417d19e4ba02ea78a41a975b3efaafddb", + "url": "https://api.github.com/repos/symfony/finder/zipball/ef71816cbb264988bb57fe6a73f610888b9aa70c", + "reference": "ef71816cbb264988bb57fe6a73f610888b9aa70c", "shasum": "" }, "require": { @@ -2857,7 +3124,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } }, "autoload": { @@ -2884,29 +3151,32 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2018-06-19T21:38:16+00:00" + "time": "2019-01-16T20:35:37+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.8.0", + "version": "v1.10.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "7cc359f1b7b80fc25ed7796be7d96adc9b354bae" + "reference": "e3d826245268269cd66f8326bd8bc066687b4a19" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/7cc359f1b7b80fc25ed7796be7d96adc9b354bae", - "reference": "7cc359f1b7b80fc25ed7796be7d96adc9b354bae", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e3d826245268269cd66f8326bd8bc066687b4a19", + "reference": "e3d826245268269cd66f8326bd8bc066687b4a19", "shasum": "" }, "require": { "php": ">=5.3.3" }, + "suggest": { + "ext-ctype": "For best performance" + }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.8-dev" + "dev-master": "1.9-dev" } }, "autoload": { @@ -2939,20 +3209,20 @@ "polyfill", "portable" ], - "time": "2018-04-30T19:57:29+00:00" + "time": "2018-08-06T14:22:27+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.8.0", + "version": "v1.10.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "3296adf6a6454a050679cde90f95350ad604b171" + "reference": "c79c051f5b3a46be09205c73b80b346e4153e494" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/3296adf6a6454a050679cde90f95350ad604b171", - "reference": "3296adf6a6454a050679cde90f95350ad604b171", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/c79c051f5b3a46be09205c73b80b346e4153e494", + "reference": "c79c051f5b3a46be09205c73b80b346e4153e494", "shasum": "" }, "require": { @@ -2964,7 +3234,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.8-dev" + "dev-master": "1.9-dev" } }, "autoload": { @@ -2998,20 +3268,20 @@ "portable", "shim" ], - "time": "2018-04-26T10:06:28+00:00" + "time": "2018-09-21T13:07:52+00:00" }, { "name": "symfony/process", - "version": "v4.1.2", + "version": "v4.2.3", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "1d1677391ecf00d1c5b9482d6050c0c27aa3ac3a" + "reference": "6c05edb11fbeff9e2b324b4270ecb17911a8b7ad" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/1d1677391ecf00d1c5b9482d6050c0c27aa3ac3a", - "reference": "1d1677391ecf00d1c5b9482d6050c0c27aa3ac3a", + "url": "https://api.github.com/repos/symfony/process/zipball/6c05edb11fbeff9e2b324b4270ecb17911a8b7ad", + "reference": "6c05edb11fbeff9e2b324b4270ecb17911a8b7ad", "shasum": "" }, "require": { @@ -3020,7 +3290,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } }, "autoload": { @@ -3047,20 +3317,20 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2018-05-31T10:17:53+00:00" + "time": "2019-01-24T22:05:03+00:00" }, { "name": "symfony/yaml", - "version": "v4.1.2", + "version": "v4.2.3", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "80e4bfa9685fc4a09acc4a857ec16974a9cd944e" + "reference": "d461670ee145092b7e2a56c1da7118f19cadadb0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/80e4bfa9685fc4a09acc4a857ec16974a9cd944e", - "reference": "80e4bfa9685fc4a09acc4a857ec16974a9cd944e", + "url": "https://api.github.com/repos/symfony/yaml/zipball/d461670ee145092b7e2a56c1da7118f19cadadb0", + "reference": "d461670ee145092b7e2a56c1da7118f19cadadb0", "shasum": "" }, "require": { @@ -3079,7 +3349,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } }, "autoload": { @@ -3106,7 +3376,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2018-05-30T07:26:09+00:00" + "time": "2019-01-16T20:35:37+00:00" }, { "name": "theseer/tokenizer", @@ -3150,20 +3420,21 @@ }, { "name": "vlucas/phpdotenv", - "version": "v2.5.0", + "version": "v2.6.1", "source": { "type": "git", "url": "https://github.com/vlucas/phpdotenv.git", - "reference": "6ae3e2e6494bb5e58c2decadafc3de7f1453f70a" + "reference": "2a7dcf7e3e02dc5e701004e51a6f304b713107d5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/6ae3e2e6494bb5e58c2decadafc3de7f1453f70a", - "reference": "6ae3e2e6494bb5e58c2decadafc3de7f1453f70a", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/2a7dcf7e3e02dc5e701004e51a6f304b713107d5", + "reference": "2a7dcf7e3e02dc5e701004e51a6f304b713107d5", "shasum": "" }, "require": { - "php": ">=5.3.9" + "php": ">=5.3.9", + "symfony/polyfill-ctype": "^1.9" }, "require-dev": { "phpunit/phpunit": "^4.8.35 || ^5.0" @@ -3171,7 +3442,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.5-dev" + "dev-master": "2.6-dev" } }, "autoload": { @@ -3196,24 +3467,25 @@ "env", "environment" ], - "time": "2018-07-01T10:25:50+00:00" + "time": "2019-01-29T11:11:52+00:00" }, { "name": "webmozart/assert", - "version": "1.3.0", + "version": "1.4.0", "source": { "type": "git", "url": "https://github.com/webmozart/assert.git", - "reference": "0df1908962e7a3071564e857d86874dad1ef204a" + "reference": "83e253c8e0be5b0257b881e1827274667c5c17a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozart/assert/zipball/0df1908962e7a3071564e857d86874dad1ef204a", - "reference": "0df1908962e7a3071564e857d86874dad1ef204a", + "url": "https://api.github.com/repos/webmozart/assert/zipball/83e253c8e0be5b0257b881e1827274667c5c17a9", + "reference": "83e253c8e0be5b0257b881e1827274667c5c17a9", "shasum": "" }, "require": { - "php": "^5.3.3 || ^7.0" + "php": "^5.3.3 || ^7.0", + "symfony/polyfill-ctype": "^1.8" }, "require-dev": { "phpunit/phpunit": "^4.6", @@ -3246,7 +3518,7 @@ "check", "validate" ], - "time": "2018-01-29T19:49:41+00:00" + "time": "2018-12-25T11:19:39+00:00" } ], "packages-dev": [], diff --git a/dev/tests/acceptance/tests/_data/catalog_products.csv b/dev/tests/acceptance/tests/_data/catalog_products.csv new file mode 100644 index 00000000000..3b580172d6a --- /dev/null +++ b/dev/tests/acceptance/tests/_data/catalog_products.csv @@ -0,0 +1,4 @@ +sku,store_view_code,attribute_set_code,product_type,categories,product_websites,name,description,short_description,weight,product_online,tax_class_name,visibility,price,special_price,special_price_from_date,special_price_to_date,url_key,meta_title,meta_keywords,meta_description,base_image,base_image_label,small_image,small_image_label,thumbnail_image,thumbnail_image_label,swatch_image,swatch_image_label,created_at,updated_at,new_from_date,new_to_date,display_product_options_in,map_price,msrp_price,map_enabled,gift_message_available,custom_design,custom_design_from,custom_design_to,custom_layout_update,page_layout,product_options_container,msrp_display_actual_price_type,country_of_manufacture,additional_attributes,qty,out_of_stock_qty,use_config_min_qty,is_qty_decimal,allow_backorders,use_config_backorders,min_cart_qty,use_config_min_sale_qty,max_cart_qty,use_config_max_sale_qty,is_in_stock,notify_on_stock_below,use_config_notify_stock_qty,manage_stock,use_config_manage_stock,use_config_qty_increments,qty_increments,use_config_enable_qty_inc,enable_qty_increments,is_decimal_divided,website_id,deferred_stock_update,use_config_deferred_stock_update,related_skus,related_position,crosssell_skus,crosssell_position,upsell_skus,upsell_position,additional_images,additional_image_labels,hide_from_product_page,custom_options,bundle_price_type,bundle_sku_type,bundle_price_view,bundle_weight_type,bundle_values,bundle_shipment_type,configurable_variations,configurable_variation_labels,associated_skus +"Simple Product for Test",,Default,simple,,base,"Simple Product for Test",,,,1,"Taxable Goods","Catalog, Search",123.0000,,,,simple-product-for-test,,,,,,,,,,,,"12/18/18, 7:50 AM","12/18/18, 7:50 AM",,,"Block after Info Column",,,,,,,,,,,,,,1000.0000,0.0000,1,0,0,1,1.0000,1,0.0000,1,1,,1,0,1,1,0.0000,1,0,0,0,0,1,,,,,,,,,,,,,,,,,,, +"Virtual Product for Test",,Default,virtual,,base,"Virtual Product for Test",,,0.0000,1,"Taxable Goods","Catalog, Search",99.9900,,,,virtual-product-for-test,,,,,,,,,,,,"12/18/18, 7:51 AM","12/18/18, 7:51 AM",,,"Block after Info Column",,,,,,,,,,,,,,1000.0000,0.0000,1,0,0,1,1.0000,1,0.0000,1,1,,1,0,1,1,0.0000,1,0,0,0,0,1,,,,,,,,,,,,,,,,,,, +"Api Downloadable Product for Test",,Default,downloadable,,base,"Api Downloadable Product for Test","API Product Description5c18fb47982621","API Product Short Description5c18fb47982e21",,1,"Taxable Goods","Catalog, Search",123.0000,,,,api-downloadable-product-for-test,,,,,,,,,,,,"12/18/18, 7:51 AM","12/18/18, 7:51 AM",,,"Block after Info Column",,,,,,,,,,,,,,1000.0000,0.0000,1,0,0,1,1.0000,1,0.0000,1,1,,1,0,1,1,0.0000,1,0,0,0,0,1,,,,,,,,,,,,,,,,,,, diff --git a/dev/tests/acceptance/tests/_data/import_updated.csv b/dev/tests/acceptance/tests/_data/import_updated.csv new file mode 100644 index 00000000000..b517150eec8 --- /dev/null +++ b/dev/tests/acceptance/tests/_data/import_updated.csv @@ -0,0 +1,4 @@ +product_websites,store_view_code,attribute_set_code,product_type,categories,sku,price,name,url_key +base,,Default,simple,Default Category/category-admin,productformagetwo68980,123,productformagetwo68980,productformagetwo68980 +,en,Default,simple,,productformagetwo68980,,productformagetwo68980-english,productformagetwo68980-english +,nl,Default,simple,,productformagetwo68980,,productformagetwo68980-dutch,productformagetwo68980-dutch diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProductCatalogSearch/Test/EndToEndB2CGuestUserTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProductCatalogSearch/Test/EndToEndB2CGuestUserTest.xml index f5cd41bda74..f0bfec543f2 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProductCatalogSearch/Test/EndToEndB2CGuestUserTest.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProductCatalogSearch/Test/EndToEndB2CGuestUserTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="EndToEndB2CGuestUserTest"> <!-- Search configurable product --> <comment userInput="Search configurable product" stepKey="commentSearchConfigurableProduct" after="searchAssertSimpleProduct2ImageNotDefault" /> @@ -26,5 +26,5 @@ <!-- @TODO: Move Image check to action group after MQE-697 is fixed --> <grabAttributeFrom selector="{{StorefrontProductInfoMainSection.productImage}}" userInput="src" stepKey="searchGrabConfigProductPageImageSrc" after="searchAssertConfigProductPage"/> <assertNotRegExp expected="'/placeholder\/image\.jpg/'" actual="$searchGrabConfigProductPageImageSrc" stepKey="searchAssertConfigProductPageImageNotDefault" after="searchGrabConfigProductPageImageSrc"/> - </test> + </test> </tests> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProductCatalogSearch/Test/EndToEndB2CLoggedInUserTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProductCatalogSearch/Test/EndToEndB2CLoggedInUserTest.xml index 3e386a034ee..9fe70c8b4dd 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProductCatalogSearch/Test/EndToEndB2CLoggedInUserTest.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProductCatalogSearch/Test/EndToEndB2CLoggedInUserTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="EndToEndB2CLoggedInUserTest"> <!-- Search configurable product --> <comment userInput="Search configurable product" stepKey="commentSearchConfigurableProduct" after="searchAssertSimpleProduct2ImageNotDefault" /> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProductWishlist/Test/EndToEndB2CLoggedInUserTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProductWishlist/Test/EndToEndB2CLoggedInUserTest.xml index d3b009eecf8..cb3d9edbc1c 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProductWishlist/Test/EndToEndB2CLoggedInUserTest.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/ConfigurableProductWishlist/Test/EndToEndB2CLoggedInUserTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="EndToEndB2CLoggedInUserTest"> <!-- Step 5: Add products to wishlist --> <!-- Add Configurable Product to wishlist --> diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQl/Client.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQl/Client.php index 5458b5cfbb7..add0510c6b4 100644 --- a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQl/Client.php +++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQl/Client.php @@ -57,8 +57,8 @@ public function postQuery(string $query, array $variables = [], string $operatio $headers = array_merge($headers, ['Accept: application/json', 'Content-Type: application/json']); $requestArray = [ 'query' => $query, - 'variables' => empty($variables) ? $variables : null, - 'operationName' => empty($operationName) ? $operationName : null + 'variables' => !empty($variables) ? $variables : null, + 'operationName' => !empty($operationName) ? $operationName : null ]; $postData = $this->json->jsonEncode($requestArray); diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductCustomOptionRepositoryTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductCustomOptionRepositoryTest.php index c335b66505b..f3be684f93a 100644 --- a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductCustomOptionRepositoryTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductCustomOptionRepositoryTest.php @@ -146,6 +146,10 @@ public function testGetList() public function testSave($optionData) { $productSku = 'simple'; + /** @var \Magento\Catalog\Model\ProductRepository $productRepository */ + $productRepository = $this->objectManager->create( + \Magento\Catalog\Model\ProductRepository::class + ); $optionDataPost = $optionData; $optionDataPost['product_sku'] = $productSku; @@ -162,6 +166,7 @@ public function testSave($optionData) ]; $result = $this->_webApiCall($serviceInfo, ['option' => $optionDataPost]); + $product = $productRepository->get($productSku); unset($result['product_sku']); unset($result['option_id']); if (!empty($result['values'])) { @@ -169,7 +174,12 @@ public function testSave($optionData) unset($result['values'][$key]['option_type_id']); } } + $this->assertEquals($optionData, $result); + $this->assertTrue($product->getHasOptions() == 1); + if ($optionDataPost['is_require']) { + $this->assertTrue($product->getRequiredOptions() == 1); + } } public function optionDataProvider() diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryProductsCountTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryProductsCountTest.php index eddd456a7b8..46309c6d97d 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryProductsCountTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryProductsCountTest.php @@ -7,11 +7,16 @@ namespace Magento\GraphQl\Catalog; -use Magento\TestFramework\TestCase\GraphQlAbstract; +use Magento\Catalog\Api\CategoryLinkManagementInterface; use Magento\Catalog\Api\ProductRepositoryInterface; -use Magento\TestFramework\Helper\Bootstrap; -use Magento\Catalog\Model\Product\Visibility; use Magento\Catalog\Model\Product\Attribute\Source\Status as productStatus; +use Magento\Catalog\Model\Product\Visibility; +use Magento\CatalogInventory\Model\Configuration; +use Magento\Config\Model\ResourceModel\Config; +use Magento\Framework\App\Config\ReinitableConfigInterface; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\TestFramework\ObjectManager; +use Magento\TestFramework\TestCase\GraphQlAbstract; /** * Class CategoryProductsCountTest @@ -25,10 +30,39 @@ class CategoryProductsCountTest extends GraphQlAbstract */ private $productRepository; + /** + * @var Config $config + */ + private $resourceConfig; + + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + + /** + * @var ReinitableConfigInterface + */ + private $reinitConfig; + + /** + * @var CategoryLinkManagementInterface + */ + private $categoryLinkManagement; + + /** + * @inheritdoc + */ protected function setUp() { - $objectManager = Bootstrap::getObjectManager(); + parent::setUp(); + + $objectManager = ObjectManager::getInstance(); $this->productRepository = $objectManager->create(ProductRepositoryInterface::class); + $this->resourceConfig = $objectManager->get(Config::class); + $this->scopeConfig = $objectManager->get(ScopeConfigInterface::class); + $this->reinitConfig = $objectManager->get(ReinitableConfigInterface::class); + $this->categoryLinkManagement = $objectManager->get(CategoryLinkManagementInterface::class); } /** @@ -82,6 +116,15 @@ public function testCategoryWithInvisibleProduct() public function testCategoryWithOutOfStockProductManageStockEnabled() { $categoryId = 2; + $sku = 'simple-out-of-stock'; + $manageStock = $this->scopeConfig->getValue(Configuration::XML_PATH_MANAGE_STOCK); + + $this->resourceConfig->saveConfig(Configuration::XML_PATH_MANAGE_STOCK, 1); + $this->reinitConfig->reinit(); + + // need to resave product to reindex it with new configuration. + $product = $this->productRepository->get($sku); + $this->productRepository->save($product); $query = <<<QUERY { @@ -93,15 +136,27 @@ public function testCategoryWithOutOfStockProductManageStockEnabled() QUERY; $response = $this->graphQlQuery($query); + $this->resourceConfig->saveConfig(Configuration::XML_PATH_MANAGE_STOCK, $manageStock); + $this->reinitConfig->reinit(); + self::assertEquals(0, $response['category']['product_count']); } /** - * @magentoApiDataFixture Magento/Catalog/_files/category_product.php + * @magentoApiDataFixture Magento/Catalog/_files/product_simple_out_of_stock.php */ public function testCategoryWithOutOfStockProductManageStockDisabled() { - $categoryId = 333; + $categoryId = 2; + $sku = 'simple-out-of-stock'; + $manageStock = $this->scopeConfig->getValue(Configuration::XML_PATH_MANAGE_STOCK); + + $this->resourceConfig->saveConfig(Configuration::XML_PATH_MANAGE_STOCK, 0); + $this->reinitConfig->reinit(); + + // need to resave product to reindex it with new configuration. + $product = $this->productRepository->get($sku); + $this->productRepository->save($product); $query = <<<QUERY { @@ -113,6 +168,9 @@ public function testCategoryWithOutOfStockProductManageStockDisabled() QUERY; $response = $this->graphQlQuery($query); + $this->resourceConfig->saveConfig(Configuration::XML_PATH_MANAGE_STOCK, $manageStock); + $this->reinitConfig->reinit(); + self::assertEquals(1, $response['category']['product_count']); } @@ -140,4 +198,84 @@ public function testCategoryWithDisabledProduct() self::assertEquals(0, $response['category']['product_count']); } + + /** + * @magentoApiDataFixture Magento/Catalog/_files/product_simple_out_of_stock.php + */ + public function testCategoryWithOutOfStockProductShowOutOfStockProduct() + { + $showOutOfStock = $this->scopeConfig->getValue(Configuration::XML_PATH_SHOW_OUT_OF_STOCK); + + $this->resourceConfig->saveConfig(Configuration::XML_PATH_SHOW_OUT_OF_STOCK, 1); + $this->reinitConfig->reinit(); + + $categoryId = 2; + + $query = <<<QUERY +{ + category(id: {$categoryId}) { + id + product_count + } +} +QUERY; + $response = $this->graphQlQuery($query); + + $this->resourceConfig->saveConfig(Configuration::XML_PATH_SHOW_OUT_OF_STOCK, $showOutOfStock); + $this->reinitConfig->reinit(); + + self::assertEquals(1, $response['category']['product_count']); + } + + /** + * @magentoApiDataFixture Magento/CatalogRule/_files/configurable_product.php + */ + public function testCategoryWithConfigurableChildrenOutOfStock() + { + $categoryId = 2; + + $this->categoryLinkManagement->assignProductToCategories('configurable', [$categoryId]); + + foreach (['simple1', 'simple2'] as $sku) { + $product = $this->productRepository->get($sku); + $product->setStockData(['is_in_stock' => 0]); + $this->productRepository->save($product); + } + + $query = <<<QUERY +{ + category(id: {$categoryId}) { + id + product_count + } +} +QUERY; + $response = $this->graphQlQuery($query); + + self::assertEquals(0, $response['category']['product_count']); + } + + /** + * @magentoApiDataFixture Magento/Catalog/_files/category_product.php + */ + public function testCategoryWithProductNotAvailableOnWebsite() + { + $product = $this->productRepository->getById(333); + $product->setWebsiteIds([]); + $this->productRepository->save($product); + + $categoryId = 333; + + $query = <<<QUERY +{ + category(id: {$categoryId}) { + id + product_count + } +} +QUERY; + $response = $this->graphQlQuery($query); + + self::assertEquals(0, $response['category']['product_count']); + } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryProductsVariantsTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryProductsVariantsTest.php index 6b57f84e4b9..1419aff867d 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryProductsVariantsTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryProductsVariantsTest.php @@ -24,6 +24,7 @@ class CategoryProductsVariantsTest extends GraphQlAbstract */ public function testGetSimpleProductsFromCategory() { + $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/360'); $query = <<<QUERY diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php index 54e98367ab8..c5fd2c49b99 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryTest.php @@ -70,21 +70,19 @@ public function testCategoriesTree() } } QUERY; - // get customer ID token /** @var \Magento\Integration\Api\CustomerTokenServiceInterface $customerTokenService */ $customerTokenService = $this->objectManager->create( \Magento\Integration\Api\CustomerTokenServiceInterface::class ); $customerToken = $customerTokenService->createCustomerAccessToken('customer@example.com', 'password'); - $headerMap = ['Authorization' => 'Bearer ' . $customerToken]; $response = $this->graphQlQuery($query, [], '', $headerMap); $responseDataObject = new DataObject($response); //Some sort of smoke testing self::assertEquals( - 'Ololo', - $responseDataObject->getData('category/children/7/children/1/description') + 'Its a description of Test Category 1.2', + $responseDataObject->getData('category/children/0/children/1/description') ); self::assertEquals( 'default-category', @@ -99,16 +97,52 @@ public function testCategoriesTree() $responseDataObject->getData('category/children/0/default_sort_by') ); self::assertCount( - 8, + 7, $responseDataObject->getData('category/children') ); self::assertCount( 2, - $responseDataObject->getData('category/children/7/children') + $responseDataObject->getData('category/children/0/children') + ); + self::assertEquals( + 13, + $responseDataObject->getData('category/children/0/children/1/id') + ); + } + + /** + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/Catalog/_files/categories.php + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function testGetCategoryById() + { + $rootCategoryId = 13; + $query = <<<QUERY +{ + category(id: {$rootCategoryId}) { + id + name + } +} +QUERY; + // get customer ID token + /** @var \Magento\Integration\Api\CustomerTokenServiceInterface $customerTokenService */ + $customerTokenService = $this->objectManager->create( + \Magento\Integration\Api\CustomerTokenServiceInterface::class ); + $customerToken = $customerTokenService->createCustomerAccessToken('customer@example.com', 'password'); + $headerMap = ['Authorization' => 'Bearer ' . $customerToken]; + $response = $this->graphQlQuery($query, [], '', $headerMap); + $responseDataObject = new DataObject($response); + //Some sort of smoke testing self::assertEquals( - 5, - $responseDataObject->getData('category/children/7/children/1/children/0/id') + 'Category 1.2', + $responseDataObject->getData('category/name') + ); + self::assertEquals( + 13, + $responseDataObject->getData('category/id') ); } @@ -259,17 +293,14 @@ public function testCategoryProducts() } } QUERY; - $response = $this->graphQlQuery($query); $this->assertArrayHasKey('products', $response['category']); $this->assertArrayHasKey('total_count', $response['category']['products']); $this->assertGreaterThanOrEqual(1, $response['category']['products']['total_count']); $this->assertEquals(1, $response['category']['products']['page_info']['current_page']); $this->assertEquals(20, $response['category']['products']['page_info']['page_size']); - $this->assertArrayHasKey('sku', $response['category']['products']['items'][0]); $firstProductSku = $response['category']['products']['items'][0]['sku']; - /** * @var ProductRepositoryInterface $productRepository */ @@ -279,7 +310,6 @@ public function testCategoryProducts() $this->assertAttributes($response['category']['products']['items'][0]); $this->assertWebsites($firstProduct, $response['category']['products']['items'][0]['websites']); } - /** * @magentoApiDataFixture Magento/Catalog/_files/categories.php */ @@ -291,9 +321,7 @@ public function testAnchorCategory() /** @var CategoryInterface $category */ $category = $categoryCollection->getFirstItem(); $categoryId = $category->getId(); - $this->assertNotEmpty($categoryId, "Preconditions failed: category is not available."); - $query = <<<QUERY { category(id: {$categoryId}) { @@ -307,7 +335,6 @@ public function testAnchorCategory() } } QUERY; - $response = $this->graphQlQuery($query); $expectedResponse = [ 'category' => [ @@ -331,7 +358,6 @@ public function testAnchorCategory() */ private function assertBaseFields($product, $actualResponse) { - $assertionMap = [ ['response_field' => 'attribute_set_id', 'expected_value' => $product->getAttributeSetId()], ['response_field' => 'created_at', 'expected_value' => $product->getCreatedAt()], @@ -365,7 +391,6 @@ private function assertBaseFields($product, $actualResponse) ['response_field' => 'type_id', 'expected_value' => $product->getTypeId()], ['response_field' => 'updated_at', 'expected_value' => $product->getUpdatedAt()], ]; - $this->assertResponseFields($actualResponse, $assertionMap); } @@ -385,7 +410,6 @@ private function assertWebsites($product, $actualResponse) 'is_default' => true, ] ]; - $this->assertEquals($actualResponse, $assertionMap); } @@ -410,7 +434,6 @@ private function assertAttributes($actualResponse) 'special_from_date', 'special_to_date', ]; - foreach ($eavAttributes as $eavAttribute) { $this->assertArrayHasKey($eavAttribute, $actualResponse); } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/ConfigurableProductFrontendLabelAttributeTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/ConfigurableProductFrontendLabelAttributeTest.php new file mode 100644 index 00000000000..32cd3a9a51d --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/ConfigurableProductFrontendLabelAttributeTest.php @@ -0,0 +1,56 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\ConfigurableProduct; + +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * Class ConfigurableProductFrontendLabelAttributeTest + * + * @package Magento\GraphQl\ConfigurableProduct + */ +class ConfigurableProductFrontendLabelAttributeTest extends GraphQlAbstract +{ + /** + * @magentoApiDataFixture Magento/ConfigurableProduct/_files/product_configurable_with_frontend_label_attribute.php + */ + public function testGetFrontendLabelAttribute() + { + $expectLabelValue = 'Default Store View label'; + $productSku = 'configurable'; + + $query = <<<QUERY +{ + products(filter: {sku: {eq: "{$productSku}"}}) { + items { + name + ... on ConfigurableProduct{ + configurable_options{ + id + label + } + } + } + } +} +QUERY; + $response = $this->graphQlQuery($query); + + $this->assertArrayHasKey('products', $response); + $this->assertArrayHasKey('items', $response['products']); + $this->assertArrayHasKey(0, $response['products']['items']); + + $product = $response['products']['items'][0]; + $this->assertArrayHasKey('configurable_options', $product); + $this->assertArrayHasKey(0, $product['configurable_options']); + $this->assertArrayHasKey('label', $product['configurable_options'][0]); + + $option = $product['configurable_options'][0]; + $this->assertEquals($expectLabelValue, $option['label']); + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/ConfigurableProductViewTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/ConfigurableProductViewTest.php index 735ae7fff64..2c2a4a0c60d 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/ConfigurableProductViewTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/ConfigurableProductViewTest.php @@ -28,6 +28,8 @@ class ConfigurableProductViewTest extends GraphQlAbstract */ public function testQueryConfigurableProductLinks() { + $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/361'); + $productSku = 'configurable'; $query @@ -407,6 +409,7 @@ private function assertConfigurableVariants($actualResponse) $variantArray['product']['price'] ); $configurableOptions = $this->getConfigurableOptions(); + $this->assertEquals(1, count($variantArray['attributes'])); foreach ($variantArray['attributes'] as $attribute) { $hasAssertion = false; foreach ($configurableOptions as $option) { diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CreateCustomerTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CreateCustomerTest.php new file mode 100644 index 00000000000..7342800379d --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/CreateCustomerTest.php @@ -0,0 +1,240 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Customer; + +use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Customer\Model\CustomerRegistry; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQlAbstract; + +class CreateCustomerTest extends GraphQlAbstract +{ + /** + * @var CustomerRegistry + */ + private $customerRegistry; + + /** + * @var CustomerRepositoryInterface + */ + private $customerRepository; + + protected function setUp() + { + parent::setUp(); + + $this->customerRegistry = Bootstrap::getObjectManager()->get(CustomerRegistry::class); + $this->customerRepository = Bootstrap::getObjectManager()->get(CustomerRepositoryInterface::class); + } + + /** + * @throws \Exception + */ + public function testCreateCustomerAccountWithPassword() + { + $newFirstname = 'Richard'; + $newLastname = 'Rowe'; + $currentPassword = 'test123#'; + $newEmail = 'customer_created' . rand(1, 2000000) . '@example.com'; + + $query = <<<QUERY +mutation { + createCustomer( + input: { + firstname: "{$newFirstname}" + lastname: "{$newLastname}" + email: "{$newEmail}" + password: "{$currentPassword}" + is_subscribed: true + } + ) { + customer { + id + firstname + lastname + email + is_subscribed + } + } +} +QUERY; + $response = $this->graphQlQuery($query); + + $this->assertEquals($newFirstname, $response['createCustomer']['customer']['firstname']); + $this->assertEquals($newLastname, $response['createCustomer']['customer']['lastname']); + $this->assertEquals($newEmail, $response['createCustomer']['customer']['email']); + $this->assertEquals(true, $response['createCustomer']['customer']['is_subscribed']); + } + + /** + * @throws \Exception + */ + public function testCreateCustomerAccountWithoutPassword() + { + $newFirstname = 'Richard'; + $newLastname = 'Rowe'; + $newEmail = 'customer_created' . rand(1, 2000000) . '@example.com'; + + $query = <<<QUERY +mutation { + createCustomer( + input: { + firstname: "{$newFirstname}" + lastname: "{$newLastname}" + email: "{$newEmail}" + is_subscribed: true + } + ) { + customer { + id + firstname + lastname + email + is_subscribed + } + } +} +QUERY; + $response = $this->graphQlQuery($query); + + $this->assertEquals($newFirstname, $response['createCustomer']['customer']['firstname']); + $this->assertEquals($newLastname, $response['createCustomer']['customer']['lastname']); + $this->assertEquals($newEmail, $response['createCustomer']['customer']['email']); + $this->assertEquals(true, $response['createCustomer']['customer']['is_subscribed']); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage "input" value should be specified + */ + public function testCreateCustomerIfInputDataIsEmpty() + { + $query = <<<QUERY +mutation { + createCustomer( + input: { + + } + ) { + customer { + id + firstname + lastname + email + is_subscribed + } + } +} +QUERY; + $this->graphQlQuery($query); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage The customer email is missing. Enter and try again. + */ + public function testCreateCustomerIfEmailMissed() + { + $newFirstname = 'Richard'; + $newLastname = 'Rowe'; + $currentPassword = 'test123#'; + + $query = <<<QUERY +mutation { + createCustomer( + input: { + firstname: "{$newFirstname}" + lastname: "{$newLastname}" + password: "{$currentPassword}" + is_subscribed: true + } + ) { + customer { + id + firstname + lastname + email + is_subscribed + } + } +} +QUERY; + $this->graphQlQuery($query); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage "Email" is not a valid email address. + */ + public function testCreateCustomerIfEmailIsNotValid() + { + $newFirstname = 'Richard'; + $newLastname = 'Rowe'; + $currentPassword = 'test123#'; + $newEmail = 'email'; + + $query = <<<QUERY +mutation { + createCustomer( + input: { + firstname: "{$newFirstname}" + lastname: "{$newLastname}" + email: "{$newEmail}" + password: "{$currentPassword}" + is_subscribed: true + } + ) { + customer { + id + firstname + lastname + email + is_subscribed + } + } +} +QUERY; + $this->graphQlQuery($query); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage Field "test123" is not defined by type CustomerInput. + */ + public function testCreateCustomerIfPassedAttributeDosNotExistsInCustomerInput() + { + $newFirstname = 'Richard'; + $newLastname = 'Rowe'; + $currentPassword = 'test123#'; + $newEmail = 'customer_created' . rand(1, 2000000) . '@example.com'; + + $query = <<<QUERY +mutation { + createCustomer( + input: { + firstname: "{$newFirstname}" + lastname: "{$newLastname}" + test123: "123test123" + email: "{$newEmail}" + password: "{$currentPassword}" + is_subscribed: true + } + ) { + customer { + id + firstname + lastname + email + is_subscribed + } + } +} +QUERY; + $this->graphQlQuery($query); + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/UpdateCustomerAddressTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/UpdateCustomerAddressTest.php index 519fe2b1405..6a9708b4f86 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/UpdateCustomerAddressTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/UpdateCustomerAddressTest.php @@ -218,13 +218,12 @@ private function assertCustomerAddressesFields(AddressInterface $address, $actua ]; $this->assertResponseFields($actualResponse, $assertionMap); $this->assertTrue(is_array([$actualResponse['region']]), "region field must be of an array type."); - // https://github.com/magento/graphql-ce/issues/270 -// $assertionRegionMap = [ -// ['response_field' => 'region', 'expected_value' => $address->getRegion()->getRegion()], -// ['response_field' => 'region_code', 'expected_value' => $address->getRegion()->getRegionCode()], -// ['response_field' => 'region_id', 'expected_value' => $address->getRegion()->getRegionId()] -// ]; -// $this->assertResponseFields($actualResponse['region'], $assertionRegionMap); + $assertionRegionMap = [ + ['response_field' => 'region', 'expected_value' => $address->getRegion()->getRegion()], + ['response_field' => 'region_code', 'expected_value' => $address->getRegion()->getRegionCode()], + ['response_field' => 'region_id', 'expected_value' => $address->getRegion()->getRegionId()] + ]; + $this->assertResponseFields($actualResponse['region'], $assertionRegionMap); } /** diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Directory/CountriesTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Directory/CountriesTest.php new file mode 100644 index 00000000000..c42ce4c46fa --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Directory/CountriesTest.php @@ -0,0 +1,45 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Directory; + +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * Test the GraphQL endpoint's Coutries query + */ +class CountriesTest extends GraphQlAbstract +{ + public function testGetCountries() + { + $query = <<<QUERY +query { + countries { + id + two_letter_abbreviation + three_letter_abbreviation + full_name_locale + full_name_english + available_regions { + id + code + name + } + } +} +QUERY; + + $result = $this->graphQlQuery($query); + $this->assertArrayHasKey('countries', $result); + $this->assertArrayHasKey('id', $result['countries'][0]); + $this->assertArrayHasKey('two_letter_abbreviation', $result['countries'][0]); + $this->assertArrayHasKey('three_letter_abbreviation', $result['countries'][0]); + $this->assertArrayHasKey('full_name_locale', $result['countries'][0]); + $this->assertArrayHasKey('full_name_english', $result['countries'][0]); + $this->assertArrayHasKey('available_regions', $result['countries'][0]); + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Directory/CountryTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Directory/CountryTest.php new file mode 100644 index 00000000000..dda5ef34224 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Directory/CountryTest.php @@ -0,0 +1,74 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Directory; + +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * Test the GraphQL endpoint's Coutries query + */ +class CountryTest extends GraphQlAbstract +{ + public function testGetCountry() + { + $query = <<<QUERY +query { + country(id: "US") { + id + two_letter_abbreviation + three_letter_abbreviation + full_name_locale + full_name_english + available_regions { + id + code + name + } + } +} +QUERY; + + $result = $this->graphQlQuery($query); + $this->assertArrayHasKey('country', $result); + $this->assertArrayHasKey('id', $result['country']); + $this->assertArrayHasKey('two_letter_abbreviation', $result['country']); + $this->assertArrayHasKey('three_letter_abbreviation', $result['country']); + $this->assertArrayHasKey('full_name_locale', $result['country']); + $this->assertArrayHasKey('full_name_english', $result['country']); + $this->assertArrayHasKey('available_regions', $result['country']); + $this->assertArrayHasKey('id', $result['country']['available_regions'][0]); + $this->assertArrayHasKey('code', $result['country']['available_regions'][0]); + $this->assertArrayHasKey('name', $result['country']['available_regions'][0]); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage GraphQL response contains errors: The country isn't available. + */ + public function testGetCountryNotFoundException() + { + $query = <<<QUERY +query { + country(id: "BLAH") { + id + two_letter_abbreviation + three_letter_abbreviation + full_name_locale + full_name_english + available_regions { + id + code + name + } + } +} +QUERY; + + $this->graphQlQuery($query); + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Directory/CurrencyTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Directory/CurrencyTest.php new file mode 100644 index 00000000000..1ff0b53dda0 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Directory/CurrencyTest.php @@ -0,0 +1,44 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Directory; + +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * Test the GraphQL endpoint's Currency query + */ +class CurrencyTest extends GraphQlAbstract +{ + public function testGetCurrency() + { + $query = <<<QUERY +query { + currency { + base_currency_code + base_currency_symbol + default_display_currecy_code + default_display_currecy_symbol + available_currency_codes + exchange_rates { + currency_to + rate + } + } +} +QUERY; + + $result = $this->graphQlQuery($query); + $this->assertArrayHasKey('currency', $result); + $this->assertArrayHasKey('base_currency_code', $result['currency']); + $this->assertArrayHasKey('base_currency_symbol', $result['currency']); + $this->assertArrayHasKey('default_display_currecy_code', $result['currency']); + $this->assertArrayHasKey('default_display_currecy_symbol', $result['currency']); + $this->assertArrayHasKey('available_currency_codes', $result['currency']); + $this->assertArrayHasKey('exchange_rates', $result['currency']); + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/IntrospectionQueryTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/IntrospectionQueryTest.php index 85b4c62c419..60acb3a7a4d 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/IntrospectionQueryTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/IntrospectionQueryTest.php @@ -12,10 +12,10 @@ class IntrospectionQueryTest extends GraphQlAbstract { /** - * Tests that Introspection is disabled when not in developer mode + * Tests that Introspection is allowed by default * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ - public function testIntrospectionQueryWithFieldArgs() + public function testIntrospectionQuery() { $query = <<<QUERY @@ -54,11 +54,6 @@ public function testIntrospectionQueryWithFieldArgs() } QUERY; - $this->expectException(\Exception::class); - $this->expectExceptionMessage( - 'GraphQL response contains errors: GraphQL introspection is not allowed, but ' . - 'the query contained __schema or __type' - ); - $this->graphQlQuery($query); + $this->assertArrayHasKey('__schema', $this->graphQlQuery($query)); } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetCartTest.php new file mode 100644 index 00000000000..78204aaa567 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetCartTest.php @@ -0,0 +1,170 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Quote; + +use Magento\Integration\Api\CustomerTokenServiceInterface; +use Magento\Quote\Model\Quote; +use Magento\Quote\Model\QuoteIdToMaskedQuoteIdInterface; +use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQlAbstract; + +/** + * Test for getting cart information + */ +class GetCartTest extends GraphQlAbstract +{ + /** + * @var CustomerTokenServiceInterface + */ + private $customerTokenService; + + /** + * @var QuoteResource + */ + private $quoteResource; + + /** + * @var Quote + */ + private $quote; + + /** + * @var QuoteIdToMaskedQuoteIdInterface + */ + private $quoteIdToMaskedId; + + /** + * @inheritdoc + */ + protected function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->quoteResource = $objectManager->create(QuoteResource::class); + $this->quote = $objectManager->create(Quote::class); + $this->quoteIdToMaskedId = $objectManager->create(QuoteIdToMaskedQuoteIdInterface::class); + $this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class); + } + + /** + * @magentoApiDataFixture Magento/Checkout/_files/quote_with_items_saved.php + */ + public function testGetOwnCartForRegisteredCustomer() + { + $reservedOrderId = 'test_order_item_with_items'; + $this->quoteResource->load( + $this->quote, + $reservedOrderId, + 'reserved_order_id' + ); + + $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$this->quote->getId()); + $query = $this->prepareGetCartQuery($maskedQuoteId); + + $response = $this->sendRequestWithToken($query); + + self::assertArrayHasKey('Cart', $response); + self::assertNotEmpty($response['Cart']['items']); + self::assertNotEmpty($response['Cart']['addresses']); + } + + /** + * @magentoApiDataFixture Magento/Checkout/_files/quote_with_items_saved.php + */ + public function testGetCartFromAnotherCustomer() + { + $reservedOrderId = 'test_order_item_with_items'; + $this->quoteResource->load( + $this->quote, + $reservedOrderId, + 'reserved_order_id' + ); + + $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$this->quote->getId()); + $query = $this->prepareGetCartQuery($maskedQuoteId); + + self::expectExceptionMessage("The current user cannot perform operations on cart \"$maskedQuoteId\""); + + $this->graphQlQuery($query); + } + + /** + * @magentoApiDataFixture Magento/Checkout/_files/active_quote.php + */ + public function testGetCartForGuest() + { + $reservedOrderId = 'test_order_1'; + $this->quoteResource->load( + $this->quote, + $reservedOrderId, + 'reserved_order_id' + ); + + $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$this->quote->getId()); + $query = $this->prepareGetCartQuery($maskedQuoteId); + + $response = $this->graphQlQuery($query); + + self::assertArrayHasKey('Cart', $response); + } + + public function testGetNonExistentCart() + { + $maskedQuoteId = 'non_existent_masked_id'; + $query = $this->prepareGetCartQuery($maskedQuoteId); + + self::expectExceptionMessage("Could not find a cart with ID \"$maskedQuoteId\""); + + $this->graphQlQuery($query); + } + + /** + * Generates query for setting the specified shipping method on cart + * + * @param string $maskedQuoteId + * @return string + */ + private function prepareGetCartQuery( + string $maskedQuoteId + ) : string { + return <<<QUERY +{ + Cart(cart_id: "$maskedQuoteId") { + applied_coupon { + code + } + items { + id + } + addresses { + firstname, + lastname, + address_type + } + } +} + +QUERY; + } + + /** + * Sends a GraphQL request with using a bearer token + * + * @param string $query + * @return array + * @throws \Magento\Framework\Exception\AuthenticationException + */ + private function sendRequestWithToken(string $query): array + { + + $customerToken = $this->customerTokenService->createCustomerAccessToken('customer@example.com', 'password'); + $headerMap = ['Authorization' => 'Bearer ' . $customerToken]; + + return $this->graphQlQuery($query, [], '', $headerMap); + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Sales/OrdersTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Sales/OrdersTest.php index 589b0bc7746..9c969befa32 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Sales/OrdersTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Sales/OrdersTest.php @@ -83,9 +83,21 @@ public function testOrdersQuery() $actualData = $response['customerOrders']['items']; foreach ($expectedData as $key => $data) { - $this->assertEquals($data['increment_id'], $actualData[$key]['increment_id']); - $this->assertEquals($data['grand_total'], $actualData[$key]['grand_total']); - $this->assertEquals($data['status'], $actualData[$key]['status']); + $this->assertEquals( + $data['increment_id'], + $actualData[$key]['increment_id'], + "increment_id is different than the expected for order - " . $data['increment_id'] + ); + $this->assertEquals( + $data['grand_total'], + $actualData[$key]['grand_total'], + "grand_total is different than the expected for order - " . $data['increment_id'] + ); + $this->assertEquals( + $data['status'], + $actualData[$key]['status'], + "status is different than the expected for order - " . $data['increment_id'] + ); } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/VariablesSupportQueryTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/VariablesSupportQueryTest.php new file mode 100644 index 00000000000..7448b165fc2 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/VariablesSupportQueryTest.php @@ -0,0 +1,81 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl; + +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQlAbstract; +use Magento\Catalog\Api\ProductRepositoryInterface; + +class VariablesSupportQueryTest extends GraphQlAbstract +{ + /** + * @var ProductRepositoryInterface + */ + private $productRepository; + + protected function setUp() + { + $this->productRepository = Bootstrap::getObjectManager()->get(ProductRepositoryInterface::class); + } + + /** + * @magentoApiDataFixture Magento/Catalog/_files/products_list.php + */ + public function testQueryObjectVariablesSupport() + { + $productSku = 'simple-249'; + $minPrice = 153; + + $query + = <<<'QUERY' +query GetProductsQuery($pageSize: Int, $filterInput: ProductFilterInput, $priceSort: SortEnum) { + products( + pageSize: $pageSize + filter: $filterInput + sort: {price: $priceSort} + ) { + items { + sku + price { + minimalPrice { + amount { + value + currency + } + } + } + } + } +} +QUERY; + + $variables = [ + 'pageSize' => 1, + 'priceSort' => 'ASC', + 'filterInput' => [ + 'min_price' => [ + 'gt' => 150, + ], + ], + ]; + + $response = $this->graphQlQuery($query, $variables); + /** @var \Magento\Catalog\Model\Product $product */ + $product = $this->productRepository->get($productSku, false, null, true); + + self::assertArrayHasKey('products', $response); + self::assertArrayHasKey('items', $response['products']); + self::assertEquals(1, count($response['products']['items'])); + self::assertArrayHasKey(0, $response['products']['items']); + self::assertEquals($product->getSku(), $response['products']['items'][0]['sku']); + self::assertEquals( + $minPrice, + $response['products']['items'][0]['price']['minimalPrice']['amount']['value'] + ); + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/Quote/Api/CartTotalRepositoryTest.php b/dev/tests/api-functional/testsuite/Magento/Quote/Api/CartTotalRepositoryTest.php index a001cae6454..a3ded4f5f12 100644 --- a/dev/tests/api-functional/testsuite/Magento/Quote/Api/CartTotalRepositoryTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Quote/Api/CartTotalRepositoryTest.php @@ -12,6 +12,8 @@ use Magento\Framework\Api\SearchCriteriaBuilder; use Magento\TestFramework\ObjectManager; use Magento\TestFramework\TestCase\WebapiAbstract; +use Magento\Quote\Model\Quote; +use Magento\Quote\Model\Quote\Address; class CartTotalRepositoryTest extends WebapiAbstract { @@ -54,36 +56,11 @@ public function testGetTotals() /** @var \Magento\Quote\Model\Quote\Address $shippingAddress */ $shippingAddress = $quote->getShippingAddress(); - $data = [ - Totals::KEY_GRAND_TOTAL => $quote->getGrandTotal(), - Totals::KEY_BASE_GRAND_TOTAL => $quote->getBaseGrandTotal(), - Totals::KEY_SUBTOTAL => $quote->getSubtotal(), - Totals::KEY_BASE_SUBTOTAL => $quote->getBaseSubtotal(), - Totals::KEY_DISCOUNT_AMOUNT => $shippingAddress->getDiscountAmount(), - Totals::KEY_BASE_DISCOUNT_AMOUNT => $shippingAddress->getBaseDiscountAmount(), - Totals::KEY_SUBTOTAL_WITH_DISCOUNT => $quote->getSubtotalWithDiscount(), - Totals::KEY_BASE_SUBTOTAL_WITH_DISCOUNT => $quote->getBaseSubtotalWithDiscount(), - Totals::KEY_SHIPPING_AMOUNT => $shippingAddress->getShippingAmount(), - Totals::KEY_BASE_SHIPPING_AMOUNT => $shippingAddress->getBaseShippingAmount(), - Totals::KEY_SHIPPING_DISCOUNT_AMOUNT => $shippingAddress->getShippingDiscountAmount(), - Totals::KEY_BASE_SHIPPING_DISCOUNT_AMOUNT => $shippingAddress->getBaseShippingDiscountAmount(), - Totals::KEY_TAX_AMOUNT => $shippingAddress->getTaxAmount(), - Totals::KEY_BASE_TAX_AMOUNT => $shippingAddress->getBaseTaxAmount(), - Totals::KEY_SHIPPING_TAX_AMOUNT => $shippingAddress->getShippingTaxAmount(), - Totals::KEY_BASE_SHIPPING_TAX_AMOUNT => $shippingAddress->getBaseShippingTaxAmount(), - Totals::KEY_SUBTOTAL_INCL_TAX => $shippingAddress->getSubtotalInclTax(), - Totals::KEY_BASE_SUBTOTAL_INCL_TAX => $shippingAddress->getBaseSubtotalTotalInclTax(), - Totals::KEY_SHIPPING_INCL_TAX => $shippingAddress->getShippingInclTax(), - Totals::KEY_BASE_SHIPPING_INCL_TAX => $shippingAddress->getBaseShippingInclTax(), - Totals::KEY_BASE_CURRENCY_CODE => $quote->getBaseCurrencyCode(), - Totals::KEY_QUOTE_CURRENCY_CODE => $quote->getQuoteCurrencyCode(), - Totals::KEY_ITEMS_QTY => $quote->getItemsQty(), - Totals::KEY_ITEMS => [$this->getQuoteItemTotalsData($quote)], - ]; + $data = $this->getData($quote, $shippingAddress); + $data = $this->formatTotalsData($data); $requestData = ['cartId' => $cartId]; - $data = $this->formatTotalsData($data); $actual = $this->_webApiCall($this->getServiceInfoForTotalsService($cartId), $requestData); unset($actual['items'][0]['options']); unset($actual['weee_tax_applied_amount']); @@ -213,7 +190,32 @@ public function testGetMyTotals() /** @var \Magento\Quote\Model\Quote\Address $shippingAddress */ $shippingAddress = $quote->getShippingAddress(); - $data = [ + $data = $this->getData($quote, $shippingAddress); + $data = $this->formatTotalsData($data); + + $actual = $this->_webApiCall($serviceInfo); + unset($actual['items'][0]['options']); + unset($actual['weee_tax_applied_amount']); + + /** TODO: cover total segments with separate test */ + unset($actual['total_segments']); + if (array_key_exists('extension_attributes', $actual)) { + unset($actual['extension_attributes']); + } + $this->assertEquals($data, $actual); + } + + /** + * Get expected data. + * + * @param Quote $quote + * @param Address $shippingAddress + * + * @return array + */ + private function getData(Quote $quote, Address $shippingAddress) : array + { + return [ Totals::KEY_GRAND_TOTAL => $quote->getGrandTotal(), Totals::KEY_BASE_GRAND_TOTAL => $quote->getBaseGrandTotal(), Totals::KEY_SUBTOTAL => $quote->getSubtotal(), @@ -239,17 +241,5 @@ public function testGetMyTotals() Totals::KEY_ITEMS_QTY => $quote->getItemsQty(), Totals::KEY_ITEMS => [$this->getQuoteItemTotalsData($quote)], ]; - - $data = $this->formatTotalsData($data); - $actual = $this->_webApiCall($serviceInfo); - unset($actual['items'][0]['options']); - unset($actual['weee_tax_applied_amount']); - - /** TODO: cover total segments with separate test */ - unset($actual['total_segments']); - if (array_key_exists('extension_attributes', $actual)) { - unset($actual['extension_attributes']); - } - $this->assertEquals($data, $actual); } } diff --git a/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartTotalRepositoryTest.php b/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartTotalRepositoryTest.php index 7ad0e62f29d..28195cca679 100644 --- a/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartTotalRepositoryTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartTotalRepositoryTest.php @@ -63,14 +63,14 @@ public function testGetTotals() $shippingAddress = $quote->getShippingAddress(); $data = [ - Totals::KEY_BASE_GRAND_TOTAL => $quote->getBaseGrandTotal(), Totals::KEY_GRAND_TOTAL => $quote->getGrandTotal(), - Totals::KEY_BASE_SUBTOTAL => $quote->getBaseSubtotal(), + Totals::KEY_BASE_GRAND_TOTAL => $quote->getBaseGrandTotal(), Totals::KEY_SUBTOTAL => $quote->getSubtotal(), - Totals::KEY_BASE_SUBTOTAL_WITH_DISCOUNT => $quote->getBaseSubtotalWithDiscount(), - Totals::KEY_SUBTOTAL_WITH_DISCOUNT => $quote->getSubtotalWithDiscount(), + Totals::KEY_BASE_SUBTOTAL => $quote->getBaseSubtotal(), Totals::KEY_DISCOUNT_AMOUNT => $shippingAddress->getDiscountAmount(), Totals::KEY_BASE_DISCOUNT_AMOUNT => $shippingAddress->getBaseDiscountAmount(), + Totals::KEY_SUBTOTAL_WITH_DISCOUNT => $quote->getSubtotalWithDiscount(), + Totals::KEY_BASE_SUBTOTAL_WITH_DISCOUNT => $quote->getBaseSubtotalWithDiscount(), Totals::KEY_SHIPPING_AMOUNT => $shippingAddress->getShippingAmount(), Totals::KEY_BASE_SHIPPING_AMOUNT => $shippingAddress->getBaseShippingAmount(), Totals::KEY_SHIPPING_DISCOUNT_AMOUNT => $shippingAddress->getShippingDiscountAmount(), @@ -94,6 +94,7 @@ public function testGetTotals() $data = $this->formatTotalsData($data); $actual = $this->_webApiCall($this->getServiceInfoForTotalsService($cartId), $requestData); + $actual = $this->formatTotalsData($actual); unset($actual['items'][0]['options']); unset($actual['weee_tax_applied_amount']); diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderGetTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderGetTest.php index 09c49bed1de..db96728e206 100644 --- a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderGetTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderGetTest.php @@ -133,6 +133,8 @@ public function testOrderGetExtensionAttributes(): void self::assertEquals($expectedTax['type'], $appliedTaxes[0]['type']); self::assertNotEmpty($appliedTaxes[0]['applied_taxes']); self::assertEquals(true, $result['extension_attributes']['converting_from_quote']); + self::assertArrayHasKey('payment_additional_info', $result['extension_attributes']); + self::assertNotEmpty($result['extension_attributes']['payment_additional_info']); } /** diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderItemGetTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderItemGetTest.php index 592bdf3d584..9ba648c7327 100644 --- a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderItemGetTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderItemGetTest.php @@ -106,5 +106,7 @@ public function testGetOrderWithDiscount() $this->assertTrue(is_array($response)); $this->assertEquals(8.00, $response['row_total']); $this->assertEquals(8.00, $response['base_row_total']); + $this->assertEquals(9.00, $response['row_total_incl_tax']); + $this->assertEquals(9.00, $response['base_row_total_incl_tax']); } } diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderListTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderListTest.php index 5050b6be7e5..506f82eab7a 100644 --- a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderListTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderListTest.php @@ -90,6 +90,8 @@ public function testOrderListExtensionAttributes() $this->assertEquals($expectedTax['type'], $appliedTaxes[0]['type']); $this->assertNotEmpty($appliedTaxes[0]['applied_taxes']); $this->assertEquals(true, $result['items'][0]['extension_attributes']['converting_from_quote']); + $this->assertArrayHasKey('payment_additional_info', $result['items'][0]['extension_attributes']); + $this->assertNotEmpty($result['items'][0]['extension_attributes']['payment_additional_info']); } /** diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderStatusHistoryAddTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderStatusHistoryAddTest.php index c5ecead00ce..9e3bd4ca484 100644 --- a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderStatusHistoryAddTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/OrderStatusHistoryAddTest.php @@ -11,6 +11,7 @@ /** * Class OrderCommentAddTest + * * @package Magento\Sales\Service\V1 */ class OrderStatusHistoryAddTest extends WebapiAbstract @@ -48,7 +49,7 @@ public function testOrderCommentAdd() OrderStatusHistoryInterface::CREATED_AT => null, OrderStatusHistoryInterface::PARENT_ID => $order->getId(), OrderStatusHistoryInterface::ENTITY_NAME => null, - OrderStatusHistoryInterface::STATUS => null, + OrderStatusHistoryInterface::STATUS => $order->getStatus(), OrderStatusHistoryInterface::IS_VISIBLE_ON_FRONT => 1, ]; @@ -69,25 +70,27 @@ public function testOrderCommentAdd() //Verification $comments = $order->load($order->getId())->getAllStatusHistory(); + $comment = reset($comments); - $commentData = reset($comments); - foreach ($commentData as $key => $value) { - $this->assertEquals( - $commentData[OrderStatusHistoryInterface::COMMENT], - $statusHistoryComment->getComment() - ); - $this->assertEquals( - $commentData[OrderStatusHistoryInterface::PARENT_ID], - $statusHistoryComment->getParentId() - ); - $this->assertEquals( - $commentData[OrderStatusHistoryInterface::IS_CUSTOMER_NOTIFIED], - $statusHistoryComment->getIsCustomerNotified() - ); - $this->assertEquals( - $commentData[OrderStatusHistoryInterface::IS_VISIBLE_ON_FRONT], - $statusHistoryComment->getIsVisibleOnFront() - ); - } + $this->assertEquals( + $commentData[OrderStatusHistoryInterface::COMMENT], + $comment->getComment() + ); + $this->assertEquals( + $commentData[OrderStatusHistoryInterface::PARENT_ID], + $comment->getParentId() + ); + $this->assertEquals( + $commentData[OrderStatusHistoryInterface::IS_CUSTOMER_NOTIFIED], + $comment->getIsCustomerNotified() + ); + $this->assertEquals( + $commentData[OrderStatusHistoryInterface::IS_VISIBLE_ON_FRONT], + $comment->getIsVisibleOnFront() + ); + $this->assertEquals( + $commentData[OrderStatusHistoryInterface::STATUS], + $comment->getStatus() + ); } } diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/RefundOrderTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/RefundOrderTest.php index 12cbe1ca0e5..8e5373ea765 100644 --- a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/RefundOrderTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/RefundOrderTest.php @@ -5,6 +5,8 @@ */ namespace Magento\Sales\Service\V1; +use Magento\Sales\Model\Order; + /** * API test for creation of Creditmemo for certain Order. */ @@ -86,10 +88,10 @@ public function testShortRequest() 'Failed asserting that proper shipping amount of the Order was refunded' ); - $this->assertNotEquals( - $existingOrder->getStatus(), + $this->assertEquals( + Order::STATE_COMPLETE, $updatedOrder->getStatus(), - 'Failed asserting that order status was changed' + 'Failed asserting that order status has not changed' ); } catch (\Magento\Framework\Exception\NoSuchEntityException $e) { $this->fail('Failed asserting that Creditmemo was created'); diff --git a/dev/tests/functional/lib/Magento/Mtf/App/State/State1.php b/dev/tests/functional/lib/Magento/Mtf/App/State/State1.php index 60abe18f5a7..fc54e73ff1a 100644 --- a/dev/tests/functional/lib/Magento/Mtf/App/State/State1.php +++ b/dev/tests/functional/lib/Magento/Mtf/App/State/State1.php @@ -7,6 +7,7 @@ namespace Magento\Mtf\App\State; use Magento\Mtf\ObjectManager; +use Magento\Mtf\Util\Command\Cli; use Magento\Mtf\Util\Protocol\CurlInterface; use Magento\Mtf\Util\Protocol\CurlTransport; @@ -27,7 +28,7 @@ class State1 extends AbstractState * * @var string */ - protected $config ='admin_session_lifetime_1_hour, wysiwyg_disabled, admin_account_sharing_enable, log_to_file'; + protected $config ='admin_session_lifetime_1_hour, wysiwyg_disabled, admin_account_sharing_enable'; /** * HTTP CURL Adapter. @@ -55,6 +56,7 @@ public function __construct( * Apply set up configuration profile. * * @return void + * @throws \Exception */ public function apply() { @@ -67,6 +69,10 @@ public function apply() ['configData' => $this->config] )->run(); } + + /** @var Cli $cli */ + $cli = $this->objectManager->create(Cli::class); + $cli->execute('setup:config:set', ['--enable-debug-logging=true']); } /** diff --git a/dev/tests/functional/lib/Magento/Mtf/Client/Element/ConditionsElement.php b/dev/tests/functional/lib/Magento/Mtf/Client/Element/ConditionsElement.php index d1fd3513024..6dbf2b1aa6a 100644 --- a/dev/tests/functional/lib/Magento/Mtf/Client/Element/ConditionsElement.php +++ b/dev/tests/functional/lib/Magento/Mtf/Client/Element/ConditionsElement.php @@ -6,9 +6,9 @@ namespace Magento\Mtf\Client\Element; -use Magento\Mtf\ObjectManager; -use Magento\Mtf\Client\Locator; use Magento\Mtf\Client\ElementInterface; +use Magento\Mtf\Client\Locator; +use Magento\Mtf\ObjectManager; /** * Typified element class for conditions. @@ -135,6 +135,13 @@ class ConditionsElement extends SimpleElement */ protected $chooserGridLocator = 'div[id*=chooser]'; + /** + * Datepicker xpath. + * + * @var string + */ + private $datepicker = './/*[contains(@class,"ui-datepicker-trigger")]'; + /** * Key of last find param. * @@ -189,10 +196,7 @@ class ConditionsElement extends SimpleElement protected $exception; /** - * Set value to conditions. - * - * @param string $value - * @return void + * @inheritdoc */ public function setValue($value) { @@ -411,7 +415,16 @@ protected function fillText($rule, ElementInterface $param) { $value = $param->find('input', Locator::SELECTOR_TAG_NAME); if ($value->isVisible()) { - $value->setValue($rule); + if (!$value->getAttribute('readonly')) { + $value->setValue($rule); + } else { + $datepicker = $param->find( + $this->datepicker, + Locator::SELECTOR_XPATH, + DatepickerElement::class + ); + $datepicker->setValue($rule); + } $apply = $param->find('.//*[@class="rule-param-apply"]', Locator::SELECTOR_XPATH); if ($apply->isVisible()) { diff --git a/dev/tests/functional/lib/Magento/Mtf/Client/Element/DatepickerElement.php b/dev/tests/functional/lib/Magento/Mtf/Client/Element/DatepickerElement.php index a0e350cb3da..eb277c2cc43 100644 --- a/dev/tests/functional/lib/Magento/Mtf/Client/Element/DatepickerElement.php +++ b/dev/tests/functional/lib/Magento/Mtf/Client/Element/DatepickerElement.php @@ -66,13 +66,16 @@ public function setValue($value) $date = $this->parseDate($value); $date[1] = ltrim($date[1], '0'); $this->click(); - $this->find($this->datePickerButton, Locator::SELECTOR_XPATH)->click(); $datapicker = $this->find($this->datePickerBlock, Locator::SELECTOR_XPATH); + $datepickerClose = $datapicker->find($this->datePickerButtonClose, Locator::SELECTOR_XPATH); + if (!$datepickerClose->isVisible()) { + $this->find($this->datePickerButton, Locator::SELECTOR_XPATH)->click(); + } $datapicker->find($this->datePickerYear, Locator::SELECTOR_XPATH, 'select')->setValue($date[2]); $datapicker->find($this->datePickerMonth, Locator::SELECTOR_XPATH, 'select')->setValue($date[0]); $datapicker->find(sprintf($this->datePickerCalendar, $date[1]), Locator::SELECTOR_XPATH)->click(); if ($datapicker->isVisible()) { - $datapicker->find($this->datePickerButtonClose, Locator::SELECTOR_XPATH)->click(); + $datepickerClose->click(); } } diff --git a/dev/tests/functional/tests/app/Magento/Backend/Test/Repository/ConfigData.xml b/dev/tests/functional/tests/app/Magento/Backend/Test/Repository/ConfigData.xml index 1792ddb5abd..9985e962b04 100644 --- a/dev/tests/functional/tests/app/Magento/Backend/Test/Repository/ConfigData.xml +++ b/dev/tests/functional/tests/app/Magento/Backend/Test/Repository/ConfigData.xml @@ -174,15 +174,6 @@ <item name="value" xsi:type="number">0</item> </field> </dataset> - - <dataset name="log_to_file"> - <field name="dev/debug/debug_logging" xsi:type="array"> - <item name="scope" xsi:type="string">default</item> - <item name="scope_id" xsi:type="number">0</item> - <item name="label" xsi:type="string">Yes</item> - <item name="value" xsi:type="number">1</item> - </field> - </dataset> <dataset name="minify_js_files"> <field name="dev/js/minify_files" xsi:type="array"> diff --git a/dev/tests/functional/tests/app/Magento/Backend/Test/TestCase/LoginAfterJSMinificationTest.php b/dev/tests/functional/tests/app/Magento/Backend/Test/TestCase/LoginAfterJSMinificationTest.php index 1c3d018af07..4a6202f815b 100644 --- a/dev/tests/functional/tests/app/Magento/Backend/Test/TestCase/LoginAfterJSMinificationTest.php +++ b/dev/tests/functional/tests/app/Magento/Backend/Test/TestCase/LoginAfterJSMinificationTest.php @@ -9,6 +9,7 @@ use Magento\Backend\Test\Page\Adminhtml\Dashboard; use Magento\Mtf\Util\Command\Cli\DeployMode; use Magento\Mtf\TestStep\TestStepFactory; +use Magento\User\Test\TestStep\LoginUserOnBackendStep; /** * Verify visibility of form elements on Configuration page. @@ -53,9 +54,11 @@ public function __inject( } /** - * Admin login test after JS minification is turned on in production mode + * Admin login test after JS minification is turned on in production mode. + * * @param DeployMode $cli * @param null $configData + * * @return void */ public function test( @@ -64,15 +67,26 @@ public function test( ) { $this->configData = $configData; - //Pre-conditions + //Pre-conditions $cli->setDeployModeToDeveloper(); - $this->objectManager->create( + $this->stepFactory->create( \Magento\Config\Test\TestStep\SetupConfigurationStep::class, ['configData' => $this->configData] )->run(); // Steps $cli->setDeployModeToProduction(); - $this->adminDashboardPage->open(); + $this->stepFactory->create(LoginUserOnBackendStep::class)->run(); + } + + /** + * @inheritdoc + */ + protected function tearDown() + { + $this->stepFactory->create( + \Magento\Config\Test\TestStep\SetupConfigurationStep::class, + ['configData' => $this->configData] + )->cleanup(); } } diff --git a/dev/tests/functional/tests/app/Magento/Bundle/Test/Fixture/Cart/Item.php b/dev/tests/functional/tests/app/Magento/Bundle/Test/Fixture/Cart/Item.php index 9b12c467e57..4d6d06ac6e6 100644 --- a/dev/tests/functional/tests/app/Magento/Bundle/Test/Fixture/Cart/Item.php +++ b/dev/tests/functional/tests/app/Magento/Bundle/Test/Fixture/Cart/Item.php @@ -46,7 +46,7 @@ public function getData($key = null) $optionData = [ 'title' => $checkoutOption['title'], 'value' => "{$qty} x {$value} {$price}", - 'sku' => "{$qty} x {$value}" + 'sku' => "{$value}" ]; $checkoutBundleOptions[$checkoutOptionKey] = $optionData; diff --git a/dev/tests/functional/tests/app/Magento/Bundle/Test/TestCase/DeleteProductEntityTest.xml b/dev/tests/functional/tests/app/Magento/Bundle/Test/TestCase/DeleteProductEntityTest.xml index 45e255649d2..157135117fb 100644 --- a/dev/tests/functional/tests/app/Magento/Bundle/Test/TestCase/DeleteProductEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Bundle/Test/TestCase/DeleteProductEntityTest.xml @@ -8,6 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Catalog\Test\TestCase\Product\DeleteProductEntityTest"> <variation name="DeleteProductEntityTestVariation4"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="products" xsi:type="string">bundleProduct::bundle_dynamic_product</data> <data name="isRequired" xsi:type="string">Yes</data> <constraint name="Magento\Catalog\Test\Constraint\AssertProductSuccessDeleteMessage" /> @@ -15,6 +16,7 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertProductIsNotDisplayingOnFrontend" /> </variation> <variation name="DeleteProductEntityTestVariation5"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="products" xsi:type="string">bundleProduct::bundle_fixed_product</data> <data name="isRequired" xsi:type="string">No</data> <constraint name="Magento\Catalog\Test\Constraint\AssertProductSuccessDeleteMessage" /> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Edit/Section/Options.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Edit/Section/Options.php index 2ac4fb81ae6..d591f3b4446 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Edit/Section/Options.php +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Adminhtml/Product/Edit/Section/Options.php @@ -403,4 +403,21 @@ public function getFileOptionElements() { return $this->_rootElement->getElements($this->hintMessage); } + + /** + * @inheritdoc + */ + protected function _fill(array $fields, SimpleElement $element = null) + { + $context = ($element === null) ? $this->_rootElement : $element; + foreach ($fields as $name => $field) { + $element = $this->getElement($context, $field); + if (!$element->isDisabled()) { + $element->getContext()->hover(); + $element->setValue($field['value']); + } else { + throw new \Exception("Unable to set value to field '$name' as it's disabled."); + } + } + } } diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Search.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Search.php index 7ca5bfd2be1..a34b97b4ce2 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Search.php +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Search.php @@ -10,7 +10,6 @@ use Magento\Mtf\Client\Locator; /** - * Class Search * Block for "Search" section */ class Search extends Block @@ -77,6 +76,7 @@ public function search($keyword, $length = null) $keyword = substr($keyword, 0, $length); } $this->fillSearch($keyword); + $this->waitForElementEnabled($this->searchButton); $this->_rootElement->find($this->searchButton)->click(); } @@ -157,4 +157,24 @@ public function clickSuggestedText($text) $searchAutocomplete = sprintf($this->searchAutocomplete, $text); $this->_rootElement->find($searchAutocomplete, Locator::SELECTOR_XPATH)->click(); } + + /** + * Wait for element is enabled. + * + * @param string $selector + * @param string $strategy + * @return bool|null + */ + public function waitForElementEnabled($selector, $strategy = Locator::SELECTOR_CSS) + { + $browser = $this->browser; + + return $browser->waitUntil( + function () use ($browser, $selector, $strategy) { + $element = $browser->find($selector, $strategy); + + return !$element->isDisabled() ? true : null; + } + ); + } } diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Category/AdvancedMoveCategoryEntityTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Category/AdvancedMoveCategoryEntityTest.xml index 96a9d91a8e5..e92edf4a143 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Category/AdvancedMoveCategoryEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Category/AdvancedMoveCategoryEntityTest.xml @@ -8,6 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Catalog\Test\TestCase\Category\AdvancedMoveCategoryEntityTest" summary="Move category from one to another" ticketId="MAGETWO-27319"> <variation name="AdvancedMoveCategoryEntityTestVariation1"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="childCategory/dataset" xsi:type="string">three_nested_categories</data> <data name="parentCategory/dataset" xsi:type="string">default</data> <data name="moveLevel" xsi:type="number">1</data> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Category/DeleteCategoryEntityTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Category/DeleteCategoryEntityTest.xml index 6951194308b..77ed04d40b7 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Category/DeleteCategoryEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Category/DeleteCategoryEntityTest.xml @@ -8,11 +8,13 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Catalog\Test\TestCase\Category\DeleteCategoryEntityTest" summary="Delete Category" ticketId="MAGETWO-23303"> <variation name="DeleteCategoryEntityTestVariation1_RootCategory" summary="Can delete a root category not assigned to any store"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="category/dataset" xsi:type="string">root_category</data> <constraint name="Magento\Catalog\Test\Constraint\AssertCategorySuccessDeleteMessage" /> <constraint name="Magento\Catalog\Test\Constraint\AssertCategoryAbsenceOnBackend" /> </variation> <variation name="DeleteCategoryEntityTestVariation2_Subcategory" summary="Can delete a subcategory"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="category/dataset" xsi:type="string">root_subcategory</data> <constraint name="Magento\Catalog\Test\Constraint\AssertCategorySuccessDeleteMessage" /> <constraint name="Magento\UrlRewrite\Test\Constraint\AssertUrlRewriteCategoryNotInGrid" /> @@ -20,6 +22,7 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertCategoryAbsenceOnFrontend" /> </variation> <variation name="DeleteCategoryEntityTestVariation3_RootCategory_AssignedToStore" summary="Cannot delete root category assigned to some store"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="category/dataset" xsi:type="string">default_category</data> <constraint name="Magento\Catalog\Test\Constraint\AssertCategoryCannotBeDeleted" /> </variation> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Category/MoveCategoryEntityTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Category/MoveCategoryEntityTest.xml index 446011902c0..a5758fe1d13 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Category/MoveCategoryEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Category/MoveCategoryEntityTest.xml @@ -8,6 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Catalog\Test\TestCase\Category\MoveCategoryEntityTest" summary="Move category from one to another" ticketId="MAGETWO-27319"> <variation name="MoveCategoryEntityTestVariation1"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="childCategory/dataset" xsi:type="string">three_nested_categories</data> <data name="parentCategory/dataset" xsi:type="string">default</data> <data name="nestingLevel" xsi:type="string">3</data> @@ -15,6 +16,7 @@ <constraint name="Magento\UrlRewrite\Test\Constraint\AssertUrlRewriteCategoryInGrid" /> </variation> <variation name="MoveCategoryEntityTestVariation2" summary="Move default subcategory with anchored parent to default subcategory" ticketId="MAGETWO-21202"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="issue" xsi:type="string">MAGETWO-65147: Category is not present in Layered navigation block when anchor is on</data> <data name="childCategory/dataset" xsi:type="string">default_subcategory_with_anchored_parent</data> <data name="parentCategory/dataset" xsi:type="string">default</data> @@ -25,6 +27,7 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertCategoryBreadcrumbs" /> </variation> <variation name="MoveCategoryEntityTestVariation3" summary="Move default anchored subcategory with anchored parent to default subcategory" ticketId="MAGETWO-21202"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="issue" xsi:type="string">MAGETWO-65147: Category is not present in Layered navigation block when anchor is on</data> <data name="childCategory/dataset" xsi:type="string">default_subcategory_with_anchored_parent</data> <data name="childCategory/data/parent_id/dataset" xsi:type="string">default_anchor_subcategory_with_anchored_parent</data> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Category/UpdateCategoryEntityFlatDataTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Category/UpdateCategoryEntityFlatDataTest.xml index 5f14f579a42..99f4b6718fe 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Category/UpdateCategoryEntityFlatDataTest.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Category/UpdateCategoryEntityFlatDataTest.xml @@ -8,6 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Catalog\Test\TestCase\Category\UpdateCategoryEntityFlatDataTest" summary="Update Category if Use Category Flat (Cron is ON, 'Update on Save' Mode)" ticketId="MAGETWO-20169"> <variation name="UpdateCategoryEntityFlatDataTestVariation1" summary="Update Category with custom name and description"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="initialCategory/dataset" xsi:type="string">default</data> <data name="category/data/name" xsi:type="string">Name%isolation%</data> <data name="category/data/description" xsi:type="string">Category Description Updated</data> @@ -23,6 +24,7 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertCategoryPage" /> </variation> <variation name="UpdateCategoryEntityFlatDataTestVariation2" summary="Include category to navigation menu"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="initialCategory/dataset" xsi:type="string">default</data> <data name="initialCategory/data/include_in_menu" xsi:type="string">No</data> <data name="category/data/include_in_menu" xsi:type="string">Yes</data> @@ -37,6 +39,7 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertCategoryInNavigationMenu" /> </variation> <variation name="UpdateCategoryEntityFlatDataTestVariation3" summary="Update category and assert assigned products"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="initialCategory/dataset" xsi:type="string">default</data> <data name="category/data/category_products/dataset" xsi:type="string">catalogProductSimple::default</data> <data name="indexers/0" xsi:type="string">Category Flat Data</data> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Category/UpdateCategoryEntityTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Category/UpdateCategoryEntityTest.xml index 76d5a532271..1cda62997e1 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Category/UpdateCategoryEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Category/UpdateCategoryEntityTest.xml @@ -8,6 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Catalog\Test\TestCase\Category\UpdateCategoryEntityTest" summary="Update Category" ticketId="MAGETWO-23290"> <variation name="UpdateCategoryEntityTestVariation1_Name_Description_UrlKey_MetaTitle_ExcludeFromMenu"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="category/data/parent_id/dataset" xsi:type="string">default_category</data> <data name="category/data/name" xsi:type="string">Name%isolation%</data> <data name="category/data/include_in_menu" xsi:type="string">No</data> @@ -22,6 +23,7 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertCategoryPage" /> </variation> <variation name="UpdateCategoryEntityTestVariation2_SortProductsBy_DefaultProductSorting_AddProduct"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="category/data/parent_id/dataset" xsi:type="string">default_category</data> <data name="category/data/available_product_listing_config" xsi:type="string">Yes</data> <data name="category/data/default_product_listing_config" xsi:type="string">No</data> @@ -36,6 +38,7 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertCategoryForAssignedProducts" /> </variation> <variation name="UpdateCategoryEntityTestVariation3_MakeInactive"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="category/data/parent_id/dataset" xsi:type="string">default_category</data> <data name="category/data/is_active" xsi:type="string">No</data> <data name="category/data/name" xsi:type="string">Name%isolation%</data> @@ -44,6 +47,7 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertCategoryIsNotActive" /> </variation> <variation name="UpdateCategoryEntityTestVariation4_ChangeCategoryNameOnStoreView" summary="Update Category with custom Store View."> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="category/data/store_id/dataset" xsi:type="string">custom</data> <data name="category/data/use_default_name" xsi:type="string">No</data> <data name="category/data/name" xsi:type="string">Category %isolation%</data> @@ -51,6 +55,7 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertCategoryWithCustomStoreOnFrontend" /> </variation> <variation name="UpdateCategoryEntityTestVariation5_ChangeCategoryUrlOnStoreView" summary="Update URL Key with custom Store View." ticketId="MAGETWO-16471"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="category/data/parent_id/dataset" xsi:type="string">default_category</data> <data name="category/data/store_id/dataset" xsi:type="string">custom</data> <data name="category/data/use_default_url_key" xsi:type="string">No</data> @@ -59,6 +64,7 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertCategoryOnCustomStore" /> </variation> <variation name="UpdateCategoryEntityTestVariation6_CheckCategoryDefaultUrlOnStoreView" summary="Check default URL Key on the custom Store View." ticketId="MAGETWO-64337"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="initialCategory/dataset" xsi:type="string">default_with_custom_url</data> <data name="category/data/parent_id/dataset" xsi:type="string">default_category</data> <data name="category/data/store_id/dataset" xsi:type="string">custom</data> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Category/UpdateInactiveCategoryEntityFlatDataTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Category/UpdateInactiveCategoryEntityFlatDataTest.xml index fd2c9afaea1..0c7d88cc920 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Category/UpdateInactiveCategoryEntityFlatDataTest.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Category/UpdateInactiveCategoryEntityFlatDataTest.xml @@ -8,6 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Catalog\Test\TestCase\Category\UpdateInactiveCategoryEntityFlatDataTest" summary="Update Category if Use Category Flat (Cron is ON, 'Update on Save' Mode)" ticketId="MAGETWO-20169"> <variation name="UpdateInactiveCategoryEntityFlatDataTestVariation1" summary="Inactive category and check that category is absent on frontend"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="initialCategory/dataset" xsi:type="string">default</data> <data name="category/data/is_active" xsi:type="string">No</data> <data name="indexers/0" xsi:type="string">Category Flat Data</data> @@ -22,6 +23,7 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertCategoryAbsenceOnFrontend" /> </variation> <variation name="UpdateInactiveCategoryEntityFlatDataTestVariation2" summary="Inactive category and check that category is not active on frontend"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="initialCategory/dataset" xsi:type="string">default</data> <data name="initialCategory/data/is_active" xsi:type="string">No</data> <data name="category/data/is_active" xsi:type="string">No</data> @@ -36,6 +38,7 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertCategoryIsNotActive" /> </variation> <variation name="UpdateInactiveCategoryEntityFlatDataTestVariation3" summary="Exclude category from navigation menu"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="initialCategory/dataset" xsi:type="string">default</data> <data name="category/data/include_in_menu" xsi:type="string">No</data> <data name="indexers/0" xsi:type="string">Category Flat Data</data> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Category/UpdateTopCategoryEntityTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Category/UpdateTopCategoryEntityTest.xml index 9126b619bbf..e8a5fd355da 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Category/UpdateTopCategoryEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Category/UpdateTopCategoryEntityTest.xml @@ -8,6 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Catalog\Test\TestCase\Category\UpdateTopCategoryEntityTest" summary="Update top category url" ticketId="MAGETWO-27327"> <variation name="UpdateCategoryEntityTestVariation1" summary="Update top category url and do not create redirect"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="initialCategory/dataset" xsi:type="string">three_nested_categories</data> <data name="nestingLevel" xsi:type="number">3</data> <data name="category/data/url_key" xsi:type="string">cat1-rewrite%isolation%</data> @@ -17,6 +18,7 @@ <constraint name="Magento\UrlRewrite\Test\Constraint\AssertUrlRewritesCategoriesInGrid" /> </variation> <variation name="UpdateCategoryEntityTestVariation2" summary="Update top category url and create redirect"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="initialCategory/dataset" xsi:type="string">three_nested_categories</data> <data name="nestingLevel" xsi:type="number">3</data> <data name="category/data/url_key" xsi:type="string">cat1-rewrite%isolation%</data> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateFlatCatalogProduct.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateFlatCatalogProductTest.php similarity index 98% rename from dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateFlatCatalogProduct.php rename to dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateFlatCatalogProductTest.php index cb5ad93ee42..8f11f31a6df 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateFlatCatalogProduct.php +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateFlatCatalogProductTest.php @@ -26,7 +26,7 @@ * * @ZephyrId MAGETWO-67570 */ -class CreateFlatCatalogProduct extends Injectable +class CreateFlatCatalogProductTest extends Injectable { /* tags */ const MVP = 'yes'; diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateFlatCatalogProduct.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateFlatCatalogProductTest.xml similarity index 82% rename from dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateFlatCatalogProduct.xml rename to dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateFlatCatalogProductTest.xml index 17d362f35ec..45161e1471f 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateFlatCatalogProduct.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateFlatCatalogProductTest.xml @@ -6,8 +6,9 @@ */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/mtf/etc/variations.xsd"> - <testCase name="Magento\Catalog\Test\TestCase\Product\CreateFlatCatalogProduct" summary="Create flat catalog Product" ticketId="MAGETWO-67570"> + <testCase name="Magento\Catalog\Test\TestCase\Product\CreateFlatCatalogProductTest" summary="Create flat catalog Product" ticketId="MAGETWO-67570"> <variation name="CheckPaginationInStorefront" ticketId="MAGETWO-67570"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="configData" xsi:type="string">category_flat,product_flat</data> <data name="productsCount" xsi:type="number">19</data> <constraint name="Magento\Catalog\Test\Constraint\AssertPaginationCorrectOnStoreFront" /> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateSimpleProductEntityByAttributeMaskSkuTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateSimpleProductEntityByAttributeMaskSkuTest.xml index aecdbc5362f..bdea332a3af 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateSimpleProductEntityByAttributeMaskSkuTest.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateSimpleProductEntityByAttributeMaskSkuTest.xml @@ -8,6 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Catalog\Test\TestCase\Product\CreateSimpleProductEntityByAttributeMaskSkuTest" summary="Create Simple Product with attribute sku mask" ticketId="MAGETWO-59861"> <variation name="CreateSimpleProductEntityByAttributeMaskSkuTest1"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="configData" xsi:type="string">attribute_product_mask_sku</data> <data name="description" xsi:type="string">Create product with country of manufacture attribute sku mask</data> <data name="product/data/url_key" xsi:type="string">simple-product-%isolation%</data> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateVirtualProductEntityTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateVirtualProductEntityTest.xml index 1449e7df0ce..a9c78117d7b 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateVirtualProductEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateVirtualProductEntityTest.xml @@ -8,6 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Catalog\Test\TestCase\Product\CreateVirtualProductEntityTest" summary="Create Virtual Product" ticketId="MAGETWO-23417"> <variation name="CreateVirtualProductEntityTestVariation1"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="description" xsi:type="string">Create product with required fields</data> <data name="product/data/url_key" xsi:type="string">virtual-product-%isolation%</data> <data name="product/data/name" xsi:type="string">VirtualProduct %isolation%</data> @@ -17,7 +18,7 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertProductInGrid" /> </variation> <variation name="CreateVirtualProductEntityTestVariation2" summary="Create product with tier price"> - <data name="tag" xsi:type="string">test_type:extended_acceptance_test</data> + <data name="tag" xsi:type="string">test_type:extended_acceptance_test, mftf_migrated:yes</data> <data name="product/data/url_key" xsi:type="string">virtual-product-%isolation%</data> <data name="product/data/name" xsi:type="string">VirtualProduct %isolation%</data> <data name="product/data/sku" xsi:type="string">virtual_sku_%isolation%</data> @@ -51,6 +52,7 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertProductSearchableBySku" /> </variation> <variation name="CreateVirtualProductEntityTestVariation4"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="description" xsi:type="string">Create product with tier price for "General" group</data> <data name="product/data/url_key" xsi:type="string">virtual-product-%isolation%</data> <data name="product/data/name" xsi:type="string">VirtualProduct %isolation%</data> @@ -71,6 +73,7 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertProductTierPriceOnProductPageWithCustomer" /> </variation> <variation name="CreateVirtualProductEntityTestVariation5"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="description" xsi:type="string">Create product with custom options suite and import options</data> <data name="product/data/url_key" xsi:type="string">virtual-product-%isolation%</data> <data name="product/data/name" xsi:type="string">VirtualProduct %isolation%</data> @@ -86,6 +89,7 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertProductCustomOptionsOnProductPage" /> </variation> <variation name="CreateVirtualProductEntityTestVariation6"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="description" xsi:type="string">Create product without manage stock</data> <data name="product/data/url_key" xsi:type="string">virtual-product-%isolation%</data> <data name="product/data/name" xsi:type="string">VirtualProduct %isolation%</data> @@ -102,6 +106,7 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertProductInStock" /> </variation> <variation name="CreateVirtualProductEntityTestVariation7"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="description" xsi:type="string">Create product out of stock with tier price</data> <data name="product/data/url_key" xsi:type="string">virtual-product-%isolation%</data> <data name="product/data/name" xsi:type="string">VirtualProduct %isolation%</data> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/DeleteProductEntityTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/DeleteProductEntityTest.xml index fc566e855c0..2d91f4a7024 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/DeleteProductEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/DeleteProductEntityTest.xml @@ -10,12 +10,13 @@ <variation name="DeleteProductEntityTestVariation1"> <data name="products" xsi:type="string">catalogProductSimple::default</data> <data name="isRequired" xsi:type="string">Yes</data> - <data name="tag" xsi:type="string">to_maintain:yes</data> + <data name="tag" xsi:type="string">to_maintain:yes, mftf_migrated:yes</data> <constraint name="Magento\Catalog\Test\Constraint\AssertProductSuccessDeleteMessage" /> <constraint name="Magento\Catalog\Test\Constraint\AssertProductNotInGrid" /> <constraint name="Magento\Catalog\Test\Constraint\AssertProductIsNotDisplayingOnFrontend" /> </variation> <variation name="DeleteProductEntityTestVariation2"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="products" xsi:type="string">catalogProductVirtual::default</data> <data name="isRequired" xsi:type="string">Yes</data> <constraint name="Magento\Catalog\Test\Constraint\AssertProductSuccessDeleteMessage" /> @@ -23,6 +24,7 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertProductIsNotDisplayingOnFrontend" /> </variation> <variation name="DeleteProductEntityTestVariation3"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="products" xsi:type="string">catalogProductSimple::with_one_custom_option</data> <data name="isRequired" xsi:type="string">Yes</data> <constraint name="Magento\Catalog\Test\Constraint\AssertProductSuccessDeleteMessage" /> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/MassProductUpdateStatusTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/MassProductUpdateStatusTest.xml index fa82fd90268..6936315a128 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/MassProductUpdateStatusTest.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/MassProductUpdateStatusTest.xml @@ -8,6 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Catalog\Test\TestCase\Product\MassProductUpdateStatusTest" summary="Update status of Products Using Mass Actions" ticketId="MAGETWO-60847"> <variation name="MassProductStatusUpdateTestVariation1"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="initialProducts" xsi:type="array"> <item name ="0" xsi:type="string">catalogProductSimple::simple_10_dollar</item> <item name ="1" xsi:type="string">catalogProductSimple::simple_10_dollar</item> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/MassProductUpdateTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/MassProductUpdateTest.xml index 6f3803d832c..d2fe51ecd81 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/MassProductUpdateTest.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/MassProductUpdateTest.xml @@ -8,6 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Catalog\Test\TestCase\Product\MassProductUpdateTest" summary="Edit Products Using Mass Actions" ticketId="MAGETWO-21128"> <variation name="MassProductPriceUpdateTestVariation1"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="configData" xsi:type="string">product_flat</data> <data name="initialProducts/0" xsi:type="string">catalogProductSimple::simple_10_dollar</data> <data name="initialProducts/1" xsi:type="string">catalogProductSimple::simple_10_dollar</data> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/NavigateUpSellProductsTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/NavigateUpSellProductsTest.xml index cf52597cfc5..0db197ba3b3 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/NavigateUpSellProductsTest.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/NavigateUpSellProductsTest.xml @@ -8,7 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Catalog\Test\TestCase\Product\NavigateUpSellProductsTest" summary="Promote Products as Up-Sells" ticketId="MAGETWO-12391"> <variation name="NavigateUpSellProductsTestVariation1" method="test"> - <data name="tag" xsi:type="string">test_type:acceptance_test, test_type:extended_acceptance_test, stable:no</data> + <data name="tag" xsi:type="string">test_type:acceptance_test, test_type:extended_acceptance_test, stable:no, mftf_migrated:yes</data> <data name="products" xsi:type="string">simple1::catalogProductSimple::product_with_category,simple2::catalogProductSimple::product_with_category,config1::configurableProduct::two_options_with_fixed_price</data> <data name="promotedProducts" xsi:type="string">simple1:simple2,config1;config1:simple2</data> <data name="navigateProductsOrder" xsi:type="string">simple1,config1,simple2</data> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ProductTypeSwitchingOnUpdateTest.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ProductTypeSwitchingOnUpdateTest.php index 43741393e79..90cd6bdb763 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ProductTypeSwitchingOnUpdateTest.php +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ProductTypeSwitchingOnUpdateTest.php @@ -143,5 +143,6 @@ protected function clearDownloadableData() /** @var Downloadable $downloadableInfoTab */ $downloadableInfoTab = $this->catalogProductEdit->getProductForm()->getSection('downloadable_information'); $downloadableInfoTab->getDownloadableBlock('Links')->clearDownloadableData(); + $downloadableInfoTab->setIsDownloadable('No'); } } diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ProductTypeSwitchingOnUpdateTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ProductTypeSwitchingOnUpdateTest.xml index f3df374a8ba..5fa1cfe5e59 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ProductTypeSwitchingOnUpdateTest.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ProductTypeSwitchingOnUpdateTest.xml @@ -11,7 +11,6 @@ <data name="productOrigin" xsi:type="string">catalogProductSimple::default</data> <data name="product" xsi:type="string">configurableProduct::default</data> <data name="actionName" xsi:type="string">-</data> - <data name="tag" xsi:type="string">to_maintain:yes</data> <constraint name="Magento\Catalog\Test\Constraint\AssertProductSaveMessage" /> <constraint name="Magento\Catalog\Test\Constraint\AssertProductInGrid" /> <constraint name="Magento\ConfigurableProduct\Test\Constraint\AssertChildProductsInGrid" /> @@ -21,7 +20,6 @@ <constraint name="Magento\ConfigurableProduct\Test\Constraint\AssertChildProductIsNotDisplayedSeparately" /> </variation> <variation name="ProductTypeSwitchingOnUpdateTestVariation2"> - <data name="tag" xsi:type="string">to_maintain:yes</data> <data name="productOrigin" xsi:type="string">catalogProductSimple::default</data> <data name="product" xsi:type="string">catalogProductVirtual::default</data> <data name="actionName" xsi:type="string">-</data> @@ -29,7 +27,6 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertProductInGrid" /> </variation> <variation name="ProductTypeSwitchingOnUpdateTestVariation3"> - <data name="tag" xsi:type="string">stable:no</data> <data name="productOrigin" xsi:type="string">configurableProduct::default</data> <data name="product" xsi:type="string">catalogProductSimple::product_without_category</data> <data name="actionName" xsi:type="string">deleteVariations</data> @@ -40,12 +37,10 @@ <data name="productOrigin" xsi:type="string">configurableProduct::default</data> <data name="product" xsi:type="string">catalogProductVirtual::required_fields</data> <data name="actionName" xsi:type="string">deleteVariations</data> - <data name="tag" xsi:type="string">to_maintain:yes</data> <constraint name="Magento\Catalog\Test\Constraint\AssertProductSaveMessage" /> <constraint name="Magento\Catalog\Test\Constraint\AssertProductInGrid" /> </variation> <variation name="ProductTypeSwitchingOnUpdateTestVariation5"> - <data name="tag" xsi:type="string">to_maintain:yes</data> <data name="productOrigin" xsi:type="string">catalogProductVirtual::default</data> <data name="product" xsi:type="string">catalogProductSimple::default</data> <data name="actionName" xsi:type="string">-</data> @@ -56,7 +51,6 @@ <data name="productOrigin" xsi:type="string">catalogProductVirtual::default</data> <data name="product" xsi:type="string">configurableProduct::not_virtual_for_type_switching</data> <data name="actionName" xsi:type="string">-</data> - <data name="tag" xsi:type="string">to_maintain:yes</data> <constraint name="Magento\Catalog\Test\Constraint\AssertProductSaveMessage" /> <constraint name="Magento\Catalog\Test\Constraint\AssertProductInGrid" /> <constraint name="Magento\ConfigurableProduct\Test\Constraint\AssertChildProductsInGrid" /> @@ -69,7 +63,6 @@ <data name="productOrigin" xsi:type="string">catalogProductVirtual::default</data> <data name="product" xsi:type="string">downloadableProduct::default</data> <data name="actionName" xsi:type="string">-</data> - <data name="tag" xsi:type="string">to_maintain:yes</data> <constraint name="Magento\Catalog\Test\Constraint\AssertProductSaveMessage" /> <constraint name="Magento\Catalog\Test\Constraint\AssertProductInGrid" /> <constraint name="Magento\Downloadable\Test\Constraint\AssertDownloadableProductForm" /> @@ -81,15 +74,13 @@ <data name="productOrigin" xsi:type="string">downloadableProduct::default</data> <data name="product" xsi:type="string">catalogProductSimple::default</data> <data name="actionName" xsi:type="string">-</data> - <data name="tag" xsi:type="string">to_maintain:yes</data> <constraint name="Magento\Catalog\Test\Constraint\AssertProductSaveMessage" /> <constraint name="Magento\Catalog\Test\Constraint\AssertProductInGrid" /> </variation> <variation name="ProductTypeSwitchingOnUpdateTestVariation9"> <data name="productOrigin" xsi:type="string">downloadableProduct::default</data> <data name="product" xsi:type="string">configurableProduct::not_virtual_for_type_switching</data> - <data name="actionName" xsi:type="string">-</data> - <data name="tag" xsi:type="string">to_maintain:yes</data> + <data name="actionName" xsi:type="string">clearDownloadableData</data> <constraint name="Magento\Catalog\Test\Constraint\AssertProductSaveMessage" /> <constraint name="Magento\Catalog\Test\Constraint\AssertProductInGrid" /> <constraint name="Magento\ConfigurableProduct\Test\Constraint\AssertChildProductsInGrid" /> @@ -99,7 +90,6 @@ <constraint name="Magento\ConfigurableProduct\Test\Constraint\AssertChildProductIsNotDisplayedSeparately" /> </variation> <variation name="ProductTypeSwitchingOnUpdateTestVariation10"> - <data name="tag" xsi:type="string">stable:no</data> <data name="productOrigin" xsi:type="string">downloadableProduct::default</data> <data name="product" xsi:type="string">catalogProductVirtual::default</data> <data name="actionName" xsi:type="string">clearDownloadableData</data> @@ -110,7 +100,6 @@ <data name="productOrigin" xsi:type="string">catalogProductSimple::default</data> <data name="product" xsi:type="string">downloadableProduct::default</data> <data name="actionName" xsi:type="string">-</data> - <data name="tag" xsi:type="string">to_maintain:yes</data> <constraint name="Magento\Catalog\Test\Constraint\AssertProductSaveMessage" /> <constraint name="Magento\Catalog\Test\Constraint\AssertProductInGrid" /> <constraint name="Magento\Downloadable\Test\Constraint\AssertDownloadableProductForm" /> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/UpdateVirtualProductEntityTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/UpdateVirtualProductEntityTest.xml index daa19a8fe6f..e2715bb6b4b 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/UpdateVirtualProductEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/UpdateVirtualProductEntityTest.xml @@ -8,6 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Catalog\Test\TestCase\Product\UpdateVirtualProductEntityTest" summary="Update Virtual Product" ticketId="MAGETWO-26204"> <variation name="UpdateVirtualProductEntityTestVariation1"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="product/data/name" xsi:type="string">VirtualProduct %isolation%</data> <data name="product/data/sku" xsi:type="string">virtual_sku_%isolation%</data> <data name="product/data/price/value" xsi:type="string">99.99</data> @@ -28,6 +29,7 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertProductSearchableBySku" /> </variation> <variation name="UpdateVirtualProductEntityTestVariation2"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="product/data/name" xsi:type="string">virtual_product_%isolation%</data> <data name="product/data/sku" xsi:type="string">virtual_sku_%isolation%</data> <data name="product/data/price/value" xsi:type="string">120.00</data> @@ -47,6 +49,7 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertProductSearchableBySku" /> </variation> <variation name="UpdateVirtualProductEntityTestVariation3"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="product/data/name" xsi:type="string">VirtualProduct %isolation%</data> <data name="product/data/sku" xsi:type="string">virtual_sku_%isolation%</data> <data name="product/data/price/value" xsi:type="string">185.00</data> @@ -67,6 +70,7 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertProductSearchableBySku" /> </variation> <variation name="UpdateVirtualProductEntityTestVariation4"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="product/data/name" xsi:type="string">virtual_product_%isolation%</data> <data name="product/data/sku" xsi:type="string">virtual_sku_%isolation%</data> <data name="product/data/price/value" xsi:type="string">99.99</data> @@ -82,6 +86,7 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertProductSearchableBySku" /> </variation> <variation name="UpdateVirtualProductEntityTestVariation5"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="product/data/name" xsi:type="string">VirtualProduct %isolation%</data> <data name="product/data/sku" xsi:type="string">virtual_sku_%isolation%</data> <data name="product/data/price/value" xsi:type="string">5.00</data> @@ -97,6 +102,7 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertProductSearchableBySku" /> </variation> <variation name="UpdateVirtualProductEntityTestVariation6"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="product/data/name" xsi:type="string">virtual_product_%isolation%</data> <data name="product/data/sku" xsi:type="string">virtual_sku_%isolation%</data> <data name="product/data/price/value" xsi:type="string">145.00</data> @@ -116,6 +122,7 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertProductSearchableBySku" /> </variation> <variation name="UpdateVirtualProductEntityTestVariation7"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="product/data/name" xsi:type="string">VirtualProduct %isolation%</data> <data name="product/data/sku" xsi:type="string">virtual_sku_%isolation%</data> <data name="product/data/price/value" xsi:type="string">99.99</data> @@ -133,6 +140,7 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertProductSearchableBySku" /> </variation> <variation name="UpdateVirtualProductEntityTestVariation8"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="product/data/name" xsi:type="string">virtual_product_%isolation%</data> <data name="product/data/sku" xsi:type="string">virtual_sku_%isolation%</data> <data name="product/data/price/value" xsi:type="string">5.00</data> @@ -149,6 +157,7 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertProductSearchableBySku" /> </variation> <variation name="UpdateVirtualProductEntityTestVariation9"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="product/data/name" xsi:type="string">VirtualProduct %isolation%</data> <data name="product/data/sku" xsi:type="string">virtual_sku_%isolation%</data> <data name="product/data/price/value" xsi:type="string">120.00</data> @@ -168,6 +177,7 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertProductSearchableBySku" /> </variation> <variation name="UpdateVirtualProductEntityTestVariation10"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="product/data/name" xsi:type="string">VirtualProduct %isolation%</data> <data name="product/data/sku" xsi:type="string">virtual_sku_%isolation%</data> <data name="product/data/price/value" xsi:type="string">99.99</data> @@ -183,6 +193,7 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertProductSearchableBySku" /> </variation> <variation name="UpdateVirtualProductEntityTestVariation11"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="product/data/name" xsi:type="string">VirtualProduct %isolation%</data> <data name="product/data/sku" xsi:type="string">virtual_sku_%isolation%</data> <data name="product/data/price/value" xsi:type="string">99.99</data> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/DeleteAttributeSetTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/DeleteAttributeSetTest.xml index 361f2acb1d8..a83fce14d93 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/DeleteAttributeSetTest.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/DeleteAttributeSetTest.xml @@ -8,7 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Catalog\Test\TestCase\ProductAttribute\DeleteAttributeSetTest" summary="Delete Attribute Set (Attribute Set)" ticketId="MAGETWO-25473"> <variation name="DeleteAttributeSetTestVariation1"> - <data name="tag" xsi:type="string">stable:no</data> + <data name="tag" xsi:type="string">stable:no, mftf_migrated:yes</data> <data name="attributeSet/dataset" xsi:type="string">custom_attribute_set</data> <data name="attributeSet/data/assigned_attributes/dataset" xsi:type="string">default</data> <data name="product/dataset" xsi:type="string">default</data> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/DeleteProductAttributeEntityTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/DeleteProductAttributeEntityTest.xml index 6cca4b3f368..11ba7266ce5 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/DeleteProductAttributeEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/DeleteProductAttributeEntityTest.xml @@ -8,6 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Catalog\Test\TestCase\ProductAttribute\DeleteProductAttributeEntityTest" summary="Delete Product Attribute" ticketId="MAGETWO-24998"> <variation name="DeleteProductAttributeEntityTestVariation1"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="attribute/dataset" xsi:type="string">attribute_type_text_field</data> <constraint name="Magento\Catalog\Test\Constraint\AssertProductAttributeSuccessDeleteMessage" /> <constraint name="Magento\Catalog\Test\Constraint\AssertProductAttributeAbsenceInGrid" /> diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/OnePageCheckoutJsValidationTest.xml b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/OnePageCheckoutJsValidationTest.xml index 6635c1edbe7..75603d12cbe 100644 --- a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/OnePageCheckoutJsValidationTest.xml +++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/OnePageCheckoutJsValidationTest.xml @@ -8,6 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Checkout\Test\TestCase\OnePageCheckoutJsValidationTest" summary="JS validation verification for Checkout flow" ticketId="MAGETWO-59697"> <variation name="OnePageCheckoutJsValidationTestVariation1" summary="JS validation is not applied for empty required checkout fields if customer did not fill them"> + <data name="issue" xsi:type="string">MAGETWO-97990: [MTF] OnePageCheckoutJsValidationTestVariation1_0 randomly fails on jenkins</data> <data name="tag" xsi:type="string">severity:S2</data> <data name="products/0" xsi:type="string">catalogProductSimple::default</data> <data name="checkoutMethod" xsi:type="string">guest</data> diff --git a/dev/tests/functional/tests/app/Magento/Cms/Test/TestCase/CreateCmsPageEntityMultipleStoreViewsTest.xml b/dev/tests/functional/tests/app/Magento/Cms/Test/TestCase/CreateCmsPageEntityMultipleStoreViewsTest.xml index 72a76dacc32..06fe76c5efd 100644 --- a/dev/tests/functional/tests/app/Magento/Cms/Test/TestCase/CreateCmsPageEntityMultipleStoreViewsTest.xml +++ b/dev/tests/functional/tests/app/Magento/Cms/Test/TestCase/CreateCmsPageEntityMultipleStoreViewsTest.xml @@ -8,6 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Cms\Test\TestCase\CreateCmsPageEntityMultipleStoreViewsTest" summary="Page cache for different CMS pages on multiple store views" ticketId="MAGETWO-52467"> <variation name="CreateCmsPageEntityMultipleStoreViewsTestVariation1"> + <data name="issue" xsi:type="string">MC-13801: Test "Page cache for different CMS pages on multiple store views" fails on Jenkins</data> <data name="cmsPages/0/is_active" xsi:type="string">Yes</data> <data name="cmsPages/0/title" xsi:type="string">NewCmsPage</data> <data name="cmsPages/0/store_id/dataset" xsi:type="string">default</data> diff --git a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/DeleteProductEntityTest.xml b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/DeleteProductEntityTest.xml index 25f31b23fa6..68dc1ecbe78 100644 --- a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/DeleteProductEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/DeleteProductEntityTest.xml @@ -8,6 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Catalog\Test\TestCase\Product\DeleteProductEntityTest"> <variation name="DeleteProductEntityTestVariation9"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="products" xsi:type="string">configurableProduct::default</data> <data name="isRequired" xsi:type="string">Yes</data> <constraint name="Magento\Catalog\Test\Constraint\AssertProductSuccessDeleteMessage" /> @@ -15,6 +16,7 @@ <constraint name="Magento\Catalog\Test\Constraint\AssertProductIsNotDisplayingOnFrontend" /> </variation> <variation name="DeleteProductEntityTestVariation10"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="products" xsi:type="string">configurableProduct::with_one_option</data> <data name="isRequired" xsi:type="string">Yes</data> <constraint name="Magento\Catalog\Test\Constraint\AssertProductSuccessDeleteMessage" /> diff --git a/dev/tests/functional/tests/app/Magento/Customer/Test/Block/Account/AddressesAdditional.php b/dev/tests/functional/tests/app/Magento/Customer/Test/Block/Account/AddressesAdditional.php index cba358549c3..13ef742d062 100644 --- a/dev/tests/functional/tests/app/Magento/Customer/Test/Block/Account/AddressesAdditional.php +++ b/dev/tests/functional/tests/app/Magento/Customer/Test/Block/Account/AddressesAdditional.php @@ -20,14 +20,14 @@ class AddressesAdditional extends Block * * @var string */ - protected $addressSelector = '//li[address[contains(.,"%s")]]'; + protected $addressSelector = '//tbody//tr[contains(.,"%s")]'; /** * Selector for addresses block * * @var string */ - protected $addressesSelector = '//li[address]'; + protected $addressesSelector = '.additional-addresses'; /** * Selector for delete link @@ -74,16 +74,19 @@ public function deleteAdditionalAddress(Address $address) */ public function isAdditionalAddressExists($address) { - $additionalAddressExists = false; - - $addresses = $this->_rootElement->getElements($this->addressesSelector, Locator::SELECTOR_XPATH); - foreach ($addresses as $addressBlock) { - if (strpos($addressBlock->getText(), $address) === 0) { - $additionalAddressExists = $addressBlock->isVisible(); + $addressExists = true; + foreach (explode("\n", $address) as $addressItem) { + $addressElement = $this->_rootElement->find( + sprintf($this->addressSelector, $addressItem), + Locator::SELECTOR_XPATH + ); + if (!$addressElement->isVisible()) { + $addressExists = false; break; } } - return $additionalAddressExists; + + return $addressExists; } /** diff --git a/dev/tests/functional/tests/app/Magento/Customer/Test/Block/Address/Renderer.php b/dev/tests/functional/tests/app/Magento/Customer/Test/Block/Address/Renderer.php index f9a3989d8f5..8d8a0cfe5ea 100644 --- a/dev/tests/functional/tests/app/Magento/Customer/Test/Block/Address/Renderer.php +++ b/dev/tests/functional/tests/app/Magento/Customer/Test/Block/Address/Renderer.php @@ -58,6 +58,11 @@ protected function getPattern() . "{{lastname}}{{depend}} {{suffix}}{{/depend}}\n{{/depend}}{{street}}\n" . "{{city}}, {{{$region}}} {{postcode}}\n{{country_id}}\n{{depend}}{{telephone}}{{/depend}}"; break; + case "html_without_company_separated_names": + $outputPattern = "{{depend}}{{prefix}}\n{{/depend}}{{firstname}}\n{{depend}}{{middlename}}\n{{/depend}}" + . "{{lastname}}{{depend}}\n{{suffix}}{{/depend}}\n{{/depend}}{{street}}\n" + . "{{city}}\n{{{$region}}}\n{{postcode}}\n{{country_id}}\n{{depend}}{{telephone}}{{/depend}}"; + break; case "html_for_select_element": $outputPattern = "{{depend}}{{prefix}} {{/depend}}{{firstname}} {{depend}}{{middlename}} {{/depend}}" . "{{lastname}}{{depend}} {{suffix}}{{/depend}}, {{/depend}}{{street}}, " diff --git a/dev/tests/functional/tests/app/Magento/Customer/Test/Constraint/AssertAdditionalAddressCreatedFrontend.php b/dev/tests/functional/tests/app/Magento/Customer/Test/Constraint/AssertAdditionalAddressCreatedFrontend.php index abfee067a73..4d086cf0605 100644 --- a/dev/tests/functional/tests/app/Magento/Customer/Test/Constraint/AssertAdditionalAddressCreatedFrontend.php +++ b/dev/tests/functional/tests/app/Magento/Customer/Test/Constraint/AssertAdditionalAddressCreatedFrontend.php @@ -28,7 +28,7 @@ public function processAssert(CustomerAccountIndex $customerAccountIndex, Addres $customerAccountIndex->getAccountMenuBlock()->openMenuItem('Address Book'); $addressRenderer = $this->objectManager->create( \Magento\Customer\Test\Block\Address\Renderer::class, - ['address' => $shippingAddress, 'type' => 'html'] + ['address' => $shippingAddress, 'type' => 'html_without_company_separated_names'] )->render(); $isAddressExists = $customerAccountIndex->getAdditionalAddressBlock() ->isAdditionalAddressExists($addressRenderer); diff --git a/dev/tests/functional/tests/app/Magento/Downloadable/Test/TestCase/DeleteProductEntityTest.xml b/dev/tests/functional/tests/app/Magento/Downloadable/Test/TestCase/DeleteProductEntityTest.xml index e2f86d82363..ffcafbe6872 100644 --- a/dev/tests/functional/tests/app/Magento/Downloadable/Test/TestCase/DeleteProductEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Downloadable/Test/TestCase/DeleteProductEntityTest.xml @@ -8,6 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Catalog\Test\TestCase\Product\DeleteProductEntityTest"> <variation name="DeleteProductEntityTestVariation7" firstConstraint="Magento\Catalog\Test\Constraint\AssertProductSuccessDeleteMessage" method="test"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="products" xsi:type="string">downloadableProduct::default</data> <data name="isRequired" xsi:type="string">Yes</data> <constraint name="Magento\Catalog\Test\Constraint\AssertProductSuccessDeleteMessage" /> diff --git a/dev/tests/functional/tests/app/Magento/GroupedProduct/Test/Block/Adminhtml/Product/Grouped/AssociatedProducts/Search/Grid.php b/dev/tests/functional/tests/app/Magento/GroupedProduct/Test/Block/Adminhtml/Product/Grouped/AssociatedProducts/Search/Grid.php index 45481d6ee07..6c19291222a 100644 --- a/dev/tests/functional/tests/app/Magento/GroupedProduct/Test/Block/Adminhtml/Product/Grouped/AssociatedProducts/Search/Grid.php +++ b/dev/tests/functional/tests/app/Magento/GroupedProduct/Test/Block/Adminhtml/Product/Grouped/AssociatedProducts/Search/Grid.php @@ -20,6 +20,13 @@ class Grid extends DataGrid */ protected $addProducts = '.action-primary[data-role="action"]'; + /** + * Grid selector. + * + * @var string + */ + private $gridSelector = '[data-role="grid-wrapper"]'; + /** * Filters array mapping * @@ -40,4 +47,59 @@ public function addProducts() { $this->_rootElement->find($this->addProducts)->click(); } + + /** + * @inheritdoc + */ + public function searchAndSelect(array $filter) + { + $this->waitGridVisible(); + $this->waitLoader(); + parent::searchAndSelect($filter); + } + + /** + * @inheritdoc + */ + protected function waitLoader() + { + parent::waitLoader(); + $this->waitGridLoaderInvisible(); + } + + /** + * Wait for grid to appear. + * + * @return void + */ + private function waitGridVisible() + { + $browser = $this->_rootElement; + $selector = $this->gridSelector; + + return $browser->waitUntil( + function () use ($browser, $selector) { + $element = $browser->find($selector); + return $element->isVisible() ? true : null; + } + ); + } + + /** + * Wait for grid spinner disappear. + * + * @return void + */ + private function waitGridLoaderInvisible() + { + $browser = $this->_rootElement; + $selector = $this->loader; + + return $browser->waitUntil( + function () use ($browser, $selector) { + $element = $browser->find($selector); + return $element->isVisible() === false ? true : null; + } + ); + } } diff --git a/dev/tests/functional/tests/app/Magento/GroupedProduct/Test/Fixture/GroupedProduct.xml b/dev/tests/functional/tests/app/Magento/GroupedProduct/Test/Fixture/GroupedProduct.xml index dce1358a1ec..59c00683e3b 100644 --- a/dev/tests/functional/tests/app/Magento/GroupedProduct/Test/Fixture/GroupedProduct.xml +++ b/dev/tests/functional/tests/app/Magento/GroupedProduct/Test/Fixture/GroupedProduct.xml @@ -58,7 +58,7 @@ <field name="sku" is_required="1" group="product-details" /> <field name="small_image" is_required="0" /> <field name="small_image_label" is_required="0" /> - <field name="status" is_required="0" /> + <field name="status" is_required="0" group="product-details" /> <field name="thumbnail" is_required="0" /> <field name="thumbnail_label" is_required="0" /> <field name="updated_at" is_required="1" /> @@ -66,7 +66,7 @@ <field name="upsell_tgtr_position_limit" is_required="0" /> <field name="url_key" is_required="0" group="search-engine-optimization" /> <field name="url_path" is_required="0" /> - <field name="visibility" is_required="0" /> + <field name="visibility" is_required="0" group="product-details" /> <field name="id" /> <field name="type_id" /> <field name="attribute_set_id" group="product-details" source="Magento\Catalog\Test\Fixture\Product\AttributeSetId" /> diff --git a/dev/tests/functional/tests/app/Magento/GroupedProduct/Test/TestCase/CreateGroupedProductEntityTest.xml b/dev/tests/functional/tests/app/Magento/GroupedProduct/Test/TestCase/CreateGroupedProductEntityTest.xml index 38ef02ff494..39f4fd08bb9 100644 --- a/dev/tests/functional/tests/app/Magento/GroupedProduct/Test/TestCase/CreateGroupedProductEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/GroupedProduct/Test/TestCase/CreateGroupedProductEntityTest.xml @@ -8,7 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\GroupedProduct\Test\TestCase\CreateGroupedProductEntityTest" summary="Create Grouped Product" ticketId="MAGETWO-24877"> <variation name="CreateGroupedProductEntityTestVariation1" summary="Create Grouped Product and Assign It to the Category" ticketId="MAGETWO-13610"> - <data name="tag" xsi:type="string">test_type:acceptance_test, test_type:extended_acceptance_test, stable:no</data> + <data name="tag" xsi:type="string">test_type:acceptance_test, test_type:extended_acceptance_test</data> <data name="product/data/url_key" xsi:type="string">test-grouped-product-%isolation%</data> <data name="product/data/name" xsi:type="string">GroupedProduct %isolation%</data> <data name="product/data/sku" xsi:type="string">GroupedProduct_sku%isolation%</data> diff --git a/dev/tests/functional/tests/app/Magento/GroupedProduct/Test/TestCase/DeleteProductEntityTest.xml b/dev/tests/functional/tests/app/Magento/GroupedProduct/Test/TestCase/DeleteProductEntityTest.xml index cf0c8c81416..e62e5ad7395 100644 --- a/dev/tests/functional/tests/app/Magento/GroupedProduct/Test/TestCase/DeleteProductEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/GroupedProduct/Test/TestCase/DeleteProductEntityTest.xml @@ -8,6 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Catalog\Test\TestCase\Product\DeleteProductEntityTest"> <variation name="DeleteProductEntityTestVariation8" firstConstraint="Magento\Catalog\Test\Constraint\AssertProductSuccessDeleteMessage" method="test"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="products" xsi:type="string">groupedProduct::default</data> <data name="isRequired" xsi:type="string">Yes</data> <constraint name="Magento\Catalog\Test\Constraint\AssertProductSuccessDeleteMessage" /> diff --git a/dev/tests/functional/tests/app/Magento/Install/Test/Page/DevdocsInstall.xml b/dev/tests/functional/tests/app/Magento/Install/Test/Page/DevdocsInstall.xml index f63e7282719..15e0b8a27d2 100644 --- a/dev/tests/functional/tests/app/Magento/Install/Test/Page/DevdocsInstall.xml +++ b/dev/tests/functional/tests/app/Magento/Install/Test/Page/DevdocsInstall.xml @@ -6,7 +6,7 @@ */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/pages.xsd"> - <page name="DevdocsInstall" mca="http://devdocs.magento.com/guides/v2.0/install-gde/install/web/install-web.html" module="Magento_Install"> + <page name="DevdocsInstall" mca="https://devdocs.magento.com/guides/v2.0/install-gde/install/web/install-web.html" module="Magento_Install"> <block name="devdocsBlock" class="Magento\Install\Test\Block\Devdocs" locator="body" strategy="css selector"/> </page> </config> diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/Constraint/AssertOrderCancelMassActionPartialFailMessage.php b/dev/tests/functional/tests/app/Magento/Sales/Test/Constraint/AssertOrderCancelMassActionFailMessage.php similarity index 88% rename from dev/tests/functional/tests/app/Magento/Sales/Test/Constraint/AssertOrderCancelMassActionPartialFailMessage.php rename to dev/tests/functional/tests/app/Magento/Sales/Test/Constraint/AssertOrderCancelMassActionFailMessage.php index ed7596bf72f..2c87d8539f0 100644 --- a/dev/tests/functional/tests/app/Magento/Sales/Test/Constraint/AssertOrderCancelMassActionPartialFailMessage.php +++ b/dev/tests/functional/tests/app/Magento/Sales/Test/Constraint/AssertOrderCancelMassActionFailMessage.php @@ -11,10 +11,10 @@ use Magento\Mtf\Constraint\AbstractConstraint; /** - * Class AssertOrderCancelAndSuccessMassActionFailMessage + * Class AssertOrderCancelMassActionFailMessage * Assert cancel fail message is displayed on order index page */ -class AssertOrderCancelMassActionPartialFailMessage extends AbstractConstraint +class AssertOrderCancelMassActionFailMessage extends AbstractConstraint { /** * Text value to be checked diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/Constraint/AssertOrderStatusIsCorrect.php b/dev/tests/functional/tests/app/Magento/Sales/Test/Constraint/AssertOrderStatusIsCorrect.php index c3e8558df7f..fc854bd8c50 100644 --- a/dev/tests/functional/tests/app/Magento/Sales/Test/Constraint/AssertOrderStatusIsCorrect.php +++ b/dev/tests/functional/tests/app/Magento/Sales/Test/Constraint/AssertOrderStatusIsCorrect.php @@ -39,8 +39,8 @@ public function processAssert( /** @var \Magento\Sales\Test\Block\Adminhtml\Order\View\Tab\Info $infoTab */ $infoTab = $salesOrderView->getOrderForm()->openTab('info')->getTab('info'); \PHPUnit\Framework\Assert::assertEquals( - $infoTab->getOrderStatus(), - $orderStatus + $orderStatus, + $infoTab->getOrderStatus() ); } diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/GridSortingTest.xml b/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/GridSortingTest.xml index 6ae9d19a898..28894ed6cc1 100644 --- a/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/GridSortingTest.xml +++ b/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/GridSortingTest.xml @@ -9,7 +9,6 @@ <testCase name="Magento\Ui\Test\TestCase\GridSortingTest" summary="Grid UI Component Sorting" ticketId="MAGETWO-41328"> <variation name="SalesOrderGridSorting"> <data name="tag" xsi:type="string">severity:S2</data> - <data name="tag" xsi:type="string">to_maintain:yes</data> <data name="description" xsi:type="string">Verify sales order grid storting</data> <data name="steps" xsi:type="array"> <item name="0" xsi:type="string">-</item> diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/MassOrdersUpdateTest.xml b/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/MassOrdersUpdateTest.xml index 99e25b06284..1f75b07c8ca 100644 --- a/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/MassOrdersUpdateTest.xml +++ b/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/MassOrdersUpdateTest.xml @@ -8,7 +8,6 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Sales\Test\TestCase\MassOrdersUpdateTest" summary="Mass Update Orders" ticketId="MAGETWO-27897"> <variation name="MassOrdersUpdateTestVariation1"> - <data name="tag" xsi:type="string">stable:no</data> <data name="description" xsi:type="string">cancel orders in status Pending and Processing</data> <data name="steps" xsi:type="string">-</data> <data name="action" xsi:type="string">Cancel</data> @@ -23,16 +22,16 @@ <data name="action" xsi:type="string">Cancel</data> <data name="ordersCount" xsi:type="string">2</data> <data name="resultStatuses" xsi:type="string">Complete,Closed</data> - <constraint name="Magento\Sales\Test\Constraint\AssertOrderCancelMassActionPartialFailMessage" /> + <constraint name="Magento\Sales\Test\Constraint\AssertOrderCancelMassActionFailMessage" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrdersInOrdersGrid" /> </variation> <variation name="MassOrdersUpdateTestVariation3"> - <data name="description" xsi:type="string">try to cancel orders in status Pending, Closed</data> + <data name="description" xsi:type="string">try to cancel orders in status Processing, Closed</data> <data name="steps" xsi:type="string">invoice|invoice, credit memo</data> <data name="action" xsi:type="string">Cancel</data> <data name="ordersCount" xsi:type="string">2</data> <data name="resultStatuses" xsi:type="string">Processing,Closed</data> - <constraint name="Magento\Sales\Test\Constraint\AssertOrderCancelMassActionPartialFailMessage" /> + <constraint name="Magento\Sales\Test\Constraint\AssertOrderCancelMassActionFailMessage" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrdersInOrdersGrid" /> </variation> <variation name="MassOrdersUpdateTestVariation4"> @@ -45,7 +44,6 @@ <constraint name="Magento\Sales\Test\Constraint\AssertOrdersInOrdersGrid" /> </variation> <variation name="MassOrdersUpdateTestVariation5"> - <data name="tag" xsi:type="string">stable:no</data> <data name="description" xsi:type="string">Try to put order in status Complete on Hold</data> <data name="steps" xsi:type="string">invoice, shipment</data> <data name="action" xsi:type="string">Hold</data> diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/MoveLastOrderedProductsOnOrderPageTest.xml b/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/MoveLastOrderedProductsOnOrderPageTest.xml index 6f568df8f21..8bb4ef56361 100644 --- a/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/MoveLastOrderedProductsOnOrderPageTest.xml +++ b/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/MoveLastOrderedProductsOnOrderPageTest.xml @@ -8,13 +8,11 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Sales\Test\TestCase\MoveLastOrderedProductsOnOrderPageTest" summary="Add Products to Order from Last Ordered Products Section" ticketId="MAGETWO-27640"> <variation name="MoveLastOrderedProductsOnOrderPageTestVariation1"> - <data name="tag" xsi:type="string">stable:no</data> <data name="order/dataset" xsi:type="string">default</data> <data name="order/data/entity_id/products" xsi:type="string">catalogProductSimple::default</data> <constraint name="Magento\Sales\Test\Constraint\AssertProductInItemsOrderedGrid" /> </variation> <variation name="MoveLastOrderedProductsOnOrderPageTestVariation2"> - <data name="issue" xsi:type="string">MAGETWO-58762: Customer grid does not open in MoveLastOrderedProductsOnOrderPageTestVariation2 on Jenkins</data> <data name="order/dataset" xsi:type="string">default</data> <data name="order/data/entity_id/products" xsi:type="string">configurableProduct::configurable_with_qty_1</data> <constraint name="Magento\Sales\Test\Constraint\AssertProductInItemsOrderedGrid" /> diff --git a/dev/tests/functional/tests/app/Magento/SalesRule/Test/Block/Adminhtml/Promo/Quote/Edit/PromoQuoteForm.php b/dev/tests/functional/tests/app/Magento/SalesRule/Test/Block/Adminhtml/Promo/Quote/Edit/PromoQuoteForm.php index 2627f99d4c8..54cec6cf279 100644 --- a/dev/tests/functional/tests/app/Magento/SalesRule/Test/Block/Adminhtml/Promo/Quote/Edit/PromoQuoteForm.php +++ b/dev/tests/functional/tests/app/Magento/SalesRule/Test/Block/Adminhtml/Promo/Quote/Edit/PromoQuoteForm.php @@ -28,6 +28,13 @@ class PromoQuoteForm extends FormSections */ protected $waitForSelectorVisible = false; + /** + * Selector of name element on the form. + * + * @var string + */ + private $nameElementSelector = 'input[name=name]'; + /** * Fill form with sections. * @@ -38,6 +45,8 @@ class PromoQuoteForm extends FormSections */ public function fill(FixtureInterface $fixture, SimpleElement $element = null, array $replace = null) { + $this->waitForElementNotVisible($this->waitForSelector); + $this->waitForElementVisible($this->nameElementSelector); $sections = $this->getFixtureFieldsByContainers($fixture); if ($replace) { $sections = $this->prepareData($sections, $replace); diff --git a/dev/tests/functional/tests/app/Magento/SalesRule/Test/Repository/SalesRule.xml b/dev/tests/functional/tests/app/Magento/SalesRule/Test/Repository/SalesRule.xml index 521d7d68ac4..5cb5b4db727 100644 --- a/dev/tests/functional/tests/app/Magento/SalesRule/Test/Repository/SalesRule.xml +++ b/dev/tests/functional/tests/app/Magento/SalesRule/Test/Repository/SalesRule.xml @@ -271,7 +271,7 @@ <field name="coupon_type" xsi:type="string">No Coupon</field> <field name="sort_order" xsi:type="string">1</field> <field name="is_rss" xsi:type="string">Yes</field> - <field name="conditions_serialized" xsi:type="string">[Total Items Quantity|equals or greater than|3]{Product attribute combination|FOUND|ALL|:[[Category|is|2]]}</field> + <field name="conditions_serialized" xsi:type="string">[Total Items Quantity|equals or greater than|3]</field> <field name="simple_action" xsi:type="string">Percent of product price discount</field> <field name="discount_amount" xsi:type="string">25</field> <field name="apply_to_shipping" xsi:type="string">No</field> diff --git a/dev/tests/functional/tests/app/Magento/SalesRule/Test/TestCase/ApplySeveralSalesRuleEntityTest.xml b/dev/tests/functional/tests/app/Magento/SalesRule/Test/TestCase/ApplySeveralSalesRuleEntityTest.xml index e160fef6095..3dfe4cf1185 100644 --- a/dev/tests/functional/tests/app/Magento/SalesRule/Test/TestCase/ApplySeveralSalesRuleEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/SalesRule/Test/TestCase/ApplySeveralSalesRuleEntityTest.xml @@ -31,7 +31,6 @@ <constraint name="Magento\SalesRule\Test\Constraint\AssertCartPriceRuleConditionIsApplied" /> </variation> <variation name="ApplySeveralSalesRuleEntityTestVariation3" summary="Rules with different priority, both are applied"> - <data name="tag" xsi:type="string">stable:no</data> <data name="salesRules/rule1" xsi:type="string">active_sales_rule_product_attribute</data> <data name="salesRules/rule2" xsi:type="string">active_sales_total_items</data> <data name="cartPrice/sub_total" xsi:type="string">250.00</data> @@ -44,7 +43,6 @@ <constraint name="Magento\SalesRule\Test\Constraint\AssertCartPriceRuleConditionIsApplied" /> </variation> <variation name="ApplySeveralSalesRuleEntityTestVariation4" summary="Rules with different priority, none are applied"> - <data name="tag" xsi:type="string">to_maintain:yes</data> <data name="salesRules/rule1" xsi:type="string">active_sales_rule_row_total</data> <data name="salesRules/rule2" xsi:type="string">active_sales_total_items</data> <data name="productForSalesRule1/dataset" xsi:type="string">simple_for_salesrule_1</data> diff --git a/dev/tests/functional/tests/app/Magento/Search/Test/TestCase/AdvancedSearchWithAttributeTest.xml b/dev/tests/functional/tests/app/Magento/Search/Test/TestCase/AdvancedSearchWithAttributeTest.xml index 13c7051d0c1..733b110ec54 100644 --- a/dev/tests/functional/tests/app/Magento/Search/Test/TestCase/AdvancedSearchWithAttributeTest.xml +++ b/dev/tests/functional/tests/app/Magento/Search/Test/TestCase/AdvancedSearchWithAttributeTest.xml @@ -8,8 +8,6 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Search\Test\TestCase\AdvancedSearchWithAttributeTest" summary="Use Advanced Search by Decimal indexable attribute if Edit/Add Attribute" ticketId="MAGETWO-25931"> <variation name="AdvancedSearchWithWeightAttributeTestVariation1"> - <data name="issue" xsi:type="string">MAGETWO-65408: [FT] Magento\Search\Test\TestCase\AdvancedSearchWithAttributeTest fails on Jenkins</data> - <data name="tag" xsi:type="string">to_maintain:yes</data> <data name="productDropDownList/0" xsi:type="string">configurable</data> <data name="productDropDownList/1" xsi:type="string">simple</data> <data name="productDropDownList/2" xsi:type="string">bundle</data> diff --git a/dev/tests/functional/tests/app/Magento/Tax/Test/TestCase/DeleteTaxRuleEntityTest.xml b/dev/tests/functional/tests/app/Magento/Tax/Test/TestCase/DeleteTaxRuleEntityTest.xml index dc5e121db3d..96bfec0121e 100644 --- a/dev/tests/functional/tests/app/Magento/Tax/Test/TestCase/DeleteTaxRuleEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Tax/Test/TestCase/DeleteTaxRuleEntityTest.xml @@ -8,6 +8,7 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> <testCase name="Magento\Tax\Test\TestCase\DeleteTaxRuleEntityTest" summary="Delete Tax Rule" ticketId="MAGETWO-20924"> <variation name="DeleteTaxRuleEntityTestVariation1"> + <data name="tag" xsi:type="string">mftf_migrated:yes</data> <data name="taxRule/dataset" xsi:type="string">tax_rule_with_custom_tax_classes</data> <data name="address/data/country_id" xsi:type="string">United States</data> <data name="address/data/region_id" xsi:type="string">California</data> diff --git a/dev/tests/functional/tests/app/Magento/Ui/Test/Block/Adminhtml/DataGrid.php b/dev/tests/functional/tests/app/Magento/Ui/Test/Block/Adminhtml/DataGrid.php index 235b0d09653..03476add669 100644 --- a/dev/tests/functional/tests/app/Magento/Ui/Test/Block/Adminhtml/DataGrid.php +++ b/dev/tests/functional/tests/app/Magento/Ui/Test/Block/Adminhtml/DataGrid.php @@ -181,7 +181,7 @@ public function resetFilter() * * @return void */ - protected function waitFilterToLoad() + public function waitFilterToLoad() { $this->getTemplateBlock()->waitLoader(); $browser = $this->_rootElement; diff --git a/dev/tests/functional/tests/app/Magento/Ui/Test/TestCase/GridSortingTest.php b/dev/tests/functional/tests/app/Magento/Ui/Test/TestCase/GridSortingTest.php index af4ccfdac9d..0574fc8dc55 100644 --- a/dev/tests/functional/tests/app/Magento/Ui/Test/TestCase/GridSortingTest.php +++ b/dev/tests/functional/tests/app/Magento/Ui/Test/TestCase/GridSortingTest.php @@ -89,6 +89,7 @@ public function test( $page->open(); /** @var DataGrid $gridBlock */ $gridBlock = $page->$gridRetriever(); + $gridBlock->waitFilterToLoad(); $gridBlock->resetFilter(); $sortingResults = []; diff --git a/dev/tests/functional/tests/app/Magento/Wishlist/Test/Block/Customer/Wishlist/Items/TopToolbar.php b/dev/tests/functional/tests/app/Magento/Wishlist/Test/Block/Customer/Wishlist/Items/TopToolbar.php new file mode 100644 index 00000000000..9d2d0fee46b --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Wishlist/Test/Block/Customer/Wishlist/Items/TopToolbar.php @@ -0,0 +1,98 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Wishlist\Test\Block\Customer\Wishlist\Items; + +use Magento\Mtf\Block\Block; +use Magento\Mtf\Client\Locator; + +/** + * Pager block for wishlist items page. + */ +class TopToolbar extends Block +{ + /** + * Selector next active element + * + * @var string + */ + private $nextPageSelector = '.item.current + .item a'; + + /** + * Selector first element + * + * @var string + */ + private $firstPageSelector = '.item>.page'; + + /** + * Selector option element + * + * @var string + */ + private $optionSelector = './/option'; + + /** + * Go to the next page + * + * @return bool + */ + public function nextPage() + { + $nextPageItem = $this->_rootElement->find($this->nextPageSelector); + if ($nextPageItem->isVisible()) { + $nextPageItem->click(); + return true; + } + return false; + } + + /** + * Go to the first page + * + * @return bool + */ + public function firstPage() + { + $firstPageItem = $this->_rootElement->find($this->firstPageSelector); + if ($firstPageItem->isVisible()) { + $firstPageItem->click(); + return true; + } + return false; + } + + /** + * Set value for limiter element by index + * + * @param int $index + * @return $this + */ + public function setLimiterValueByIndex($index) + { + $options = $this->_rootElement->getElements($this->optionSelector, Locator::SELECTOR_XPATH); + if (isset($options[$index])) { + $options[$index]->click(); + } + return $this; + } + + /** + * Get value for limiter element by index + * + * @param int $index + * @return int|null + */ + public function getLimitedValueByIndex($index) + { + $options = $this->_rootElement->getElements($this->optionSelector, Locator::SELECTOR_XPATH); + if (isset($options[$index])) { + return $options[$index]->getValue(); + } + return null; + } +} diff --git a/dev/tests/functional/tests/app/Magento/Wishlist/Test/Constraint/AssertProductIsPresentInWishlist.php b/dev/tests/functional/tests/app/Magento/Wishlist/Test/Constraint/AssertProductIsPresentInWishlist.php index ae994f84c47..058c764be16 100644 --- a/dev/tests/functional/tests/app/Magento/Wishlist/Test/Constraint/AssertProductIsPresentInWishlist.php +++ b/dev/tests/functional/tests/app/Magento/Wishlist/Test/Constraint/AssertProductIsPresentInWishlist.php @@ -36,6 +36,13 @@ public function processAssert( $cmsIndex->getLinksBlock()->openLink('My Account'); $customerAccountIndex->getAccountMenuBlock()->openMenuItem('My Wish List'); + $isProductVisible = $wishlistIndex->getWishlistBlock()->getProductItemsBlock()->getItemProduct($product) + ->isVisible(); + while (!$isProductVisible && $wishlistIndex->getTopToolbar()->nextPage()) { + $isProductVisible = $wishlistIndex->getWishlistBlock()->getProductItemsBlock()->getItemProduct($product) + ->isVisible(); + } + \PHPUnit\Framework\Assert::assertTrue( $wishlistIndex->getWishlistBlock()->getProductItemsBlock()->getItemProduct($product)->isVisible(), $product->getName() . ' is not visible on Wish List page.' diff --git a/dev/tests/functional/tests/app/Magento/Wishlist/Test/Constraint/AssertProductRegularPriceOnStorefront.php b/dev/tests/functional/tests/app/Magento/Wishlist/Test/Constraint/AssertProductRegularPriceOnStorefront.php index 68e30e13558..dc71939d479 100644 --- a/dev/tests/functional/tests/app/Magento/Wishlist/Test/Constraint/AssertProductRegularPriceOnStorefront.php +++ b/dev/tests/functional/tests/app/Magento/Wishlist/Test/Constraint/AssertProductRegularPriceOnStorefront.php @@ -44,44 +44,40 @@ public function processAssert( $cmsIndex->getLinksBlock()->openLink('My Account'); $customerAccountIndex->getAccountMenuBlock()->openMenuItem('My Wish List'); - $productRegularPrice = 0; - if ($product instanceof GroupedProduct) { - $associatedProducts = $product->getAssociated(); + $isProductVisible = $wishlistIndex->getWishlistBlock() + ->getProductItemsBlock() + ->getItemProduct($product) + ->isVisible(); + while (!$isProductVisible && $wishlistIndex->getTopToolbar()->nextPage()) { + $isProductVisible = $wishlistIndex->getWishlistBlock() + ->getProductItemsBlock() + ->getItemProduct($product) + ->isVisible(); + } - /** @var \Magento\Catalog\Test\Fixture\CatalogProductSimple $associatedProduct */ - foreach ($associatedProducts['products'] as $key => $associatedProduct) { - $qty = $associatedProducts['assigned_products'][$key]['qty']; - $price = $associatedProduct->getPrice(); - $productRegularPrice += $qty * $price; - } + if ($product instanceof GroupedProduct) { + $productRegularPrice = $this->getGroupedProductRegularPrice($product); } elseif ($product instanceof BundleProduct) { - $bundleSelection = (array)$product->getBundleSelections(); - foreach ($bundleSelection['products'] as $bundleOption) { - $regularBundleProductPrice = 0; - /** @var \Magento\Catalog\Test\Fixture\CatalogProductSimple $bundleProduct */ - foreach ($bundleOption as $bundleProduct) { - $checkoutData = $bundleProduct->getCheckoutData(); - $bundleProductPrice = $checkoutData['qty'] * $checkoutData['cartItem']['price']; - if (0 === $regularBundleProductPrice) { - $regularBundleProductPrice = $bundleProductPrice; - } else { - $regularBundleProductPrice = max([$bundleProductPrice, $regularBundleProductPrice]); - } - } - $productRegularPrice += $regularBundleProductPrice; - } + $productRegularPrice = $this->getBundleProductRegularPrice($product); } else { - $productRegularPrice = (float)$product->getPrice(); + $productRegularPrice = (float) $product->getPrice(); } - $productItem = $wishlistIndex->getWishlistBlock()->getProductItemsBlock()->getItemProduct($product); - $wishListProductRegularPrice = (float)$productItem->getRegularPrice(); + $productItem = $wishlistIndex->getWishlistBlock() + ->getProductItemsBlock() + ->getItemProduct($product); - \PHPUnit\Framework\Assert::assertEquals( - $this->regularPriceLabel, - $productItem->getPriceLabel(), - 'Wrong product regular price is displayed.' - ); + $wishListProductRegularPrice = $product instanceof BundleProduct + ? (float)$productItem->getPrice() + : (float)$productItem->getRegularPrice(); + + if (!$product instanceof BundleProduct) { + \PHPUnit\Framework\Assert::assertEquals( + $this->regularPriceLabel, + $productItem->getPriceLabel(), + 'Wrong product regular price is displayed.' + ); + } \PHPUnit\Framework\Assert::assertNotEmpty( $wishListProductRegularPrice, @@ -95,6 +91,52 @@ public function processAssert( ); } + /** + * Retrieve grouped product regular price + * + * @param GroupedProduct $product + * @return float + */ + private function getGroupedProductRegularPrice(GroupedProduct $product) + { + $productRegularPrice = 0; + $associatedProducts = $product->getAssociated(); + /** @var \Magento\Catalog\Test\Fixture\CatalogProductSimple $associatedProduct */ + foreach ($associatedProducts['products'] as $key => $associatedProduct) { + $qty = $associatedProducts['assigned_products'][$key]['qty']; + $price = $associatedProduct->getPrice(); + $productRegularPrice += $qty * $price; + } + return $productRegularPrice; + } + + /** + * Retrieve bundle product regular price + * + * @param BundleProduct $product + * @return float + */ + private function getBundleProductRegularPrice(BundleProduct $product) + { + $productRegularPrice = 0; + $bundleSelection = (array) $product->getBundleSelections(); + foreach ($bundleSelection['products'] as $bundleOption) { + $regularBundleProductPrice = 0; + /** @var \Magento\Catalog\Test\Fixture\CatalogProductSimple $bundleProduct */ + foreach ($bundleOption as $bundleProduct) { + $checkoutData = $bundleProduct->getCheckoutData(); + $bundleProductPrice = $checkoutData['qty'] * $checkoutData['cartItem']['price']; + if (0 === $regularBundleProductPrice) { + $regularBundleProductPrice = $bundleProductPrice; + } else { + $regularBundleProductPrice = max([$bundleProductPrice, $regularBundleProductPrice]); + } + } + $productRegularPrice += $regularBundleProductPrice; + } + return $productRegularPrice; + } + /** * Returns a string representation of the object. * diff --git a/dev/tests/functional/tests/app/Magento/Wishlist/Test/Page/WishlistIndex.xml b/dev/tests/functional/tests/app/Magento/Wishlist/Test/Page/WishlistIndex.xml index 141bc8c5898..4e67c8d4e1d 100644 --- a/dev/tests/functional/tests/app/Magento/Wishlist/Test/Page/WishlistIndex.xml +++ b/dev/tests/functional/tests/app/Magento/Wishlist/Test/Page/WishlistIndex.xml @@ -9,5 +9,6 @@ <page name="WishlistIndex" mca="wishlist/index/index" module="Magento_Wishlist"> <block name="messagesBlock" class="Magento\Backend\Test\Block\Messages" locator=".messages" strategy="css selector"/> <block name="wishlistBlock" class="Magento\Wishlist\Test\Block\Customer\Wishlist" locator="#wishlist-view-form" strategy="css selector"/> + <block name="topToolbar" class="Magento\Wishlist\Test\Block\Customer\Wishlist\Items\TopToolbar" locator=".//*[contains(@class,'wishlist-toolbar')][2]" strategy="xpath"/> </page> </config> diff --git a/dev/tests/functional/tests/app/Magento/Wishlist/Test/TestCase/AddProductToWishlistEntityTest.xml b/dev/tests/functional/tests/app/Magento/Wishlist/Test/TestCase/AddProductToWishlistEntityTest.xml index 06b1a6078d5..e5fa4b6fc11 100644 --- a/dev/tests/functional/tests/app/Magento/Wishlist/Test/TestCase/AddProductToWishlistEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Wishlist/Test/TestCase/AddProductToWishlistEntityTest.xml @@ -108,7 +108,7 @@ </variation> <variation name="AddProductToWishlistEntityTestVariation14" ticketId="MAGETWO-90131"> <data name="product" xsi:type="array"> - <item name="0" xsi:type="string">bundleProduct::with_special_price_and_custom_options</item> + <item name="0" xsi:type="string">bundleProduct::default_with_one_simple_product</item> </data> <constraint name="Magento\Wishlist\Test\Constraint\AssertAddProductToWishlistSuccessMessage"/> <constraint name="Magento\Wishlist\Test\Constraint\AssertProductIsPresentInWishlist"/> diff --git a/dev/tests/integration/etc/di/preferences/ce.php b/dev/tests/integration/etc/di/preferences/ce.php index b871fe19059..3065b171264 100644 --- a/dev/tests/integration/etc/di/preferences/ce.php +++ b/dev/tests/integration/etc/di/preferences/ce.php @@ -27,4 +27,7 @@ \Magento\Framework\App\Config\ScopeConfigInterface::class => \Magento\TestFramework\App\Config::class, \Magento\Framework\App\ResourceConnection\ConfigInterface::class => \Magento\Framework\App\ResourceConnection\Config::class, + \Magento\Framework\Lock\Backend\Cache::class => + \Magento\TestFramework\Lock\Backend\DummyLocker::class, + \Magento\Framework\Session\SessionStartChecker::class => \Magento\TestFramework\Session\SessionStartChecker::class, ]; diff --git a/dev/tests/integration/framework/Magento/TestFramework/Helper/Amqp.php b/dev/tests/integration/framework/Magento/TestFramework/Helper/Amqp.php index 992b980d6a8..aa0c790eeac 100644 --- a/dev/tests/integration/framework/Magento/TestFramework/Helper/Amqp.php +++ b/dev/tests/integration/framework/Magento/TestFramework/Helper/Amqp.php @@ -13,26 +13,62 @@ */ class Amqp { + const CONFIG_PATH_HOST = 'queue/amqp/host'; + const CONFIG_PATH_USER = 'queue/amqp/user'; + const CONFIG_PATH_PASSWORD = 'queue/amqp/password'; + const DEFAULT_MANAGEMENT_PORT = '15672'; + /** * @var Curl */ private $curl; + /** + * @var \Magento\Framework\App\DeploymentConfig + */ + private $deploymentConfig; + /** * RabbitMQ API host * * @var string */ - private $host = 'http://localhost:15672/api/'; + private $host; /** * Initialize dependencies. + * @param \Magento\Framework\App\DeploymentConfig $deploymentConfig */ - public function __construct() - { + public function __construct( + \Magento\Framework\App\DeploymentConfig $deploymentConfig = null + ) { + $this->deploymentConfig = $deploymentConfig ?? \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->get(\Magento\Framework\App\DeploymentConfig::class); $this->curl = new Curl(); - $this->curl->setCredentials('guest', 'guest'); + $this->curl->setCredentials( + $this->deploymentConfig->get(self::CONFIG_PATH_USER), + $this->deploymentConfig->get(self::CONFIG_PATH_PASSWORD) + ); $this->curl->addHeader('content-type', 'application/json'); + $this->host = sprintf( + 'http://%s:%s/api/', + $this->deploymentConfig->get(self::CONFIG_PATH_HOST), + defined('RABBITMQ_MANAGEMENT_PORT') ? RABBITMQ_MANAGEMENT_PORT : self::DEFAULT_MANAGEMENT_PORT + ); + } + + /** + * Check that the RabbitMQ instance has the management plugin installed and the api is available. + * + * @return bool + */ + public function isAvailable(): bool + { + $this->curl->get($this->host . 'overview'); + $data = $this->curl->getBody(); + $data = json_decode($data, true); + + return isset($data['management_version']); } /** @@ -55,6 +91,7 @@ public function getExchanges() /** * Get declared exchange bindings. * + * @param string $name * @return array */ public function getExchangeBindings($name) @@ -82,6 +119,8 @@ public function getConnections() } /** + * Clear Queue + * * @param string $name * @param int $numMessages * @return string @@ -101,7 +140,7 @@ public function clearQueue(string $name, int $numMessages = 50) /** * Delete connection * - * @param $name + * @param string $name * @return string $data */ public function deleteConnection($name) diff --git a/dev/tests/integration/framework/Magento/TestFramework/Lock/Backend/DummyLocker.php b/dev/tests/integration/framework/Magento/TestFramework/Lock/Backend/DummyLocker.php new file mode 100644 index 00000000000..41125493643 --- /dev/null +++ b/dev/tests/integration/framework/Magento/TestFramework/Lock/Backend/DummyLocker.php @@ -0,0 +1,40 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\TestFramework\Lock\Backend; + +use Magento\Framework\Lock\LockManagerInterface; + +/** + * Dummy locker for the integration framework. + */ +class DummyLocker implements LockManagerInterface +{ + /** + * @inheritdoc + */ + public function lock(string $name, int $timeout = -1): bool + { + return true; + } + + /** + * @inheritdoc + */ + public function unlock(string $name): bool + { + return true; + } + + /** + * @inheritdoc + */ + public function isLocked(string $name): bool + { + return false; + } +} diff --git a/dev/tests/integration/framework/Magento/TestFramework/MessageQueue/PublisherConsumerController.php b/dev/tests/integration/framework/Magento/TestFramework/MessageQueue/PublisherConsumerController.php index 14847ae5066..9ca351aa1cf 100644 --- a/dev/tests/integration/framework/Magento/TestFramework/MessageQueue/PublisherConsumerController.php +++ b/dev/tests/integration/framework/Magento/TestFramework/MessageQueue/PublisherConsumerController.php @@ -12,6 +12,9 @@ use Magento\Framework\OsInfo; use Magento\TestFramework\Helper\Amqp; +/** + * Publisher Consumer Controller + */ class PublisherConsumerController { /** @@ -49,6 +52,16 @@ class PublisherConsumerController */ private $amqpHelper; + /** + * PublisherConsumerController constructor. + * @param PublisherInterface $publisher + * @param OsInfo $osInfo + * @param Amqp $amqpHelper + * @param string $logFilePath + * @param array $consumers + * @param array $appInitParams + * @param null|int $maxMessages + */ public function __construct( PublisherInterface $publisher, OsInfo $osInfo, @@ -75,11 +88,8 @@ public function __construct( */ public function initialize() { - if ($this->osInfo->isWindows()) { - throw new EnvironmentPreconditionException( - "This test relies on *nix shell and should be skipped in Windows environment." - ); - } + $this->validateEnvironmentPreconditions(); + $connections = $this->amqpHelper->getConnections(); foreach (array_keys($connections) as $connectionName) { $this->amqpHelper->deleteConnection($connectionName); @@ -108,6 +118,27 @@ public function initialize() } } + /** + * Validate environment preconditions + * + * @throws EnvironmentPreconditionException + * @throws PreconditionFailedException + */ + private function validateEnvironmentPreconditions() + { + if ($this->osInfo->isWindows()) { + throw new EnvironmentPreconditionException( + "This test relies on *nix shell and should be skipped in Windows environment." + ); + } + + if (!$this->amqpHelper->isAvailable()) { + throw new PreconditionFailedException( + 'This test relies on RabbitMQ Management Plugin.' + ); + } + } + /** * Stop Consumers */ @@ -121,6 +152,8 @@ public function stopConsumers() } /** + * Get Consumers ProcessIds + * * @return array */ public function getConsumersProcessIds() @@ -133,6 +166,8 @@ public function getConsumersProcessIds() } /** + * Get Consumer ProcessIds + * * @param string $consumer * @return string[] */ @@ -167,8 +202,10 @@ private function getConsumerStartCommand($consumer, $withEnvVariables = false) } /** + * Wait for asynchronous result + * * @param callable $condition - * @param $params + * @param array $params * @throws PreconditionFailedException */ public function waitForAsynchronousResult(callable $condition, $params) @@ -185,6 +222,8 @@ public function waitForAsynchronousResult(callable $condition, $params) } /** + * Get publisher + * * @return PublisherInterface */ public function getPublisher() diff --git a/dev/tests/integration/framework/Magento/TestFramework/Session/SessionStartChecker.php b/dev/tests/integration/framework/Magento/TestFramework/Session/SessionStartChecker.php new file mode 100644 index 00000000000..136b0565a72 --- /dev/null +++ b/dev/tests/integration/framework/Magento/TestFramework/Session/SessionStartChecker.php @@ -0,0 +1,25 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\TestFramework\Session; + +/** + * Class to check if session can be started or not. Dummy for integration tests. + */ +class SessionStartChecker extends \Magento\Framework\Session\SessionStartChecker +{ + /** + * Can session be started or not. + * + * @return bool + */ + public function check() : bool + { + return true; + } +} diff --git a/dev/tests/integration/framework/Magento/TestFramework/TestCase/AbstractBackendController.php b/dev/tests/integration/framework/Magento/TestFramework/TestCase/AbstractBackendController.php index b2e0b57bae7..7a387bd41ee 100644 --- a/dev/tests/integration/framework/Magento/TestFramework/TestCase/AbstractBackendController.php +++ b/dev/tests/integration/framework/Magento/TestFramework/TestCase/AbstractBackendController.php @@ -5,8 +5,6 @@ */ namespace Magento\TestFramework\TestCase; -use Magento\Framework\App\Request\Http as HttpRequest; - /** * A parent class for backend controllers - contains directives for admin user creation and authentication. * @@ -122,7 +120,7 @@ public function testAclHasAccess() */ public function testAclNoAccess() { - if ($this->resource === null) { + if ($this->resource === null || $this->uri === null) { $this->markTestIncomplete('Acl test is not complete'); } if ($this->httpMethod) { diff --git a/dev/tests/integration/phpunit.xml.dist b/dev/tests/integration/phpunit.xml.dist index b54a504a1be..815abde6ac2 100644 --- a/dev/tests/integration/phpunit.xml.dist +++ b/dev/tests/integration/phpunit.xml.dist @@ -72,6 +72,8 @@ <!-- Connection parameters for MongoDB library tests --> <!--<const name="MONGODB_CONNECTION_STRING" value="mongodb://localhost:27017"/>--> <!--<const name="MONGODB_DATABASE_NAME" value="magento_integration_tests"/>--> + <!-- Connection parameters for RabbitMQ tests --> + <!--<const name="RABBITMQ_MANAGEMENT_PORT" value="15672"/>--> </php> <!-- Test listeners --> <listeners> diff --git a/dev/tests/integration/testsuite/Magento/AsynchronousOperations/Model/MassScheduleTest.php b/dev/tests/integration/testsuite/Magento/AsynchronousOperations/Model/MassScheduleTest.php index 305c3550269..c0cc1763b26 100644 --- a/dev/tests/integration/testsuite/Magento/AsynchronousOperations/Model/MassScheduleTest.php +++ b/dev/tests/integration/testsuite/Magento/AsynchronousOperations/Model/MassScheduleTest.php @@ -128,7 +128,10 @@ public function sendBulk($products) } $this->clearProducts(); - $result = $this->massSchedule->publishMass('async.V1.products.POST', $products); + $result = $this->massSchedule->publishMass( + 'async.magento.catalog.api.productrepositoryinterface.save.post', + $products + ); //assert bulk accepted with no errors $this->assertFalse($result->isErrors()); @@ -206,7 +209,7 @@ public function testScheduleMassOneEntityFailure($products) $expectedErrorMessage = "Data item corresponding to \"product\" " . "must be specified in the message with topic " . - "\"async.V1.products.POST\"."; + "\"async.magento.catalog.api.productrepositoryinterface.save.post\"."; $this->assertEquals( $expectedErrorMessage, $reasonException->getMessage() diff --git a/dev/tests/integration/testsuite/Magento/Authorizenet/Controller/Directpost/Payment/BackendResponseTest.php b/dev/tests/integration/testsuite/Magento/Authorizenet/Controller/Directpost/Payment/BackendResponseTest.php index 28d4395e7e4..7ab55dc7fd9 100644 --- a/dev/tests/integration/testsuite/Magento/Authorizenet/Controller/Directpost/Payment/BackendResponseTest.php +++ b/dev/tests/integration/testsuite/Magento/Authorizenet/Controller/Directpost/Payment/BackendResponseTest.php @@ -23,7 +23,7 @@ public function testUnauthorizedRequest() $data = [ 'x_response_code' => 1, 'x_response_reason_code' => 1, - 'x_invoice_num' => 1, + 'x_invoice_num' => '1', 'x_amount' => 16, 'x_trans_id' => '32iiw5ve', 'x_card_type' => 'American Express', @@ -48,7 +48,7 @@ public function testSuccess() $data = [ 'x_response_code' => 1, 'x_response_reason_code' => 1, - 'x_invoice_num' => 1, + 'x_invoice_num' => '1', 'x_amount' => 16, 'x_trans_id' => '32iiw5ve', 'x_card_type' => 'American Express', diff --git a/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Fixture/full_order_with_capture.php b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Fixture/full_order_with_capture.php new file mode 100644 index 00000000000..4ef5a4dd14c --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Fixture/full_order_with_capture.php @@ -0,0 +1,45 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +use Magento\AuthorizenetAcceptjs\Gateway\Config; +use Magento\Sales\Model\Order\Payment; +use Magento\Sales\Model\OrderRepository; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\Sales\Api\TransactionRepositoryInterface; +use Magento\Sales\Model\Order\Payment\Transaction; +use Magento\Sales\Model\Order\Payment\Transaction\BuilderInterface as TransactionBuilder; + +$order = include __DIR__ . '/../_files/full_order.php'; + +$objectManager = Bootstrap::getObjectManager(); + +/** @var Payment $payment */ +$payment = $order->getPayment(); +$payment->setMethod(Config::METHOD); +$payment->setAuthorizationTransaction(false); +$payment->setParentTransactionId(4321); + + +/** @var OrderRepository $orderRepo */ +$orderRepo = $objectManager->get(OrderRepository::class); +$orderRepo->save($order); + +/** @var TransactionBuilder $transactionBuilder */ +$transactionBuilder = $objectManager->create(TransactionBuilder::class); +$transactionAuthorize = $transactionBuilder->setPayment($payment) + ->setOrder($order) + ->setTransactionId(1234) + ->build(Transaction::TYPE_AUTH); +$transactionCapture = $transactionBuilder->setPayment($payment) + ->setOrder($order) + ->setTransactionId(4321) + ->build(Transaction::TYPE_CAPTURE); + +$transactionRepository = $objectManager->create(TransactionRepositoryInterface::class); +$transactionRepository->save($transactionAuthorize); +$transactionRepository->save($transactionCapture); diff --git a/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Fixture/full_order_with_capture_rollback.php b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Fixture/full_order_with_capture_rollback.php new file mode 100644 index 00000000000..1a2cb2532fe --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Fixture/full_order_with_capture_rollback.php @@ -0,0 +1,28 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\TestFramework\ObjectManager; + +$objectManager = ObjectManager::getInstance(); + +/** @var SearchCriteriaBuilder $searchCriteriaBuilder */ +$searchCriteriaBuilder = $objectManager->get(SearchCriteriaBuilder::class); +$searchCriteria = $searchCriteriaBuilder->addFilter('increment_id', '100000001') + ->create(); + +/** @var OrderRepositoryInterface $orderRepository */ +$orderRepository = $objectManager->get(OrderRepositoryInterface::class); +$items = $orderRepository->getList($searchCriteria) + ->getItems(); + +foreach ($items as $item) { + $orderRepository->delete($item); +} + +require __DIR__ . '/../../../Magento/Catalog/_files/product_simple_rollback.php'; diff --git a/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Fixture/order_auth_only.php b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Fixture/order_auth_only.php new file mode 100644 index 00000000000..b1d0521c9c6 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Fixture/order_auth_only.php @@ -0,0 +1,74 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\Sales\Model\Order; +use Magento\Sales\Model\Order\Address; +use Magento\Sales\Model\Order\Item as OrderItem; +use Magento\Store\Model\StoreManagerInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\ObjectManager; +use Magento\Sales\Api\TransactionRepositoryInterface; +use Magento\Sales\Model\Order\Payment\Transaction; +use Magento\Sales\Model\Order\Payment\Transaction\BuilderInterface as TransactionBuilder; + +/** @var ObjectManager $objectManager */ +$objectManager = Bootstrap::getObjectManager(); + +$addressData = include __DIR__ . '/../../Sales/_files/address_data.php'; +require __DIR__ . '/../../../Magento/Catalog/_files/product_simple.php'; + +$billingAddress = $objectManager->create(Address::class, ['data' => $addressData]); +$billingAddress->setAddressType('billing'); + +$shippingAddress = clone $billingAddress; +$shippingAddress->setId(null) + ->setAddressType('shipping'); + +/** @var OrderItem $orderItem */ +$orderItem = $objectManager->create(OrderItem::class); +$orderItem->setProductId($product->getId()) + ->setQtyOrdered(2) + ->setBasePrice($product->getPrice()) + ->setPrice($product->getPrice()) + ->setRowTotal($product->getPrice()) + ->setProductType('simple'); + +require __DIR__ . '/payment.php'; + +$order = $objectManager->create(Order::class); +$order->setIncrementId('100000002') + ->setSubtotal($product->getPrice() * 2) + ->setBaseSubtotal($product->getPrice() * 2) + ->setCustomerEmail('admin@example.com') + ->setCustomerIsGuest(true) + ->setBillingAddress($billingAddress) + ->setShippingAddress($shippingAddress) + ->setStoreId( + $objectManager->get(StoreManagerInterface::class)->getStore() + ->getId() + ) + ->addItem($orderItem) + ->setPayment($payment); + +$payment->setParentTransactionId(1234); + +/** @var OrderRepositoryInterface $orderRepository */ +$orderRepository = $objectManager->get(OrderRepositoryInterface::class); +$orderRepository->save($order); + +/** @var TransactionBuilder $transactionBuilder */ +$transactionBuilder = $objectManager->create(TransactionBuilder::class); +$transactionAuthorize = $transactionBuilder->setPayment($payment) + ->setOrder($order) + ->setTransactionId(1234) + ->build(Transaction::TYPE_AUTH); + +$transactionAuthorize->setAdditionalInformation('real_transaction_id', '1234'); + +$transactionRepository = $objectManager->create(TransactionRepositoryInterface::class); +$transactionRepository->save($transactionAuthorize); diff --git a/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Fixture/order_auth_only_rollback.php b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Fixture/order_auth_only_rollback.php new file mode 100644 index 00000000000..5a65a1fc0d0 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Fixture/order_auth_only_rollback.php @@ -0,0 +1,9 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +require __DIR__ . '/order_captured_rollback.php'; diff --git a/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Fixture/order_captured.php b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Fixture/order_captured.php new file mode 100644 index 00000000000..9bfc863df7d --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Fixture/order_captured.php @@ -0,0 +1,77 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\Sales\Model\Order; +use Magento\Sales\Model\Order\Address; +use Magento\Sales\Model\Order\Item as OrderItem; +use Magento\Store\Model\StoreManagerInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\ObjectManager; +use Magento\Sales\Api\TransactionRepositoryInterface; +use Magento\Sales\Model\Order\Payment\Transaction; +use Magento\Sales\Model\Order\Payment\Transaction\BuilderInterface as TransactionBuilder; + +/** @var ObjectManager $objectManager */ +$objectManager = Bootstrap::getObjectManager(); + +$addressData = include __DIR__ . '/../../Sales/_files/address_data.php'; +require __DIR__ . '/../../../Magento/Catalog/_files/product_simple.php'; + +$billingAddress = $objectManager->create(Address::class, ['data' => $addressData]); +$billingAddress->setAddressType('billing'); + +$shippingAddress = clone $billingAddress; +$shippingAddress->setId(null) + ->setAddressType('shipping'); + +/** @var OrderItem $orderItem */ +$orderItem = $objectManager->create(OrderItem::class); +$orderItem->setProductId($product->getId()) + ->setQtyOrdered(2) + ->setBasePrice($product->getPrice()) + ->setPrice($product->getPrice()) + ->setRowTotal($product->getPrice()) + ->setProductType('simple'); + +require __DIR__ . '/payment.php'; + +$order = $objectManager->create(Order::class); +$order->setIncrementId('100000002') + ->setSubtotal($product->getPrice() * 2) + ->setBaseSubtotal($product->getPrice() * 2) + ->setCustomerEmail('admin@example.com') + ->setCustomerIsGuest(true) + ->setBillingAddress($billingAddress) + ->setShippingAddress($shippingAddress) + ->setStoreId( + $objectManager->get(StoreManagerInterface::class)->getStore() + ->getId() + ) + ->addItem($orderItem) + ->setPayment($payment); + +$payment->setParentTransactionId(4321); + +/** @var OrderRepositoryInterface $orderRepository */ +$orderRepository = $objectManager->get(OrderRepositoryInterface::class); +$orderRepository->save($order); + +/** @var TransactionBuilder $transactionBuilder */ +$transactionBuilder = $objectManager->create(TransactionBuilder::class); +$transactionAuthorize = $transactionBuilder->setPayment($payment) + ->setOrder($order) + ->setTransactionId(1234) + ->build(Transaction::TYPE_AUTH); +$transactionCapture = $transactionBuilder->setPayment($payment) + ->setOrder($order) + ->setTransactionId(4321) + ->build(Transaction::TYPE_CAPTURE); + +$transactionRepository = $objectManager->create(TransactionRepositoryInterface::class); +$transactionRepository->save($transactionAuthorize); +$transactionRepository->save($transactionCapture); diff --git a/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Fixture/order_captured_rollback.php b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Fixture/order_captured_rollback.php new file mode 100644 index 00000000000..a2da0b639e9 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Fixture/order_captured_rollback.php @@ -0,0 +1,28 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\TestFramework\ObjectManager; + +$objectManager = ObjectManager::getInstance(); + +/** @var SearchCriteriaBuilder $searchCriteriaBuilder */ +$searchCriteriaBuilder = $objectManager->get(SearchCriteriaBuilder::class); +$searchCriteria = $searchCriteriaBuilder->addFilter('increment_id', '100000002') + ->create(); + +/** @var OrderRepositoryInterface $orderRepository */ +$orderRepository = $objectManager->get(OrderRepositoryInterface::class); +$items = $orderRepository->getList($searchCriteria) + ->getItems(); + +foreach ($items as $item) { + $orderRepository->delete($item); +} + +require __DIR__ . '/../../../Magento/Catalog/_files/product_simple_rollback.php'; diff --git a/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Fixture/payment.php b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Fixture/payment.php new file mode 100644 index 00000000000..5b15e356a7d --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Fixture/payment.php @@ -0,0 +1,19 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\AuthorizenetAcceptjs\Gateway\Config; +use Magento\Sales\Model\Order\Payment; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\ObjectManager; + +/** @var ObjectManager $objectManager */ +$objectManager = Bootstrap::getObjectManager(); + +/** @var Payment $payment */ +$payment = $objectManager->create(Payment::class); +$payment->setMethod(Config::METHOD); +$payment->setAuthorizationTransaction(true); diff --git a/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Gateway/AbstractTest.php b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Gateway/AbstractTest.php new file mode 100644 index 00000000000..f1458a19012 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Gateway/AbstractTest.php @@ -0,0 +1,91 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Gateway; + +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Framework\App\Area; +use Magento\Framework\HTTP\ZendClient; +use Magento\Framework\HTTP\ZendClientFactory; +use PHPUnit\Framework\MockObject\Builder\InvocationMocker; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\ObjectManager; +use Magento\Payment\Gateway\Data\PaymentDataObjectFactory; +use Magento\Quote\Model\Quote\PaymentFactory; +use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\Sales\Model\Order; +use Zend_Http_Response; + +abstract class AbstractTest extends TestCase +{ + /** + * @var ObjectManager + */ + protected $objectManager; + + /** + * @var ZendClient|MockObject|InvocationMocker + */ + protected $clientMock; + + /** + * @var PaymentFactory + */ + protected $paymentFactory; + + /** + * @var Zend_Http_Response + */ + protected $responseMock; + + /** + * @throws \Magento\Framework\Exception\LocalizedException + */ + protected function setUp() + { + $bootstrap = Bootstrap::getInstance(); + $bootstrap->loadArea(Area::AREA_FRONTEND); + $this->objectManager = Bootstrap::getObjectManager(); + $this->clientMock = $this->createMock(ZendClient::class); + $this->responseMock = $this->createMock(Zend_Http_Response::class); + $this->clientMock->method('request') + ->willReturn($this->responseMock); + $this->clientMock->method('setUri') + ->with('https://apitest.authorize.net/xml/v1/request.api'); + $clientFactoryMock = $this->createMock(ZendClientFactory::class); + $clientFactoryMock->method('create') + ->willReturn($this->clientMock); + /** @var PaymentDataObjectFactory $paymentFactory */ + $this->paymentFactory = $this->objectManager->get(PaymentDataObjectFactory::class); + $this->objectManager->addSharedInstance($clientFactoryMock, ZendClientFactory::class); + } + + protected function tearDown() + { + $this->objectManager->removeSharedInstance(ZendClientFactory::class); + parent::tearDown(); + } + + protected function getOrderWithIncrementId(string $incrementId): Order + { + /** @var OrderRepositoryInterface $orderRepository */ + $orderRepository = $this->objectManager->get(OrderRepositoryInterface::class); + $searchCriteria = $this->objectManager->get(SearchCriteriaBuilder::class) + ->addFilter('increment_id', $incrementId) + ->create(); + /** @var Order $order */ + $order = current( + $orderRepository->getList($searchCriteria) + ->getItems() + ); + + return $order; + } +} diff --git a/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Gateway/Command/AcceptFdsCommandTest.php b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Gateway/Command/AcceptFdsCommandTest.php new file mode 100644 index 00000000000..394d9de6684 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Gateway/Command/AcceptFdsCommandTest.php @@ -0,0 +1,49 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Gateway\Command; + +use Magento\AuthorizenetAcceptjs\Gateway\AbstractTest; +use Magento\Payment\Gateway\Command\CommandPoolInterface; +use Magento\Sales\Model\Order\Payment; + +class AcceptFdsCommandTest extends AbstractTest +{ + /** + * @magentoConfigFixture default_store payment/authorizenet_acceptjs/environment sandbox + * @magentoConfigFixture default_store payment/authorizenet_acceptjs/login someusername + * @magentoConfigFixture default_store payment/authorizenet_acceptjs/trans_key somepassword + * @magentoConfigFixture default_store payment/authorizenet_acceptjs/trans_signature_key abc + * @magentoDataFixture Magento/AuthorizenetAcceptjs/Fixture/order_auth_only.php + */ + public function testAcceptFdsCommand() + { + /** @var CommandPoolInterface $commandPool */ + $commandPool = $this->objectManager->get('AuthorizenetAcceptjsCommandPool'); + $command = $commandPool->get('accept_fds'); + + $order = $this->getOrderWithIncrementId('100000002'); + $payment = $order->getPayment(); + $paymentDO = $this->paymentFactory->create($payment); + + $expectedRequest = include __DIR__ . '/../../_files/expected_request/accept_fds.php'; + $response = include __DIR__ . '/../../_files/response/generic_success.php'; + + $this->clientMock->expects($this->once()) + ->method('setRawData') + ->with(json_encode($expectedRequest), 'application/json'); + + $this->responseMock->expects($this->once()) + ->method('getBody') + ->willReturn(json_encode($response)); + + $command->execute([ + 'payment' => $paymentDO + ]); + } +} diff --git a/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Gateway/Command/AuthorizeCommandTest.php b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Gateway/Command/AuthorizeCommandTest.php new file mode 100644 index 00000000000..9affd80be06 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Gateway/Command/AuthorizeCommandTest.php @@ -0,0 +1,70 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Gateway\Command; + +use Magento\AuthorizenetAcceptjs\Gateway\AbstractTest; +use Magento\Payment\Gateway\Command\CommandPoolInterface; +use Magento\Sales\Model\Order\Payment; +use Magento\Sales\Model\Order\Payment\Transaction; + +class AuthorizeCommandTest extends AbstractTest +{ + /** + * @magentoConfigFixture default_store payment/authorizenet_acceptjs/environment sandbox + * @magentoConfigFixture default_store payment/authorizenet_acceptjs/login someusername + * @magentoConfigFixture default_store payment/authorizenet_acceptjs/trans_key somepassword + * @magentoConfigFixture default_store payment/authorizenet_acceptjs/trans_signature_key abc + */ + public function testAuthorizeCommand() + { + /** @var CommandPoolInterface $commandPool */ + $commandPool = $this->objectManager->get('AuthorizenetAcceptjsCommandPool'); + $command = $commandPool->get('authorize'); + + $order = include __DIR__ . '/../../_files/full_order.php'; + $payment = $order->getPayment(); + + $paymentDO = $this->paymentFactory->create($payment); + + $expectedRequest = include __DIR__ . '/../../_files/expected_request/authorize.php'; + $response = include __DIR__ . '/../../_files/response/authorize.php'; + + $this->clientMock->method('setRawData') + ->with(json_encode($expectedRequest), 'application/json'); + + $this->responseMock->method('getBody') + ->willReturn(json_encode($response)); + + $command->execute([ + 'payment' => $paymentDO, + 'amount' => 100.00 + ]); + + /** @var Payment $payment */ + $rawDetails = [ + 'authCode' => 'abc123', + 'avsResultCode' => 'Y', + 'cvvResultCode' => 'P', + 'cavvResultCode' => '2', + 'accountType' => 'Visa', + ]; + $this->assertSame('1111', $payment->getCcLast4()); + $this->assertSame('Y', $payment->getCcAvsStatus()); + $this->assertFalse($payment->getData('is_transaction_closed')); + + $transactionDetails = $payment->getTransactionAdditionalInfo(); + foreach ($rawDetails as $key => $value) { + $this->assertSame($value, $payment->getAdditionalInformation($key)); + $this->assertSame($value, $transactionDetails[Transaction::RAW_DETAILS][$key]); + } + + $this->assertSame('123456', $payment->getTransactionId()); + $this->assertSame('123456', $transactionDetails['real_transaction_id']); + } +} diff --git a/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Gateway/Command/CancelCommandTest.php b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Gateway/Command/CancelCommandTest.php new file mode 100644 index 00000000000..aa606a50ae6 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Gateway/Command/CancelCommandTest.php @@ -0,0 +1,62 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Gateway\Command; + +use Magento\AuthorizenetAcceptjs\Gateway\AbstractTest; +use Magento\Payment\Gateway\Command\CommandPoolInterface; +use Magento\Sales\Model\Order\Payment; + +class CancelCommandTest extends AbstractTest +{ + /** + * @magentoConfigFixture default_store payment/authorizenet_acceptjs/environment sandbox + * @magentoConfigFixture default_store payment/authorizenet_acceptjs/login someusername + * @magentoConfigFixture default_store payment/authorizenet_acceptjs/trans_key somepassword + * @magentoConfigFixture default_store payment/authorizenet_acceptjs/trans_signature_key abc + * @magentoDataFixture Magento/AuthorizenetAcceptjs/Fixture/order_auth_only.php + * @dataProvider aliasesProvider + */ + public function testCancelCommand(string $commandName) + { + /** @var CommandPoolInterface $commandPool */ + $commandPool = $this->objectManager->get('AuthorizenetAcceptjsCommandPool'); + $command = $commandPool->get($commandName); + + $order = $this->getOrderWithIncrementId('100000002'); + $payment = $order->getPayment(); + $paymentDO = $this->paymentFactory->create($payment); + + $expectedRequest = include __DIR__ . '/../../_files/expected_request/void.php'; + $response = include __DIR__ . '/../../_files/response/void.php'; + + $this->clientMock->method('setRawData') + ->with(json_encode($expectedRequest), 'application/json'); + + $this->responseMock->method('getBody') + ->willReturn(json_encode($response)); + + $command->execute([ + 'payment' => $paymentDO + ]); + + /** @var Payment $payment */ + + $this->assertTrue($payment->getIsTransactionClosed()); + $this->assertTrue($payment->getShouldCloseParentTransaction()); + $this->assertArrayNotHasKey('real_transaction_id', $payment->getTransactionAdditionalInfo()); + } + + public function aliasesProvider() + { + return [ + ['cancel'], + ['deny_payment'] + ]; + } +} diff --git a/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Gateway/Command/FetchTransactionInfoCommandTest.php b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Gateway/Command/FetchTransactionInfoCommandTest.php new file mode 100644 index 00000000000..1651dfc7db3 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Gateway/Command/FetchTransactionInfoCommandTest.php @@ -0,0 +1,184 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Gateway\Command; + +use Magento\AuthorizenetAcceptjs\Gateway\AbstractTest; +use Magento\Payment\Gateway\Command\CommandPoolInterface; +use Magento\Sales\Model\Order\Payment; + +class FetchTransactionInfoCommandTest extends AbstractTest +{ + /** + * @magentoConfigFixture default_store payment/authorizenet_acceptjs/environment sandbox + * @magentoConfigFixture default_store payment/authorizenet_acceptjs/login someusername + * @magentoConfigFixture default_store payment/authorizenet_acceptjs/trans_key somepassword + * @magentoConfigFixture default_store payment/authorizenet_acceptjs/trans_signature_key abc + * @magentoConfigFixture default_store payment/authorizenet_acceptjs/transactionSyncKeys transId,transactionType + * @magentoDataFixture Magento/AuthorizenetAcceptjs/Fixture/order_auth_only.php + */ + public function testTransactionApproved() + { + /** @var CommandPoolInterface $commandPool */ + $commandPool = $this->objectManager->get('AuthorizenetAcceptjsCommandPool'); + $command = $commandPool->get('fetch_transaction_information'); + + $order = $this->getOrderWithIncrementId('100000002'); + $payment = $order->getPayment(); + $paymentDO = $this->paymentFactory->create($payment); + + $expectedRequest = include __DIR__ . '/../../_files/expected_request/transaction_details_authorized.php'; + $response = include __DIR__ . '/../../_files/response/transaction_details_authorized.php'; + + $this->clientMock->method('setRawData') + ->with(json_encode($expectedRequest), 'application/json'); + + $this->responseMock->method('getBody') + ->willReturn(json_encode($response)); + + $result = $command->execute([ + 'payment' => $paymentDO + ]); + + $expected = [ + 'transId' => '1234', + 'transactionType' => 'authOnlyTransaction' + ]; + $this->assertSame($expected, $result); + + /** @var Payment $payment */ + $this->assertTrue($payment->getIsTransactionApproved()); + $this->assertFalse($payment->getIsTransactionDenied()); + } + + /** + * @magentoConfigFixture default_store payment/authorizenet_acceptjs/environment sandbox + * @magentoConfigFixture default_store payment/authorizenet_acceptjs/login someusername + * @magentoConfigFixture default_store payment/authorizenet_acceptjs/trans_key somepassword + * @magentoConfigFixture default_store payment/authorizenet_acceptjs/trans_signature_key abc + * * @magentoConfigFixture default_store payment/authorizenet_acceptjs/transactionSyncKeys transId,transactionType + * @magentoDataFixture Magento/AuthorizenetAcceptjs/Fixture/order_auth_only.php + */ + public function testTransactionVoided() + { + /** @var CommandPoolInterface $commandPool */ + $commandPool = $this->objectManager->get('AuthorizenetAcceptjsCommandPool'); + $command = $commandPool->get('fetch_transaction_information'); + + $order = $this->getOrderWithIncrementId('100000002'); + $payment = $order->getPayment(); + $paymentDO = $this->paymentFactory->create($payment); + + $expectedRequest = include __DIR__ . '/../../_files/expected_request/transaction_details_authorized.php'; + $response = include __DIR__ . '/../../_files/response/transaction_details_voided.php'; + + $this->clientMock->method('setRawData') + ->with(json_encode($expectedRequest), 'application/json'); + + $this->responseMock->method('getBody') + ->willReturn(json_encode($response)); + + $result = $command->execute([ + 'payment' => $paymentDO + ]); + + $expected = [ + 'transId' => '1234', + 'transactionType' => 'authOnlyTransaction' + ]; + $this->assertSame($expected, $result); + + /** @var Payment $payment */ + $this->assertFalse($payment->getIsTransactionApproved()); + $this->assertTrue($payment->getIsTransactionDenied()); + } + + /** + * @magentoConfigFixture default_store payment/authorizenet_acceptjs/environment sandbox + * @magentoConfigFixture default_store payment/authorizenet_acceptjs/login someusername + * @magentoConfigFixture default_store payment/authorizenet_acceptjs/trans_key somepassword + * @magentoConfigFixture default_store payment/authorizenet_acceptjs/trans_signature_key abc + * @magentoConfigFixture default_store payment/authorizenet_acceptjs/transactionSyncKeys transId,transactionType + * @magentoDataFixture Magento/AuthorizenetAcceptjs/Fixture/order_auth_only.php + */ + public function testTransactionDenied() + { + /** @var CommandPoolInterface $commandPool */ + $commandPool = $this->objectManager->get('AuthorizenetAcceptjsCommandPool'); + $command = $commandPool->get('fetch_transaction_information'); + + $order = $this->getOrderWithIncrementId('100000002'); + $payment = $order->getPayment(); + $paymentDO = $this->paymentFactory->create($payment); + + $expectedRequest = include __DIR__ . '/../../_files/expected_request/transaction_details_authorized.php'; + $response = include __DIR__ . '/../../_files/response/transaction_details_voided.php'; + + $this->clientMock->method('setRawData') + ->with(json_encode($expectedRequest), 'application/json'); + + $this->responseMock->method('getBody') + ->willReturn(json_encode($response)); + + $result = $command->execute([ + 'payment' => $paymentDO + ]); + + $expected = [ + 'transId' => '1234', + 'transactionType' => 'authOnlyTransaction' + ]; + $this->assertSame($expected, $result); + + /** @var Payment $payment */ + $this->assertFalse($payment->getIsTransactionApproved()); + $this->assertTrue($payment->getIsTransactionDenied()); + } + + /** + * @magentoConfigFixture default_store payment/authorizenet_acceptjs/environment sandbox + * @magentoConfigFixture default_store payment/authorizenet_acceptjs/login someusername + * @magentoConfigFixture default_store payment/authorizenet_acceptjs/trans_key somepassword + * @magentoConfigFixture default_store payment/authorizenet_acceptjs/trans_signature_key abc + * @magentoConfigFixture default_store payment/authorizenet_acceptjs/transactionSyncKeys transId,transactionType + * @magentoDataFixture Magento/AuthorizenetAcceptjs/Fixture/order_auth_only.php + */ + public function testTransactionPending() + { + /** @var CommandPoolInterface $commandPool */ + $commandPool = $this->objectManager->get('AuthorizenetAcceptjsCommandPool'); + $command = $commandPool->get('fetch_transaction_information'); + + $order = $this->getOrderWithIncrementId('100000002'); + $payment = $order->getPayment(); + $paymentDO = $this->paymentFactory->create($payment); + + $expectedRequest = include __DIR__ . '/../../_files/expected_request/transaction_details_authorized.php'; + $response = include __DIR__ . '/../../_files/response/transaction_details_fds_pending.php'; + + $this->clientMock->method('setRawData') + ->with(json_encode($expectedRequest), 'application/json'); + + $this->responseMock->method('getBody') + ->willReturn(json_encode($response)); + + $result = $command->execute([ + 'payment' => $paymentDO + ]); + + $expected = [ + 'transId' => '1234', + 'transactionType' => 'authOnlyTransaction' + ]; + $this->assertSame($expected, $result); + + /** @var Payment $payment */ + $this->assertNull($payment->getIsTransactionApproved()); + $this->assertNull($payment->getIsTransactionDenied()); + } +} diff --git a/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Gateway/Command/RefundSettledCommandTest.php b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Gateway/Command/RefundSettledCommandTest.php new file mode 100644 index 00000000000..6e06d749f39 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Gateway/Command/RefundSettledCommandTest.php @@ -0,0 +1,53 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Gateway\Command; + +use Magento\AuthorizenetAcceptjs\Gateway\AbstractTest; +use Magento\Payment\Gateway\Command\CommandPoolInterface; +use Magento\Sales\Model\Order\Payment; + +class RefundSettledCommandTest extends AbstractTest +{ + /** + * @magentoConfigFixture default_store payment/authorizenet_acceptjs/environment sandbox + * @magentoConfigFixture default_store payment/authorizenet_acceptjs/login someusername + * @magentoConfigFixture default_store payment/authorizenet_acceptjs/trans_key somepassword + * @magentoConfigFixture default_store payment/authorizenet_acceptjs/trans_signature_key abc + * @magentoDataFixture Magento/AuthorizenetAcceptjs/Fixture/full_order_with_capture.php + */ + public function testRefundSettledCommand() + { + /** @var CommandPoolInterface $commandPool */ + $commandPool = $this->objectManager->get('AuthorizenetAcceptjsCommandPool'); + $command = $commandPool->get('refund_settled'); + + $order = $this->getOrderWithIncrementId('100000001'); + $payment = $order->getPayment(); + + $paymentDO = $this->paymentFactory->create($payment); + + $expectedRequest = include __DIR__ . '/../../_files/expected_request/refund.php'; + $response = include __DIR__ . '/../../_files/response/refund.php'; + + $this->clientMock->method('setRawData') + ->with(json_encode($expectedRequest), 'application/json'); + + $this->responseMock->method('getBody') + ->willReturn(json_encode($response)); + + $command->execute([ + 'payment' => $paymentDO, + 'amount' => 100.00 + ]); + + /** @var Payment $payment */ + $this->assertTrue($payment->getIsTransactionClosed()); + $this->assertSame('5678', $payment->getTransactionId()); + } +} diff --git a/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Gateway/Command/SaleCommandTest.php b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Gateway/Command/SaleCommandTest.php new file mode 100644 index 00000000000..7ae03d36cb7 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Gateway/Command/SaleCommandTest.php @@ -0,0 +1,71 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Gateway\Command; + +use Magento\AuthorizenetAcceptjs\Gateway\AbstractTest; +use Magento\Payment\Gateway\Command\CommandPoolInterface; +use Magento\Sales\Model\Order\Payment; +use Magento\Sales\Model\Order\Payment\Transaction; + +class SaleCommandTest extends AbstractTest +{ + /** + * @magentoConfigFixture default_store payment/authorizenet_acceptjs/environment sandbox + * @magentoConfigFixture default_store payment/authorizenet_acceptjs/login someusername + * @magentoConfigFixture default_store payment/authorizenet_acceptjs/trans_key somepassword + * @magentoConfigFixture default_store payment/authorizenet_acceptjs/trans_signature_key abc + */ + public function testSaleCommand() + { + /** @var CommandPoolInterface $commandPool */ + $commandPool = $this->objectManager->get('AuthorizenetAcceptjsCommandPool'); + $command = $commandPool->get('sale'); + + $order = include __DIR__ . '/../../_files/full_order.php'; + $payment = $order->getPayment(); + + $paymentDO = $this->paymentFactory->create($payment); + + $expectedRequest = include __DIR__ . '/../../_files/expected_request/sale.php'; + $response = include __DIR__ . '/../../_files/response/sale.php'; + + $this->clientMock->method('setRawData') + ->with(json_encode($expectedRequest), 'application/json'); + + $this->responseMock->method('getBody') + ->willReturn(json_encode($response)); + + $command->execute([ + 'payment' => $paymentDO, + 'amount' => 100.00 + ]); + + /** @var Payment $payment */ + $rawDetails = [ + 'authCode' => 'abc123', + 'avsResultCode' => 'Y', + 'cvvResultCode' => 'P', + 'cavvResultCode' => '2', + 'accountType' => 'Visa', + ]; + $this->assertSame('1111', $payment->getCcLast4()); + $this->assertSame('Y', $payment->getCcAvsStatus()); + + $transactionDetails = $payment->getTransactionAdditionalInfo(); + foreach ($rawDetails as $key => $value) { + $this->assertSame($value, $payment->getAdditionalInformation($key)); + $this->assertSame($value, $transactionDetails[Transaction::RAW_DETAILS][$key]); + } + + $this->assertSame('123456', $payment->getTransactionId()); + $this->assertSame('123456', $transactionDetails['real_transaction_id']); + $this->assertTrue($payment->getShouldCloseParentTransaction()); + $this->assertFalse($payment->getData('is_transaction_closed')); + } +} diff --git a/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Gateway/Command/SettleCommandTest.php b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Gateway/Command/SettleCommandTest.php new file mode 100644 index 00000000000..bb0a259b165 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Gateway/Command/SettleCommandTest.php @@ -0,0 +1,52 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Gateway\Command; + +use Magento\AuthorizenetAcceptjs\Gateway\AbstractTest; +use Magento\Payment\Gateway\Command\CommandPoolInterface; +use Magento\Sales\Model\Order\Payment; + +class SettleCommandTest extends AbstractTest +{ + /** + * @magentoConfigFixture default_store payment/authorizenet_acceptjs/environment sandbox + * @magentoConfigFixture default_store payment/authorizenet_acceptjs/login someusername + * @magentoConfigFixture default_store payment/authorizenet_acceptjs/trans_key somepassword + * @magentoConfigFixture default_store payment/authorizenet_acceptjs/trans_signature_key abc + * @magentoDataFixture Magento/AuthorizenetAcceptjs/Fixture/order_auth_only.php + */ + public function testRefundSettledCommand() + { + /** @var CommandPoolInterface $commandPool */ + $commandPool = $this->objectManager->get('AuthorizenetAcceptjsCommandPool'); + $command = $commandPool->get('settle'); + + $order = $this->getOrderWithIncrementId('100000002'); + $payment = $order->getPayment(); + + $paymentDO = $this->paymentFactory->create($payment); + + $expectedRequest = include __DIR__ . '/../../_files/expected_request/settle.php'; + $response = include __DIR__ . '/../../_files/response/settle.php'; + + $this->clientMock->method('setRawData') + ->with(json_encode($expectedRequest), 'application/json'); + + $this->responseMock->method('getBody') + ->willReturn(json_encode($response)); + + $command->execute([ + 'payment' => $paymentDO, + 'amount' => 100.00 + ]); + + /** @var Payment $payment */ + $this->assertTrue($payment->getShouldCloseParentTransaction()); + } +} diff --git a/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Gateway/Command/TransactionDetailsCommandTest.php b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Gateway/Command/TransactionDetailsCommandTest.php new file mode 100644 index 00000000000..d81cffc413b --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Gateway/Command/TransactionDetailsCommandTest.php @@ -0,0 +1,50 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Gateway\Command; + +use Magento\AuthorizenetAcceptjs\Gateway\AbstractTest; +use Magento\Payment\Gateway\Command\CommandPoolInterface; + +class TransactionDetailsCommandTest extends AbstractTest +{ + /** + * @magentoConfigFixture default_store payment/authorizenet_acceptjs/environment sandbox + * @magentoConfigFixture default_store payment/authorizenet_acceptjs/login someusername + * @magentoConfigFixture default_store payment/authorizenet_acceptjs/trans_key somepassword + * @magentoConfigFixture default_store payment/authorizenet_acceptjs/trans_signature_key abc + * @magentoDataFixture Magento/AuthorizenetAcceptjs/Fixture/order_captured.php + */ + public function testTransactionDetails() + { + /** @var CommandPoolInterface $commandPool */ + $commandPool = $this->objectManager->get('AuthorizenetAcceptjsCommandPool'); + $command = $commandPool->get('get_transaction_details'); + + $order = $this->getOrderWithIncrementId('100000002'); + $payment = $order->getPayment(); + $paymentDO = $this->paymentFactory->create($payment); + + $expectedRequest = include __DIR__ . '/../../_files/expected_request/transaction_details.php'; + $response = include __DIR__ . '/../../_files/response/transaction_details_settled_capture.php'; + + $this->clientMock->method('setRawData') + ->with(json_encode($expectedRequest), 'application/json'); + + $this->responseMock->method('getBody') + ->willReturn(json_encode($response)); + + $result = $command->execute([ + 'payment' => $paymentDO + ]); + + $resultData = $result->get(); + + $this->assertEquals($response, $resultData); + } +} diff --git a/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Gateway/Command/VoidCommandTest.php b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Gateway/Command/VoidCommandTest.php new file mode 100644 index 00000000000..f74f8542bfd --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Gateway/Command/VoidCommandTest.php @@ -0,0 +1,53 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Gateway\Command; + +use Magento\AuthorizenetAcceptjs\Gateway\AbstractTest; +use Magento\Payment\Gateway\Command\CommandPoolInterface; +use Magento\Sales\Model\Order\Payment; + +class VoidCommandTest extends AbstractTest +{ + /** + * @magentoConfigFixture default_store payment/authorizenet_acceptjs/environment sandbox + * @magentoConfigFixture default_store payment/authorizenet_acceptjs/login someusername + * @magentoConfigFixture default_store payment/authorizenet_acceptjs/trans_key somepassword + * @magentoConfigFixture default_store payment/authorizenet_acceptjs/trans_signature_key abc + * @magentoDataFixture Magento/AuthorizenetAcceptjs/Fixture/order_auth_only.php + */ + public function testVoidCommand() + { + /** @var CommandPoolInterface $commandPool */ + $commandPool = $this->objectManager->get('AuthorizenetAcceptjsCommandPool'); + $command = $commandPool->get('void'); + + $order = $this->getOrderWithIncrementId('100000002'); + $payment = $order->getPayment(); + $paymentDO = $this->paymentFactory->create($payment); + + $expectedRequest = include __DIR__ . '/../../_files/expected_request/void.php'; + $response = include __DIR__ . '/../../_files/response/void.php'; + + $this->clientMock->method('setRawData') + ->with(json_encode($expectedRequest), 'application/json'); + + $this->responseMock->method('getBody') + ->willReturn(json_encode($response)); + + $command->execute([ + 'payment' => $paymentDO + ]); + + /** @var Payment $payment */ + + $this->assertTrue($payment->getIsTransactionClosed()); + $this->assertTrue($payment->getShouldCloseParentTransaction()); + $this->assertEquals('1234', $payment->getTransactionAdditionalInfo()['real_transaction_id']); + } +} diff --git a/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Gateway/ConfigTest.php b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Gateway/ConfigTest.php new file mode 100644 index 00000000000..7e6aeda5a7a --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Gateway/ConfigTest.php @@ -0,0 +1,44 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\AuthorizenetAcceptjs\Gateway; + +use Magento\Payment\Model\Method\Adapter; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\ObjectManager; +use PHPUnit\Framework\TestCase; + +class ConfigTest extends TestCase +{ + /** + * @var ObjectManager + */ + private $objectManager; + + protected function setUp() + { + $this->objectManager = Bootstrap::getObjectManager(); + } + + public function testVerifyConfiguration() + { + /** @var Adapter $paymentAdapter */ + $paymentAdapter = $this->objectManager->get('AuthorizenetAcceptjsFacade'); + + $this->assertEquals('authorizenet_acceptjs', $paymentAdapter->getCode()); + $this->assertTrue($paymentAdapter->canAuthorize()); + $this->assertTrue($paymentAdapter->canCapture()); + $this->assertFalse($paymentAdapter->canCapturePartial()); + $this->assertTrue($paymentAdapter->canRefund()); + $this->assertTrue($paymentAdapter->canUseCheckout()); + $this->assertTrue($paymentAdapter->canVoid()); + $this->assertTrue($paymentAdapter->canUseInternal()); + $this->assertTrue($paymentAdapter->canEdit()); + $this->assertTrue($paymentAdapter->canFetchTransactionInfo()); + } +} diff --git a/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/expected_request/accept_fds.php b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/expected_request/accept_fds.php new file mode 100644 index 00000000000..d843de1c2ca --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/expected_request/accept_fds.php @@ -0,0 +1,20 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +return [ + 'updateHeldTransactionRequest' => [ + 'merchantAuthentication' => [ + 'name' => 'someusername', + 'transactionKey' => 'somepassword' + ], + 'heldTransactionRequest' => [ + 'action' => 'approve', + 'refTransId' => '1234', + ] + ] +]; diff --git a/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/expected_request/authorize.php b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/expected_request/authorize.php new file mode 100644 index 00000000000..16debdb2ef8 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/expected_request/authorize.php @@ -0,0 +1,66 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +return [ + 'createTransactionRequest' => [ + 'merchantAuthentication' =>[ + 'name' => 'someusername', + 'transactionKey' => 'somepassword', + ], + 'transactionRequest' => [ + 'transactionType' => 'authOnlyTransaction', + 'amount' => '100.00', + 'payment' => [ + 'opaqueData' => [ + 'dataDescriptor' => 'mydescriptor', + 'dataValue' => 'myvalue', + ], + ], + 'solution' => [ + 'id' => 'AAA102993', + ], + 'order' => [ + 'invoiceNumber' => '100000001', + ], + 'poNumber' => null, + 'customer' => [ + 'id' => 1, + 'email' => 'admin@example.com', + ], + 'billTo' => [ + 'firstName' => 'firstname', + 'lastName' => 'lastname', + 'company' => '', + 'address' => 'street', + 'city' => 'Los Angeles', + 'state' => 'CA', + 'zip' => '11111', + 'country' => 'US', + ], + 'shipTo' => [ + 'firstName' => 'John', + 'lastName' => 'Doe', + 'company' => '', + 'address' => '6161 West Centinela Avenue', + 'city' => 'Los Angeles', + 'state' => 'CA', + 'zip' => '11111', + 'country' => 'US', + ], + 'customerIP' => '127.0.0.1', + 'userFields' => [ + 'userField' => [ + [ + 'name' => 'transactionType', + 'value' => 'authOnlyTransaction', + ], + ], + ], + ], + ] +]; diff --git a/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/expected_request/refund.php b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/expected_request/refund.php new file mode 100644 index 00000000000..5ed331d076f --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/expected_request/refund.php @@ -0,0 +1,56 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +return [ + 'createTransactionRequest' => [ + 'merchantAuthentication' =>[ + 'name' => 'someusername', + 'transactionKey' => 'somepassword', + ], + 'transactionRequest' => [ + 'transactionType' => 'refundTransaction', + 'amount' => '100.00', + 'payment' => [ + 'creditCard' => [ + 'cardNumber' => '1111', + 'expirationDate' => 'XXXX' + ] + ], + 'refTransId' => '4321', + 'order' => [ + 'invoiceNumber' => '100000001', + ], + 'poNumber' => null, + 'customer' => [ + 'id' => '1', + 'email' => 'admin@example.com', + ], + 'billTo' => [ + 'firstName' => 'firstname', + 'lastName' => 'lastname', + 'company' => '', + 'address' => 'street', + 'city' => 'Los Angeles', + 'state' => 'CA', + 'zip' => '11111', + 'country' => 'US', + ], + 'shipTo' => [ + 'firstName' => 'John', + 'lastName' => 'Doe', + 'company' => '', + 'address' => '6161 West Centinela Avenue', + 'city' => 'Los Angeles', + 'state' => 'CA', + 'zip' => '11111', + 'country' => 'US', + ], + 'customerIP' => '127.0.0.1' + ], + ] +]; diff --git a/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/expected_request/sale.php b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/expected_request/sale.php new file mode 100644 index 00000000000..4514acbcb66 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/expected_request/sale.php @@ -0,0 +1,66 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +return [ + 'createTransactionRequest' => [ + 'merchantAuthentication' =>[ + 'name' => 'someusername', + 'transactionKey' => 'somepassword', + ], + 'transactionRequest' => [ + 'transactionType' => 'authCaptureTransaction', + 'amount' => '100.00', + 'payment' => [ + 'opaqueData' => [ + 'dataDescriptor' => 'mydescriptor', + 'dataValue' => 'myvalue', + ], + ], + 'solution' => [ + 'id' => 'AAA102993', + ], + 'order' => [ + 'invoiceNumber' => '100000001', + ], + 'poNumber' => null, + 'customer' => [ + 'id' => 1, + 'email' => 'admin@example.com', + ], + 'billTo' => [ + 'firstName' => 'firstname', + 'lastName' => 'lastname', + 'company' => '', + 'address' => 'street', + 'city' => 'Los Angeles', + 'state' => 'CA', + 'zip' => '11111', + 'country' => 'US', + ], + 'shipTo' => [ + 'firstName' => 'John', + 'lastName' => 'Doe', + 'company' => '', + 'address' => '6161 West Centinela Avenue', + 'city' => 'Los Angeles', + 'state' => 'CA', + 'zip' => '11111', + 'country' => 'US', + ], + 'customerIP' => '127.0.0.1', + 'userFields' => [ + 'userField' => [ + [ + 'name' => 'transactionType', + 'value' => 'authCaptureTransaction', + ], + ], + ], + ], + ] +]; diff --git a/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/expected_request/settle.php b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/expected_request/settle.php new file mode 100644 index 00000000000..b4fa88cc1e5 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/expected_request/settle.php @@ -0,0 +1,28 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +return [ + 'createTransactionRequest' => [ + 'merchantAuthentication' =>[ + 'name' => 'someusername', + 'transactionKey' => 'somepassword', + ], + 'transactionRequest' => [ + 'transactionType' => 'priorAuthCaptureTransaction', + 'refTransId' => '1234', + 'userFields' => [ + 'userField' => [ + [ + 'name' => 'transactionType', + 'value' => 'priorAuthCaptureTransaction', + ], + ], + ], + ], + ] +]; diff --git a/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/expected_request/transaction_details.php b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/expected_request/transaction_details.php new file mode 100644 index 00000000000..11033386676 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/expected_request/transaction_details.php @@ -0,0 +1,17 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +return [ + 'getTransactionDetailsRequest' => [ + 'merchantAuthentication' => [ + 'name' => 'someusername', + 'transactionKey' => 'somepassword' + ], + 'transId' => '4321' + ] +]; diff --git a/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/expected_request/transaction_details_authorized.php b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/expected_request/transaction_details_authorized.php new file mode 100644 index 00000000000..c3ffdedba68 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/expected_request/transaction_details_authorized.php @@ -0,0 +1,17 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +return [ + 'getTransactionDetailsRequest' => [ + 'merchantAuthentication' => [ + 'name' => 'someusername', + 'transactionKey' => 'somepassword' + ], + 'transId' => '1234' + ] +]; diff --git a/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/expected_request/void.php b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/expected_request/void.php new file mode 100644 index 00000000000..a1d3dade74f --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/expected_request/void.php @@ -0,0 +1,20 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +return [ + 'createTransactionRequest' => [ + 'merchantAuthentication' => [ + 'name' => 'someusername', + 'transactionKey' => 'somepassword', + ], + 'transactionRequest' =>[ + 'transactionType' => 'voidTransaction', + 'refTransId' => '1234', + ], + ] +]; diff --git a/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/full_order.php b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/full_order.php new file mode 100644 index 00000000000..cac7c38971a --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/full_order.php @@ -0,0 +1,125 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\Product\Attribute\Source\Status; +use Magento\Catalog\Model\Product\Type; +use Magento\Catalog\Model\Product\Visibility; +use Magento\Sales\Model\Order; +use Magento\Sales\Model\Order\Payment; +use Magento\Sales\Model\Order\Address; +use Magento\Sales\Model\Order\Item; +use Magento\TestFramework\Helper\Bootstrap; + +$addressData = include __DIR__ . '/../../../Magento/Sales/_files/address_data.php'; +require __DIR__ . '/../../../Magento/Customer/_files/customer.php'; + +$objectManager = Bootstrap::getObjectManager(); + +/** @var $product Product */ +$product = $objectManager->create(Product::class); +$product->isObjectNew(true); +$product->setTypeId(Type::TYPE_SIMPLE) + ->setId(1) + ->setAttributeSetId(4) + ->setWebsiteIds([1]) + ->setName('Simple Product') + ->setSku('simple') + ->setPrice(10) + ->setWeight(1) + ->setShortDescription('Short description') + ->setTaxClassId(0) + ->setDescription('Description with <b>html tag</b>') + ->setMetaTitle('meta title') + ->setMetaKeyword('meta keyword') + ->setMetaDescription('meta description') + ->setVisibility(Visibility::VISIBILITY_BOTH) + ->setStatus(Status::STATUS_ENABLED) + ->setStockData([ + 'use_config_manage_stock' => 1, + 'qty' => 100, + 'is_qty_decimal' => 0, + 'is_in_stock' => 1, + ])->setCanSaveCustomOptions(true) + ->setHasOptions(false); + +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->create(ProductRepositoryInterface::class); +$productRepository->save($product); + + +$billingAddress = $objectManager->create(Address::class, ['data' => $addressData]); +$billingAddress->setAddressType('billing'); + +$shippingAddress = clone $billingAddress; +$shippingAddress->setId(null) + ->setAddressType('shipping') + ->setStreet(['6161 West Centinela Avenue']) + ->setFirstname('John') + ->setLastname('Doe') + ->setShippingMethod('flatrate_flatrate'); + +$payment = $objectManager->create(Payment::class); +$payment->setAdditionalInformation('ccLast4', '1111'); +$payment->setAdditionalInformation('opaqueDataDescriptor', 'mydescriptor'); +$payment->setAdditionalInformation('opaqueDataValue', 'myvalue'); + +/** @var Item $orderItem */ +$orderItem1 = $objectManager->create(Item::class); +$orderItem1->setProductId($product->getId()) + ->setSku($product->getSku()) + ->setName($product->getName()) + ->setQtyOrdered(1) + ->setBasePrice($product->getPrice()) + ->setPrice($product->getPrice()) + ->setRowTotal($product->getPrice()) + ->setProductType($product->getTypeId()); + +/** @var Item $orderItem */ +$orderItem2 = $objectManager->create(Item::class); +$orderItem2->setProductId($product->getId()) + ->setSku('simple2') + ->setName('Simple product') + ->setPrice(100) + ->setQtyOrdered(2) + ->setBasePrice($product->getPrice()) + ->setPrice($product->getPrice()) + ->setRowTotal($product->getPrice()) + ->setProductType($product->getTypeId()); + +$orderAmount = 100; +$customerEmail = $billingAddress->getEmail(); + +/** @var Order $order */ +$order = $objectManager->create(Order::class); +$order->setIncrementId('100000001') + ->setState(Order::STATE_PROCESSING) + ->setStatus(Order::STATE_PROCESSING) + ->setCustomerId($customer->getId()) + ->setCustomerIsGuest(false) + ->setRemoteIp('127.0.0.1') + ->setCreatedAt(date('Y-m-d 00:00:55')) + ->setOrderCurrencyCode('USD') + ->setBaseCurrencyCode('USD') + ->setSubtotal($orderAmount) + ->setGrandTotal($orderAmount) + ->setBaseSubtotal($orderAmount) + ->setBaseGrandTotal($orderAmount) + ->setCustomerEmail($customerEmail) + ->setBillingAddress($billingAddress) + ->setShippingAddress($shippingAddress) + ->setShippingDescription('Flat Rate - Fixed') + ->setShippingAmount(10) + ->setStoreId(1) + ->addItem($orderItem1) + ->addItem($orderItem2) + ->setQuoteId(1) + ->setPayment($payment); + +return $order; diff --git a/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/response/authorize.php b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/response/authorize.php new file mode 100644 index 00000000000..f80495137ca --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/response/authorize.php @@ -0,0 +1,47 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +return [ + 'transactionResponse' => [ + 'responseCode' => '1', + 'authCode' => 'abc123', + 'avsResultCode' => 'Y', + 'cvvResultCode' => 'P', + 'cavvResultCode' => '2', + 'transId' => '123456', + 'refTransID' => '', + 'transHash' => 'foobar', + 'testRequest' => '0', + 'accountNumber' => 'XXXX1111', + 'accountType' => 'Visa', + 'messages' => [ + [ + 'code' => '1', + 'description' => 'This transaction has been approved.' + ] + ], + 'userFields' => [ + [ + 'name' => 'transactionType', + 'value' => 'authOnlyTransaction' + ] + ], + 'transHashSha2' => 'CD1E57FB1B5C876FDBD536CB16F8BBBA687580EDD78DD881C7F14DC4467C32BF6C' + . '808620FBD59E5977DF19460B98CCFC0DA0D90755992C0D611CABB8E2BA52B0', + 'SupplementalDataQualificationIndicator' => 0 + ], + 'messages' => [ + 'resultCode' => 'Ok', + 'message' => [ + [ + 'code' => 'I00001', + 'text' => 'Successful.' + ] + ] + ] +]; diff --git a/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/response/generic_success.php b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/response/generic_success.php new file mode 100644 index 00000000000..ea7662e3193 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/response/generic_success.php @@ -0,0 +1,17 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +return [ + 'messages' => [ + 'resultCode' => 'Ok', + 'message' => [ + 'code' => 'I00001', + 'text' => 'Successful' + ] + ] +]; diff --git a/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/response/refund.php b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/response/refund.php new file mode 100644 index 00000000000..536f51d659a --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/response/refund.php @@ -0,0 +1,40 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +return [ + 'transactionResponse' => [ + 'responseCode' => '1', + 'authCode' => '', + 'avsResultCode' => 'P', + 'cvvResultCode' => '', + 'cavvResultCode' => '', + 'transId' => '5678', + 'refTransID' => '4321', + 'testRequest' => '0', + 'accountNumber' => 'XXXX1111', + 'accountType' => 'Visa', + 'messages' => [ + [ + 'code' => '1', + 'description' => 'This transaction has been approved.' + ] + ], + 'transHashSha2' => '78BD31BA5BCDF3C3FA3C8373D8DF80EF07FC7E02C3545FCF18A408E2F76ED4F20D' + . 'FF007221374B576FDD1BFD953B3E5CF37249CEC4C135EEF975F7B478D8452C', + 'SupplementalDataQualificationIndicator' => 0 + ], + 'messages' => [ + 'resultCode' => 'Ok', + 'message' => [ + [ + 'code' => 'I00001', + 'text' => 'Successful.' + ] + ] + ] +]; diff --git a/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/response/sale.php b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/response/sale.php new file mode 100644 index 00000000000..74a80110ade --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/response/sale.php @@ -0,0 +1,47 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +return [ + 'transactionResponse' => [ + 'responseCode' => '1', + 'authCode' => 'abc123', + 'avsResultCode' => 'Y', + 'cvvResultCode' => 'P', + 'cavvResultCode' => '2', + 'transId' => '123456', + 'refTransID' => '', + 'transHash' => 'foobar', + 'testRequest' => '0', + 'accountNumber' => 'XXXX1111', + 'accountType' => 'Visa', + 'messages' => [ + [ + 'code' => '1', + 'description' => 'This transaction has been approved.' + ] + ], + 'userFields' => [ + [ + 'name' => 'transactionType', + 'value' => 'authCaptureTransaction' + ] + ], + 'transHashSha2' => 'CD1E57FB1B5C876FDBD536CB16F8BBBA687580EDD78DD881C7F14DC4467C32BF6C' + . '808620FBD59E5977DF19460B98CCFC0DA0D90755992C0D611CABB8E2BA52B0', + 'SupplementalDataQualificationIndicator' => 0 + ], + 'messages' => [ + 'resultCode' => 'Ok', + 'message' => [ + [ + 'code' => 'I00001', + 'text' => 'Successful.' + ] + ] + ] +]; diff --git a/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/response/settle.php b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/response/settle.php new file mode 100644 index 00000000000..5e54c301987 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/response/settle.php @@ -0,0 +1,40 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +return [ + 'transactionResponse' => [ + 'responseCode' => '1', + 'authCode' => '', + 'avsResultCode' => 'P', + 'cvvResultCode' => '', + 'cavvResultCode' => '', + 'transId' => '1234', + 'refTransID' => '1234', + 'testRequest' => '0', + 'accountNumber' => 'XXXX1111', + 'accountType' => 'Visa', + 'messages' => [ + [ + 'code' => '1', + 'description' => 'This transaction has been approved.' + ] + ], + 'transHashSha2' => '1B22AB4E4DF750CF2E0D1944BB6903537C145545C7313C87B6FD4A6384' + . '709EA2609CE9A9788C128F2F2EAEEE474F6010418904648C6D000BE3AF7BCD98A5AD8F', + 'SupplementalDataQualificationIndicator' => 0 + ], + 'messages' => [ + 'resultCode' => 'Ok', + 'message' => [ + [ + 'code' => 'I00001', + 'text' => 'Successful.' + ] + ] + ] +]; diff --git a/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/response/transaction_details_authorized.php b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/response/transaction_details_authorized.php new file mode 100644 index 00000000000..80fd24a5c60 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/response/transaction_details_authorized.php @@ -0,0 +1,22 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +return [ + 'messages' => [ + 'resultCode' => 'Ok', + 'message' => [ + 'code' => 'I00001', + 'text' => 'Successful' + ] + ], + 'transaction' => [ + 'transId' => '1234', + 'transactionType' => 'authOnlyTransaction', + 'transactionStatus' => 'authorizedPendingCapture' + ] +]; diff --git a/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/response/transaction_details_declined.php b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/response/transaction_details_declined.php new file mode 100644 index 00000000000..24c9353e408 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/response/transaction_details_declined.php @@ -0,0 +1,22 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +return [ + 'messages' => [ + 'resultCode' => 'Ok', + 'message' => [ + 'code' => 'I00001', + 'text' => 'Successful' + ] + ], + 'transaction' => [ + 'transId' => '1234', + 'transactionType' => 'authOnlyTransaction', + 'transactionStatus' => 'declined' + ] +]; diff --git a/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/response/transaction_details_fds_pending.php b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/response/transaction_details_fds_pending.php new file mode 100644 index 00000000000..de045f30ab2 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/response/transaction_details_fds_pending.php @@ -0,0 +1,22 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +return [ + 'messages' => [ + 'resultCode' => 'Ok', + 'message' => [ + 'code' => 'I00001', + 'text' => 'Successful' + ] + ], + 'transaction' => [ + 'transId' => '1234', + 'transactionType' => 'authOnlyTransaction', + 'transactionStatus' => 'FDSPendingReview' + ] +]; diff --git a/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/response/transaction_details_settled_capture.php b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/response/transaction_details_settled_capture.php new file mode 100644 index 00000000000..5df2f03a943 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/response/transaction_details_settled_capture.php @@ -0,0 +1,22 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +return [ + 'messages' => [ + 'resultCode' => 'Ok', + 'message' => [ + 'code' => 'I00001', + 'text' => 'Successful' + ] + ], + 'transaction' => [ + 'transId' => '4321', + 'transactionType' => 'captureOnlyTransaction', + 'transactionStatus' => 'settledSuccessfully' + ] +]; diff --git a/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/response/transaction_details_voided.php b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/response/transaction_details_voided.php new file mode 100644 index 00000000000..7ee735cd8cf --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/response/transaction_details_voided.php @@ -0,0 +1,22 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +return [ + 'messages' => [ + 'resultCode' => 'Ok', + 'message' => [ + 'code' => 'I00001', + 'text' => 'Successful' + ] + ], + 'transaction' => [ + 'transId' => '1234', + 'transactionType' => 'authOnlyTransaction', + 'transactionStatus' => 'void' + ] +]; diff --git a/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/response/void.php b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/response/void.php new file mode 100644 index 00000000000..eb71de4dd96 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/response/void.php @@ -0,0 +1,30 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +return [ + 'messages' => [ + 'resultCode' => 'Ok', + 'message' => [ + 'code' => 'I00001', + 'text' => 'Successful' + ] + ], + 'transactionResponse' => [ + 'responseCode' => '1', + 'messages' => [ + 'message' => [ + [ + 'code' => 1 + ] + ] + ], + 'transHashSha2' => '1B22AB4E4DF750CF2E0D1944BB6903537C145545C7313C87B6FD4A6384709E' + . 'A2609CE9A9788C128F2F2EAEEE474F6010418904648C6D000BE3AF7BCD98A5AD8F', + 'transId' => '1234' + ] +]; diff --git a/dev/tests/integration/testsuite/Magento/Bundle/Model/Plugin/Frontend/ProductTest.php b/dev/tests/integration/testsuite/Magento/Bundle/Model/Plugin/Frontend/ProductTest.php new file mode 100644 index 00000000000..91dcd5f3e8d --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Bundle/Model/Plugin/Frontend/ProductTest.php @@ -0,0 +1,71 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Bundle\Model\Plugin\Frontend; + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Interception\PluginList; +use PHPUnit\Framework\TestCase; + +/** + * Test bundle fronted product plugin adds children products ids to bundle product identities. + */ +class ProductTest extends TestCase +{ + /** + * Check, product plugin is registered for storefront. + * + * @magentoAppArea frontend + * @return void + */ + public function testProductIsRegistered(): void + { + $pluginInfo = Bootstrap::getObjectManager()->get(PluginList::class) + ->get(\Magento\Catalog\Model\Product::class, []); + $this->assertSame(Product::class, $pluginInfo['bundle']['instance']); + } + + /** + * Check plugin will add children ids to bundle product identities on storefront. + * + * @magentoDataFixture Magento/Bundle/_files/product.php + * @magentoAppArea frontend + * @return void + */ + public function testGetIdentitiesForBundleProductOnStorefront(): void + { + $productRepository = Bootstrap::getObjectManager()->get(ProductRepositoryInterface::class); + $bundleProduct = $productRepository->get('bundle-product'); + $simpleProduct = $productRepository->get('simple'); + $expectedIdentities = [ + 'cat_p_' . $bundleProduct->getId(), + 'cat_p', + 'cat_p_' . $simpleProduct->getId(), + + ]; + $this->assertEquals($expectedIdentities, $bundleProduct->getIdentities()); + } + + /** + * Check plugin won't add children ids to bundle product identities in admin area. + * + * @magentoDataFixture Magento/Bundle/_files/product.php + * @magentoAppArea adminhtml + * @return void + */ + public function testGetIdentitiesForBundleProductInAdminArea(): void + { + $productRepository = Bootstrap::getObjectManager()->get(ProductRepositoryInterface::class); + $bundleProduct = $productRepository->get('bundle-product'); + $expectedIdentities = [ + 'cat_p_' . $bundleProduct->getId(), + ]; + $this->assertEquals($expectedIdentities, $bundleProduct->getIdentities()); + } +} diff --git a/dev/tests/integration/testsuite/Magento/BundleImportExport/Model/BundleTest.php b/dev/tests/integration/testsuite/Magento/BundleImportExport/Model/BundleTest.php index e15f8d47a7b..864bdaa2a13 100644 --- a/dev/tests/integration/testsuite/Magento/BundleImportExport/Model/BundleTest.php +++ b/dev/tests/integration/testsuite/Magento/BundleImportExport/Model/BundleTest.php @@ -9,7 +9,10 @@ class BundleTest extends AbstractProductExportImportTestCase { - public function exportImportDataProvider() + /** + * @return array + */ + public function exportImportDataProvider(): array { return [ // @todo uncomment after MAGETWO-49677 resolved @@ -45,17 +48,13 @@ public function exportImportDataProvider() ]; } - public function importReplaceDataProvider() - { - return $this->exportImportDataProvider(); - } - /** - * @param \Magento\Catalog\Model\Product $expectedProduct - * @param \Magento\Catalog\Model\Product $actualProduct + * @inheritdoc */ - protected function assertEqualsSpecificAttributes($expectedProduct, $actualProduct) - { + protected function assertEqualsSpecificAttributes( + \Magento\Catalog\Model\Product $expectedProduct, + \Magento\Catalog\Model\Product $actualProduct + ): void { $expectedBundleProductOptions = $expectedProduct->getExtensionAttributes()->getBundleProductOptions(); $actualBundleProductOptions = $actualProduct->getExtensionAttributes()->getBundleProductOptions(); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/categories.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/categories.php index a903274793c..a5ab9619324 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/categories.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/categories.php @@ -178,7 +178,7 @@ ->setParentId(3) ->setPath('1/2/3/13') ->setLevel(3) - ->setDescription('Ololo') + ->setDescription('Its a description of Test Category 1.2') ->setAvailableSortBy('name') ->setDefaultSortBy('name') ->setIsActive(true) diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/text_attribute_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_text_attribute_rollback.php similarity index 84% rename from dev/tests/integration/testsuite/Magento/Catalog/_files/text_attribute_rollback.php rename to dev/tests/integration/testsuite/Magento/Catalog/_files/product_text_attribute_rollback.php index cbc0476efd1..a9ab0e11312 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/text_attribute_rollback.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_text_attribute_rollback.php @@ -3,13 +3,15 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + /* Delete attribute with text_attribute code */ -$registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get('Magento\Framework\Registry'); +$registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Framework\Registry::class); $registry->unregister('isSecureArea'); $registry->register('isSecureArea', true); + /** @var $attribute \Magento\Catalog\Model\ResourceModel\Eav\Attribute */ $attribute = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( - 'Magento\Catalog\Model\ResourceModel\Eav\Attribute' + \Magento\Catalog\Model\ResourceModel\Eav\Attribute::class ); $attribute->load('text_attribute', 'attribute_code'); $attribute->delete(); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_new.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_new.php index 7d6e2e6f978..2cd0dd2c775 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_new.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_new.php @@ -15,8 +15,8 @@ ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) ->setWebsiteIds([1]) ->setStockData(['qty' => 100, 'is_in_stock' => 1, 'manage_stock' => 1]) - ->setNewsFromDate(date('Y-m-d', strtotime('-2 day'))) - ->setNewsToDate(date('Y-m-d', strtotime('+2 day'))) + ->setNewsFromDate(date('Y-m-d H:i:s', strtotime('-2 day'))) + ->setNewsToDate(date('Y-m-d H:i:s', strtotime('+2 day'))) ->setDescription('description') ->setShortDescription('short desc') ->save(); diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/AbstractProductExportImportTestCase.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/AbstractProductExportImportTestCase.php index b562879b319..d3a2e4c53f2 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/AbstractProductExportImportTestCase.php +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/AbstractProductExportImportTestCase.php @@ -55,6 +55,16 @@ abstract class AbstractProductExportImportTestCase extends \PHPUnit\Framework\Te 'is_salable', // stock indexation is not performed during import ]; + /** + * @var array + */ + private static $attributesToRefresh = [ + 'tax_class_id', + ]; + + /** + * @inheritdoc + */ protected function setUp() { $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); @@ -65,12 +75,17 @@ protected function setUp() \Magento\CatalogImportExport\Model\Import\Product\Type\AbstractType::$commonAttributesCache = []; } + /** + * @inheritdoc + */ protected function tearDown() { - $this->executeRollbackFixtures($this->fixtures); + $this->executeFixtures($this->fixtures, true); } /** + * Run import/export tests. + * * @magentoAppArea adminhtml * @magentoDbIsolation disabled * @magentoAppIsolation enabled @@ -78,36 +93,60 @@ protected function tearDown() * @param array $fixtures * @param string[] $skus * @param string[] $skippedAttributes + * @return void * @dataProvider exportImportDataProvider */ - public function testExport($fixtures, $skus, $skippedAttributes = []) + public function testImportExport(array $fixtures, array $skus, array $skippedAttributes = []): void { $this->fixtures = $fixtures; - $this->executeFixtures($fixtures, $skus); + $this->executeFixtures($fixtures); $this->modifyData($skus); $skippedAttributes = array_merge(self::$skippedAttributes, $skippedAttributes); - $this->executeExportTest($skus, $skippedAttributes); + $csvFile = $this->executeExportTest($skus, $skippedAttributes); + + $this->executeImportReplaceTest($skus, $skippedAttributes, false, $csvFile); + $this->executeImportReplaceTest($skus, $skippedAttributes, true, $csvFile); + $this->executeImportDeleteTest($skus, $csvFile); } - abstract public function exportImportDataProvider(); + /** + * Provide data for import/export. + * + * @return array + */ + abstract public function exportImportDataProvider(): array; /** + * Modify data. + * * @param array $skus + * @return void * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - protected function modifyData($skus) + protected function modifyData(array $skus): void { } /** + * Prepare product. + * * @param \Magento\Catalog\Model\Product $product + * @return void * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public function prepareProduct($product) + public function prepareProduct(\Magento\Catalog\Model\Product $product): void { } - protected function executeExportTest($skus, $skippedAttributes) + /** + * Execute export test. + * + * @param array $skus + * @param array $skippedAttributes + * @return string + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ + protected function executeExportTest(array $skus, array $skippedAttributes): string { $index = 0; $ids = []; @@ -140,10 +179,23 @@ protected function executeExportTest($skus, $skippedAttributes) $this->assertEqualsSpecificAttributes($origProducts[$index], $newProduct); } + + return $csvfile; } - private function assertEqualsOtherThanSkippedAttributes($expected, $actual, $skippedAttributes) - { + /** + * Assert data equals (ignore skipped attributes). + * + * @param array $expected + * @param array $actual + * @param array $skippedAttributes + * @return void + */ + private function assertEqualsOtherThanSkippedAttributes( + array $expected, + array $actual, + array $skippedAttributes + ): void { foreach ($expected as $key => $value) { if (is_object($value) || in_array($key, $skippedAttributes)) { continue; @@ -158,134 +210,93 @@ private function assertEqualsOtherThanSkippedAttributes($expected, $actual, $ski } /** - * @magentoAppArea adminhtml - * @magentoDbIsolation disabled - * @magentoAppIsolation enabled + * Execute import test with delete behavior. * - * @param array $fixtures - * @param string[] $skus - * @dataProvider exportImportDataProvider - * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @param array $skus + * @param string|null $csvFile + * @return void */ - public function testImportDelete($fixtures, $skus, $skippedAttributes = []) - { - $this->fixtures = $fixtures; - $this->executeFixtures($fixtures, $skus); - $this->modifyData($skus); - $this->executeImportDeleteTest($skus); - } - - protected function executeImportDeleteTest($skus) + protected function executeImportDeleteTest(array $skus, string $csvFile = null): void { - $csvfile = $this->exportProducts(); - $this->importProducts($csvfile, \Magento\ImportExport\Model\Import::BEHAVIOR_DELETE); - /** @var \Magento\Catalog\Model\Product $product */ - $product = $this->objectManager->create(\Magento\Catalog\Model\Product::class); + $csvFile = $csvFile ?? $this->exportProducts(); + $this->importProducts($csvFile, \Magento\ImportExport\Model\Import::BEHAVIOR_DELETE); foreach ($skus as $sku) { $productId = $this->productResource->getIdBySku($sku); - $product->load($productId); - $this->assertNull($product->getId()); + $this->assertFalse($productId); } } /** - * Execute fixtures + * Execute fixtures. * - * @param array $skus * @param array $fixtures + * @param bool $rollback * @return void * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - protected function executeFixtures($fixtures, $skus = []) + protected function executeFixtures(array $fixtures, bool $rollback = false) { foreach ($fixtures as $fixture) { - $fixturePath = $this->fileSystem->getDirectoryRead(DirectoryList::ROOT) - ->getAbsolutePath('/dev/tests/integration/testsuite/' . $fixture); + $fixturePath = $this->resolveFixturePath($fixture, $rollback); include $fixturePath; } } /** - * Execute rollback fixtures + * Resolve fixture path. * - * @param array $fixtures - * @return void + * @param string $fixture + * @param bool $rollback + * @return string */ - private function executeRollbackFixtures($fixtures) + private function resolveFixturePath(string $fixture, bool $rollback = false) { - foreach ($fixtures as $fixture) { - $fixturePath = $this->fileSystem->getDirectoryRead(DirectoryList::ROOT) - ->getAbsolutePath('/dev/tests/integration/testsuite/' . $fixture); + $fixturePath = $this->fileSystem->getDirectoryRead(DirectoryList::ROOT) + ->getAbsolutePath('/dev/tests/integration/testsuite/' . $fixture); + if ($rollback) { $fileInfo = pathinfo($fixturePath); $extension = ''; if (isset($fileInfo['extension'])) { $extension = '.' . $fileInfo['extension']; } - $rollbackfixturePath = $fileInfo['dirname'] . '/' . $fileInfo['filename'] . '_rollback' . $extension; - if (file_exists($rollbackfixturePath)) { - include $rollbackfixturePath; - } + $fixturePath = $fileInfo['dirname'] . '/' . $fileInfo['filename'] . '_rollback' . $extension; } + + return $fixturePath; } /** + * Assert that specific attributes equal. + * * @param \Magento\Catalog\Model\Product $expectedProduct * @param \Magento\Catalog\Model\Product $actualProduct * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - protected function assertEqualsSpecificAttributes($expectedProduct, $actualProduct) - { + protected function assertEqualsSpecificAttributes( + \Magento\Catalog\Model\Product $expectedProduct, + \Magento\Catalog\Model\Product $actualProduct + ): void { // check custom options } /** - * @magentoAppArea adminhtml - * @magentoDbIsolation disabled - * @magentoAppIsolation enabled + * Execute import test with replace behavior. * - * @param array $fixtures - * @param string[] $skus - * @param string[] $skippedAttributes - * @dataProvider importReplaceDataProvider - */ - public function testImportReplace($fixtures, $skus, $skippedAttributes = []) - { - $this->fixtures = $fixtures; - $this->executeFixtures($fixtures, $skus); - $this->modifyData($skus); - $skippedAttributes = array_merge(self::$skippedAttributes, $skippedAttributes); - $this->executeImportReplaceTest($skus, $skippedAttributes); - } - - /** - * @magentoAppArea adminhtml - * @magentoDbIsolation disabled - * @magentoAppIsolation enabled - * - * @param array $fixtures - * @param string[] $skus - * @param string[] $skippedAttributes - * @dataProvider importReplaceDataProvider - */ - public function testImportReplaceWithPagination($fixtures, $skus, $skippedAttributes = []) - { - $this->fixtures = $fixtures; - $this->executeFixtures($fixtures, $skus); - $this->modifyData($skus); - $skippedAttributes = array_merge(self::$skippedAttributes, $skippedAttributes); - $this->executeImportReplaceTest($skus, $skippedAttributes, true); - } - - /** * @param string[] $skus * @param string[] $skippedAttributes * @param bool $usePagination - * + * @param string|null $csvfile + * @return void + * @throws \Magento\Framework\Exception\NoSuchEntityException * @SuppressWarnings(PHPMD.NPathComplexity) * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ - protected function executeImportReplaceTest($skus, $skippedAttributes, $usePagination = false) - { + protected function executeImportReplaceTest( + $skus, + $skippedAttributes, + $usePagination = false, + string $csvfile = null + ) { $replacedAttributes = [ 'row_id', 'entity_id', @@ -293,6 +304,7 @@ protected function executeImportReplaceTest($skus, $skippedAttributes, $usePagin 'media_gallery' ]; $skippedAttributes = array_merge($replacedAttributes, $skippedAttributes); + $this->cleanAttributesCache(); $index = 0; $ids = []; @@ -316,15 +328,15 @@ protected function executeImportReplaceTest($skus, $skippedAttributes, $usePagin $itemsPerPageProperty->setValue($exportProduct, 1); } - $csvfile = $this->exportProducts($exportProduct); + $csvfile = $csvfile ?? $this->exportProducts($exportProduct); $this->importProducts($csvfile, \Magento\ImportExport\Model\Import::BEHAVIOR_REPLACE); while ($index > 0) { $index--; $newProduct = $productRepository->get($skus[$index], false, Store::DEFAULT_STORE_ID, true); // check original product is deleted - $origProduct = $this->objectManager->create(\Magento\Catalog\Model\Product::class)->load($ids[$index]); - $this->assertNull($origProduct->getId()); + $productId = $this->productResource->getIdBySku($ids[$index]); + $this->assertFalse($productId); // check new product data // @todo uncomment or remove after MAGETWO-49806 resolved @@ -342,7 +354,7 @@ protected function executeImportReplaceTest($skus, $skippedAttributes, $usePagin array_filter($origProductData[$attribute]) : $origProductData[$attribute]; if (!empty($expected)) { - $actual = isset($newProductData[$attribute]) ? $newProductData[$attribute] : null; + $actual = $newProductData[$attribute] ?? null; $actual = is_array($actual) ? array_filter($actual) : $actual; $this->assertNotEquals($expected, $actual, $attribute . ' is expected to be changed'); } @@ -352,7 +364,7 @@ protected function executeImportReplaceTest($skus, $skippedAttributes, $usePagin } /** - * Export products in the system + * Export products in the system. * * @param \Magento\CatalogImportExport\Model\Export\Product|null $exportProduct * @return string Return exported file name @@ -371,17 +383,18 @@ private function exportProducts(\Magento\CatalogImportExport\Model\Export\Produc ) ); $this->assertNotEmpty($exportProduct->export()); + return $csvfile; } /** - * Import products from the given file + * Import products from the given file. * * @param string $csvfile * @param string $behavior * @return void */ - private function importProducts($csvfile, $behavior) + private function importProducts(string $csvfile, string $behavior): void { /** @var \Magento\CatalogImportExport\Model\Import\Product $importModel */ $importModel = $this->objectManager->create( @@ -437,15 +450,33 @@ private function importProducts($csvfile, $behavior) } /** + * Extract error message. + * * @param \Magento\ImportExport\Model\Import\ErrorProcessing\ProcessingError[] $errors * @return string */ - private function extractErrorMessage($errors) + private function extractErrorMessage(array $errors): string { $errorMessage = ''; foreach ($errors as $error) { $errorMessage = "\n" . $error->getErrorMessage(); } + return $errorMessage; } + + /** + * Clean import attribute cache. + * + * @return void + */ + private function cleanAttributesCache(): void + { + foreach (self::$attributesToRefresh as $attributeCode) { + $attributeId = Import\Product\Type\AbstractType::$attributeCodeToId[$attributeCode] ?? null; + if ($attributeId !== null) { + unset(Import\Product\Type\AbstractType::$commonAttributesCache[$attributeId]); + } + } + } } diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php index e5d97ac0e68..c4c6d3ba2d1 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php @@ -2272,6 +2272,20 @@ public function testImportWithBackordersEnabled(): void $this->assertFalse($product->getDataByKey('quantity_and_stock_status')['is_in_stock']); } + /** + * Test that imported product stock status with stock quantity > 0 and backorders functionality disabled + * can be set to 'out of stock'. + * + * @magentoDbIsolation enabled + * @magentoAppIsolation enabled + */ + public function testImportWithBackordersDisabled(): void + { + $this->importFile('products_to_import_with_backorders_disabled_and_not_0_qty.csv'); + $product = $this->getProductBySku('simple_new'); + $this->assertFalse($product->getDataByKey('quantity_and_stock_status')['is_in_stock']); + } + /** * Import file by providing import filename in parameters. * diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_with_backorders_disabled_and_not_0_qty.csv b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_with_backorders_disabled_and_not_0_qty.csv new file mode 100644 index 00000000000..b22427a8af1 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_with_backorders_disabled_and_not_0_qty.csv @@ -0,0 +1,2 @@ +sku,store_view_code,attribute_set_code,product_type,categories,product_websites,name,description,short_description,weight,product_online,tax_class_name,visibility,price,special_price,special_price_from_date,special_price_to_date,url_key,meta_title,meta_keywords,meta_description,base_image,base_image_label,small_image,small_image_label,thumbnail_image,thumbnail_image_label,created_at,updated_at,new_from_date,new_to_date,display_product_options_in,map_price,msrp_price,map_enabled,gift_message_available,custom_design,custom_design_from,custom_design_to,custom_layout_update,page_layout,product_options_container,msrp_display_actual_price_type,country_of_manufacture,additional_attributes,qty,out_of_stock_qty,use_config_min_qty,is_qty_decimal,allow_backorders,use_config_backorders,min_cart_qty,use_config_min_sale_qty,max_cart_qty,use_config_max_sale_qty,is_in_stock,notify_on_stock_below,use_config_notify_stock_qty,manage_stock,use_config_manage_stock,use_config_qty_increments,qty_increments,use_config_enable_qty_inc,enable_qty_increments,is_decimal_divided,website_id,related_skus,crosssell_skus,upsell_skus,additional_images,additional_image_labels,hide_from_product_page,custom_options,bundle_price_type,bundle_sku_type,bundle_price_view,bundle_weight_type,bundle_values,associated_skus +simple_new,,Default,simple,,base,New Product,,,,1,Taxable Goods,"Catalog, Search",10,,,,new-product,New Product,New Product,New Product ,,,,,,,10/20/2015 7:05,10/20/2015 7:05,,,Block after Info Column,,,,,,,,,,,,,"has_options=1,quantity_and_stock_status=In Stock,required_options=1",100,0,1,0,0,0,1,1,10000,1,0,1,1,1,0,1,1,0,0,0,1,,,,,,,,,,,,, diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/ProductTest.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/ProductTest.php index 186c6b8a92b..c39acbc3387 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/ProductTest.php @@ -13,7 +13,7 @@ class ProductTest extends AbstractProductExportImportTestCase /** * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ - public function exportImportDataProvider() + public function exportImportDataProvider(): array { return [ 'product_export_data' => [ @@ -136,11 +136,6 @@ public function exportImportDataProvider() ]; } - public function importReplaceDataProvider() - { - return $this->exportImportDataProvider(); - } - /** * Fixing https://github.com/magento-engcom/import-export-improvements/issues/50 means that during import images * can now get renamed for this we need to skip the attribute checking and instead check that the images contain @@ -150,8 +145,10 @@ public function importReplaceDataProvider() * @param \Magento\Catalog\Model\Product $expectedProduct * @param \Magento\Catalog\Model\Product $actualProduct */ - protected function assertEqualsSpecificAttributes($expectedProduct, $actualProduct) - { + protected function assertEqualsSpecificAttributes( + \Magento\Catalog\Model\Product $expectedProduct, + \Magento\Catalog\Model\Product $actualProduct + ): void { if (!empty($actualProduct->getImage()) && !empty($expectedProduct->getImage()) ) { diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/product_export_data_rollback.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/product_export_data_rollback.php index 168073bc6ab..c57c7c3fd6a 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/product_export_data_rollback.php +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/product_export_data_rollback.php @@ -5,10 +5,10 @@ */ /** Delete all products */ -require dirname(dirname(__DIR__)) . '/Catalog/_files/products_with_multiselect_attribute_rollback.php'; +include dirname(dirname(__DIR__)) . '/Catalog/_files/products_with_multiselect_attribute_rollback.php'; /** Delete text attribute */ -require dirname(dirname(__DIR__)) . '/Catalog/_files/text_attribute_rollback.php'; +include dirname(dirname(__DIR__)) . '/Catalog/_files/product_text_attribute_rollback.php'; -require dirname(dirname(__DIR__)) . '/Store/_files/second_store_rollback.php'; +include dirname(dirname(__DIR__)) . '/Store/_files/second_store_rollback.php'; -require dirname(dirname(__DIR__)) . '/Catalog/_files/category_rollback.php'; +include dirname(dirname(__DIR__)) . '/Catalog/_files/category_rollback.php'; diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/product_export_data_special_chars_rollback.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/product_export_data_special_chars_rollback.php index 168073bc6ab..c57c7c3fd6a 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/product_export_data_special_chars_rollback.php +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/product_export_data_special_chars_rollback.php @@ -5,10 +5,10 @@ */ /** Delete all products */ -require dirname(dirname(__DIR__)) . '/Catalog/_files/products_with_multiselect_attribute_rollback.php'; +include dirname(dirname(__DIR__)) . '/Catalog/_files/products_with_multiselect_attribute_rollback.php'; /** Delete text attribute */ -require dirname(dirname(__DIR__)) . '/Catalog/_files/text_attribute_rollback.php'; +include dirname(dirname(__DIR__)) . '/Catalog/_files/product_text_attribute_rollback.php'; -require dirname(dirname(__DIR__)) . '/Store/_files/second_store_rollback.php'; +include dirname(dirname(__DIR__)) . '/Store/_files/second_store_rollback.php'; -require dirname(dirname(__DIR__)) . '/Catalog/_files/category_rollback.php'; +include dirname(dirname(__DIR__)) . '/Catalog/_files/category_rollback.php'; diff --git a/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/FullTest.php b/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/FullTest.php index 59ad91ae7b0..56c5db5572a 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/FullTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/FullTest.php @@ -14,6 +14,9 @@ use Magento\Catalog\Model\Product; use Magento\TestFramework\Helper\Bootstrap; +/** + * Class for testing fulltext index rebuild + */ class FullTest extends \PHPUnit\Framework\TestCase { /** @@ -21,6 +24,9 @@ class FullTest extends \PHPUnit\Framework\TestCase */ protected $actionFull; + /** + * @inheritdoc + */ protected function setUp() { $this->actionFull = Bootstrap::getObjectManager()->create( @@ -29,6 +35,8 @@ protected function setUp() } /** + * Testing fulltext index rebuild + * * @magentoDataFixture Magento/CatalogSearch/_files/products_for_index.php * @magentoDataFixture Magento/CatalogSearch/_files/product_configurable_not_available.php * @magentoDataFixture Magento/Framework/Search/_files/product_configurable.php @@ -39,7 +47,6 @@ public function testGetIndexData() $productRepository = Bootstrap::getObjectManager()->get(ProductRepositoryInterface::class); $allowedStatuses = Bootstrap::getObjectManager()->get(Status::class)->getVisibleStatusIds(); $allowedVisibility = Bootstrap::getObjectManager()->get(Engine::class)->getAllowedVisibility(); - $result = iterator_to_array($this->actionFull->rebuildStoreIndex(Store::DISTRO_STORE_ID)); $this->assertNotEmpty($result); @@ -58,7 +65,10 @@ public function testGetIndexData() } /** + * Prepare and return expected index data + * * @return array + * @throws \Magento\Framework\Exception\NoSuchEntityException */ private function getExpectedIndexData() { @@ -68,32 +78,48 @@ private function getExpectedIndexData() $nameId = $attributeRepository->get(ProductInterface::NAME)->getAttributeId(); /** @see dev/tests/integration/testsuite/Magento/Framework/Search/_files/configurable_attribute.php */ $configurableId = $attributeRepository->get('test_configurable')->getAttributeId(); + $statusId = $attributeRepository->get(ProductInterface::STATUS)->getAttributeId(); + $taxClassId = $attributeRepository + ->get(\Magento\Customer\Api\Data\GroupInterface::TAX_CLASS_ID) + ->getAttributeId(); return [ 'configurable' => [ $skuId => 'configurable', $configurableId => 'Option 1 | Option 2', $nameId => 'Configurable Product | Configurable OptionOption 1 | Configurable OptionOption 2', + $taxClassId => 'Taxable Goods | Taxable Goods | Taxable Goods', + $statusId => 'Enabled | Enabled | Enabled' ], 'index_enabled' => [ $skuId => 'index_enabled', $nameId => 'index enabled', + $taxClassId => 'Taxable Goods', + $statusId => 'Enabled' ], 'index_visible_search' => [ $skuId => 'index_visible_search', $nameId => 'index visible search', + $taxClassId => 'Taxable Goods', + $statusId => 'Enabled' ], 'index_visible_category' => [ $skuId => 'index_visible_category', $nameId => 'index visible category', + $taxClassId => 'Taxable Goods', + $statusId => 'Enabled' ], 'index_visible_both' => [ $skuId => 'index_visible_both', $nameId => 'index visible both', + $taxClassId => 'Taxable Goods', + $statusId => 'Enabled' ] ]; } /** + * Testing fulltext index rebuild with configurations + * * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php */ public function testRebuildStoreIndexConfigurable() @@ -114,6 +140,8 @@ public function testRebuildStoreIndexConfigurable() } /** + * Get product Id by its SKU + * * @param string $sku * @return int */ diff --git a/dev/tests/integration/testsuite/Magento/Checkout/_files/active_quote.php b/dev/tests/integration/testsuite/Magento/Checkout/_files/active_quote.php index 2d948ebeb01..52437ef828a 100644 --- a/dev/tests/integration/testsuite/Magento/Checkout/_files/active_quote.php +++ b/dev/tests/integration/testsuite/Magento/Checkout/_files/active_quote.php @@ -9,3 +9,11 @@ ->setIsMultiShipping(false) ->setReservedOrderId('test_order_1') ->save(); + +/** @var \Magento\Quote\Model\QuoteIdMask $quoteIdMask */ +$quoteIdMask = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->create(\Magento\Quote\Model\QuoteIdMaskFactory::class) + ->create(); +$quoteIdMask->setQuoteId($quote->getId()); +$quoteIdMask->setDataChanges(true); +$quoteIdMask->save(); diff --git a/dev/tests/integration/testsuite/Magento/Checkout/_files/quote_with_shipping_method.php b/dev/tests/integration/testsuite/Magento/Checkout/_files/quote_with_shipping_method.php index 3c54fe16db7..61779da29c6 100644 --- a/dev/tests/integration/testsuite/Magento/Checkout/_files/quote_with_shipping_method.php +++ b/dev/tests/integration/testsuite/Magento/Checkout/_files/quote_with_shipping_method.php @@ -11,10 +11,18 @@ require 'quote_with_address_saved.php'; +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); +$rate = $objectManager->get(\Magento\Quote\Model\Quote\Address\Rate::class); + $quote->load('test_order_1', 'reserved_order_id'); $shippingAddress = $quote->getShippingAddress(); $shippingAddress->setShippingMethod('flatrate_flatrate') ->setShippingDescription('Flat Rate - Fixed') - ->setShippingAmount(10.0) - ->setBaseShippingAmount(12.0) ->save(); + +$rate->setPrice(0) + ->setAddressId($shippingAddress->getId()) + ->save(); +$shippingAddress->setBaseShippingAmount($rate->getPrice()); +$shippingAddress->setShippingAmount($rate->getPrice()); +$rate->delete(); diff --git a/dev/tests/integration/testsuite/Magento/Config/Console/Command/ConfigSetCommandTest.php b/dev/tests/integration/testsuite/Magento/Config/Console/Command/ConfigSetCommandTest.php index f68f3cf37b0..73f1dd9e7b7 100644 --- a/dev/tests/integration/testsuite/Magento/Config/Console/Command/ConfigSetCommandTest.php +++ b/dev/tests/integration/testsuite/Magento/Config/Console/Command/ConfigSetCommandTest.php @@ -28,7 +28,7 @@ /** * Tests the different flows of config:set command. * - * {@inheritdoc} + * @inheritdoc * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @magentoDbIsolation enabled */ @@ -292,8 +292,7 @@ public function runExtendedDataProvider() * @param string $scope * @param $scopeCode string|null * @dataProvider configSetValidationErrorDataProvider - * - * @magentoDbIsolation enabled + * @magentoDbIsolation disabled */ public function testConfigSetValidationError( $path, @@ -307,6 +306,7 @@ public function testConfigSetValidationError( /** * Data provider for testConfigSetValidationError + * * @return array */ public function configSetValidationErrorDataProvider() @@ -399,7 +399,6 @@ public function testConfigSetCurrency() * Saving values with successful validation * * @dataProvider configSetValidDataProvider - * * @magentoDbIsolation enabled */ public function testConfigSetValid() diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableImportExport/Model/ConfigurableTest.php b/dev/tests/integration/testsuite/Magento/ConfigurableImportExport/Model/ConfigurableTest.php index 5184a375633..338daa56450 100644 --- a/dev/tests/integration/testsuite/Magento/ConfigurableImportExport/Model/ConfigurableTest.php +++ b/dev/tests/integration/testsuite/Magento/ConfigurableImportExport/Model/ConfigurableTest.php @@ -9,7 +9,10 @@ class ConfigurableTest extends AbstractProductExportImportTestCase { - public function exportImportDataProvider() + /** + * @return array + */ + public function exportImportDataProvider(): array { return [ 'configurable-product' => [ @@ -34,11 +37,12 @@ public function exportImportDataProvider() } /** - * @param \Magento\Catalog\Model\Product $expectedProduct - * @param \Magento\Catalog\Model\Product $actualProduct + * @inheritdoc */ - protected function assertEqualsSpecificAttributes($expectedProduct, $actualProduct) - { + protected function assertEqualsSpecificAttributes( + \Magento\Catalog\Model\Product $expectedProduct, + \Magento\Catalog\Model\Product $actualProduct + ): void { /** @var \Magento\ConfigurableProduct\Model\Product\Type\Configurable $productType */ $productType = $expectedProduct->getTypeInstance(); $expectedAssociatedProducts = $productType->getUsedProductCollection($expectedProduct); @@ -95,12 +99,16 @@ protected function assertEqualsSpecificAttributes($expectedProduct, $actualProdu } } - public function importReplaceDataProvider() - { - $data = $this->exportImportDataProvider(); - foreach ($data as $key => $value) { - $data[$key][2] = array_merge($value[2], ['_cache_instance_product_set_attributes']); - } - return $data; + /** + * @inheritdoc + */ + protected function executeImportReplaceTest( + $skus, + $skippedAttributes, + $usePagination = false, + string $csvfile = null + ) { + $skippedAttributes = array_merge($skippedAttributes, ['_cache_instance_product_set_attributes']); + parent::executeImportReplaceTest($skus, $skippedAttributes, $usePagination, $csvfile); } } diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/Plugin/Frontend/ProductIdentitiesExtenderTest.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/Plugin/Frontend/ProductIdentitiesExtenderTest.php new file mode 100644 index 00000000000..1fffd701c50 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/Plugin/Frontend/ProductIdentitiesExtenderTest.php @@ -0,0 +1,73 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\ConfigurableProduct\Model\Plugin\Frontend; + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Interception\PluginList; +use PHPUnit\Framework\TestCase; + +/** + * Test configurable fronted product plugin will add children products ids to configurable product identities. + */ +class ProductIdentitiesExtenderTest extends TestCase +{ + /** + * Check, product identities extender plugin is registered for storefront. + * + * @magentoAppArea frontend + * @return void + */ + public function testIdentitiesExtenderIsRegistered(): void + { + $pluginInfo = Bootstrap::getObjectManager()->get(PluginList::class) + ->get(\Magento\Catalog\Model\Product::class, []); + $this->assertSame(ProductIdentitiesExtender::class, $pluginInfo['product_identities_extender']['instance']); + } + + /** + * Check plugin will add children ids to configurable product identities on storefront. + * + * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php + * @magentoAppArea frontend + * @return void + */ + public function testGetIdentitiesForConfigurableProductOnStorefront(): void + { + $productRepository = Bootstrap::getObjectManager()->get(ProductRepositoryInterface::class); + $configurableProduct = $productRepository->get('configurable'); + $simpleProduct1 = $productRepository->get('simple_10'); + $simpleProduct2 = $productRepository->get('simple_20'); + $expectedIdentities = [ + 'cat_p_' . $configurableProduct->getId(), + 'cat_p', + 'cat_p_' . $simpleProduct1->getId(), + 'cat_p_' . $simpleProduct2->getId(), + + ]; + $this->assertEquals($expectedIdentities, $configurableProduct->getIdentities()); + } + + /** + * Check plugin won't add children ids to configurable product identities in admin area. + * + * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php + * @magentoAppArea adminhtml + * @return void + */ + public function testGetIdentitiesForConfigurableProductInAdminArea(): void + { + $productRepository = Bootstrap::getObjectManager()->get(ProductRepositoryInterface::class); + $configurableProduct = $productRepository->get('configurable'); + $expectedIdentities = [ + 'cat_p_' . $configurableProduct->getId(), + ]; + $this->assertEquals($expectedIdentities, $configurableProduct->getIdentities()); + } +} diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_configurable_with_frontend_label_attribute.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_configurable_with_frontend_label_attribute.php new file mode 100644 index 00000000000..69607ffb445 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_configurable_with_frontend_label_attribute.php @@ -0,0 +1,22 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Eav\Model\Entity\Attribute\FrontendLabel; +use Magento\TestFramework\Helper\Bootstrap; + +require __DIR__ . '/configurable_products.php'; + +// Add frontend label to created attribute: +$frontendLabelAttribute = Bootstrap::getObjectManager()->get(FrontendLabel::class); +$frontendLabelAttribute->setStoreId(1); +$frontendLabelAttribute->setLabel('Default Store View label'); + +$frontendLabels = $attribute->getFrontendLabels(); +$frontendLabels[] = $frontendLabelAttribute; + +$attribute->setFrontendLabels($frontendLabels); +$attributeRepository->save($attribute); diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_configurable_with_frontend_label_attribute_rollback.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_configurable_with_frontend_label_attribute_rollback.php new file mode 100644 index 00000000000..616bd44666e --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_configurable_with_frontend_label_attribute_rollback.php @@ -0,0 +1,8 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +require __DIR__ . '/configurable_products_rollback.php'; diff --git a/dev/tests/integration/testsuite/Magento/Customer/Block/Address/BookTest.php b/dev/tests/integration/testsuite/Magento/Customer/Block/Address/BookTest.php index 4c31fb740c5..1ef7d54c5aa 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Block/Address/BookTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Block/Address/BookTest.php @@ -65,6 +65,7 @@ public function testGetAddressEditUrl() * @magentoDataFixture Magento/Customer/_files/customer_two_addresses.php * @magentoDataFixture Magento/Customer/_files/customer_no_address.php * @dataProvider hasPrimaryAddressDataProvider + * @magentoAppIsolation enabled */ public function testHasPrimaryAddress($customerId, $expected) { @@ -82,6 +83,7 @@ public function hasPrimaryAddressDataProvider() /** * @magentoDataFixture Magento/Customer/_files/customer.php * @magentoDataFixture Magento/Customer/_files/customer_two_addresses.php + * @magentoAppIsolation enabled */ public function testGetAdditionalAddresses() { @@ -98,6 +100,7 @@ public function testGetAdditionalAddresses() /** * @magentoDataFixture Magento/Customer/_files/customer_no_address.php * @dataProvider getAdditionalAddressesDataProvider + * @magentoAppIsolation enabled */ public function testGetAdditionalAddressesNegative($customerId, $expected) { @@ -115,6 +118,7 @@ public function getAdditionalAddressesDataProvider() /** * @magentoDataFixture Magento/Customer/_files/customer.php * @magentoDataFixture Magento/Customer/_files/customer_address.php + * @magentoAppIsolation enabled */ public function testGetAddressHtml() { @@ -134,6 +138,7 @@ public function testGetAddressHtmlWithoutAddress() /** * @magentoDataFixture Magento/Customer/_files/customer.php + * @magentoAppIsolation enabled */ public function testGetCustomer() { @@ -158,6 +163,7 @@ public function testGetCustomerMissingCustomer() * @magentoDataFixture Magento/Customer/_files/customer_two_addresses.php * @magentoDataFixture Magento/Customer/_files/customer_no_address.php * @dataProvider getDefaultBillingDataProvider + * @magentoAppIsolation enabled */ public function testGetDefaultBilling($customerId, $expected) { @@ -175,6 +181,7 @@ public function getDefaultBillingDataProvider() * @magentoDataFixture Magento/Customer/_files/customer_two_addresses.php * @magentoDataFixture Magento/Customer/_files/customer_no_address.php * @dataProvider getDefaultShippingDataProvider + * @magentoAppIsolation enabled */ public function testGetDefaultShipping($customerId, $expected) { diff --git a/dev/tests/integration/testsuite/Magento/Customer/Block/Address/GridTest.php b/dev/tests/integration/testsuite/Magento/Customer/Block/Address/GridTest.php new file mode 100644 index 00000000000..ac11c6c08bd --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/Block/Address/GridTest.php @@ -0,0 +1,141 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Customer\Block\Address; + +use Magento\TestFramework\Helper\Bootstrap; + +/** + * Integration tests for the \Magento\Customer\Block\Address\Grid class + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class GridTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\Framework\View\LayoutInterface + */ + private $layout; + + /** + * @var \Magento\Customer\Helper\Session\CurrentCustomer + */ + protected $currentCustomer; + + protected function setUp() + { + /** @var \PHPUnit_Framework_MockObject_MockObject $blockMock */ + $blockMock = $this->getMockBuilder( + \Magento\Framework\View\Element\BlockInterface::class + )->disableOriginalConstructor()->setMethods( + ['setTitle', 'toHtml'] + )->getMock(); + $blockMock->expects($this->any())->method('setTitle'); + + $this->currentCustomer = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->get(\Magento\Customer\Helper\Session\CurrentCustomer::class); + $this->layout = Bootstrap::getObjectManager()->get(\Magento\Framework\View\LayoutInterface::class); + $this->layout->setBlock('head', $blockMock); + } + + protected function tearDown() + { + $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + /** @var \Magento\Customer\Model\CustomerRegistry $customerRegistry */ + $customerRegistry = $objectManager->get(\Magento\Customer\Model\CustomerRegistry::class); + // Cleanup customer from registry + $customerRegistry->remove(1); + } + + /** + * @magentoDataFixture Magento/Customer/_files/customer.php + * @magentoAppIsolation enabled + */ + public function testGetAddressEditUrl() + { + $gridBlock = $this->createBlockForCustomer(1); + + $this->assertEquals( + 'http://localhost/index.php/customer/address/edit/id/1/', + $gridBlock->getAddressEditUrl(1) + ); + } + + /** + * @magentoDataFixture Magento/Customer/_files/customer.php + * @magentoDataFixture Magento/Customer/_files/customer_two_addresses.php + * @magentoAppIsolation enabled + */ + public function testGetAdditionalAddresses() + { + $gridBlock = $this->createBlockForCustomer(1); + $this->assertNotNull($gridBlock->getAdditionalAddresses()); + $this->assertCount(1, $gridBlock->getAdditionalAddresses()); + $this->assertInstanceOf( + \Magento\Customer\Api\Data\AddressInterface::class, + $gridBlock->getAdditionalAddresses()[0] + ); + $this->assertEquals(2, $gridBlock->getAdditionalAddresses()[0]->getId()); + } + + /** + * @magentoDataFixture Magento/Customer/_files/customer_no_address.php + * @dataProvider getAdditionalAddressesDataProvider + * @magentoAppIsolation enabled + */ + public function testGetAdditionalAddressesNegative($customerId, $expected) + { + $gridBlock = $this->createBlockForCustomer($customerId); + $this->currentCustomer->setCustomerId($customerId); + $this->assertEquals($expected, $gridBlock->getAdditionalAddresses()); + } + + public function getAdditionalAddressesDataProvider() + { + return ['5' => [5, []]]; + } + + /** + * @magentoDataFixture Magento/Customer/_files/customer_no_address.php + * @magentoAppIsolation enabled + */ + public function testGetAddressHtmlWithoutAddress() + { + $gridBlock = $this->createBlockForCustomer(5); + $this->assertEquals('', $gridBlock->getAddressHtml(null)); + } + + /** + * @magentoDataFixture Magento/Customer/_files/customer.php + * @magentoAppIsolation enabled + */ + public function testGetCustomer() + { + $gridBlock = $this->createBlockForCustomer(1); + /** @var CustomerRepositoryInterface $customerRepository */ + $customerRepository = Bootstrap::getObjectManager()->get( + \Magento\Customer\Api\CustomerRepositoryInterface::class + ); + $customer = $customerRepository->getById(1); + $object = $gridBlock->getCustomer(); + $this->assertEquals($customer, $object); + } + + /** + * Create address book block for customer + * + * @param int $customerId + * @return \Magento\Framework\View\Element\BlockInterface + */ + private function createBlockForCustomer($customerId) + { + $this->currentCustomer->setCustomerId($customerId); + return $this->layout->createBlock( + \Magento\Customer\Block\Address\Grid::class, + '', + ['currentCustomer' => $this->currentCustomer] + ); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Customer/Block/Widget/GenderTest.php b/dev/tests/integration/testsuite/Magento/Customer/Block/Widget/GenderTest.php index 2cc2c2a426d..3883f3f88ee 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Block/Widget/GenderTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Block/Widget/GenderTest.php @@ -15,6 +15,9 @@ class GenderTest extends \PHPUnit\Framework\TestCase /** @var Gender */ protected $_block; + /** @var \Magento\Customer\Model\Attribute */ + private $_model; + /** * Test initialization and set up. Create the Gender block. * @return void @@ -28,6 +31,8 @@ protected function setUp() )->createBlock( \Magento\Customer\Block\Widget\Gender::class ); + $this->_model = $objectManager->create(\Magento\Customer\Model\Attribute::class); + $this->_model->loadByCode('customer', 'gender'); } /** @@ -49,7 +54,8 @@ public function testGetGenderOptions() public function testToHtml() { $html = $this->_block->toHtml(); - $this->assertContains('<span>Gender</span>', $html); + $attributeLabel = $this->_model->getStoreLabel(); + $this->assertContains('<span>' . $attributeLabel . '</span>', $html); $this->assertContains('<option value="1">Male</option>', $html); $this->assertContains('<option value="2">Female</option>', $html); $this->assertContains('<option value="3">Not Specified</option>', $html); diff --git a/dev/tests/integration/testsuite/Magento/Customer/Block/Widget/TaxvatTest.php b/dev/tests/integration/testsuite/Magento/Customer/Block/Widget/TaxvatTest.php index 3650a7e95a3..3bc9fea5db3 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Block/Widget/TaxvatTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Block/Widget/TaxvatTest.php @@ -22,7 +22,13 @@ public function testToHtml() \Magento\Customer\Block\Widget\Taxvat::class ); - $this->assertContains('title="Tax/VAT number"', $block->toHtml()); + $model = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + \Magento\Customer\Model\Attribute::class + ); + $model->loadByCode('customer', 'taxvat'); + $attributeLabel = $model->getStoreLabel(); + + $this->assertContains('title="' . $block->escapeHtmlAttr($attributeLabel) . '"', $block->toHtml()); $this->assertNotContains('required', $block->toHtml()); } @@ -38,13 +44,14 @@ public function testToHtmlRequired() ); $model->loadByCode('customer', 'taxvat')->setIsRequired(true); $model->save(); + $attributeLabel = $model->getStoreLabel(); /** @var \Magento\Customer\Block\Widget\Taxvat $block */ $block = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( \Magento\Customer\Block\Widget\Taxvat::class ); - $this->assertContains('title="Tax/VAT number"', $block->toHtml()); + $this->assertContains('title="' . $block->escapeHtmlAttr($attributeLabel) . '"', $block->toHtml()); $this->assertContains('required', $block->toHtml()); } diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/AccountTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/AccountTest.php index c94948e23ab..ea7a7710acb 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Controller/AccountTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/AccountTest.php @@ -17,18 +17,35 @@ use Magento\Framework\App\Http; use Magento\Framework\Data\Form\FormKey; use Magento\Framework\Message\MessageInterface; -use Magento\Store\Model\ScopeInterface; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\Request; use Magento\TestFramework\Response; use Zend\Stdlib\Parameters; use Magento\Framework\App\Request\Http as HttpRequest; +use Magento\TestFramework\Mail\Template\TransportBuilderMock; +use Magento\Framework\Serialize\Serializer\Json; +use Magento\Framework\Stdlib\CookieManagerInterface; +use Magento\Theme\Controller\Result\MessagePlugin; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class AccountTest extends \Magento\TestFramework\TestCase\AbstractController { + /** + * @var TransportBuilderMock + */ + private $transportBuilderMock; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + $this->transportBuilderMock = $this->_objectManager->get(TransportBuilderMock::class); + } + /** * Login the user * @@ -133,11 +150,7 @@ public function testForgotPasswordEmailMessageWithSpecialCharacters() $this->dispatch('customer/account/forgotPasswordPost'); $this->assertRedirect($this->stringContains('customer/account/')); - /** @var \Magento\TestFramework\Mail\Template\TransportBuilderMock $transportBuilder */ - $transportBuilder = $this->_objectManager->get( - \Magento\TestFramework\Mail\Template\TransportBuilderMock::class - ); - $subject = $transportBuilder->getSentMessage()->getSubject(); + $subject = $this->transportBuilderMock->getSentMessage()->getSubject(); $this->assertContains( 'Test special\' characters', $subject @@ -260,26 +273,10 @@ public function testNoFormKeyCreatePostAction() /** * @magentoDbIsolation enabled * @magentoAppIsolation enabled + * @magentoDataFixture Magento/Customer/_files/customer_confirmation_config_disable.php */ public function testNoConfirmCreatePostAction() { - /** @var \Magento\Framework\App\Config\MutableScopeConfigInterface $mutableScopeConfig */ - $mutableScopeConfig = Bootstrap::getObjectManager() - ->get(\Magento\Framework\App\Config\MutableScopeConfigInterface::class); - - $scopeValue = $mutableScopeConfig->getValue( - 'customer/create_account/confirm', - ScopeInterface::SCOPE_WEBSITES, - null - ); - - $mutableScopeConfig->setValue( - 'customer/create_account/confirm', - 0, - ScopeInterface::SCOPE_WEBSITES, - null - ); - $this->fillRequestWithAccountDataAndFormKey('test1@email.com'); $this->dispatch('customer/account/createPost'); $this->assertRedirect($this->stringEndsWith('customer/account/')); @@ -287,38 +284,15 @@ public function testNoConfirmCreatePostAction() $this->equalTo(['Thank you for registering with Main Website Store.']), MessageInterface::TYPE_SUCCESS ); - - $mutableScopeConfig->setValue( - 'customer/create_account/confirm', - $scopeValue, - ScopeInterface::SCOPE_WEBSITES, - null - ); } /** * @magentoDbIsolation enabled * @magentoAppIsolation enabled + * @magentoDataFixture Magento/Customer/_files/customer_confirmation_config_enable.php */ public function testWithConfirmCreatePostAction() { - /** @var \Magento\Framework\App\Config\MutableScopeConfigInterface $mutableScopeConfig */ - $mutableScopeConfig = Bootstrap::getObjectManager() - ->get(\Magento\Framework\App\Config\MutableScopeConfigInterface::class); - - $scopeValue = $mutableScopeConfig->getValue( - 'customer/create_account/confirm', - ScopeInterface::SCOPE_WEBSITES, - null - ); - - $mutableScopeConfig->setValue( - 'customer/create_account/confirm', - 1, - ScopeInterface::SCOPE_WEBSITES, - null - ); - $this->fillRequestWithAccountDataAndFormKey('test2@email.com'); $this->dispatch('customer/account/createPost'); $this->assertRedirect($this->stringContains('customer/account/index/')); @@ -330,13 +304,6 @@ public function testWithConfirmCreatePostAction() ]), MessageInterface::TYPE_SUCCESS ); - - $mutableScopeConfig->setValue( - 'customer/create_account/confirm', - $scopeValue, - ScopeInterface::SCOPE_WEBSITES, - null - ); } /** @@ -730,6 +697,46 @@ public function testLoginPostRedirect($redirectDashboard, string $redirectUrl) $this->assertTrue($this->_objectManager->get(Session::class)->isLoggedIn()); } + /** + * Test that confirmation email address displays special characters correctly. + * + * @magentoDbIsolation enabled + * @magentoDataFixture Magento/Customer/_files/customer_confirmation_email_address_with_special_chars.php + * + * @return void + */ + public function testConfirmationEmailWithSpecialCharacters(): void + { + $email = 'customer+confirmation@example.com'; + $this->dispatch('customer/account/confirmation/email/customer%2Bconfirmation%40email.com'); + $this->getRequest()->setPostValue('email', $email); + $this->dispatch('customer/account/confirmation/email/customer%2Bconfirmation%40email.com'); + + $this->assertRedirect($this->stringContains('customer/account/index')); + $this->assertSessionMessages( + $this->equalTo(['Please check your email for confirmation key.']), + MessageInterface::TYPE_SUCCESS + ); + + /** @var $message \Magento\Framework\Mail\Message */ + $message = $this->transportBuilderMock->getSentMessage(); + $rawMessage = $message->getRawMessage(); + + $this->assertContains('To: ' . $email, $rawMessage); + + $content = $message->getBody()->getPartContent(0); + $confirmationUrl = $this->getConfirmationUrlFromMessageContent($content); + $this->setRequestInfo($confirmationUrl, 'confirm'); + $this->clearCookieMessagesList(); + $this->dispatch($confirmationUrl); + + $this->assertRedirect($this->stringContains('customer/account/index')); + $this->assertSessionMessages( + $this->equalTo(['Thank you for registering with Main Website Store.']), + MessageInterface::TYPE_SUCCESS + ); + } + /** * Data provider for testLoginPostRedirect. * @@ -847,4 +854,53 @@ private function assertResponseRedirect(Response $response, string $redirectUrl) $this->assertTrue($response->isRedirect()); $this->assertSame($redirectUrl, $response->getHeader('Location')->getUri()); } + + /** + * Add new request info (request uri, path info, action name). + * + * @param string $uri + * @param string $actionName + * @return void + */ + private function setRequestInfo(string $uri, string $actionName): void + { + $this->getRequest() + ->setRequestUri($uri) + ->setPathInfo() + ->setActionName($actionName); + } + + /** + * Clear cookie messages list. + * + * @return void + */ + private function clearCookieMessagesList(): void + { + $cookieManager = $this->_objectManager->get(CookieManagerInterface::class); + $jsonSerializer = $this->_objectManager->get(Json::class); + $cookieManager->setPublicCookie( + MessagePlugin::MESSAGES_COOKIES_NAME, + $jsonSerializer->serialize([]) + ); + } + + /** + * Get confirmation URL from message content. + * + * @param string $content + * @return string + */ + private function getConfirmationUrlFromMessageContent(string $content): string + { + $confirmationUrl = ''; + + if (preg_match('<a\s*href="(?<url>.*?)".*>', $content, $matches)) { + $confirmationUrl = $matches['url']; + $confirmationUrl = str_replace('http://localhost/index.php/', '', $confirmationUrl); + $confirmationUrl = html_entity_decode($confirmationUrl); + } + + return $confirmationUrl; + } } diff --git a/dev/tests/integration/testsuite/Magento/Customer/Model/AddressTest.php b/dev/tests/integration/testsuite/Magento/Customer/Model/AddressTest.php index 017532fb392..b6e8cba82ad 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Model/AddressTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Model/AddressTest.php @@ -67,4 +67,28 @@ public function testUpdateDataOverrideExistingData() $this->assertEquals('CompanyZ', $updatedAddressData->getCompany()); $this->assertEquals('99999', $updatedAddressData->getPostcode()); } + + /** + * @magentoDataFixture Magento/Customer/_files/customer_sample.php + */ + public function testUpdateDataForExistingCustomer() + { + /** @var \Magento\Customer\Model\CustomerRegistry $customerRegistry */ + $customerRegistry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(CustomerRegistry::class); + /** @var \Magento\Customer\Model\Data\Address $addressData */ + $updatedAddressData = $this->addressFactory->create() + ->setId(1) + ->setCustomerId($customerRegistry->retrieveByEmail('customer@example.com')->getId()) + ->setCity('CityZ') + ->setCompany('CompanyZ') + ->setPostcode('99999'); + $updatedAddressData = $this->addressModel->updateData($updatedAddressData)->getDataModel(); + + $this->assertEquals(1, $updatedAddressData->getId()); + $this->assertEquals('CityZ', $updatedAddressData->getCity()); + $this->assertEquals('CompanyZ', $updatedAddressData->getCompany()); + $this->assertEquals('99999', $updatedAddressData->getPostcode()); + $this->assertEquals(true, $updatedAddressData->isDefaultBilling()); + $this->assertEquals(true, $updatedAddressData->isDefaultShipping()); + } } diff --git a/dev/tests/integration/testsuite/Magento/Customer/Model/CustomerMetadataTest.php b/dev/tests/integration/testsuite/Magento/Customer/Model/CustomerMetadataTest.php index 794fce17480..a5c69bcd323 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Model/CustomerMetadataTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Model/CustomerMetadataTest.php @@ -239,10 +239,10 @@ public function testGetCustomerAttributeMetadata() $this->assertNotEmpty($attributes); // remove odd extension attributes - $allAtrributes = $expectAttrsWithVals; - $allAtrributes['created_at'] = $attributes['created_at']; - $allAtrributes['updated_at'] = $attributes['updated_at']; - $attributes = array_intersect_key($attributes, $allAtrributes); + $allAttributes = $expectAttrsWithVals; + $allAttributes['created_at'] = $attributes['created_at']; + $allAttributes['updated_at'] = $attributes['updated_at']; + $attributes = array_intersect_key($attributes, $allAttributes); foreach ($attributes as $attributeCode => $attributeValue) { $this->assertNotNull($attributeCode); diff --git a/dev/tests/integration/testsuite/Magento/Customer/Model/ResourceModel/AddressRepositoryTest.php b/dev/tests/integration/testsuite/Magento/Customer/Model/ResourceModel/AddressRepositoryTest.php index 41776983898..381c580f55e 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Model/ResourceModel/AddressRepositoryTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Model/ResourceModel/AddressRepositoryTest.php @@ -17,6 +17,8 @@ use Magento\Store\Api\WebsiteRepositoryInterface; /** + * Class with integration tests for AddressRepository. + * * @SuppressWarnings(PHPMD.TooManyMethods) * @SuppressWarnings(PHPMD.ExcessivePublicCount) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -38,6 +40,9 @@ class AddressRepositoryTest extends \PHPUnit\Framework\TestCase /** @var \Magento\Framework\Api\DataObjectHelper */ private $dataObjectHelper; + /** + * Set up. + */ protected function setUp() { $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); @@ -86,6 +91,9 @@ protected function setUp() $this->expectedAddresses = [$address, $address2]; } + /** + * Tear down. + */ protected function tearDown() { $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); @@ -95,6 +103,8 @@ protected function tearDown() } /** + * Test for save address changes. + * * @magentoDataFixture Magento/Customer/_files/customer.php * @magentoDataFixture Magento/Customer/_files/customer_address.php * @magentoDataFixture Magento/Customer/_files/customer_two_addresses.php @@ -116,6 +126,8 @@ public function testSaveAddressChanges() } /** + * Test for method save address with new id. + * * @magentoDataFixture Magento/Customer/_files/customer.php * @magentoDataFixture Magento/Customer/_files/customer_address.php * @magentoDataFixture Magento/Customer/_files/customer_two_addresses.php @@ -130,6 +142,8 @@ public function testSaveAddressesIdSetButNotAlreadyExisting() } /** + * Test for method get address by id. + * * @magentoDataFixture Magento/Customer/_files/customer.php * @magentoDataFixture Magento/Customer/_files/customer_address.php * @magentoDataFixture Magento/Customer/_files/customer_two_addresses.php @@ -143,6 +157,8 @@ public function testGetAddressById() } /** + * Test for method get address by id with incorrect id. + * * @magentoDataFixture Magento/Customer/_files/customer.php * @expectedException \Magento\Framework\Exception\NoSuchEntityException * @expectedExceptionMessage No such entity with addressId = 12345 @@ -153,6 +169,8 @@ public function testGetAddressByIdBadAddressId() } /** + * Test for method save new address. + * * @magentoDataFixture Magento/Customer/_files/customer.php * @magentoDataFixture Magento/Customer/_files/customer_address.php * @magentoAppIsolation enabled @@ -179,6 +197,8 @@ public function testSaveNewAddress() } /** + * Test for method saaveNewAddress with new attributes. + * * @magentoDataFixture Magento/Customer/_files/customer.php * @magentoDataFixture Magento/Customer/_files/customer_address.php * @magentoAppIsolation enabled @@ -204,6 +224,8 @@ public function testSaveNewAddressWithAttributes() } /** + * Test for saving address with invalid address. + * * @magentoDataFixture Magento/Customer/_files/customer.php * @magentoDataFixture Magento/Customer/_files/customer_address.php * @magentoAppIsolation enabled @@ -227,6 +249,11 @@ public function testSaveNewInvalidAddress() } } + /** + * Test for saving address without existing customer. + * + * @return void + */ public function testSaveAddressesCustomerIdNotExist() { $proposedAddress = $this->_createSecondAddress()->setCustomerId(4200); @@ -238,6 +265,11 @@ public function testSaveAddressesCustomerIdNotExist() } } + /** + * Test for saving addresses with invalid customer id. + * + * @return void + */ public function testSaveAddressesCustomerIdInvalid() { $proposedAddress = $this->_createSecondAddress()->setCustomerId('this_is_not_a_valid_id'); @@ -250,6 +282,8 @@ public function testSaveAddressesCustomerIdInvalid() } /** + * Test for delete method. + * * @magentoDataFixture Magento/Customer/_files/customer.php * @magentoDataFixture Magento/Customer/_files/customer_address.php */ @@ -273,6 +307,8 @@ public function testDeleteAddress() } /** + * Test for deleteAddressById. + * * @magentoDataFixture Magento/Customer/_files/customer.php * @magentoDataFixture Magento/Customer/_files/customer_address.php */ @@ -296,6 +332,8 @@ public function testDeleteAddressById() } /** + * Test delete address from customer with incorrect address id. + * * @magentoDataFixture Magento/Customer/_files/customer.php */ public function testDeleteAddressFromCustomerBadAddressId() @@ -309,10 +347,13 @@ public function testDeleteAddressFromCustomerBadAddressId() } /** + * Test for searching addressed. + * * @param \Magento\Framework\Api\Filter[] $filters * @param \Magento\Framework\Api\Filter[] $filterGroup * @param \Magento\Framework\Api\SortOrder[] $filterOrders * @param array $expectedResult array of expected results indexed by ID + * @param int $currentPage current page for search criteria * * @dataProvider searchAddressDataProvider * @@ -320,7 +361,7 @@ public function testDeleteAddressFromCustomerBadAddressId() * @magentoDataFixture Magento/Customer/_files/customer_two_addresses.php * @magentoAppIsolation enabled */ - public function testSearchAddresses($filters, $filterGroup, $filterOrders, $expectedResult) + public function testSearchAddresses($filters, $filterGroup, $filterOrders, $expectedResult, $currentPage) { /** @var \Magento\Framework\Api\SearchCriteriaBuilder $searchBuilder */ $searchBuilder = $this->objectManager->create(\Magento\Framework\Api\SearchCriteriaBuilder::class); @@ -337,7 +378,7 @@ public function testSearchAddresses($filters, $filterGroup, $filterOrders, $expe } $searchBuilder->setPageSize(1); - $searchBuilder->setCurrentPage(2); + $searchBuilder->setCurrentPage($currentPage); $searchCriteria = $searchBuilder->create(); $searchResults = $this->repository->getList($searchCriteria); @@ -355,6 +396,11 @@ public function testSearchAddresses($filters, $filterGroup, $filterOrders, $expe $this->assertEquals($expectedResult[$expectedResultIndex]['firstname'], $items[0]->getFirstname()); } + /** + * Data provider for searchAddresses. + * + * @return array + */ public function searchAddressDataProvider() { /** @@ -375,6 +421,7 @@ public function searchAddressDataProvider() [ ['id' => 1, 'city' => 'CityM', 'postcode' => 75477, 'firstname' => 'John'], ], + 1 ], 'Address with city CityM' => [ [$filterBuilder->setField('city')->setValue('CityM')->create()], @@ -383,6 +430,7 @@ public function searchAddressDataProvider() [ ['id' => 1, 'city' => 'CityM', 'postcode' => 75477, 'firstname' => 'John'], ], + 1 ], 'Addresses with firstname John sorted by firstname desc, city asc' => [ [$filterBuilder->setField('firstname')->setValue('John')->create()], @@ -395,6 +443,7 @@ public function searchAddressDataProvider() ['id' => 1, 'city' => 'CityM', 'postcode' => 75477, 'firstname' => 'John'], ['id' => 2, 'city' => 'CityX', 'postcode' => 47676, 'firstname' => 'John'], ], + 2 ], 'Addresses with postcode of either 75477 or 47676 sorted by city desc' => [ [], @@ -409,6 +458,7 @@ public function searchAddressDataProvider() ['id' => 2, 'city' => 'CityX', 'postcode' => 47676, 'firstname' => 'John'], ['id' => 1, 'city' => 'CityM', 'postcode' => 75477, 'firstname' => 'John'], ], + 2 ], 'Addresses with postcode greater than 0 sorted by firstname asc, postcode desc' => [ [$filterBuilder->setField('postcode')->setValue('0')->setConditionType('gt')->create()], @@ -421,11 +471,14 @@ public function searchAddressDataProvider() ['id' => 2, 'city' => 'CityX', 'postcode' => 47676, 'firstname' => 'John'], ['id' => 1, 'city' => 'CityM', 'postcode' => 75477, 'firstname' => 'John'], ], + 2 ], ]; } /** + * Test for save addresses with restricted countries. + * * @magentoDataFixture Magento/Customer/Fixtures/customer_sec_website.php */ public function testSaveAddressWithRestrictedCountries() diff --git a/dev/tests/integration/testsuite/Magento/Customer/_files/customer_confirmation_config_disable.php b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_confirmation_config_disable.php new file mode 100644 index 00000000000..7d4e451db51 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_confirmation_config_disable.php @@ -0,0 +1,21 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +use Magento\Store\Model\ScopeInterface; +use Magento\Framework\App\Config\MutableScopeConfigInterface; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); +$mutableScopeConfig = $objectManager->create(MutableScopeConfigInterface::class); + +$mutableScopeConfig->setValue( + 'customer/create_account/confirm', + 0, + ScopeInterface::SCOPE_WEBSITES, + null +); diff --git a/dev/tests/integration/testsuite/Magento/Customer/_files/customer_confirmation_config_disable_rollback.php b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_confirmation_config_disable_rollback.php new file mode 100644 index 00000000000..36743b4a20e --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_confirmation_config_disable_rollback.php @@ -0,0 +1,23 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +use Magento\Config\Model\ResourceModel\Config; +use Magento\Framework\Registry; +use Magento\TestFramework\Helper\Bootstrap; + +/** @var Registry $registry */ +$registry = Bootstrap::getObjectManager()->get(Registry::class); +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +/** @var Config $config */ +$config = Bootstrap::getObjectManager()->create(Config::class); +$config->deleteConfig('customer/create_account/confirm'); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/Customer/_files/customer_confirmation_config_enable.php b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_confirmation_config_enable.php new file mode 100644 index 00000000000..c8deb7ec2a5 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_confirmation_config_enable.php @@ -0,0 +1,21 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +use Magento\Store\Model\ScopeInterface; +use Magento\Framework\App\Config\MutableScopeConfigInterface; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); +$mutableScopeConfig = $objectManager->create(MutableScopeConfigInterface::class); + +$mutableScopeConfig->setValue( + 'customer/create_account/confirm', + 1, + ScopeInterface::SCOPE_WEBSITES, + null +); diff --git a/dev/tests/integration/testsuite/Magento/Customer/_files/customer_confirmation_config_enable_rollback.php b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_confirmation_config_enable_rollback.php new file mode 100644 index 00000000000..36743b4a20e --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_confirmation_config_enable_rollback.php @@ -0,0 +1,23 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +use Magento\Config\Model\ResourceModel\Config; +use Magento\Framework\Registry; +use Magento\TestFramework\Helper\Bootstrap; + +/** @var Registry $registry */ +$registry = Bootstrap::getObjectManager()->get(Registry::class); +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +/** @var Config $config */ +$config = Bootstrap::getObjectManager()->create(Config::class); +$config->deleteConfig('customer/create_account/confirm'); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/Customer/_files/customer_confirmation_email_address_with_special_chars.php b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_confirmation_email_address_with_special_chars.php new file mode 100644 index 00000000000..c4f046bac57 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_confirmation_email_address_with_special_chars.php @@ -0,0 +1,37 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Customer\Api\Data\CustomerInterface; +use Magento\Customer\Model\Customer; +use Magento\TestFramework\Helper\Bootstrap; + +include __DIR__ . '/customer_confirmation_config_enable.php'; + +$objectManager = Bootstrap::getObjectManager(); + +/** @var Customer $customer */ +$customer = $objectManager->create(Customer::class); +/** @var CustomerRepositoryInterface $customerRepository */ +$customerRepository = $objectManager->create(CustomerRepositoryInterface::class); +/** @var CustomerInterface $customerInterface */ +$customerInterface = $objectManager->create(CustomerInterface::class); + +$customerInterface->setWebsiteId(1) + ->setEmail('customer+confirmation@example.com') + ->setConfirmation($customer->getRandomConfirmationKey()) + ->setGroupId(1) + ->setStoreId(1) + ->setFirstname('John') + ->setLastname('Smith') + ->setDefaultBilling(1) + ->setDefaultShipping(1) + ->setTaxvat('12') + ->setGender(0); + +$customerRepository->save($customerInterface, 'password'); diff --git a/dev/tests/integration/testsuite/Magento/Customer/_files/customer_confirmation_email_address_with_special_chars_rollback.php b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_confirmation_email_address_with_special_chars_rollback.php new file mode 100644 index 00000000000..7a0ebf74ed8 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_confirmation_email_address_with_special_chars_rollback.php @@ -0,0 +1,31 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +include __DIR__ . '/customer_confirmation_config_enable_rollback.php'; + +use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Framework\Registry; +use Magento\TestFramework\Helper\Bootstrap; + +/** @var Registry $registry */ +$registry = Bootstrap::getObjectManager()->get(Registry::class); +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +/** @var CustomerRepositoryInterface $customerRepository */ +$customerRepository = Bootstrap::getObjectManager()->create(CustomerRepositoryInterface::class); + +try { + $customer = $customerRepository->get('customer+confirmation@example.com'); + $customerRepository->delete($customer); +} catch (\Magento\Framework\Exception\NoSuchEntityException $e) { + // Customer with the specified email does not exist +} + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/Customer/_files/customer_sample.php b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_sample.php index e12eec293f2..1af64898705 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/_files/customer_sample.php +++ b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_sample.php @@ -19,6 +19,7 @@ 'lastname' => 'test lastname', 'email' => 'customer@example.com', 'default_billing' => 1, + 'default_shipping' => 1, 'password' => '123123q', 'attribute_set_id' => 1, ]; diff --git a/dev/tests/integration/testsuite/Magento/Developer/Model/Logger/Handler/DebugTest.php b/dev/tests/integration/testsuite/Magento/Developer/Model/Logger/Handler/DebugTest.php index 3bef48d8801..f7a47017f8b 100644 --- a/dev/tests/integration/testsuite/Magento/Developer/Model/Logger/Handler/DebugTest.php +++ b/dev/tests/integration/testsuite/Magento/Developer/Model/Logger/Handler/DebugTest.php @@ -5,34 +5,20 @@ */ namespace Magento\Developer\Model\Logger\Handler; -use Magento\Config\Console\Command\ConfigSetCommand; -use Magento\Framework\App\Config; -use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Config\Setup\ConfigOptionsList; +use Magento\Framework\App\DeploymentConfig; use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\App\State; use Magento\Framework\Filesystem; use Magento\Framework\Filesystem\Directory\WriteInterface; use Magento\Framework\Logger\Monolog; +use Magento\Framework\Shell; +use Magento\Setup\Mvc\Bootstrap\InitParamListener; use Magento\TestFramework\Helper\Bootstrap; -use Magento\Deploy\Model\Mode; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\OutputInterface; +use Magento\TestFramework\ObjectManager; /** - * Preconditions - * - Developer mode enabled - * - Log file isn't exists - * - 'Log to file' setting are enabled - * - * Test steps - * - Enable production mode without compilation - * - Try to log message into log file - * - Assert that log file isn't exists - * - Assert that 'Log to file' setting are disabled - * - * - Enable 'Log to file' setting - * - Try to log message into debug file - * - Assert that log file is exists - * - Assert that log file contain logged message + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class DebugTest extends \PHPUnit\Framework\TestCase { @@ -42,127 +28,212 @@ class DebugTest extends \PHPUnit\Framework\TestCase private $logger; /** - * @var Mode + * @var WriteInterface */ - private $mode; + private $etcDirectory; /** - * @var InputInterface|\PHPUnit_Framework_MockObject_MockObject + * @var ObjectManager */ - private $inputMock; + private $objectManager; /** - * @var OutputInterface|\PHPUnit_Framework_MockObject_MockObject + * @var Shell */ - private $outputMock; + private $shell; /** - * @var ConfigSetCommand + * @var DeploymentConfig */ - private $configSetCommand; + private $deploymentConfig; /** - * @var WriteInterface + * @var string */ - private $etcDirectory; + private $debugLogPath = ''; + + /** + * @var string + */ + private static $backupFile = 'env.base.php'; + + /** + * @var string + */ + private static $configFile = 'env.php'; /** - * @var Config + * @var Debug */ - private $appConfig; + private $debugHandler; + /** + * @inheritdoc + * @throws \Magento\Framework\Exception\FileSystemException + * @throws \Exception + */ public function setUp() { + $this->objectManager = Bootstrap::getObjectManager(); + $this->shell = $this->objectManager->get(Shell::class); + $this->logger = $this->objectManager->get(Monolog::class); + $this->deploymentConfig = $this->objectManager->get(DeploymentConfig::class); + /** @var Filesystem $filesystem */ - $filesystem = Bootstrap::getObjectManager()->create(Filesystem::class); + $filesystem = $this->objectManager->create(Filesystem::class); $this->etcDirectory = $filesystem->getDirectoryWrite(DirectoryList::CONFIG); - $this->etcDirectory->copyFile('env.php', 'env.base.php'); - - $this->inputMock = $this->getMockBuilder(InputInterface::class) - ->getMockForAbstractClass(); - $this->outputMock = $this->getMockBuilder(OutputInterface::class) - ->getMockForAbstractClass(); - $this->logger = Bootstrap::getObjectManager()->get(Monolog::class); - $this->mode = Bootstrap::getObjectManager()->create( - Mode::class, - [ - 'input' => $this->inputMock, - 'output' => $this->outputMock - ] - ); - $this->configSetCommand = Bootstrap::getObjectManager()->create(ConfigSetCommand::class); - $this->appConfig = Bootstrap::getObjectManager()->create(Config::class); - - // Preconditions - $this->mode->enableDeveloperMode(); - $this->enableDebugging(); - if (file_exists($this->getDebuggerLogPath())) { - unlink($this->getDebuggerLogPath()); - } + $this->etcDirectory->copyFile(self::$configFile, self::$backupFile); } + /** + * @inheritdoc + * @throws \Magento\Framework\Exception\FileSystemException + */ public function tearDown() { - $this->etcDirectory->delete('env.php'); - $this->etcDirectory->renameFile('env.base.php', 'env.php'); + $this->reinitDeploymentConfig(); + $this->etcDirectory->delete(self::$backupFile); } - private function enableDebugging() + /** + * @param bool $flag + * @throws \Magento\Framework\Exception\LocalizedException + */ + private function enableDebugging(bool $flag) { - $this->inputMock = $this->getMockBuilder(InputInterface::class) - ->getMockForAbstractClass(); - $this->outputMock = $this->getMockBuilder(OutputInterface::class) - ->getMockForAbstractClass(); - $this->inputMock->expects($this->exactly(4)) - ->method('getOption') - ->withConsecutive( - [ConfigSetCommand::OPTION_LOCK_ENV], - [ConfigSetCommand::OPTION_LOCK_CONFIG], - [ConfigSetCommand::OPTION_SCOPE], - [ConfigSetCommand::OPTION_SCOPE_CODE] - ) - ->willReturnOnConsecutiveCalls( - true, - false, - ScopeConfigInterface::SCOPE_TYPE_DEFAULT, - null - ); - $this->inputMock->expects($this->exactly(2)) - ->method('getArgument') - ->withConsecutive([ConfigSetCommand::ARG_PATH], [ConfigSetCommand::ARG_VALUE]) - ->willReturnOnConsecutiveCalls('dev/debug/debug_logging', 1); - $this->outputMock->expects($this->once()) - ->method('writeln') - ->with('<info>Value was saved in app/etc/env.php and locked.</info>'); - $this->assertFalse((bool)$this->configSetCommand->run($this->inputMock, $this->outputMock)); + $this->shell->execute( + PHP_BINARY . ' -f %s setup:config:set -n --%s=%s --%s=%s', + [ + BP . '/bin/magento', + ConfigOptionsList::INPUT_KEY_DEBUG_LOGGING, + (int)$flag, + InitParamListener::BOOTSTRAP_PARAM, + urldecode(http_build_query(Bootstrap::getInstance()->getAppInitParams())), + ] + ); + $this->deploymentConfig->resetData(); + $this->assertSame((int)$flag, $this->deploymentConfig->get(ConfigOptionsList::CONFIG_PATH_DEBUG_LOGGING)); } + /** + * @throws \Magento\Framework\Exception\LocalizedException + */ public function testDebugInProductionMode() { $message = 'test message'; + $this->reinitDebugHandler(State::MODE_PRODUCTION); - $this->mode->enableProductionModeMinimal(); + $this->removeDebugLog(); $this->logger->debug($message); $this->assertFileNotExists($this->getDebuggerLogPath()); - $this->assertFalse((bool)$this->appConfig->getValue('dev/debug/debug_logging')); + $this->assertNull($this->deploymentConfig->get(ConfigOptionsList::CONFIG_PATH_DEBUG_LOGGING)); - $this->enableDebugging(); - $this->logger->debug($message); + $this->checkCommonFlow($message); + $this->reinitDeploymentConfig(); + } + + /** + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function testDebugInDeveloperMode() + { + $message = 'test message'; + $this->reinitDebugHandler(State::MODE_DEVELOPER); + $this->removeDebugLog(); + $this->logger->debug($message); $this->assertFileExists($this->getDebuggerLogPath()); $this->assertContains($message, file_get_contents($this->getDebuggerLogPath())); + $this->assertNull($this->deploymentConfig->get(ConfigOptionsList::CONFIG_PATH_DEBUG_LOGGING)); + + $this->checkCommonFlow($message); + $this->reinitDeploymentConfig(); } /** - * @return bool|string + * @return string */ private function getDebuggerLogPath() { - foreach ($this->logger->getHandlers() as $handler) { - if ($handler instanceof Debug) { - return $handler->getUrl(); + if (!$this->debugLogPath) { + foreach ($this->logger->getHandlers() as $handler) { + if ($handler instanceof Debug) { + $this->debugLogPath = $handler->getUrl(); + } } } - return false; + + return $this->debugLogPath; + } + + /** + * @throws \Magento\Framework\Exception\FileSystemException + */ + private function reinitDeploymentConfig() + { + $this->etcDirectory->delete(self::$configFile); + $this->etcDirectory->copyFile(self::$backupFile, self::$configFile); + } + + /** + * @param string $instanceMode + * @throws \Magento\Framework\Exception\LocalizedException + */ + private function reinitDebugHandler(string $instanceMode) + { + $this->debugHandler = $this->objectManager->create( + Debug::class, + [ + 'filePath' => Bootstrap::getInstance()->getAppTempDir(), + 'state' => $this->objectManager->create( + State::class, + [ + 'mode' => $instanceMode, + ] + ), + ] + ); + $this->logger->setHandlers( + [ + $this->debugHandler, + ] + ); + } + + /** + * @return void + */ + private function detachLogger() + { + $this->debugHandler->close(); + } + + /** + * @return void + */ + private function removeDebugLog() + { + $this->detachLogger(); + if (file_exists($this->getDebuggerLogPath())) { + unlink($this->getDebuggerLogPath()); + } + } + + /** + * @param string $message + * @throws \Magento\Framework\Exception\LocalizedException + */ + private function checkCommonFlow(string $message) + { + $this->enableDebugging(true); + $this->removeDebugLog(); + $this->logger->debug($message); + $this->assertFileExists($this->getDebuggerLogPath()); + $this->assertContains($message, file_get_contents($this->getDebuggerLogPath())); + + $this->enableDebugging(false); + $this->removeDebugLog(); + $this->logger->debug($message); + $this->assertFileNotExists($this->getDebuggerLogPath()); } } diff --git a/dev/tests/integration/testsuite/Magento/Dhl/Model/CarrierTest.php b/dev/tests/integration/testsuite/Magento/Dhl/Model/CarrierTest.php new file mode 100644 index 00000000000..8874d880a4d --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Dhl/Model/CarrierTest.php @@ -0,0 +1,241 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Dhl\Model; + +use Magento\Framework\HTTP\ZendClient; +use Magento\Framework\HTTP\ZendClientFactory; +use Magento\Framework\Simplexml\Element; +use Magento\Shipping\Model\Tracking\Result\Error; +use Magento\Shipping\Model\Tracking\Result\Status; +use PHPUnit_Framework_MockObject_MockObject as MockObject; + +class CarrierTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\Dhl\Model\Carrier + */ + private $dhlCarrier; + + /** + * @var ZendClient|MockObject + */ + private $httpClientMock; + + /** + * @var \Zend_Http_Response|MockObject + */ + private $httpResponseMock; + + protected function setUp() + { + $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + $this->dhlCarrier = $objectManager->create( + \Magento\Dhl\Model\Carrier::class, + ['httpClientFactory' => $this->getHttpClientFactory()] + ); + } + + /** + * @magentoConfigFixture default_store carriers/dhl/id CustomerSiteID + * @magentoConfigFixture default_store carriers/dhl/password CustomerPassword + * @param string[] $trackingNumbers + * @param string $responseXml + * @param $expectedTrackingData + * @param string $expectedRequestXml + * @dataProvider getTrackingDataProvider + */ + public function testGetTracking( + $trackingNumbers, + string $responseXml, + $expectedTrackingData, + string $expectedRequestXml = '' + ) { + $this->httpResponseMock->method('getBody') + ->willReturn($responseXml); + $trackingResult = $this->dhlCarrier->getTracking($trackingNumbers); + $this->assertTrackingResult($expectedTrackingData, $trackingResult->getAllTrackings()); + if ($expectedRequestXml !== '') { + $method = new \ReflectionMethod($this->httpClientMock, '_prepareBody'); + $method->setAccessible(true); + $requestXml = $method->invoke($this->httpClientMock); + $this->assertRequest($expectedRequestXml, $requestXml); + } + } + + /** + * Get tracking data provider + * @return array + */ + public function getTrackingDataProvider() : array + { + $expectedMultiAWBRequestXml = file_get_contents(__DIR__ . '/../_files/TrackingRequest_MultipleAWB.xml'); + $multiAWBResponseXml = file_get_contents(__DIR__ . '/../_files/TrackingResponse_MultipleAWB.xml'); + $expectedSingleAWBRequestXml = file_get_contents(__DIR__ . '/../_files/TrackingRequest_SingleAWB.xml'); + $singleAWBResponseXml = file_get_contents(__DIR__ . '/../_files/TrackingResponse_SingleAWB.xml'); + $singleNoDataResponseXml = file_get_contents(__DIR__ . '/../_files/SingleknownTrackResponse-no-data-found.xml'); + $failedResponseXml = file_get_contents(__DIR__ . '/../_files/Track-res-XML-Parse-Err.xml'); + $expectedTrackingDataA = [ + 'carrier' => 'dhl', + 'carrier_title' => 'DHL', + 'tracking' => 4781584780, + 'service' => 'DOCUMENT', + 'progressdetail' => [ + [ + 'activity' => 'SD Shipment information received', + 'deliverydate' => '2017-12-25', + 'deliverytime' => '14:38:00', + 'deliverylocation' => 'BEIJING-CHN [PEK]' + ] + ], + 'weight' => '0.5 K', + ]; + $expectedTrackingDataB = [ + 'carrier' => 'dhl', + 'carrier_title' => 'DHL', + 'tracking' => 4781585060, + 'service' => 'NOT RESTRICTED FOR TRANSPORT,', + 'progressdetail' => [ + [ + 'activity' => 'SD Shipment information received', + 'deliverydate' => '2017-12-24', + 'deliverytime' => '13:35:00', + 'deliverylocation' => 'HONG KONG-HKG [HKG]' + ] + ], + 'weight' => '2.0 K', + ]; + $expectedTrackingDataC = [ + 'carrier' => 'dhl', + 'carrier_title' => 'DHL', + 'tracking' => 5702254250, + 'service' => 'CD', + 'progressdetail' => [ + [ + 'activity' => 'SD Shipment information received', + 'deliverydate' => '2017-12-24', + 'deliverytime' => '04:12:00', + 'deliverylocation' => 'BIRMINGHAM-GBR [BHX]' + ] + ], + 'weight' => '0.12 K', + ]; + $expectedTrackingDataD = [ + 'carrier' => 'dhl', + 'carrier_title' => 'DHL', + 'tracking' => 4781585060, + 'error_message' => __('Unable to retrieve tracking') + ]; + $expectedTrackingDataE = [ + 'carrier' => 'dhl', + 'carrier_title' => 'DHL', + 'tracking' => 111, + 'error_message' => __( + 'Error #%1 : %2', + '111', + ' Error Parsing incoming request XML + Error: The content of element type + "ShipperReference" must match + "(ReferenceID,ReferenceType?)". at line + 16, column 22' + ) + ]; + return [ + 'multi-AWB' => [ + ['4781584780', '4781585060', '5702254250'], + $multiAWBResponseXml, + [$expectedTrackingDataA, $expectedTrackingDataB, $expectedTrackingDataC], + $expectedMultiAWBRequestXml + ], + 'single-AWB' => [ + ['4781585060'], + $singleAWBResponseXml, + [$expectedTrackingDataB], + $expectedSingleAWBRequestXml + ], + 'single-AWB-no-data' => [ + ['4781585061'], + $singleNoDataResponseXml, + [$expectedTrackingDataD] + ], + 'failed-response' => [ + ['4781585060-failed'], + $failedResponseXml, + [$expectedTrackingDataE] + ] + ]; + } + + /** + * Get mocked Http Client Factory + * + * @return MockObject + */ + private function getHttpClientFactory(): MockObject + { + $this->httpResponseMock = $this->getMockBuilder(\Zend_Http_Response::class) + ->disableOriginalConstructor() + ->getMock(); + $this->httpClientMock = $this->getMockBuilder(ZendClient::class) + ->disableOriginalConstructor() + ->setMethods(['request']) + ->getMock(); + $this->httpClientMock->method('request') + ->willReturn($this->httpResponseMock); + /** @var ZendClientFactory|MockObject $httpClientFactoryMock */ + $httpClientFactoryMock = $this->getMockBuilder(ZendClientFactory::class) + ->disableOriginalConstructor() + ->getMock(); + $httpClientFactoryMock->method('create') + ->willReturn($this->httpClientMock); + + return $httpClientFactoryMock; + } + + /** + * Assert request + * + * @param string $expectedRequestXml + * @param string $requestXml + */ + private function assertRequest(string $expectedRequestXml, string $requestXml): void + { + $expectedRequestElement = new Element($expectedRequestXml); + $requestElement = new Element($requestXml); + $requestMessageTime = $requestElement->Request->ServiceHeader->MessageTime->__toString(); + $this->assertEquals( + 1, + preg_match("/\d{4}\-\d{2}\-\d{2}T\d{2}\:\d{2}\:\d{2}\+\d{2}\:\d{2}/", $requestMessageTime) + ); + $expectedRequestElement->Request->ServiceHeader->MessageTime = $requestMessageTime; + $messageReference = $requestElement->Request->ServiceHeader->MessageReference->__toString(); + $this->assertStringStartsWith('MAGE_TRCK_', $messageReference); + $this->assertGreaterThanOrEqual(28, strlen($messageReference)); + $this->assertLessThanOrEqual(32, strlen($messageReference)); + $requestElement->Request->ServiceHeader->MessageReference = 'MAGE_TRCK_28TO32_Char_CHECKED'; + $this->assertXmlStringEqualsXmlString($expectedRequestElement->asXML(), $requestElement->asXML()); + } + + /** + * Assert tracking + * + * @param array|null $expectedTrackingData + * @param Status[]|null $trackingResults + * @return void + */ + private function assertTrackingResult($expectedTrackingData, $trackingResults): void + { + if (null === $expectedTrackingData) { + $this->assertNull($trackingResults); + } else { + $ctr = 0; + foreach ($trackingResults as $trackingResult) { + $this->assertEquals($expectedTrackingData[$ctr++], $trackingResult->getData()); + } + } + } +} diff --git a/dev/tests/integration/testsuite/Magento/Dhl/_files/SingleknownTrackResponse-no-data-found.xml b/dev/tests/integration/testsuite/Magento/Dhl/_files/SingleknownTrackResponse-no-data-found.xml new file mode 100755 index 00000000000..9887cecbd2d --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Dhl/_files/SingleknownTrackResponse-no-data-found.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<req:TrackingResponse xmlns:req="http://www.dhl.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.dhl.com TrackingResponse.xsd"> + <Response> + <ServiceHeader> + <MessageTime>2018-02-27T12:59:34+01:00</MessageTime> + <MessageReference>1234567890123456789012345678</MessageReference> + <SiteID>CustomerSiteID</SiteID> + </ServiceHeader> + </Response> + <AWBInfo> + <AWBNumber>4781585060</AWBNumber> + <Status> + <ActionStatus>No Shipments Found</ActionStatus> + <Condition> + <ConditionCode>209</ConditionCode> + <ConditionData>No Shipments Found for AWBNumber 6017300993</ConditionData> + </Condition> + </Status> + </AWBInfo> + <LanguageCode>String</LanguageCode> +</req:TrackingResponse> +<!-- ServiceInvocationId:20180227125934_5793_74fbd9e1-a8b0-4f6a-a326-26aae979e5f0 --> diff --git a/dev/tests/integration/testsuite/Magento/Dhl/_files/Track-res-XML-Parse-Err.xml b/dev/tests/integration/testsuite/Magento/Dhl/_files/Track-res-XML-Parse-Err.xml new file mode 100755 index 00000000000..c2abd68d3c4 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Dhl/_files/Track-res-XML-Parse-Err.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<req:ShipmentTrackingErrorResponse xmlns:req="http://www.dhl.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.dhl.com track-err-res.xsd"> + <Response> + <ServiceHeader> + <MessageTime>2018-02-27T12:55:05+01:00</MessageTime> + </ServiceHeader> + <Status> + <ActionStatus>Failure</ActionStatus> + <Condition> + <ConditionCode>111</ConditionCode> + <ConditionData> Error Parsing incoming request XML + Error: The content of element type + "ShipperReference" must match + "(ReferenceID,ReferenceType?)". at line + 16, column 22</ConditionData> + </Condition> + </Status> + </Response> +</req:ShipmentTrackingErrorResponse> +<!-- ServiceInvocationId:20180227125505_5793_2008671c-9292-4790-87b6-b02ccdf913db --> diff --git a/dev/tests/integration/testsuite/Magento/Dhl/_files/TrackingRequest_MultipleAWB.xml b/dev/tests/integration/testsuite/Magento/Dhl/_files/TrackingRequest_MultipleAWB.xml new file mode 100755 index 00000000000..c0a18fcc4e2 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Dhl/_files/TrackingRequest_MultipleAWB.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<req:KnownTrackingRequest xmlns:req="http://www.dhl.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.dhl.com TrackingRequestKnown-1.0.xsd" schemaVersion="1.0"> + <Request> + <ServiceHeader> + <MessageTime>2002-06-25T11:28:56-08:00</MessageTime> + <MessageReference>MAGE_TRCK_28TO32_Char_CHECKED</MessageReference> + <SiteID>CustomerSiteID</SiteID> + <Password>CustomerPassword</Password> + </ServiceHeader> + </Request> + <LanguageCode>en</LanguageCode> + <AWBNumber>4781584780</AWBNumber> + <AWBNumber>4781585060</AWBNumber> + <AWBNumber>5702254250</AWBNumber> + <LevelOfDetails>ALL_CHECK_POINTS</LevelOfDetails> +</req:KnownTrackingRequest> + + diff --git a/dev/tests/integration/testsuite/Magento/Dhl/_files/TrackingRequest_SingleAWB.xml b/dev/tests/integration/testsuite/Magento/Dhl/_files/TrackingRequest_SingleAWB.xml new file mode 100755 index 00000000000..dac69a0d68c --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Dhl/_files/TrackingRequest_SingleAWB.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<req:KnownTrackingRequest xmlns:req="http://www.dhl.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.dhl.com TrackingRequestKnown-1.0.xsd" schemaVersion="1.0"> + <Request> + <ServiceHeader> + <MessageTime>2002-06-25T11:28:56-08:00</MessageTime> + <MessageReference>MAGE_TRCK_28TO32_Char_CHECKED</MessageReference> + <SiteID>CustomerSiteID</SiteID> + <Password>CustomerPassword</Password> + </ServiceHeader> + </Request> + <LanguageCode>en</LanguageCode> + <AWBNumber>4781585060</AWBNumber> + <LevelOfDetails>ALL_CHECK_POINTS</LevelOfDetails> +</req:KnownTrackingRequest> \ No newline at end of file diff --git a/dev/tests/integration/testsuite/Magento/Dhl/_files/TrackingResponse_MultipleAWB.xml b/dev/tests/integration/testsuite/Magento/Dhl/_files/TrackingResponse_MultipleAWB.xml new file mode 100755 index 00000000000..369236d80c6 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Dhl/_files/TrackingResponse_MultipleAWB.xml @@ -0,0 +1,174 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<req:TrackingResponse xmlns:req="http://www.dhl.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.dhl.com TrackingResponse.xsd"> + <Response> + <ServiceHeader> + <MessageTime>2018-02-27T12:43:44+01:00</MessageTime> + <MessageReference>1234567890123456789012345678</MessageReference> + <SiteID>CustomerSiteID</SiteID> + </ServiceHeader> + </Response> + <AWBInfo> + <AWBNumber>4781584780</AWBNumber> + <Status> + <ActionStatus>success</ActionStatus> + </Status> + <ShipmentInfo> + <OriginServiceArea> + <ServiceAreaCode>PEK</ServiceAreaCode> + <Description>BEIJING-CHN</Description> + </OriginServiceArea> + <DestinationServiceArea> + <ServiceAreaCode>PHL</ServiceAreaCode> + <Description>WEST PHILADELPHIA,PA-USA</Description> + </DestinationServiceArea> + <ShipperName>THE EXP HIGH SCH ATT TO BNU</ShipperName> + <ShipperAccountNumber>123456789</ShipperAccountNumber> + <ConsigneeName>HAVEFORD COLLEGE</ConsigneeName> + <ShipmentDate>2017-12-25T14:38:00</ShipmentDate> + <Pieces>1</Pieces> + <Weight>0.5</Weight> + <WeightUnit>K</WeightUnit> + <GlobalProductCode>D</GlobalProductCode> + <ShipmentDesc>DOCUMENT</ShipmentDesc> + <DlvyNotificationFlag>Y</DlvyNotificationFlag> + <Shipper> + <City>BEIJING</City> + <PostalCode>100032</PostalCode> + <CountryCode>CN</CountryCode> + </Shipper> + <Consignee> + <City>HAVERFORD</City> + <DivisionCode>PA</DivisionCode> + <PostalCode>19041</PostalCode> + <CountryCode>US</CountryCode> + </Consignee> + <ShipperReference> + <ReferenceID>2469</ReferenceID> + </ShipperReference> + <ShipmentEvent> + <Date>2017-12-25</Date> + <Time>14:38:00</Time> + <ServiceEvent> + <EventCode>SD</EventCode> + <Description>Shipment information received</Description> + </ServiceEvent> + <Signatory/> + <ServiceArea> + <ServiceAreaCode>PEK</ServiceAreaCode> + <Description>BEIJING-CHN</Description> + </ServiceArea> + </ShipmentEvent> + </ShipmentInfo> + </AWBInfo> + <AWBInfo> + <AWBNumber>4781585060</AWBNumber> + <Status> + <ActionStatus>success</ActionStatus> + </Status> + <ShipmentInfo> + <OriginServiceArea> + <ServiceAreaCode>HKG</ServiceAreaCode> + <Description>HONG KONG-HKG</Description> + </OriginServiceArea> + <DestinationServiceArea> + <ServiceAreaCode>HKG</ServiceAreaCode> + <Description>HONG KONG-HKG</Description> + </DestinationServiceArea> + <ShipperName>NET-A-PORTER</ShipperName> + <ShipperAccountNumber>123456789</ShipperAccountNumber> + <ConsigneeName>NICOLE LI</ConsigneeName> + <ShipmentDate>2017-12-24T13:35:00</ShipmentDate> + <Pieces>1</Pieces> + <Weight>2.0</Weight> + <WeightUnit>K</WeightUnit> + <GlobalProductCode>N</GlobalProductCode> + <ShipmentDesc>NOT RESTRICTED FOR TRANSPORT,</ShipmentDesc> + <DlvyNotificationFlag>Y</DlvyNotificationFlag> + <Shipper> + <City>HONG KONG</City> + <CountryCode>HK</CountryCode> + </Shipper> + <Consignee> + <City>HONG KONG</City> + <DivisionCode>CH</DivisionCode> + <CountryCode>HK</CountryCode> + </Consignee> + <ShipperReference> + <ReferenceID>1060571</ReferenceID> + </ShipperReference> + <ShipmentEvent> + <Date>2017-12-24</Date> + <Time>13:35:00</Time> + <ServiceEvent> + <EventCode>SD</EventCode> + <Description>Shipment information received</Description> + </ServiceEvent> + <Signatory/> + <ServiceArea> + <ServiceAreaCode>HKG</ServiceAreaCode> + <Description>HONG KONG-HKG</Description> + </ServiceArea> + </ShipmentEvent> + </ShipmentInfo> + </AWBInfo> + <AWBInfo> + <AWBNumber>5702254250</AWBNumber> + <Status> + <ActionStatus>success</ActionStatus> + </Status> + <ShipmentInfo> + <OriginServiceArea> + <ServiceAreaCode>BHX</ServiceAreaCode> + <Description>BIRMINGHAM-GBR</Description> + </OriginServiceArea> + <DestinationServiceArea> + <ServiceAreaCode>AOI</ServiceAreaCode> + <Description>ANCONA-ITA</Description> + </DestinationServiceArea> + <ShipperName>AMAZON EU SARL</ShipperName> + <ShipperAccountNumber>123456789</ShipperAccountNumber> + <ConsigneeName>MATTEO LOMBO</ConsigneeName> + <ShipmentDate>2017-12-24T04:12:00</ShipmentDate> + <Pieces>1</Pieces> + <Weight>0.12</Weight> + <WeightUnit>K</WeightUnit> + <GlobalProductCode>U</GlobalProductCode> + <ShipmentDesc>CD</ShipmentDesc> + <DlvyNotificationFlag>Y</DlvyNotificationFlag> + <Shipper> + <City>PETERBOROUGH</City> + <PostalCode>PE2 9EN</PostalCode> + <CountryCode>GB</CountryCode> + </Shipper> + <Consignee> + <City>ORTONA</City> + <PostalCode>66026</PostalCode> + <CountryCode>IT</CountryCode> + </Consignee> + <ShipperReference> + <ReferenceID>DGWYDy4xN_1</ReferenceID> + </ShipperReference> + <ShipmentEvent> + <Date>2017-12-24</Date> + <Time>04:12:00</Time> + <ServiceEvent> + <EventCode>SD</EventCode> + <Description>Shipment information received</Description> + </ServiceEvent> + <Signatory/> + <ServiceArea> + <ServiceAreaCode>BHX</ServiceAreaCode> + <Description>BIRMINGHAM-GBR</Description> + </ServiceArea> + </ShipmentEvent> + </ShipmentInfo> + </AWBInfo> + <LanguageCode>en</LanguageCode> +</req:TrackingResponse> +<!-- ServiceInvocationId:20180227124344_5793_23bed3d9-e792-4955-8055-9472b1b41929 --> diff --git a/dev/tests/integration/testsuite/Magento/Dhl/_files/TrackingResponse_SingleAWB.xml b/dev/tests/integration/testsuite/Magento/Dhl/_files/TrackingResponse_SingleAWB.xml new file mode 100755 index 00000000000..ef303eaab64 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Dhl/_files/TrackingResponse_SingleAWB.xml @@ -0,0 +1,69 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<req:TrackingResponse xmlns:req="http://www.dhl.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.dhl.com TrackingResponse.xsd"> + <Response> + <ServiceHeader> + <MessageTime>2018-02-27T12:27:42+01:00</MessageTime> + <MessageReference>1234567890123456789012345678</MessageReference> + <SiteID>CustomerSiteID</SiteID> + </ServiceHeader> + </Response> + <AWBInfo> + <AWBNumber>4781585060</AWBNumber> + <Status> + <ActionStatus>success</ActionStatus> + </Status> + <ShipmentInfo> + <OriginServiceArea> + <ServiceAreaCode>HKG</ServiceAreaCode> + <Description>HONG KONG-HKG</Description> + </OriginServiceArea> + <DestinationServiceArea> + <ServiceAreaCode>HKG</ServiceAreaCode> + <Description>HONG KONG-HKG</Description> + </DestinationServiceArea> + <ShipperName>NET-A-PORTER</ShipperName> + <ShipperAccountNumber>123456789</ShipperAccountNumber> + <ConsigneeName>NICOLE LI</ConsigneeName> + <ShipmentDate>2017-12-24T13:35:00</ShipmentDate> + <Pieces>1</Pieces> + <Weight>2.0</Weight> + <WeightUnit>K</WeightUnit> + <GlobalProductCode>N</GlobalProductCode> + <ShipmentDesc>NOT RESTRICTED FOR TRANSPORT,</ShipmentDesc> + <DlvyNotificationFlag>Y</DlvyNotificationFlag> + <Shipper> + <City>HONG KONG</City> + <CountryCode>HK</CountryCode> + </Shipper> + <Consignee> + <City>HONG KONG</City> + <DivisionCode>CH</DivisionCode> + <CountryCode>HK</CountryCode> + </Consignee> + <ShipperReference> + <ReferenceID>1060571</ReferenceID> + </ShipperReference> + <ShipmentEvent> + <Date>2017-12-24</Date> + <Time>13:35:00</Time> + <ServiceEvent> + <EventCode>SD</EventCode> + <Description>Shipment information received</Description> + </ServiceEvent> + <Signatory/> + <ServiceArea> + <ServiceAreaCode>HKG</ServiceAreaCode> + <Description>HONG KONG-HKG</Description> + </ServiceArea> + </ShipmentEvent> + </ShipmentInfo> + </AWBInfo> + <LanguageCode>en</LanguageCode> +</req:TrackingResponse> +<!-- ServiceInvocationId:20180227122741_5793_e0f8c40e-5245-4737-ab31-323030366721 --> diff --git a/dev/tests/integration/testsuite/Magento/Downloadable/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/LinksTest.php b/dev/tests/integration/testsuite/Magento/Downloadable/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/LinksTest.php index c743fcec1dd..b07a6506c1b 100644 --- a/dev/tests/integration/testsuite/Magento/Downloadable/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/LinksTest.php +++ b/dev/tests/integration/testsuite/Magento/Downloadable/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/LinksTest.php @@ -5,6 +5,13 @@ */ namespace Magento\Downloadable\Block\Adminhtml\Catalog\Product\Edit\Tab\Downloadable; +/** + * Class LinksTest + * + * @package Magento\Downloadable\Block\Adminhtml\Catalog\Product\Edit\Tab\Downloadable + * @deprecated + * @see \Magento\Downloadable\Ui\DataProvider\Product\Form\Modifier\Links + */ class LinksTest extends \PHPUnit\Framework\TestCase { /** diff --git a/dev/tests/integration/testsuite/Magento/Downloadable/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/SamplesTest.php b/dev/tests/integration/testsuite/Magento/Downloadable/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/SamplesTest.php index 28d36803583..3f3b3bd6219 100644 --- a/dev/tests/integration/testsuite/Magento/Downloadable/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/SamplesTest.php +++ b/dev/tests/integration/testsuite/Magento/Downloadable/Block/Adminhtml/Catalog/Product/Edit/Tab/Downloadable/SamplesTest.php @@ -5,6 +5,13 @@ */ namespace Magento\Downloadable\Block\Adminhtml\Catalog\Product\Edit\Tab\Downloadable; +/** + * Class SamplesTest + * + * @package Magento\Downloadable\Block\Adminhtml\Catalog\Product\Edit\Tab\Downloadable + * @deprecated + * @see \Magento\Downloadable\Ui\DataProvider\Product\Form\Modifier\Samples + */ class SamplesTest extends \PHPUnit\Framework\TestCase { public function testGetUploadButtonsHtml() diff --git a/dev/tests/integration/testsuite/Magento/DownloadableImportExport/Model/DownloadableTest.php b/dev/tests/integration/testsuite/Magento/DownloadableImportExport/Model/DownloadableTest.php index c80cd13a168..d0e4471e2ea 100644 --- a/dev/tests/integration/testsuite/Magento/DownloadableImportExport/Model/DownloadableTest.php +++ b/dev/tests/integration/testsuite/Magento/DownloadableImportExport/Model/DownloadableTest.php @@ -9,7 +9,10 @@ class DownloadableTest extends AbstractProductExportImportTestCase { - public function exportImportDataProvider() + /** + * @return array + */ + public function exportImportDataProvider(): array { return [ 'downloadable-product' => [ @@ -31,79 +34,32 @@ public function exportImportDataProvider() ]; } - public function importReplaceDataProvider() - { - return $this->exportImportDataProvider(); - } - - /** - * @param array $fixtures - * @param string[] $skus - * @param string[] $skippedAttributes - * @dataProvider exportImportDataProvider - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - * - * @todo remove after MAGETWO-38240 resolved - */ - public function testExport($fixtures, $skus, $skippedAttributes = [], $rollbackFixtures = []) - { - $this->markTestSkipped('Uncomment after MAGETWO-38240 resolved'); - } - - /** - * @param array $fixtures - * @param string[] $skus - * @dataProvider exportImportDataProvider - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - * - * @todo remove after MAGETWO-38240 resolved - */ - public function testImportDelete($fixtures, $skus, $skippedAttributes = [], $rollbackFixtures = []) - { - $this->markTestSkipped('Uncomment after MAGETWO-38240 resolved'); - } - /** - * @magentoAppArea adminhtml - * @magentoDbIsolation enabled - * @magentoAppIsolation enabled - * - * @param array $fixtures - * @param string[] $skus - * @param string[] $skippedAttributes - * @dataProvider importReplaceDataProvider - * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * Run import/export tests. * - * @todo remove after MAGETWO-38240 resolved - */ - public function testImportReplace($fixtures, $skus, $skippedAttributes = [], $rollbackFixtures = []) - { - $this->markTestSkipped('Uncomment after MAGETWO-38240 resolved'); - } - - /** * @magentoAppArea adminhtml - * @magentoDbIsolation enabled + * @magentoDbIsolation disabled * @magentoAppIsolation enabled * * @param array $fixtures * @param string[] $skus * @param string[] $skippedAttributes - * @dataProvider importReplaceDataProvider - * + * @return void + * @dataProvider exportImportDataProvider * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public function testImportReplaceWithPagination($fixtures, $skus, $skippedAttributes = []) + public function testImportExport(array $fixtures, array $skus, array $skippedAttributes = []): void { $this->markTestSkipped('Uncomment after MAGETWO-38240 resolved'); } /** - * @param \Magento\Catalog\Model\Product $expectedProduct - * @param \Magento\Catalog\Model\Product $actualProduct + * @inheritdoc */ - protected function assertEqualsSpecificAttributes($expectedProduct, $actualProduct) - { + protected function assertEqualsSpecificAttributes( + \Magento\Catalog\Model\Product $expectedProduct, + \Magento\Catalog\Model\Product $actualProduct + ): void { $expectedProductLinks = $expectedProduct->getExtensionAttributes()->getDownloadableProductLinks(); $expectedProductSamples = $expectedProduct->getExtensionAttributes()->getDownloadableProductSamples(); diff --git a/dev/tests/integration/testsuite/Magento/Elasticsearch/SearchAdapter/AdapterTest.php b/dev/tests/integration/testsuite/Magento/Elasticsearch/SearchAdapter/AdapterTest.php index dc288a18fad..8d80fd8533d 100644 --- a/dev/tests/integration/testsuite/Magento/Elasticsearch/SearchAdapter/AdapterTest.php +++ b/dev/tests/integration/testsuite/Magento/Elasticsearch/SearchAdapter/AdapterTest.php @@ -369,4 +369,18 @@ public function dateDataProvider() [['from' => '2000-02-01T00:00:00Z', 'to' => ''], 0], ]; } + + public function filterByAttributeValuesDataProvider() + { + $variations = parent::filterByAttributeValuesDataProvider(); + + $variations['quick search by date'] = [ + 'quick_search_container', + [ + 'search_term' => '2000-10-30', + ], + ]; + + return $variations; + } } diff --git a/dev/tests/integration/testsuite/Magento/EncryptionKey/Controller/Adminhtml/Crypt/Key/SaveTest.php b/dev/tests/integration/testsuite/Magento/EncryptionKey/Controller/Adminhtml/Crypt/Key/SaveTest.php index 22e8a5911f0..822c3c03188 100644 --- a/dev/tests/integration/testsuite/Magento/EncryptionKey/Controller/Adminhtml/Crypt/Key/SaveTest.php +++ b/dev/tests/integration/testsuite/Magento/EncryptionKey/Controller/Adminhtml/Crypt/Key/SaveTest.php @@ -72,7 +72,7 @@ public function testSaveActionWithInvalidKey() $this->assertRedirect(); $this->assertSessionMessages( - $this->contains('The encryption key format is invalid.'), + $this->contains('Encryption key must be 32 character string without any white space.'), \Magento\Framework\Message\MessageInterface::TYPE_ERROR ); } diff --git a/dev/tests/integration/testsuite/Magento/EncryptionKey/Setup/Patch/Data/SodiumChachaPatchTest.php b/dev/tests/integration/testsuite/Magento/EncryptionKey/Setup/Patch/Data/SodiumChachaPatchTest.php index 3a47692bdb9..a563641a4f8 100644 --- a/dev/tests/integration/testsuite/Magento/EncryptionKey/Setup/Patch/Data/SodiumChachaPatchTest.php +++ b/dev/tests/integration/testsuite/Magento/EncryptionKey/Setup/Patch/Data/SodiumChachaPatchTest.php @@ -41,6 +41,9 @@ public function testChangeEncryptionKey() $structureMock->expects($this->once()) ->method('getFieldPathsByAttribute') ->will($this->returnValue([$testPath])); + $structureMock->expects($this->once()) + ->method('getFieldPaths') + ->willReturn([]); /** @var \Magento\Config\Model\ResourceModel\Config $configModel */ $configModel = $this->objectManager->create(\Magento\Config\Model\ResourceModel\Config::class); diff --git a/dev/tests/integration/testsuite/Magento/Framework/Encryption/EncryptorTest.php b/dev/tests/integration/testsuite/Magento/Framework/Encryption/EncryptorTest.php index 2ba9109df86..88c567da752 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Encryption/EncryptorTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/Encryption/EncryptorTest.php @@ -10,18 +10,64 @@ class EncryptorTest extends \PHPUnit\Framework\TestCase /** * @var \Magento\Framework\Encryption\Encryptor */ - protected $_model; + private $encryptor; protected function setUp() { - $this->_model = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + $this->encryptor = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( \Magento\Framework\Encryption\Encryptor::class ); } public function testEncryptDecrypt() { - $this->assertEquals('', $this->_model->decrypt($this->_model->encrypt(''))); - $this->assertEquals('test', $this->_model->decrypt($this->_model->encrypt('test'))); + $this->assertEquals('', $this->encryptor->decrypt($this->encryptor->encrypt(''))); + $this->assertEquals('test', $this->encryptor->decrypt($this->encryptor->encrypt('test'))); + } + + /** + * @param string $key + * @dataProvider validEncryptionKeyDataProvider + */ + public function testValidateKey($key) + { + $this->encryptor->validateKey($key); + } + + public function validEncryptionKeyDataProvider() + { + return [ + '32 numbers' => ['12345678901234567890123456789012'], + '32 characters' => ['aBcdeFghIJKLMNOPQRSTUvwxYzabcdef'], + '32 special characters' => ['!@#$%^&*()_+~`:;"<>,.?/|*&^%$#@!'], + '32 combination' =>['1234eFghI1234567^&*(890123456789'], + ]; + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage Encryption key must be 32 character string without any white space. + * + * @param string $key + * @dataProvider invalidEncryptionKeyDataProvider + */ + public function testValidateKeyInvalid($key) + { + $this->encryptor->validateKey($key); + } + + public function invalidEncryptionKeyDataProvider() + { + return [ + 'empty string' => [''], + 'leading space' => [' 1234567890123456789012345678901'], + 'tailing space' => ['1234567890123456789012345678901 '], + 'space in the middle' => ['12345678901 23456789012345678901'], + 'tab in the middle' => ['12345678901 23456789012345678'], + 'return in the middle' => ['12345678901 + 23456789012345678901'], + '31 characters' => ['1234567890123456789012345678901'], + '33 characters' => ['123456789012345678901234567890123'], + ]; } } diff --git a/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/TopologyTest.php b/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/TopologyTest.php index 189d189d32c..c2521c27a0c 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/TopologyTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/TopologyTest.php @@ -5,6 +5,9 @@ */ namespace Magento\Framework\MessageQueue; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\MessageQueue\PreconditionFailedException; + /** * @see dev/tests/integration/_files/Magento/TestModuleMessageQueueConfiguration * @see dev/tests/integration/_files/Magento/TestModuleMessageQueueConfigOverride @@ -25,7 +28,12 @@ class TopologyTest extends \PHPUnit\Framework\TestCase protected function setUp() { - $this->helper = new \Magento\TestFramework\Helper\Amqp(); + $this->helper = Bootstrap::getObjectManager()->create(\Magento\TestFramework\Helper\Amqp::class); + + if (!$this->helper->isAvailable()) { + $this->fail('This test relies on RabbitMQ Management Plugin.'); + } + $this->declaredExchanges = $this->helper->getExchanges(); } @@ -39,6 +47,7 @@ public function testTopologyInstallation(array $expectedConfig, array $bindingCo $name = $expectedConfig['name']; $this->assertArrayHasKey($name, $this->declaredExchanges); unset($this->declaredExchanges[$name]['message_stats']); + unset($this->declaredExchanges[$name]['user_who_performed_action']); $this->assertEquals( $expectedConfig, $this->declaredExchanges[$name], diff --git a/dev/tests/integration/testsuite/Magento/Framework/Search/_files/filterable_attributes.php b/dev/tests/integration/testsuite/Magento/Framework/Search/_files/filterable_attributes.php index b09af48b5f9..f4f3337a253 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Search/_files/filterable_attributes.php +++ b/dev/tests/integration/testsuite/Magento/Framework/Search/_files/filterable_attributes.php @@ -20,6 +20,10 @@ CategorySetup::class, ['resourceName' => 'catalog_setup'] ); +$productEntityTypeId = $installer->getEntityTypeId( + \Magento\Catalog\Api\Data\ProductAttributeInterface::ENTITY_TYPE_CODE +); + $selectOptions = []; $selectAttributes = []; foreach (range(1, 2) as $index) { @@ -30,7 +34,7 @@ $selectAttribute->setData( [ 'attribute_code' => 'select_attribute_' . $index, - 'entity_type_id' => $installer->getEntityTypeId('catalog_product'), + 'entity_type_id' => $productEntityTypeId, 'is_global' => 1, 'is_user_defined' => 1, 'frontend_input' => 'select', @@ -56,7 +60,8 @@ ); $selectAttribute->save(); /* Assign attribute to attribute set */ - $installer->addAttributeToGroup('catalog_product', 'Default', 'General', $selectAttribute->getId()); + $installer->addAttributeToGroup($productEntityTypeId, 'Default', 'General', $selectAttribute->getId()); + /** @var $selectOptions Collection */ $selectOption = Bootstrap::getObjectManager()->create( Collection::class @@ -65,6 +70,26 @@ $selectAttributes[$index] = $selectAttribute; $selectOptions[$index] = $selectOption; } + +$dateAttribute = Bootstrap::getObjectManager()->create(Attribute::class); +$dateAttribute->setData( + [ + 'attribute_code' => 'date_attribute', + 'entity_type_id' => $productEntityTypeId, + 'is_global' => 1, + 'is_filterable' => 1, + 'backend_type' => 'datetime', + 'frontend_input' => 'date', + 'frontend_label' => 'Test Date', + 'is_searchable' => 1, + 'is_filterable_in_search' => 1, + ] +); +$dateAttribute->save(); +/* Assign attribute to attribute set */ +$installer->addAttributeToGroup($productEntityTypeId, 'Default', 'General', $dateAttribute->getId()); + +$productAttributeSetId = $installer->getAttributeSetId($productEntityTypeId, 'Default'); /* Create simple products per each first attribute option */ foreach ($selectOptions[1] as $option) { /** @var $product Product */ @@ -74,7 +99,7 @@ $product->setTypeId( Type::TYPE_SIMPLE )->setAttributeSetId( - $installer->getAttributeSetId('catalog_product', 'Default') + $productAttributeSetId )->setWebsiteIds( [1] )->setName( @@ -92,6 +117,7 @@ )->setStockData( ['use_config_manage_stock' => 1, 'qty' => 5, 'is_in_stock' => 1] )->save(); + Bootstrap::getObjectManager()->get( Action::class )->updateAttributes( @@ -99,6 +125,7 @@ [ $selectAttributes[1]->getAttributeCode() => $option->getId(), $selectAttributes[2]->getAttributeCode() => $selectOptions[2]->getLastItem()->getId(), + $dateAttribute->getAttributeCode() => '10/30/2000', ], $product->getStoreId() ); diff --git a/dev/tests/integration/testsuite/Magento/Framework/Search/_files/filterable_attributes_rollback.php b/dev/tests/integration/testsuite/Magento/Framework/Search/_files/filterable_attributes_rollback.php index 18a5372d06d..fd413726b26 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Search/_files/filterable_attributes_rollback.php +++ b/dev/tests/integration/testsuite/Magento/Framework/Search/_files/filterable_attributes_rollback.php @@ -13,6 +13,7 @@ $registry = Bootstrap::getObjectManager()->get(Registry::class); $registry->unregister('isSecureArea'); $registry->register('isSecureArea', true); + /** @var $productCollection \Magento\Catalog\Model\ResourceModel\Product\Collection */ $productCollection = Bootstrap::getObjectManager() ->create(Product::class) @@ -20,17 +21,26 @@ foreach ($productCollection as $product) { $product->delete(); } + /** @var $attribute Attribute */ $attribute = Bootstrap::getObjectManager()->create( Attribute::class ); /** @var $installer CategorySetup */ $installer = Bootstrap::getObjectManager()->create(CategorySetup::class); +$productEntityTypeId = $installer->getEntityTypeId( + \Magento\Catalog\Api\Data\ProductAttributeInterface::ENTITY_TYPE_CODE +); foreach (range(1, 2) as $index) { - $attribute->loadByCode($installer->getEntityTypeId('catalog_product'), 'select_attribute_' . $index); + $attribute->loadByCode($productEntityTypeId, 'select_attribute_' . $index); if ($attribute->getId()) { $attribute->delete(); } } +$attribute->loadByCode($productEntityTypeId, 'date_attribute'); +if ($attribute->getId()) { + $attribute->delete(); +} + $registry->unregister('isSecureArea'); $registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/GraphQl/Controller/GraphQlControllerTest.php b/dev/tests/integration/testsuite/Magento/GraphQl/Controller/GraphQlControllerTest.php index 16a15cfcd2e..384892d6fd5 100644 --- a/dev/tests/integration/testsuite/Magento/GraphQl/Controller/GraphQlControllerTest.php +++ b/dev/tests/integration/testsuite/Magento/GraphQl/Controller/GraphQlControllerTest.php @@ -116,7 +116,6 @@ public function testDispatch() : void */ public function testError() : void { - $this->markTestSkipped('Causes failiure with php unit and php 7.2'); $query = <<<QUERY { diff --git a/dev/tests/integration/testsuite/Magento/GroupedImportExport/Model/GroupedTest.php b/dev/tests/integration/testsuite/Magento/GroupedImportExport/Model/GroupedTest.php index 67817b068ff..afd515757ae 100644 --- a/dev/tests/integration/testsuite/Magento/GroupedImportExport/Model/GroupedTest.php +++ b/dev/tests/integration/testsuite/Magento/GroupedImportExport/Model/GroupedTest.php @@ -9,7 +9,10 @@ class GroupedTest extends AbstractProductExportImportTestCase { - public function exportImportDataProvider() + /** + * @return array + */ + public function exportImportDataProvider(): array { return [ 'grouped-product' => [ @@ -23,17 +26,13 @@ public function exportImportDataProvider() ]; } - public function importReplaceDataProvider() - { - return $this->exportImportDataProvider(); - } - /** - * @param \Magento\Catalog\Model\Product $expectedProduct - * @param \Magento\Catalog\Model\Product $actualProduct + * @inheritdoc */ - protected function assertEqualsSpecificAttributes($expectedProduct, $actualProduct) - { + protected function assertEqualsSpecificAttributes( + \Magento\Catalog\Model\Product $expectedProduct, + \Magento\Catalog\Model\Product $actualProduct + ): void { $expectedAssociatedProducts = $expectedProduct->getTypeInstance()->getAssociatedProducts($expectedProduct); $actualAssociatedProducts = $actualProduct->getTypeInstance()->getAssociatedProducts($actualProduct); diff --git a/dev/tests/integration/testsuite/Magento/ImportExport/Controller/Adminhtml/Import/ValidateTest.php b/dev/tests/integration/testsuite/Magento/ImportExport/Controller/Adminhtml/Import/ValidateTest.php index 9afce0ed10b..a3cf42b4848 100644 --- a/dev/tests/integration/testsuite/Magento/ImportExport/Controller/Adminhtml/Import/ValidateTest.php +++ b/dev/tests/integration/testsuite/Magento/ImportExport/Controller/Adminhtml/Import/ValidateTest.php @@ -3,9 +3,11 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\ImportExport\Controller\Adminhtml\Import; use Magento\Framework\Filesystem\DirectoryList; +use Magento\Framework\HTTP\Adapter\FileTransferFactory; use Magento\ImportExport\Model\Import; use Magento\ImportExport\Model\Import\ErrorProcessing\ProcessingErrorAggregatorInterface; @@ -25,7 +27,7 @@ class ValidateTest extends \Magento\TestFramework\TestCase\AbstractBackendContro * @magentoDbIsolation enabled * @SuppressWarnings(PHPMD.Superglobals) */ - public function testValidationReturn(string $fileName, string $mimeType, string $message, string $delimiter) + public function testValidationReturn(string $fileName, string $mimeType, string $message, string $delimiter): void { $validationStrategy = ProcessingErrorAggregatorInterface::VALIDATION_STRATEGY_STOP_ON_ERROR; @@ -62,10 +64,7 @@ public function testValidationReturn(string $fileName, string $mimeType, string $this->_objectManager->configure( [ - 'preferences' => [ - \Magento\Framework\HTTP\Adapter\FileTransferFactory::class => - \Magento\ImportExport\Controller\Adminhtml\Import\HttpFactoryMock::class - ] + 'preferences' => [FileTransferFactory::class => HttpFactoryMock::class] ] ); @@ -82,7 +81,7 @@ public function testValidationReturn(string $fileName, string $mimeType, string /** * @return array */ - public function validationDataProvider() + public function validationDataProvider(): array { return [ [ diff --git a/dev/tests/integration/testsuite/Magento/MessageQueue/Model/Cron/ConsumersRunnerTest.php b/dev/tests/integration/testsuite/Magento/MessageQueue/Model/Cron/ConsumersRunnerTest.php index 3fa80a2dcda..2eda32e894d 100644 --- a/dev/tests/integration/testsuite/Magento/MessageQueue/Model/Cron/ConsumersRunnerTest.php +++ b/dev/tests/integration/testsuite/Magento/MessageQueue/Model/Cron/ConsumersRunnerTest.php @@ -109,20 +109,6 @@ protected function setUp() }); } - /** - * Checks that pid files are created - * - * @return void - */ - public function testCheckThatPidFilesWasCreated() - { - $this->markTestSkipped('MC-5904: Test Fails randomly,'); - $this->consumersRunner->run(); - foreach ($this->consumerConfig->getConsumers() as $consumer) { - $this->waitConsumerPidFile($consumer->getName()); - } - } - /** * Tests running of specific consumer and his re-running when it is working * @@ -130,8 +116,6 @@ public function testCheckThatPidFilesWasCreated() */ public function testSpecificConsumerAndRerun() { - $this->markTestSkipped('MC-5904: Test Fails randomly,'); - $specificConsumer = 'quoteItemCleaner'; $pidFilePath = $this->getPidFileName($specificConsumer); $config = $this->config; @@ -188,23 +172,6 @@ public function testCronJobDisabled() } } - /** - * @param string $consumerName - * @return void - */ - private function waitConsumerPidFile($consumerName) - { - $pidFileFullPath = $this->getPidFileFullPath($consumerName); - $i = 0; - do { - sleep(1); - } while (!file_exists($pidFileFullPath) && ($i++ < 60)); - - if (!file_exists($pidFileFullPath)) { - $this->fail($consumerName . ' pid file does not exist.'); - } - } - /** * @return array */ diff --git a/dev/tests/integration/testsuite/Magento/Paypal/Model/Config/Structure/Reader/_files/expected/config.xml b/dev/tests/integration/testsuite/Magento/Paypal/Model/Config/Structure/Reader/_files/expected/config.xml index 2bd346a6e8f..222b9974177 100644 --- a/dev/tests/integration/testsuite/Magento/Paypal/Model/Config/Structure/Reader/_files/expected/config.xml +++ b/dev/tests/integration/testsuite/Magento/Paypal/Model/Config/Structure/Reader/_files/expected/config.xml @@ -347,7 +347,7 @@ <field id="enable_payflow_link"/> </requires> </field> - <field id="enable_express_checkout_bml" extends="payment_all_paypal/express_checkout/express_checkout_required/enable_express_checkout_bml" sortOrder="41"> + <field id="enable_express_checkout_bml" extends="payment_all_paypal/express_checkout/express_checkout_required/enable_express_checkout_bml" sortOrder="41" showInDefault="1" showInWebsite="1"> <comment><![CDATA[Payflow Link lets you give customers access to financing through PayPal Credit® - at no additional cost to you. You get paid up front, even though customers have more time to pay. A pre-integrated payment button lets customers pay quickly with PayPal Credit®. <a href="https://www.paypal.com/webapps/mpp/promotional-financing" target="_blank">Learn More</a>]]> @@ -358,7 +358,7 @@ <field id="enable_express_checkout"/> </requires> </field> - <field id="express_checkout_bml_sort_order" sortOrder="50" extends="payment_all_paypal/express_checkout/express_checkout_required/express_checkout_bml_sort_order"> + <field id="express_checkout_bml_sort_order" sortOrder="50" extends="payment_all_paypal/express_checkout/express_checkout_required/express_checkout_bml_sort_order" showInDefault="1" showInWebsite="1"> <config_path>payment/payflow_express_bml/sort_order</config_path> <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Field\Depends\BmlSortOrder</frontend_model> <depends> @@ -640,6 +640,7 @@ </depends> </field> <field id="api_wizard" translate="button_label attribute sandbox_button_label" sortOrder="70" showInDefault="1" showInWebsite="1"> + <attribute type="button_label">Get Credentials from PayPal</attribute> <attribute type="button_url"> <![CDATA[https://www.paypal.com/webapps/merchantboarding/webflow/externalpartnerflow]]> @@ -727,7 +728,7 @@ </depends> <validate>required-entry</validate> </field> - <field id="enable_express_checkout_bml" translate="label comment" type="select" sortOrder="23" showInDefault="1" showInWebsite="1"> + <field id="enable_express_checkout_bml" translate="label comment" type="select" sortOrder="23" showInDefault="0" showInWebsite="0"> <label>Enable PayPal Credit</label> <comment><![CDATA[PayPal Express Checkout lets you give customers access to financing through PayPal Credit® - at no additional cost to you. You get paid up front, even though customers have more time to pay. A pre-integrated payment button lets customers pay quickly with PayPal Credit®. @@ -740,7 +741,7 @@ <field id="enable_express_checkout"/> </requires> </field> - <field id="express_checkout_bml_sort_order" translate="label" type="text" sortOrder="25" showInDefault="1" showInWebsite="1" showInStore="1"> + <field id="express_checkout_bml_sort_order" translate="label" type="text" sortOrder="25" showInDefault="0" showInWebsite="0" showInStore="0"> <label>Sort Order PayPal Credit</label> <config_path>payment/paypal_express_bml/sort_order</config_path> <frontend_class>validate-number</frontend_class> @@ -1214,6 +1215,262 @@ </tooltip> <attribute type="shared">1</attribute> </field> + <field id="checkout_display" translate="label" sortOrder="80" showInDefault="1" showInWebsite="1" showInStore="1"> + <label>Customize Smart Buttons</label> + <frontend_model>Magento\Config\Block\System\Config\Form\Field\Heading</frontend_model> + <attribute type="shared">1</attribute> + </field> + <group id="checkout_page_button" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="90"> + <label>Checkout Page</label> + <field id="checkout_page_button_customize" translate="label" type="select" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="10"> + <label>Customize Button</label> + <config_path>paypal/style/checkout_page_button_customize</config_path> + <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> + <attribute type="shared">1</attribute> + </field> + <field id="checkout_page_button_label" translate="label comment" type="select" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="20"> + <label>Label</label> + <comment><![CDATA[The installment feature is available only in these locales: en_MX, es_MX, en_BR, pt_BR.]]></comment> + <config_path>paypal/style/checkout_page_button_label</config_path> + <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Field\Depends\ButtonStylesLabel</frontend_model> + <source_model>Magento\Paypal\Model\System\Config\Source\ButtonStyles::getLabel</source_model> + <depends> + <field id="checkout_page_button_customize">1</field> + </depends> + <attribute type="shared">1</attribute> + </field> + <field id="checkout_page_button_mx_installment_period" translate="label" type="select" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="20"> + <label>Mexico Installment Period</label> + <config_path>paypal/style/checkout_page_button_mx_installment_period</config_path> + <source_model>Magento\Paypal\Model\System\Config\Source\ButtonStyles::getMxInstallmentPeriod</source_model> + <depends> + <field id="checkout_page_button_customize">1</field> + <field id="checkout_page_button_label">installment</field> + </depends> + <attribute type="shared">1</attribute> + </field> + <field id="checkout_page_button_br_installment_period" translate="label" type="select" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="20"> + <label>Brazil Installment Period</label> + <config_path>paypal/style/checkout_page_button_br_installment_period</config_path> + <source_model>Magento\Paypal\Model\System\Config\Source\ButtonStyles::getBrInstallmentPeriod</source_model> + <depends> + <field id="checkout_page_button_customize">1</field> + <field id="checkout_page_button_label">installment</field> + </depends> + <attribute type="shared">1</attribute> + </field> + <field id="checkout_page_button_layout" translate="label" type="select" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="30"> + <label>Layout</label> + <config_path>paypal/style/checkout_page_button_layout</config_path> + <source_model>Magento\Paypal\Model\System\Config\Source\ButtonStyles::getLayout</source_model> + <depends> + <field id="checkout_page_button_customize">1</field> + <field id="checkout_page_button_label" negative="1">credit</field> + </depends> + <attribute type="shared">1</attribute> + </field> + <field id="checkout_page_button_size" translate="label tooltip" type="select" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="40"> + <label>Size</label> + <config_path>paypal/style/checkout_page_button_size</config_path> + <source_model>Magento\Paypal\Model\System\Config\Source\ButtonStyles::getSize</source_model> + <tooltip>Select Responsive to ensure the PayPal button renders correctly on mobile devices.</tooltip> + <depends> + <field id="checkout_page_button_customize">1</field> + </depends> + <attribute type="shared">1</attribute> + </field> + <field id="checkout_page_button_shape" translate="label" type="select" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="50"> + <label>Shape</label> + <config_path>paypal/style/checkout_page_button_shape</config_path> + <source_model>Magento\Paypal\Model\System\Config\Source\ButtonStyles::getShape</source_model> + <depends> + <field id="checkout_page_button_customize">1</field> + </depends> + <attribute type="shared">1</attribute> + </field> + <field id="checkout_page_button_color" translate="label" type="select" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="60"> + <label>Color</label> + <config_path>paypal/style/checkout_page_button_color</config_path> + <source_model>Magento\Paypal\Model\System\Config\Source\ButtonStyles::getColor</source_model> + <depends> + <field id="checkout_page_button_customize">1</field> + <field id="checkout_page_button_label" negative="1">credit</field> + </depends> + <attribute type="shared">1</attribute> + </field> + </group> + <group id="product_page_button" translate="label comment" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="100"> + <label>Product Pages</label> + <field id="product_page_button_customize" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_customize"> + <config_path>paypal/style/product_page_button_customize</config_path> + </field> + <field id="product_page_button_label" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_label"> + <config_path>paypal/style/product_page_button_label</config_path> + <depends> + <field id="product_page_button_customize">1</field> + </depends> + </field> + <field id="product_page_button_mx_installment_period" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_mx_installment_period"> + <config_path>paypal/style/product_page_button_mx_installment_period</config_path> + <depends> + <field id="product_page_button_customize">1</field> + <field id="product_page_button_label">installment</field> + </depends> + </field> + <field id="product_page_button_br_installment_period" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_br_installment_period"> + <config_path>paypal/style/product_page_button_br_installment_period</config_path> + <depends> + <field id="product_page_button_customize">1</field> + <field id="product_page_button_label">installment</field> + </depends> + </field> + <field id="product_page_button_layout" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_layout"> + <config_path>paypal/style/product_page_button_layout</config_path> + <depends> + <field id="product_page_button_customize">1</field> + <field id="product_page_button_label" negative="1">credit</field> + </depends> + </field> + <field id="product_page_button_size" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_size"> + <config_path>paypal/style/product_page_button_size</config_path> + <depends> + <field id="product_page_button_customize">1</field> + </depends> + </field> + <field id="product_page_button_shape" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_shape"> + <config_path>paypal/style/product_page_button_shape</config_path> + <depends> + <field id="product_page_button_customize">1</field> + </depends> + </field> + <field id="product_page_button_color" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_color"> + <config_path>paypal/style/product_page_button_color</config_path> + <depends> + <field id="product_page_button_customize">1</field> + <field id="product_page_button_label" negative="1">credit</field> + </depends> + </field> + </group> + <group id="cart_page_button" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="110"> + <label>Cart Page</label> + <field id="cart_page_button_customize" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_customize"> + <config_path>paypal/style/cart_page_button_customize</config_path> + </field> + <field id="cart_page_button_label" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_label"> + <config_path>paypal/style/cart_page_button_label</config_path> + <depends> + <field id="cart_page_button_customize">1</field> + </depends> + </field> + <field id="cart_page_button_mx_installment_period" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_mx_installment_period"> + <config_path>paypal/style/cart_page_button_mx_installment_period</config_path> + <depends> + <field id="cart_page_button_customize">1</field> + <field id="cart_page_button_label">installment</field> + </depends> + </field> + <field id="cart_page_button_br_installment_period" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_br_installment_period"> + <config_path>paypal/style/cart_page_button_br_installment_period</config_path> + <depends> + <field id="cart_page_button_customize">1</field> + <field id="cart_page_button_label">installment</field> + </depends> + </field> + <field id="cart_page_button_layout" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_layout"> + <config_path>paypal/style/cart_page_button_layout</config_path> + <depends> + <field id="cart_page_button_customize">1</field> + <field id="cart_page_button_label" negative="1">credit</field> + </depends> + </field> + <field id="cart_page_button_size" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_size"> + <config_path>paypal/style/cart_page_button_size</config_path> + <depends> + <field id="cart_page_button_customize">1</field> + </depends> + </field> + <field id="cart_page_button_shape" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_shape"> + <config_path>paypal/style/cart_page_button_shape</config_path> + <depends> + <field id="cart_page_button_customize">1</field> + </depends> + </field> + <field id="cart_page_button_color" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_color"> + <config_path>paypal/style/cart_page_button_color</config_path> + <depends> + <field id="cart_page_button_customize">1</field> + <field id="cart_page_button_label" negative="1">credit</field> + </depends> + </field> + </group> + <group id="mini_cart_page_button" translate="label" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="120"> + <label>Mini Cart</label> + <field id="mini_cart_page_button_customize" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_customize"> + <config_path>paypal/style/mini_cart_page_button_customize</config_path> + </field> + <field id="mini_cart_page_button_label" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_label"> + <config_path>paypal/style/mini_cart_page_button_label</config_path> + <depends> + <field id="mini_cart_page_button_customize">1</field> + </depends> + </field> + <field id="mini_cart_page_button_mx_installment_period" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_mx_installment_period"> + <config_path>paypal/style/mini_cart_page_button_mx_installment_period</config_path> + <depends> + <field id="mini_cart_page_button_customize">1</field> + <field id="mini_cart_page_button_label">installment</field> + </depends> + </field> + <field id="mini_cart_page_button_br_installment_period" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_br_installment_period"> + <config_path>paypal/style/mini_cart_page_button_br_installment_period</config_path> + <depends> + <field id="mini_cart_page_button_customize">1</field> + <field id="mini_cart_page_button_label">installment</field> + </depends> + </field> + <field id="mini_cart_page_button_layout" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_layout"> + <config_path>paypal/style/mini_cart_page_button_layout</config_path> + <depends> + <field id="mini_cart_page_button_customize">1</field> + <field id="mini_cart_page_button_label" negative="1">credit</field> + </depends> + </field> + <field id="mini_cart_page_button_size" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_size"> + <config_path>paypal/style/mini_cart_page_button_size</config_path> + <depends> + <field id="mini_cart_page_button_customize">1</field> + </depends> + </field> + <field id="mini_cart_page_button_shape" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_shape"> + <config_path>paypal/style/mini_cart_page_button_shape</config_path> + <depends> + <field id="mini_cart_page_button_customize">1</field> + </depends> + </field> + <field id="mini_cart_page_button_color" extends="payment_all_paypal/express_checkout/settings_ec/settings_ec_advanced/express_checkout_frontend/checkout_page_button/checkout_page_button_color"> + <config_path>paypal/style/mini_cart_page_button_color</config_path> + <depends> + <field id="mini_cart_page_button_customize">1</field> + <field id="mini_cart_page_button_label" negative="1">credit</field> + </depends> + </field> + </group> + <group id="features" translate="label comment" showInDefault="1" showInWebsite="1" showInStore="1" sortOrder="130"> + <label>Features</label> + <field id="disable_funding_options" translate="label comment" type="multiselect" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1"> + <label>Disable Funding Options</label> + <comment> + <![CDATA[PayPal will automatically display each enabled funding option to eligible buyers. + For example, PayPal Credit is only shown to buyers in countries where PayPal Credit is + offered and the currency offered by the merchant is USD.]]> + </comment> + <config_path>paypal/style/disable_funding_options</config_path> + <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\MultiSelect\DisabledFundingOptions</frontend_model> + <source_model>Magento\Paypal\Model\System\Config\Source\DisableFundingOptions</source_model> + <attribute type="shared">1</attribute> + <can_be_empty>1</can_be_empty> + </field> + </group> </group> </group> </group> @@ -1258,7 +1515,7 @@ <frontend_class>paypal-enabler paypal-ec-separate</frontend_class> </field> - <field id="enable_express_checkout_bml" extends="payment_all_paypal/express_checkout/express_checkout_required/enable_express_checkout_bml" sortOrder="21"> + <field id="enable_express_checkout_bml" extends="payment_all_paypal/express_checkout/express_checkout_required/enable_express_checkout_bml" sortOrder="21" showInDefault="1" showInWebsite="1"> <comment><![CDATA[Payments Pro Hosted Solution lets you give customers access to financing through PayPal Credit® - at no additional cost to you. You get paid up front, even though customers have more time to pay. A pre-integrated payment button lets customers pay quickly with PayPal Credit®. <a href="https://www.paypal.com/webapps/mpp/promotional-financing" target="_blank">Learn More</a>]]> @@ -1533,7 +1790,7 @@ <field id="enable_payflow_advanced"/> </requires> </field> - <field id="enable_express_checkout_bml" sortOrder="42" extends="payment_all_paypal/express_checkout/express_checkout_required/enable_express_checkout_bml"> + <field id="enable_express_checkout_bml" sortOrder="42" extends="payment_all_paypal/express_checkout/express_checkout_required/enable_express_checkout_bml" showInDefault="1" showInWebsite="1"> <comment><![CDATA[PayPal Express Checkout Payflow Edition lets you give customers access to financing through PayPal Credit® - at no additional cost to you. You get paid up front, even though customers have more time to pay. A pre-integrated payment button lets customers pay quickly with PayPal Credit®. <a href="https://www.paypal.com/webapps/mpp/promotional-financing" target="_blank">Learn More</a>]]> @@ -1544,7 +1801,7 @@ <field id="enable_payflow_advanced"/> </requires> </field> - <field id="express_checkout_bml_sort_order" sortOrder="50" extends="payment_all_paypal/express_checkout/express_checkout_required/express_checkout_bml_sort_order"> + <field id="express_checkout_bml_sort_order" sortOrder="50" extends="payment_all_paypal/express_checkout/express_checkout_required/express_checkout_bml_sort_order" showInDefault="1" showInWebsite="1"> <config_path>payment/payflow_express_bml/sort_order</config_path> <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Field\Depends\BmlSortOrder</frontend_model> <depends> @@ -1816,6 +2073,8 @@ <field id="enable_paypal_payflow"> <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Field\Enable\Payment</frontend_model> </field> + <field id="enable_in_context_checkout" showInDefault="0" showInWebsite="0"/> + <field id="merchant_id" showInDefault="0" showInWebsite="0"/> <group id="paypal_payflow_api_settings" translate="label"> <label>Payflow Pro and Express Checkout</label> <field id="business_account" extends="payment_all_paypal/express_checkout/express_checkout_required/express_checkout_required_express_checkout/business_account" translate="label" sortOrder="10"> @@ -1830,8 +2089,6 @@ <field id="enable_paypal_payflow"/> </requires> </field> - <field id="enable_in_context_checkout" showInDefault="0" showInWebsite="0"/> - <field id="merchant_id" showInDefault="0" showInWebsite="0"/> <field id="enable_express_checkout_bml_payflow" translate="label" type="select" sortOrder="21" showInWebsite="1" showInDefault="1"> <label>Enable PayPal Credit</label> <comment><![CDATA[PayPal Express Checkout Payflow Edition lets you give customers access to financing through PayPal Credit® - at no additional cost to you. @@ -1845,7 +2102,7 @@ <field id="enable_paypal_payflow"/> </requires> </field> - <field id="express_checkout_bml_sort_order" sortOrder="30" extends="payment_all_paypal/express_checkout/express_checkout_required/express_checkout_bml_sort_order"> + <field id="express_checkout_bml_sort_order" sortOrder="30" extends="payment_all_paypal/express_checkout/express_checkout_required/express_checkout_bml_sort_order" showInDefault="1" showInWebsite="1"> <config_path>payment/payflow_express_bml/sort_order</config_path> <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Field\Depends\BmlSortOrder</frontend_model> <depends> diff --git a/dev/tests/integration/testsuite/Magento/Persistent/Block/Header/AdditionalTest.php b/dev/tests/integration/testsuite/Magento/Persistent/Block/Header/AdditionalTest.php index fba5354b691..2e447e7854a 100644 --- a/dev/tests/integration/testsuite/Magento/Persistent/Block/Header/AdditionalTest.php +++ b/dev/tests/integration/testsuite/Magento/Persistent/Block/Header/AdditionalTest.php @@ -56,8 +56,8 @@ public function testToHtml() $this->_customerSession->loginById(1); $translation = __('Not you?'); - $this->assertStringMatchesFormat( - '%A<span>%A<a%Ahref="' . $this->_block->getHref() . '"%A>' . $translation . '</a>%A</span>%A', + $this->assertContains( + '<a href="' . $this->_block->getHref() . '">' . $translation . '</a>', $this->_block->toHtml() ); $this->_customerSession->logout(); diff --git a/dev/tests/integration/testsuite/Magento/Persistent/Model/ObserverTest.php b/dev/tests/integration/testsuite/Magento/Persistent/Model/ObserverTest.php index ecf2cd77a13..d0c253fc7a6 100644 --- a/dev/tests/integration/testsuite/Magento/Persistent/Model/ObserverTest.php +++ b/dev/tests/integration/testsuite/Magento/Persistent/Model/ObserverTest.php @@ -9,7 +9,6 @@ use Magento\Customer\Model\Context; /** - * @magentoDataFixture Magento/Persistent/_files/persistent.php * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class ObserverTest extends \PHPUnit\Framework\TestCase @@ -29,11 +28,6 @@ class ObserverTest extends \PHPUnit\Framework\TestCase */ protected $customerRepository; - /** - * @var \Magento\Persistent\Helper\Session - */ - protected $_persistentSessionHelper; - /** * @var \Magento\Framework\ObjectManagerInterface */ @@ -44,11 +38,6 @@ class ObserverTest extends \PHPUnit\Framework\TestCase */ protected $_observer; - /** - * @var \Magento\Customer\Model\Session - */ - protected $_customerSession; - /** * @var \Magento\Checkout\Model\Session | \PHPUnit_Framework_MockObject_MockObject */ @@ -58,8 +47,6 @@ public function setUp() { $this->_objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); - $this->_customerSession = $this->_objectManager->get(\Magento\Customer\Model\Session::class); - $this->_customerViewHelper = $this->_objectManager->create( \Magento\Customer\Helper\View::class ); @@ -75,8 +62,6 @@ public function setUp() \Magento\Checkout\Model\Session::class )->disableOriginalConstructor()->setMethods([])->getMock(); - $this->_persistentSessionHelper = $this->_objectManager->create(\Magento\Persistent\Helper\Session::class); - $this->_observer = $this->_objectManager->create( \Magento\Persistent\Model\Observer::class, [ @@ -89,16 +74,11 @@ public function setUp() } /** - * @magentoConfigFixture current_store persistent/options/enabled 1 - * @magentoConfigFixture current_store persistent/options/remember_enabled 1 - * @magentoConfigFixture current_store persistent/options/remember_default 1 * @magentoAppArea frontend * @magentoAppIsolation enabled */ public function testEmulateWelcomeBlock() { - $this->_customerSession->loginById(1); - $httpContext = new \Magento\Framework\App\Http\Context(); $httpContext->setValue(Context::CONTEXT_AUTH, 1, 1); $block = $this->_objectManager->create( @@ -108,15 +88,7 @@ public function testEmulateWelcomeBlock() ] ); $this->_observer->emulateWelcomeBlock($block); - $customerName = $this->_escaper->escapeHtml( - $this->_customerViewHelper->getCustomerName( - $this->customerRepository->getById( - $this->_persistentSessionHelper->getSession()->getCustomerId() - ) - ) - ); - $translation = __('Welcome, %1!', $customerName)->__toString(); - $this->assertStringMatchesFormat('%A' . $translation . '%A', $block->getWelcome()->__toString()); - $this->_customerSession->logout(); + + $this->assertEquals(' ', $block->getWelcome()); } } diff --git a/dev/tests/integration/testsuite/Magento/Sales/Block/Adminhtml/Order/Create/Form/AccountTest.php b/dev/tests/integration/testsuite/Magento/Sales/Block/Adminhtml/Order/Create/Form/AccountTest.php index 999522a49e0..b289e9b9455 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/Block/Adminhtml/Order/Create/Form/AccountTest.php +++ b/dev/tests/integration/testsuite/Magento/Sales/Block/Adminhtml/Order/Create/Form/AccountTest.php @@ -11,26 +11,37 @@ namespace Magento\Sales\Block\Adminhtml\Order\Create\Form; use Magento\Backend\Model\Session\Quote as SessionQuote; +use Magento\Customer\Api\Data\AttributeMetadataInterface; use Magento\Customer\Api\Data\AttributeMetadataInterfaceFactory; +use Magento\Customer\Model\Data\Option; use Magento\Customer\Model\Metadata\Form; use Magento\Customer\Model\Metadata\FormFactory; use Magento\Framework\View\LayoutInterface; use Magento\Quote\Model\Quote; use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\ObjectManager; +use PHPUnit\Framework\MockObject\MockObject; /** * @magentoAppArea adminhtml */ class AccountTest extends \PHPUnit\Framework\TestCase { - /** @var Account */ + /** + * @var Account + */ private $accountBlock; /** - * @var Bootstrap + * @var ObjectManager */ private $objectManager; + /** + * @var SessionQuote|MockObject + */ + private $session; + /** * @magentoDataFixture Magento/Sales/_files/quote.php */ @@ -38,19 +49,23 @@ protected function setUp() { $this->objectManager = Bootstrap::getObjectManager(); $quote = $this->objectManager->create(Quote::class)->load(1); - $sessionQuoteMock = $this->getMockBuilder( - SessionQuote::class - )->disableOriginalConstructor()->setMethods( - ['getCustomerId', 'getStore', 'getStoreId', 'getQuote'] - )->getMock(); - $sessionQuoteMock->expects($this->any())->method('getCustomerId')->will($this->returnValue(1)); - $sessionQuoteMock->expects($this->any())->method('getQuote')->will($this->returnValue($quote)); + + $this->session = $this->getMockBuilder(SessionQuote::class) + ->disableOriginalConstructor() + ->setMethods(['getCustomerId', 'getStore', 'getStoreId', 'getQuote', 'getQuoteId']) + ->getMock(); + $this->session->method('getCustomerId') + ->willReturn(1); + $this->session->method('getQuote') + ->willReturn($quote); + $this->session->method('getQuoteId') + ->willReturn($quote->getId()); /** @var LayoutInterface $layout */ $layout = $this->objectManager->get(LayoutInterface::class); $this->accountBlock = $layout->createBlock( Account::class, 'address_block' . rand(), - ['sessionQuote' => $sessionQuoteMock] + ['sessionQuote' => $this->session] ); parent::setUp(); } @@ -62,13 +77,13 @@ public function testGetForm() { $expectedFields = ['group_id', 'email']; $form = $this->accountBlock->getForm(); - $this->assertEquals(1, $form->getElements()->count(), "Form has invalid number of fieldsets"); + self::assertEquals(1, $form->getElements()->count(), "Form has invalid number of fieldsets"); $fieldset = $form->getElements()[0]; - $this->assertEquals(count($expectedFields), $fieldset->getElements()->count()); + self::assertEquals(count($expectedFields), $fieldset->getElements()->count()); foreach ($fieldset->getElements() as $element) { - $this->assertTrue( + self::assertTrue( in_array($element->getId(), $expectedFields), sprintf('Unexpected field "%s" in form.', $element->getId()) ); @@ -79,6 +94,7 @@ public function testGetForm() * Tests a case when user defined custom attribute has default value. * * @magentoDataFixture Magento/Customer/_files/customer.php + * @magentoConfigFixture current_store customer/create_account/default_group 3 */ public function testGetFormWithUserDefinedAttribute() { @@ -91,18 +107,27 @@ public function testGetFormWithUserDefinedAttribute() $form = $accountBlock->getForm(); $form->setUseContainer(true); + $content = $form->toHtml(); - $this->assertContains( + self::assertContains( '<option value="1" selected="selected">Yes</option>', - $form->toHtml(), - 'Default value for user defined custom attribute should be selected' + $content, + 'Default value for user defined custom attribute should be selected.' + ); + + self::assertContains( + '<option value="3" selected="selected">Customer Group 1</option>', + $content, + 'The Customer Group specified for the chosen store should be selected.' ); } /** - * @return \PHPUnit_Framework_MockObject_MockObject + * Creates a mock for Form object. + * + * @return MockObject */ - private function getFormFactoryMock(): \PHPUnit_Framework_MockObject_MockObject + private function getFormFactoryMock(): MockObject { /** @var AttributeMetadataInterfaceFactory $attributeMetadataFactory */ $attributeMetadataFactory = $this->objectManager->create(AttributeMetadataInterfaceFactory::class); @@ -113,11 +138,12 @@ private function getFormFactoryMock(): \PHPUnit_Framework_MockObject_MockObject ->setDefaultValue('1') ->setFrontendLabel('Yes/No'); + /** @var Form|MockObject $form */ $form = $this->getMockBuilder(Form::class) ->disableOriginalConstructor() ->getMock(); $form->method('getUserAttributes')->willReturn([$booleanAttribute]); - $form->method('getSystemAttributes')->willReturn([]); + $form->method('getSystemAttributes')->willReturn([$this->createCustomerGroupAttribute()]); $formFactory = $this->getMockBuilder(FormFactory::class) ->disableOriginalConstructor() @@ -126,4 +152,33 @@ private function getFormFactoryMock(): \PHPUnit_Framework_MockObject_MockObject return $formFactory; } + + /** + * Creates a customer group attribute object. + * + * @return AttributeMetadataInterface + */ + private function createCustomerGroupAttribute(): AttributeMetadataInterface + { + /** @var Option $option1 */ + $option1 = $this->objectManager->create(Option::class); + $option1->setValue(3); + $option1->setLabel('Customer Group 1'); + + /** @var Option $option2 */ + $option2 = $this->objectManager->create(Option::class); + $option2->setValue(4); + $option2->setLabel('Customer Group 2'); + + /** @var AttributeMetadataInterfaceFactory $attributeMetadataFactory */ + $attributeMetadataFactory = $this->objectManager->create(AttributeMetadataInterfaceFactory::class); + $attribute = $attributeMetadataFactory->create() + ->setAttributeCode('group_id') + ->setBackendType('static') + ->setFrontendInput('select') + ->setOptions([$option1, $option2]) + ->setIsRequired(true); + + return $attribute; + } } diff --git a/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/Create/SaveTest.php b/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/Create/SaveTest.php index e2638b5df1f..f863edd0492 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/Create/SaveTest.php +++ b/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/Create/SaveTest.php @@ -9,21 +9,61 @@ use Magento\Customer\Api\CustomerRepositoryInterface; use Magento\Framework\Api\SearchCriteriaBuilder; use Magento\Framework\App\Request\Http; +use Magento\Framework\Data\Form\FormKey; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Message\MessageInterface; use Magento\Quote\Api\CartRepositoryInterface; +use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Model\OrderRepository; use Magento\Sales\Model\Service\OrderService; +use Magento\TestFramework\Mail\Template\TransportBuilderMock; use Magento\TestFramework\TestCase\AbstractBackendController; +use PHPUnit\Framework\Constraint\StringContains; use PHPUnit_Framework_MockObject_MockObject as MockObject; +/** + * Class test backend order save. + * + * @magentoAppArea adminhtml + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class SaveTest extends AbstractBackendController { + /** + * @var TransportBuilderMock + */ + private $transportBuilder; + + /** + * @var FormKey + */ + private $formKey; + + /** + * @var string + */ + protected $resource = 'Magento_Sales::create'; + + /** + * @var string + */ + protected $uri = 'backend/sales/order_create/save'; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + $this->transportBuilder = $this->_objectManager->get(TransportBuilderMock::class); + $this->formKey = $this->_objectManager->get(FormKey::class); + } + /** * Checks a case when order creation is failed on payment method processing but new customer already created * in the database and after new controller dispatching the customer should be already loaded in session * to prevent invalid validation. * - * @magentoAppArea adminhtml * @magentoDataFixture Magento/Sales/_files/quote_with_new_customer.php */ public function testExecuteWithPaymentOperation() @@ -36,7 +76,7 @@ public function testExecuteWithPaymentOperation() $email = 'john.doe001@test.com'; $data = [ 'account' => [ - 'email' => $email + 'email' => $email, ] ]; $this->getRequest()->setMethod(Http::METHOD_POST); @@ -66,13 +106,52 @@ public function testExecuteWithPaymentOperation() $this->_objectManager->removeSharedInstance(OrderService::class); } + /** + * @magentoDbIsolation enabled + * @magentoDataFixture Magento/Sales/_files/guest_quote_with_addresses.php + * + * @return void + */ + public function testSendEmailOnOrderSave(): void + { + $this->prepareRequest(['send_confirmation' => true]); + $this->dispatch('backend/sales/order_create/save'); + $this->assertSessionMessages( + $this->equalTo([(string)__('You created the order.')]), + MessageInterface::TYPE_SUCCESS + ); + + $this->assertRedirect($this->stringContains('sales/order/view/')); + + $orderId = $this->getOrderId(); + if ($orderId === false) { + $this->fail('Order is not created.'); + } + $order = $this->getOrder($orderId); + + $message = $this->transportBuilder->getSentMessage(); + $subject = __('Your %1 order confirmation', $order->getStore()->getFrontendName())->render(); + $assert = $this->logicalAnd( + new StringContains($order->getBillingAddress()->getName()), + new StringContains( + 'Thank you for your order from ' . $order->getStore()->getFrontendName() + ), + new StringContains( + "Your Order <span class=\"no-link\">#{$order->getIncrementId()}</span>" + ) + ); + + $this->assertEquals($message->getSubject(), $subject); + $this->assertThat($message->getRawMessage(), $assert); + } + /** * Gets quote by reserved order id. * * @param string $reservedOrderId * @return \Magento\Quote\Api\Data\CartInterface */ - private function getQuote($reservedOrderId) + private function getQuote(string $reservedOrderId): \Magento\Quote\Api\Data\CartInterface { /** @var SearchCriteriaBuilder $searchCriteriaBuilder */ $searchCriteriaBuilder = $this->_objectManager->get(SearchCriteriaBuilder::class); @@ -82,6 +161,81 @@ private function getQuote($reservedOrderId) /** @var CartRepositoryInterface $quoteRepository */ $quoteRepository = $this->_objectManager->get(CartRepositoryInterface::class); $items = $quoteRepository->getList($searchCriteria)->getItems(); + return array_pop($items); } + + /** + * @inheritdoc + * @magentoDbIsolation enabled + * @magentoDataFixture Magento/Sales/_files/guest_quote_with_addresses.php + */ + public function testAclHasAccess() + { + $this->prepareRequest(); + + parent::testAclHasAccess(); + } + + /** + * @inheritdoc + * @magentoDbIsolation enabled + * @magentoDataFixture Magento/Sales/_files/guest_quote_with_addresses.php + */ + public function testAclNoAccess() + { + $this->prepareRequest(); + + parent::testAclNoAccess(); + } + + /** + * @param int $orderId + * @return OrderInterface + */ + private function getOrder(int $orderId): OrderInterface + { + return $this->_objectManager->get(OrderRepository::class)->get($orderId); + } + + /** + * @param array $params + * @return void + */ + private function prepareRequest(array $params = []): void + { + $quote = $this->getQuote('guest_quote'); + $session = $this->_objectManager->get(Quote::class); + $session->setQuoteId($quote->getId()); + $session->setCustomerId(0); + + $email = 'john.doe001@test.com'; + $data = [ + 'account' => [ + 'email' => $email, + ], + ]; + + $data = array_replace_recursive($data, $params); + + $this->getRequest() + ->setMethod('POST') + ->setParams(['form_key' => $this->formKey->getFormKey()]) + ->setPostValue(['order' => $data]); + } + + /** + * @return string|bool + */ + protected function getOrderId() + { + $currentUrl = $this->getResponse()->getHeader('Location'); + $orderId = false; + + if (preg_match('/order_id\/(?<order_id>\d+)/', $currentUrl, $matches)) { + $orderId = $matches['order_id'] ?? ''; + } + + return $orderId; + } } diff --git a/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/Creditmemo/AbstractCreditmemoControllerTest.php b/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/Creditmemo/AbstractCreditmemoControllerTest.php new file mode 100644 index 00000000000..2a773171502 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/Creditmemo/AbstractCreditmemoControllerTest.php @@ -0,0 +1,92 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Sales\Controller\Adminhtml\Order\Creditmemo; + +use Magento\Framework\Api\SearchCriteria; +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Framework\Data\Form\FormKey; +use Magento\Sales\Api\Data\CreditmemoInterface; +use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Model\OrderRepository; +use Magento\TestFramework\Mail\Template\TransportBuilderMock; +use Magento\TestFramework\TestCase\AbstractBackendController; + +/** + * Abstract backend creditmemo test. + */ +class AbstractCreditmemoControllerTest extends AbstractBackendController +{ + /** + * @var TransportBuilderMock + */ + protected $transportBuilder; + + /** + * @var OrderRepository + */ + protected $orderRepository; + + /** + * @var FormKey + */ + protected $formKey; + + /** + * @var string + */ + protected $resource = 'Magento_Sales::sales_creditmemo'; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + $this->transportBuilder = $this->_objectManager->get(TransportBuilderMock::class); + $this->orderRepository = $this->_objectManager->get(OrderRepository::class); + $this->formKey = $this->_objectManager->get(FormKey::class); + } + + /** + * @param string $incrementalId + * @return OrderInterface|null + */ + protected function getOrder(string $incrementalId) + { + /** @var SearchCriteria $searchCriteria */ + $searchCriteria = $this->_objectManager->create(SearchCriteriaBuilder::class) + ->addFilter(OrderInterface::INCREMENT_ID, $incrementalId) + ->create(); + + $orders = $this->orderRepository->getList($searchCriteria)->getItems(); + /** @var OrderInterface|null $order */ + $order = reset($orders); + + return $order; + } + + /** + * @param OrderInterface $order + * @return CreditmemoInterface + */ + protected function getCreditMemo(OrderInterface $order): CreditmemoInterface + { + /** @var \Magento\Sales\Model\ResourceModel\Order\Creditmemo\Collection $creditMemoCollection */ + $creditMemoCollection = $this->_objectManager->create( + \Magento\Sales\Model\ResourceModel\Order\Creditmemo\CollectionFactory::class + )->create(); + + /** @var CreditmemoInterface $creditMemo */ + $creditMemo = $creditMemoCollection + ->setOrderFilter($order) + ->setPageSize(1) + ->getFirstItem(); + + return $creditMemo; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/Creditmemo/AddCommentTest.php b/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/Creditmemo/AddCommentTest.php new file mode 100644 index 00000000000..2f23da8b3db --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/Creditmemo/AddCommentTest.php @@ -0,0 +1,102 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Sales\Controller\Adminhtml\Order\Creditmemo; + +use PHPUnit\Framework\Constraint\RegularExpression; +use PHPUnit\Framework\Constraint\StringContains; + +/** + * Class verifies creditmemo add comment functionality. + * + * @magentoDbIsolation enabled + * @magentoAppArea adminhtml + * @magentoDataFixture Magento/Sales/_files/creditmemo_for_get.php + */ +class AddCommentTest extends AbstractCreditmemoControllerTest +{ + /** + * @var string + */ + protected $uri = 'backend/sales/order_creditmemo/addComment'; + + /** + * @return void + */ + public function testSendEmailOnAddCreditmemoComment(): void + { + $comment = 'Test Credit Memo Comment'; + $order = $this->prepareRequest( + [ + 'comment' => ['comment' => $comment, 'is_customer_notified' => true], + ] + ); + $this->dispatch('backend/sales/order_creditmemo/addComment'); + $html = $this->getResponse()->getBody(); + $this->assertContains($comment, $html); + + $message = $this->transportBuilder->getSentMessage(); + $subject =__('Update to your %1 credit memo', $order->getStore()->getFrontendName())->render(); + $messageConstraint = $this->logicalAnd( + new StringContains($order->getBillingAddress()->getName()), + new RegularExpression( + sprintf( + "/Your order #%s has been updated with a status of.*%s/", + $order->getIncrementId(), + $order->getFrontendStatusLabel() + ) + ), + new StringContains($comment) + ); + + $this->assertEquals($message->getSubject(), $subject); + $this->assertThat($message->getRawMessage(), $messageConstraint); + } + + /** + * @inheritdoc + */ + public function testAclHasAccess() + { + $this->prepareRequest(['comment' => ['comment' => 'Comment']]); + + parent::testAclHasAccess(); + } + + /** + * @inheritdoc + */ + public function testAclNoAccess() + { + $this->prepareRequest(['comment' => ['comment' => 'Comment']]); + + parent::testAclNoAccess(); + } + + /** + * @param array $params + * @return \Magento\Sales\Api\Data\OrderInterface|null + */ + private function prepareRequest(array $params = []) + { + $order = $this->getOrder('100000001'); + $creditmemo = $this->getCreditMemo($order); + + $this->getRequest()->setMethod('POST'); + $this->getRequest()->setParams( + [ + 'id' => $creditmemo->getEntityId(), + 'form_key' => $this->formKey->getFormKey(), + ] + ); + + $data = $params ?? []; + $this->getRequest()->setPostValue($data); + + return $order; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/Creditmemo/SaveTest.php b/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/Creditmemo/SaveTest.php new file mode 100644 index 00000000000..fa5da2e0e50 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/Creditmemo/SaveTest.php @@ -0,0 +1,99 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Sales\Controller\Adminhtml\Order\Creditmemo; + +use PHPUnit\Framework\Constraint\StringContains; + +/** + * Class tests creditmemo creation in backend. + * + * @magentoDbIsolation enabled + * @magentoAppArea adminhtml + * @magentoDataFixture Magento/Sales/_files/invoice.php + */ +class SaveTest extends AbstractCreditmemoControllerTest +{ + /** + * @var string + */ + protected $uri = 'backend/sales/order_creditmemo/save'; + + /** + * @return void + */ + public function testSendEmailOnCreditmemoSave(): void + { + $order = $this->prepareRequest(['creditmemo' => ['send_email' => true]]); + $this->dispatch('backend/sales/order_creditmemo/save'); + + $this->assertSessionMessages( + $this->equalTo([(string)__('You created the credit memo.')]), + \Magento\Framework\Message\MessageInterface::TYPE_SUCCESS + ); + $this->assertRedirect($this->stringContains('sales/order/view/order_id/' . $order->getEntityId())); + + $creditMemo = $this->getCreditMemo($order); + $message = $this->transportBuilder->getSentMessage(); + $subject = __('Credit memo for your %1 order', $order->getStore()->getFrontendName())->render(); + $messageConstraint = $this->logicalAnd( + new StringContains($order->getBillingAddress()->getName()), + new StringContains( + 'Thank you for your order from ' . $creditMemo->getStore()->getFrontendName() + ), + new StringContains( + "Your Credit Memo #{$creditMemo->getIncrementId()} for Order #{$order->getIncrementId()}" + ) + ); + + $this->assertEquals($message->getSubject(), $subject); + $this->assertThat($message->getRawMessage(), $messageConstraint); + } + + /** + * @inheritdoc + */ + public function testAclHasAccess() + { + $this->prepareRequest(); + + parent::testAclHasAccess(); + } + + /** + * @inheritdoc + */ + public function testAclNoAccess() + { + $this->prepareRequest(); + + parent::testAclNoAccess(); + } + + /** + * @param array $params + * @return \Magento\Sales\Api\Data\OrderInterface|null + */ + private function prepareRequest(array $params = []) + { + $order = $this->getOrder('100000001'); + $this->getRequest()->setMethod('POST'); + $this->getRequest()->setParams( + [ + 'order_id' => $order->getEntityId(), + 'form_key' => $this->formKey->getFormKey(), + ] + ); + + $data = ['creditmemo' => ['do_offline' => true]]; + $data = array_replace_recursive($data, $params); + + $this->getRequest()->setPostValue($data); + + return $order; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/EmailTest.php b/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/EmailTest.php new file mode 100644 index 00000000000..4d19106ad8e --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/EmailTest.php @@ -0,0 +1,136 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Sales\Controller\Adminhtml\Order; + +use Magento\Framework\Api\SearchCriteria; +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Model\OrderRepository; +use Magento\TestFramework\Mail\Template\TransportBuilderMock; +use PHPUnit\Framework\Constraint\StringContains; + +/** + * Class verifies order send email functionality. + * + * @magentoDbIsolation enabled + * @magentoAppArea adminhtml + * @magentoDataFixture Magento/Sales/_files/order.php + */ +class EmailTest extends \Magento\TestFramework\TestCase\AbstractBackendController +{ + /** + * @var OrderRepository + */ + private $orderRepository; + + /** + * @var TransportBuilderMock + */ + private $transportBuilder; + + /** + * @var string + */ + protected $resource = 'Magento_Sales::email'; + + /** + * @var string + */ + protected $uri = 'backend/sales/order/email'; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + $this->orderRepository = $this->_objectManager->get(OrderRepository::class); + $this->transportBuilder = $this->_objectManager->get(TransportBuilderMock::class); + } + + /** + * @return void + */ + public function testSendOrderEmail(): void + { + $order = $this->prepareRequest(); + $this->dispatch('backend/sales/order/email'); + + $this->assertSessionMessages( + $this->equalTo([(string)__('You sent the order email.')]), + \Magento\Framework\Message\MessageInterface::TYPE_SUCCESS + ); + + $redirectUrl = 'sales/order/view/order_id/' . $order->getEntityId(); + $this->assertRedirect($this->stringContains($redirectUrl)); + + $message = $this->transportBuilder->getSentMessage(); + $subject = __('Your %1 order confirmation', $order->getStore()->getFrontendName())->render(); + $assert = $this->logicalAnd( + new StringContains($order->getBillingAddress()->getName()), + new StringContains( + 'Thank you for your order from ' . $order->getStore()->getFrontendName() + ), + new StringContains( + "Your Order <span class=\"no-link\">#{$order->getIncrementId()}</span>" + ) + ); + + $this->assertEquals($message->getSubject(), $subject); + $this->assertThat($message->getRawMessage(), $assert); + } + + /** + * @inheritdoc + */ + public function testAclHasAccess() + { + $this->prepareRequest(); + + parent::testAclHasAccess(); + } + + /** + * @inheritdoc + */ + public function testAclNoAccess() + { + $this->prepareRequest(); + + parent::testAclNoAccess(); + } + + /** + * @param string $incrementalId + * @return OrderInterface|null + */ + private function getOrder(string $incrementalId) + { + /** @var SearchCriteria $searchCriteria */ + $searchCriteria = $this->_objectManager->create(SearchCriteriaBuilder::class) + ->addFilter(OrderInterface::INCREMENT_ID, $incrementalId) + ->create(); + + $orders = $this->orderRepository->getList($searchCriteria)->getItems(); + /** @var OrderInterface|null $order */ + $order = reset($orders); + + return $order; + } + + /** + * @return OrderInterface|null + */ + private function prepareRequest() + { + $order = $this->getOrder('100000001'); + $this->getRequest()->setParams(['order_id' => $order->getEntityId()]); + + return $order; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/Invoice/AbstractInvoiceControllerTest.php b/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/Invoice/AbstractInvoiceControllerTest.php new file mode 100644 index 00000000000..3ba54418b6c --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/Invoice/AbstractInvoiceControllerTest.php @@ -0,0 +1,92 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Sales\Controller\Adminhtml\Order\Invoice; + +use Magento\Framework\Api\SearchCriteria; +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Framework\Data\Form\FormKey; +use Magento\Sales\Api\Data\InvoiceInterface; +use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Model\OrderRepository; +use Magento\TestFramework\Mail\Template\TransportBuilderMock; +use Magento\TestFramework\TestCase\AbstractBackendController; + +/** + * Abstract backend invoice test. + */ +class AbstractInvoiceControllerTest extends AbstractBackendController +{ + /** + * @var TransportBuilderMock + */ + protected $transportBuilder; + + /** + * @var OrderRepository + */ + protected $orderRepository; + + /** + * @var FormKey + */ + protected $formKey; + + /** + * @var string + */ + protected $resource = 'Magento_Sales::sales_invoice'; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + $this->transportBuilder = $this->_objectManager->get(TransportBuilderMock::class); + $this->orderRepository = $this->_objectManager->get(OrderRepository::class); + $this->formKey = $this->_objectManager->get(FormKey::class); + } + + /** + * @param string $incrementalId + * @return OrderInterface|null + */ + protected function getOrder(string $incrementalId) + { + /** @var SearchCriteria $searchCriteria */ + $searchCriteria = $this->_objectManager->create(SearchCriteriaBuilder::class) + ->addFilter(OrderInterface::INCREMENT_ID, $incrementalId) + ->create(); + + $orders = $this->orderRepository->getList($searchCriteria)->getItems(); + /** @var OrderInterface $order */ + $order = reset($orders); + + return $order; + } + + /** + * @param OrderInterface $order + * @return InvoiceInterface + */ + protected function getInvoiceByOrder(OrderInterface $order): InvoiceInterface + { + /** @var \Magento\Sales\Model\ResourceModel\Order\Invoice\Collection $invoiceCollection */ + $invoiceCollection = $this->_objectManager->create( + \Magento\Sales\Model\ResourceModel\Order\Invoice\CollectionFactory::class + )->create(); + + /** @var InvoiceInterface $invoice */ + $invoice = $invoiceCollection + ->setOrderFilter($order) + ->setPageSize(1) + ->getFirstItem(); + + return $invoice; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/Invoice/AddCommentTest.php b/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/Invoice/AddCommentTest.php new file mode 100644 index 00000000000..81e1dd7afc4 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/Invoice/AddCommentTest.php @@ -0,0 +1,103 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Sales\Controller\Adminhtml\Order\Invoice; + +use PHPUnit\Framework\Constraint\RegularExpression; +use PHPUnit\Framework\Constraint\StringContains; + +/** + * Class verifies invoice add comment functionality. + * + * @magentoDbIsolation enabled + * @magentoAppArea adminhtml + * @magentoDataFixture Magento/Sales/_files/invoice.php + */ +class AddCommentTest extends AbstractInvoiceControllerTest +{ + /** + * @var string + */ + protected $uri = 'backend/sales/order_invoice/addComment'; + + /** + * @return void + */ + public function testSendEmailOnAddInvoiceComment(): void + { + $comment = 'Test Invoice Comment'; + $order = $this->prepareRequest( + [ + 'comment' => ['comment' => $comment, 'is_customer_notified' => true], + ] + ); + $this->dispatch('backend/sales/order_invoice/addComment'); + + $html = $this->getResponse()->getBody(); + $this->assertContains($comment, $html); + + $message = $this->transportBuilder->getSentMessage(); + $subject = __('Update to your %1 invoice', $order->getStore()->getFrontendName())->render(); + $messageConstraint = $this->logicalAnd( + new StringContains($order->getBillingAddress()->getName()), + new RegularExpression( + sprintf( + "/Your order #%s has been updated with a status of.*%s/", + $order->getIncrementId(), + $order->getFrontendStatusLabel() + ) + ), + new StringContains($comment) + ); + + $this->assertEquals($message->getSubject(), $subject); + $this->assertThat($message->getRawMessage(), $messageConstraint); + } + + /** + * @inheritdoc + */ + public function testAclHasAccess() + { + $this->prepareRequest(['comment' => ['comment' => 'Comment']]); + + parent::testAclHasAccess(); + } + + /** + * @inheritdoc + */ + public function testAclNoAccess() + { + $this->prepareRequest(['comment' => ['comment' => 'Comment']]); + + parent::testAclNoAccess(); + } + + /** + * @param array $params + * @return \Magento\Sales\Api\Data\OrderInterface|null + */ + private function prepareRequest(array $params = []) + { + $order = $this->getOrder('100000001'); + $invoice = $this->getInvoiceByOrder($order); + + $this->getRequest()->setMethod('POST'); + $this->getRequest()->setParams( + [ + 'id' => $invoice->getEntityId(), + 'form_key' => $this->formKey->getFormKey(), + ] + ); + + $data = $params ?? []; + $this->getRequest()->setPostValue($data); + + return $order; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/Invoice/EmailTest.php b/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/Invoice/EmailTest.php new file mode 100644 index 00000000000..85223528ec8 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/Invoice/EmailTest.php @@ -0,0 +1,88 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Sales\Controller\Adminhtml\Order\Invoice; + +use PHPUnit\Framework\Constraint\StringContains; + +/** + * Class verifies invoice send email functionality. + * + * @magentoDbIsolation enabled + * @magentoAppArea adminhtml + * @magentoDataFixture Magento/Sales/_files/invoice.php + */ +class EmailTest extends AbstractInvoiceControllerTest +{ + /** + * @var string + */ + protected $uri = 'backend/sales/order_invoice/email'; + + /** + * @return void + */ + public function testSendInvoiceEmail(): void + { + $order = $this->getOrder('100000001'); + $invoice = $this->getInvoiceByOrder($order); + + $this->getRequest()->setParams(['invoice_id' => $invoice->getEntityId()]); + $this->dispatch('backend/sales/order_invoice/email'); + + $this->assertSessionMessages( + $this->equalTo([(string)__('You sent the message.')]), + \Magento\Framework\Message\MessageInterface::TYPE_SUCCESS + ); + + $redirectUrl = sprintf( + 'sales/invoice/view/order_id/%s/invoice_id/%s', + $order->getEntityId(), + $invoice->getEntityId() + ); + $this->assertRedirect($this->stringContains($redirectUrl)); + + $message = $this->transportBuilder->getSentMessage(); + $subject = __('Invoice for your %1 order', $order->getStore()->getFrontendName())->render(); + $messageConstraint = $this->logicalAnd( + new StringContains($invoice->getBillingAddress()->getName()), + new StringContains( + 'Thank you for your order from ' . $invoice->getStore()->getFrontendName() + ), + new StringContains( + "Your Invoice #{$invoice->getIncrementId()} for Order #{$order->getIncrementId()}" + ) + ); + + $this->assertEquals($message->getSubject(), $subject); + $this->assertThat($message->getRawMessage(), $messageConstraint); + } + + /** + * @inheritdoc + */ + public function testAclHasAccess() + { + $order = $this->getOrder('100000001'); + $invoice = $this->getInvoiceByOrder($order); + $this->uri .= '/invoice_id/' . $invoice->getEntityId(); + + parent::testAclHasAccess(); + } + + /** + * @inheritdoc + */ + public function testAclNoAccess() + { + $order = $this->getOrder('100000001'); + $invoice = $this->getInvoiceByOrder($order); + $this->uri .= '/invoice_id/' . $invoice->getEntityId(); + + parent::testAclNoAccess(); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/Invoice/SaveTest.php b/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/Invoice/SaveTest.php new file mode 100644 index 00000000000..68074e38d9a --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/Invoice/SaveTest.php @@ -0,0 +1,97 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Sales\Controller\Adminhtml\Order\Invoice; + +use PHPUnit\Framework\Constraint\StringContains; + +/** + * Class tests invoice creation in backend. + * + * @magentoDbIsolation enabled + * @magentoAppArea adminhtml + * @magentoDataFixture Magento/Sales/_files/order.php + */ +class SaveTest extends AbstractInvoiceControllerTest +{ + /** + * @var string + */ + protected $uri = 'backend/sales/order_invoice/save'; + + /** + * @return void + */ + public function testSendEmailOnInvoiceSave(): void + { + $order = $this->prepareRequest(['invoice' => ['send_email' => true]]); + $this->dispatch('backend/sales/order_invoice/save'); + + $this->assertSessionMessages( + $this->equalTo([(string)__('The invoice has been created.')]), + \Magento\Framework\Message\MessageInterface::TYPE_SUCCESS + ); + $this->assertRedirect($this->stringContains('sales/order/view/order_id/' . $order->getEntityId())); + + $invoice = $this->getInvoiceByOrder($order); + $message = $this->transportBuilder->getSentMessage(); + $subject = __('Invoice for your %1 order', $order->getStore()->getFrontendName())->render(); + $messageConstraint = $this->logicalAnd( + new StringContains($invoice->getBillingAddress()->getName()), + new StringContains( + 'Thank you for your order from ' . $invoice->getStore()->getFrontendName() + ), + new StringContains( + "Your Invoice #{$invoice->getIncrementId()} for Order #{$order->getIncrementId()}" + ) + ); + + $this->assertEquals($message->getSubject(), $subject); + $this->assertThat($message->getRawMessage(), $messageConstraint); + } + + /** + * @inheritdoc + */ + public function testAclHasAccess() + { + $this->prepareRequest(); + + parent::testAclHasAccess(); + } + + /** + * @inheritdoc + */ + public function testAclNoAccess() + { + $this->prepareRequest(); + + parent::testAclNoAccess(); + } + + /** + * @param array $params + * @return \Magento\Sales\Api\Data\OrderInterface|null + */ + private function prepareRequest(array $params = []) + { + $order = $this->getOrder('100000001'); + $this->getRequest()->setMethod('POST'); + $this->getRequest()->setParams( + [ + 'order_id' => $order->getEntityId(), + 'form_key' => $this->formKey->getFormKey(), + ] + ); + + $data = $params ?? []; + $this->getRequest()->setPostValue($data); + + return $order; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Sales/Model/Order/CreateTest.php b/dev/tests/integration/testsuite/Magento/Sales/Model/Order/CreateTest.php new file mode 100644 index 00000000000..1035ce15923 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/Model/Order/CreateTest.php @@ -0,0 +1,100 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Sales\Model\Order; + +use Magento\Checkout\Model\Session as CheckoutSession; +use Magento\Framework\Data\Form\FormKey; +use Magento\Framework\ObjectManagerInterface; +use Magento\Quote\Api\GuestCartManagementInterface; +use Magento\Quote\Model\Quote; +use Magento\Quote\Model\QuoteIdMask; +use Magento\Quote\Model\QuoteIdMaskFactory; +use Magento\Sales\Model\OrderRepository; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Mail\Template\TransportBuilderMock; +use PHPUnit\Framework\Constraint\StringContains; + +/** + * Class verifies order creation. + * + * @magentoDbIsolation enabled + * @magentoAppArea frontend + */ +class CreateTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var ObjectManagerInterface + */ + private $objectManager; + + /** + * @var TransportBuilderMock + */ + private $transportBuilder; + + /** + * @var QuoteIdMaskFactory + */ + private $quoteIdMaskFactory; + + /** + * @var FormKey + */ + private $formKey; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + $this->objectManager = Bootstrap::getObjectManager(); + $this->transportBuilder = $this->objectManager->get(TransportBuilderMock::class); + $this->quoteIdMaskFactory = $this->objectManager->get(QuoteIdMaskFactory::class); + $this->formKey = $this->objectManager->get(FormKey::class); + } + + /** + * @magentoDataFixture Magento/Sales/_files/guest_quote_with_addresses.php + * @return void + */ + public function testSendEmailOnOrderPlace(): void + { + /** @var Quote $quote */ + $quote = $this->objectManager->create(Quote::class); + $quote->load('guest_quote', 'reserved_order_id'); + + $checkoutSession = $this->objectManager->get(CheckoutSession::class); + $checkoutSession->setQuoteId($quote->getId()); + + /** @var QuoteIdMask $quoteIdMask */ + $quoteIdMask = $this->quoteIdMaskFactory->create(); + $quoteIdMask->load($quote->getId(), 'quote_id'); + $cartId = $quoteIdMask->getMaskedId(); + + /** @var GuestCartManagementInterface $cartManagement */ + $cartManagement = $this->objectManager->get(GuestCartManagementInterface::class); + $orderId = $cartManagement->placeOrder($cartId); + $order = $this->objectManager->get(OrderRepository::class)->get($orderId); + + $message = $this->transportBuilder->getSentMessage(); + $subject = __('Your %1 order confirmation', $order->getStore()->getFrontendName())->render(); + $assert = $this->logicalAnd( + new StringContains($order->getBillingAddress()->getName()), + new StringContains( + 'Thank you for your order from ' . $order->getStore()->getFrontendName() + ), + new StringContains( + "Your Order <span class=\"no-link\">#{$order->getIncrementId()}</span>" + ) + ); + + $this->assertEquals($message->getSubject(), $subject); + $this->assertThat($message->getRawMessage(), $assert); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/guest_quote_with_addresses.php b/dev/tests/integration/testsuite/Magento/Sales/_files/guest_quote_with_addresses.php new file mode 100644 index 00000000000..b8f2ca38e24 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/guest_quote_with_addresses.php @@ -0,0 +1,68 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +require __DIR__ . '/address_list.php'; + +\Magento\TestFramework\Helper\Bootstrap::getInstance()->loadArea(\Magento\Framework\App\Area::AREA_FRONTEND); + +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); +/** @var \Magento\Catalog\Model\Product $product */ +$product = $objectManager->create(\Magento\Catalog\Model\Product::class); +$product->setTypeId('simple') + ->setAttributeSetId($product->getDefaultAttributeSetId()) + ->setName('Simple Product') + ->setSku('simple-product-guest-quote') + ->setPrice(10) + ->setTaxClassId(0) + ->setMetaTitle('meta title') + ->setMetaKeyword('meta keyword') + ->setMetaDescription('meta description') + ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH) + ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) + ->setStockData( + [ + 'qty' => 100, + 'is_in_stock' => 1, + ] + )->save(); + +$productRepository = $objectManager->create(\Magento\Catalog\Api\ProductRepositoryInterface::class); +$product = $productRepository->get('simple-product-guest-quote'); + +$addressData = reset($addresses); + +$billingAddress = $objectManager->create( + \Magento\Quote\Model\Quote\Address::class, + ['data' => $addressData] +); +$billingAddress->setAddressType('billing'); + +$shippingAddress = clone $billingAddress; +$shippingAddress->setId(null)->setAddressType('shipping'); + +$store = $objectManager->get(\Magento\Store\Model\StoreManagerInterface::class)->getStore(); + +/** @var \Magento\Quote\Model\Quote $quote */ +$quote = $objectManager->create(\Magento\Quote\Model\Quote::class); +$quote->setCustomerIsGuest(true) + ->setStoreId($store->getId()) + ->setReservedOrderId('guest_quote') + ->setBillingAddress($billingAddress) + ->setShippingAddress($shippingAddress) + ->addProduct($product); +$quote->getPayment()->setMethod('checkmo'); +$quote->getShippingAddress()->setShippingMethod('flatrate_flatrate')->setCollectShippingRates(true); +$quote->collectTotals(); + +$quoteRepository = $objectManager->create(\Magento\Quote\Api\CartRepositoryInterface::class); +$quoteRepository->save($quote); + +/** @var \Magento\Quote\Model\QuoteIdMask $quoteIdMask */ +$quoteIdMask = $objectManager->create(\Magento\Quote\Model\QuoteIdMaskFactory::class)->create(); +$quoteIdMask->setQuoteId($quote->getId()); +$quoteIdMask->setDataChanges(true); +$quoteIdMask->save(); diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/guest_quote_with_addresses_rollback.php b/dev/tests/integration/testsuite/Magento/Sales/_files/guest_quote_with_addresses_rollback.php new file mode 100644 index 00000000000..02c42153b72 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/guest_quote_with_addresses_rollback.php @@ -0,0 +1,32 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +/** @var \Magento\Framework\Registry $registry */ +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); +$registry = $objectManager->get(\Magento\Framework\Registry::class); +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +/** @var $quote \Magento\Quote\Model\Quote */ +$quote = $objectManager->create(\Magento\Quote\Model\Quote::class); +$quote->load('guest_quote', 'reserved_order_id'); +if ($quote->getId()) { + $quote->delete(); +} + +/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->create(\Magento\Catalog\Api\ProductRepositoryInterface::class); + +try { + $product = $productRepository->get('simple-product-guest-quote', false, null, true); + $productRepository->delete($product); +} catch (\Magento\Framework\Exception\NoSuchEntityException $exception) { + //Product already removed +} + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/order.php b/dev/tests/integration/testsuite/Magento/Sales/_files/order.php index a1c5f827776..6b9cf3bc613 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/_files/order.php +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/order.php @@ -44,7 +44,8 @@ ->setBasePrice($product->getPrice()) ->setPrice($product->getPrice()) ->setRowTotal($product->getPrice()) - ->setProductType('simple'); + ->setProductType('simple') + ->setName($product->getName()); /** @var Order $order */ $order = $objectManager->create(Order::class); diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/order_list.php b/dev/tests/integration/testsuite/Magento/Sales/_files/order_list.php index 251a3845800..1f4253f1848 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/_files/order_list.php +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/order_list.php @@ -7,6 +7,7 @@ use Magento\Sales\Model\Order; use Magento\Sales\Api\OrderRepositoryInterface; use Magento\Sales\Model\Order\Address as OrderAddress; +use Magento\Sales\Model\Order\Payment; require 'order.php'; /** @var Order $order */ @@ -68,13 +69,26 @@ $shippingAddress = clone $billingAddress; $shippingAddress->setId(null)->setAddressType('shipping'); + /** @var Payment $payment */ + $payment = $objectManager->create(Payment::class); + $payment->setMethod('checkmo') + ->setAdditionalInformation('last_trans_id', '11122') + ->setAdditionalInformation( + 'metadata', + [ + 'type' => 'free', + 'fraudulent' => false, + ] + ); + $order ->setData($orderData) ->addItem($orderItem) ->setCustomerIsGuest(true) ->setCustomerEmail('customer@null.com') ->setBillingAddress($billingAddress) - ->setShippingAddress($shippingAddress); + ->setShippingAddress($shippingAddress) + ->setPayment($payment); $orderRepository->save($order); $orderList[] = $order; diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_discount.php b/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_discount.php index a83b01589ea..29a7aa4d903 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_discount.php +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_discount.php @@ -47,7 +47,9 @@ ->setProductType('simple') ->setDiscountAmount(2) ->setBaseRowTotal($product->getPrice()) - ->setBaseDiscountAmount(2); + ->setBaseDiscountAmount(2) + ->setTaxAmount(1) + ->setBaseTaxAmount(1); /** @var Order $order */ $order = $objectManager->create(Order::class); diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_shipping_and_invoice.php b/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_shipping_and_invoice.php index 4c892904b3c..61d8be98bdd 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_shipping_and_invoice.php +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/order_with_shipping_and_invoice.php @@ -44,5 +44,6 @@ $items[$orderItem->getId()] = $orderItem->getQtyOrdered(); } $shipment = $objectManager->get(ShipmentFactory::class)->create($order, $items); +$shipment->register(); $transaction->addObject($invoice)->addObject($shipment)->addObject($order)->save(); diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/orders_with_customer.php b/dev/tests/integration/testsuite/Magento/Sales/_files/orders_with_customer.php index 753adb1f385..1a0a94b0ca9 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/_files/orders_with_customer.php +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/orders_with_customer.php @@ -33,6 +33,7 @@ 'grand_total' => 130.00, 'base_grand_total' => 130.00, 'subtotal' => 130.00, + 'total_paid' => 130.00, 'store_id' => 0, 'website_id' => 0, 'payment' => $payment @@ -55,6 +56,7 @@ 'grand_total' => 150.00, 'base_grand_total' => 150.00, 'subtotal' => 150.00, + 'total_paid' => 150.00, 'store_id' => 1, 'website_id' => 1, 'payment' => $payment @@ -66,6 +68,7 @@ 'grand_total' => 160.00, 'base_grand_total' => 160.00, 'subtotal' => 160.00, + 'total_paid' => 160.00, 'store_id' => 1, 'website_id' => 1, 'payment' => $payment @@ -89,6 +92,15 @@ $shippingAddress = clone $billingAddress; $shippingAddress->setId(null)->setAddressType('shipping'); + /** @var Order\Item $orderItem */ + $orderItem = $objectManager->create(Order\Item::class); + $orderItem->setProductId($product->getId()) + ->setQtyOrdered(2) + ->setBasePrice($product->getPrice()) + ->setPrice($product->getPrice()) + ->setRowTotal($product->getPrice()) + ->setProductType('simple'); + $order ->setData($orderData) ->addItem($orderItem) diff --git a/dev/tests/integration/testsuite/Magento/Search/Model/SynonymReaderTest.php b/dev/tests/integration/testsuite/Magento/Search/Model/SynonymReaderTest.php index b9ba89ba531..2d0020ba226 100644 --- a/dev/tests/integration/testsuite/Magento/Search/Model/SynonymReaderTest.php +++ b/dev/tests/integration/testsuite/Magento/Search/Model/SynonymReaderTest.php @@ -48,7 +48,22 @@ public static function loadByPhraseDataProvider() ['synonyms' => 'queen,monarch', 'store_id' => 1, 'website_id' => 0], ['synonyms' => 'british,english', 'store_id' => 1, 'website_id' => 0] ] - ] + ], + [ + 'query_value', [] + ], + [ + 'query_value+', [] + ], + [ + 'query_value-', [] + ], + [ + 'query_@value', [] + ], + [ + 'query_value+@', [] + ], ]; } diff --git a/dev/tests/integration/testsuite/Magento/Shipping/Controller/Adminhtml/Order/Shipment/AbstractShipmentControllerTest.php b/dev/tests/integration/testsuite/Magento/Shipping/Controller/Adminhtml/Order/Shipment/AbstractShipmentControllerTest.php new file mode 100644 index 00000000000..0a1926d5862 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Shipping/Controller/Adminhtml/Order/Shipment/AbstractShipmentControllerTest.php @@ -0,0 +1,92 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Shipping\Controller\Adminhtml\Order\Shipment; + +use Magento\Framework\Api\SearchCriteria; +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Framework\Data\Form\FormKey; +use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Api\Data\ShipmentInterface; +use Magento\Sales\Model\OrderRepository; +use Magento\TestFramework\Mail\Template\TransportBuilderMock; +use Magento\TestFramework\TestCase\AbstractBackendController; + +/** + * Abstract backend shipment test. + */ +class AbstractShipmentControllerTest extends AbstractBackendController +{ + /** + * @var TransportBuilderMock + */ + protected $transportBuilder; + + /** + * @var OrderRepository + */ + protected $orderRepository; + + /** + * @var FormKey + */ + protected $formKey; + + /** + * @var string + */ + protected $resource = 'Magento_Sales::shipment'; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + $this->transportBuilder = $this->_objectManager->get(TransportBuilderMock::class); + $this->orderRepository = $this->_objectManager->get(OrderRepository::class); + $this->formKey = $this->_objectManager->get(FormKey::class); + } + + /** + * @param string $incrementalId + * @return OrderInterface|null + */ + protected function getOrder(string $incrementalId) + { + /** @var SearchCriteria $searchCriteria */ + $searchCriteria = $this->_objectManager->create(SearchCriteriaBuilder::class) + ->addFilter(OrderInterface::INCREMENT_ID, $incrementalId) + ->create(); + + $orders = $this->orderRepository->getList($searchCriteria)->getItems(); + /** @var OrderInterface|null $order */ + $order = reset($orders); + + return $order; + } + + /** + * @param OrderInterface $order + * @return ShipmentInterface + */ + protected function getShipment(OrderInterface $order): ShipmentInterface + { + /** @var \Magento\Sales\Model\ResourceModel\Order\Shipment\Collection $shipmentCollection */ + $shipmentCollection = $this->_objectManager->create( + \Magento\Sales\Model\ResourceModel\Order\Shipment\CollectionFactory::class + )->create(); + + /** @var ShipmentInterface $shipment */ + $shipment = $shipmentCollection + ->setOrderFilter($order) + ->setPageSize(1) + ->getFirstItem(); + + return $shipment; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Shipping/Controller/Adminhtml/Order/Shipment/AddCommentTest.php b/dev/tests/integration/testsuite/Magento/Shipping/Controller/Adminhtml/Order/Shipment/AddCommentTest.php new file mode 100644 index 00000000000..25a44bab629 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Shipping/Controller/Adminhtml/Order/Shipment/AddCommentTest.php @@ -0,0 +1,102 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Shipping\Controller\Adminhtml\Order\Shipment; + +use PHPUnit\Framework\Constraint\RegularExpression; +use PHPUnit\Framework\Constraint\StringContains; + +/** + * Class verifies shipment add comment functionality. + * + * @magentoDbIsolation enabled + * @magentoAppArea adminhtml + * @magentoDataFixture Magento/Sales/_files/shipment.php + */ +class AddCommentTest extends AbstractShipmentControllerTest +{ + /** + * @var string + */ + protected $uri = 'backend/admin/order_shipment/addComment'; + + /** + * @return void + */ + public function testSendEmailOnShipmentCommentAdd(): void + { + $comment = 'Test Shipment Comment'; + $order = $this->prepareRequest( + [ + 'comment' => ['comment' => $comment, 'is_customer_notified' => true], + ] + ); + $this->dispatch('backend/admin/order_shipment/addComment'); + $html = $this->getResponse()->getBody(); + $this->assertContains($comment, $html); + + $message = $this->transportBuilder->getSentMessage(); + $subject =__('Update to your %1 shipment', $order->getStore()->getFrontendName())->render(); + $messageConstraint = $this->logicalAnd( + new StringContains($order->getBillingAddress()->getName()), + new RegularExpression( + sprintf( + "/Your order #%s has been updated with a status of.*%s/", + $order->getIncrementId(), + $order->getFrontendStatusLabel() + ) + ), + new StringContains($comment) + ); + + $this->assertEquals($message->getSubject(), $subject); + $this->assertThat($message->getRawMessage(), $messageConstraint); + } + + /** + * @inheritdoc + */ + public function testAclHasAccess() + { + $this->prepareRequest(['comment', ['comment' => 'Comment']]); + + parent::testAclHasAccess(); + } + + /** + * @inheritdoc + */ + public function testAclNoAccess() + { + $this->prepareRequest(['comment', ['comment' => 'Comment']]); + + parent::testAclNoAccess(); + } + + /** + * @param array $params + * @return \Magento\Sales\Api\Data\OrderInterface|null + */ + private function prepareRequest(array $params = []) + { + $order = $this->getOrder('100000001'); + $shipment = $this->getShipment($order); + + $this->getRequest()->setMethod('POST'); + $this->getRequest()->setParams( + [ + 'id' => $shipment->getEntityId(), + 'form_key' => $this->formKey->getFormKey(), + ] + ); + + $data = $params ?? []; + $this->getRequest()->setPostValue($data); + + return $order; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Shipping/Controller/Adminhtml/Order/Shipment/SaveTest.php b/dev/tests/integration/testsuite/Magento/Shipping/Controller/Adminhtml/Order/Shipment/SaveTest.php new file mode 100644 index 00000000000..27b5bb02d4b --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Shipping/Controller/Adminhtml/Order/Shipment/SaveTest.php @@ -0,0 +1,97 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Shipping\Controller\Adminhtml\Order\Shipment; + +use PHPUnit\Framework\Constraint\StringContains; + +/** + * Class verifies shipment creation functionality. + * + * @magentoDbIsolation enabled + * @magentoAppArea adminhtml + * @magentoDataFixture Magento/Sales/_files/order.php + */ +class SaveTest extends AbstractShipmentControllerTest +{ + /** + * @var string + */ + protected $uri = 'backend/admin/order_shipment/save'; + + /** + * @return void + */ + public function testSendEmailOnShipmentSave(): void + { + $order = $this->prepareRequest(['shipment' => ['send_email' => true]]); + $this->dispatch('backend/admin/order_shipment/save'); + + $this->assertSessionMessages( + $this->equalTo([(string)__('The shipment has been created.')]), + \Magento\Framework\Message\MessageInterface::TYPE_SUCCESS + ); + $this->assertRedirect($this->stringContains('sales/order/view/order_id/' . $order->getEntityId())); + + $shipment = $this->getShipment($order); + $message = $this->transportBuilder->getSentMessage(); + $subject = __('Your %1 order has shipped', $order->getStore()->getFrontendName())->render(); + $messageConstraint = $this->logicalAnd( + new StringContains($order->getBillingAddress()->getName()), + new StringContains( + 'Thank you for your order from ' . $shipment->getStore()->getFrontendName() + ), + new StringContains( + "Your Shipment #{$shipment->getIncrementId()} for Order #{$order->getIncrementId()}" + ) + ); + + $this->assertEquals($message->getSubject(), $subject); + $this->assertThat($message->getRawMessage(), $messageConstraint); + } + + /** + * @inheritdoc + */ + public function testAclHasAccess() + { + $this->prepareRequest(); + + parent::testAclHasAccess(); + } + + /** + * @inheritdoc + */ + public function testAclNoAccess() + { + $this->prepareRequest(); + + parent::testAclNoAccess(); + } + + /** + * @param array $params + * @return \Magento\Sales\Api\Data\OrderInterface|null + */ + private function prepareRequest(array $params = []) + { + $order = $this->getOrder('100000001'); + $this->getRequest()->setMethod('POST'); + $this->getRequest()->setParams( + [ + 'order_id' => $order->getEntityId(), + 'form_key' => $this->formKey->getFormKey(), + ] + ); + + $data = $params ?? []; + $this->getRequest()->setPostValue($data); + + return $order; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Ups/Model/CarrierTest.php b/dev/tests/integration/testsuite/Magento/Ups/Model/CarrierTest.php index aa301e8d3e4..fe4067cdc49 100644 --- a/dev/tests/integration/testsuite/Magento/Ups/Model/CarrierTest.php +++ b/dev/tests/integration/testsuite/Magento/Ups/Model/CarrierTest.php @@ -69,6 +69,7 @@ public function testGetShipConfirmUrlLive() */ public function testCollectFreeRates() { + $this->markTestSkipped('Test is blocked by MAGETWO-97467.'); $rateRequest = Bootstrap::getObjectManager()->get(RateRequestFactory::class)->create(); $rateRequest->setDestCountryId('US'); $rateRequest->setDestRegionId('CA'); diff --git a/dev/tests/integration/testsuite/Magento/UrlRewrite/Model/StoreSwitcher/RewriteUrlTest.php b/dev/tests/integration/testsuite/Magento/UrlRewrite/Model/StoreSwitcher/RewriteUrlTest.php index 2cb86358667..a8ff9e41178 100644 --- a/dev/tests/integration/testsuite/Magento/UrlRewrite/Model/StoreSwitcher/RewriteUrlTest.php +++ b/dev/tests/integration/testsuite/Magento/UrlRewrite/Model/StoreSwitcher/RewriteUrlTest.php @@ -92,7 +92,8 @@ public function testSwitchToExistingPage(): void $storeRepository = $this->objectManager->create(\Magento\Store\Api\StoreRepositoryInterface::class); $toStore = $storeRepository->get($toStoreCode); - $redirectUrl = $expectedUrl = "http://localhost/page-c"; + $redirectUrl = "http://localhost/index.php/page-c/"; + $expectedUrl = "http://localhost/index.php/page-c-on-2nd-store"; $this->assertEquals($expectedUrl, $this->storeSwitcher->switch($fromStore, $toStore, $redirectUrl)); } diff --git a/dev/tests/integration/testsuite/Magento/Widget/Controller/Adminhtml/Widget/InstanceTest.php b/dev/tests/integration/testsuite/Magento/Widget/Controller/Adminhtml/Widget/InstanceTest.php index 5a07bdcfca3..cc6f2793fcf 100644 --- a/dev/tests/integration/testsuite/Magento/Widget/Controller/Adminhtml/Widget/InstanceTest.php +++ b/dev/tests/integration/testsuite/Magento/Widget/Controller/Adminhtml/Widget/InstanceTest.php @@ -38,6 +38,12 @@ public function testEditAction() public function testBlocksAction() { + \Magento\TestFramework\Helper\Bootstrap::getInstance() + ->loadArea(\Magento\Framework\App\Area::AREA_FRONTEND); + $theme = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get( + \Magento\Framework\View\DesignInterface::class + )->setDefaultDesignTheme()->getDesignTheme(); + $this->getRequest()->setParam('theme_id', $theme->getId()); $this->dispatch('backend/admin/widget_instance/blocks'); $this->assertStringStartsWith('<select name="block" id=""', $this->getResponse()->getBody()); } diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/variations.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/variations.test.js index c98c459d988..6b68978380e 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/variations.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/variations.test.js @@ -46,8 +46,8 @@ define([ variation.serializeData(); - expect(variation.source.data['configurable-matrix']).toBeUndefined(); - expect(variation.source.data['associated_product_ids']).toBeUndefined(); + expect(variation.source.data['configurable-matrix']).toEqual(matrix); + expect(variation.source.data['associated_product_ids']).toEqual(ids); expect(variation.source.data['configurable-matrix-serialized']).toEqual(resultMatrix); expect(variation.source.data['associated_product_ids_serialized']).toEqual(resultIds); }); @@ -112,8 +112,8 @@ define([ variation.source.data['associated_product_ids_serialized'] = JSON.stringify(['some old data']); variation.serializeData(); - expect(variation.source.data['configurable-matrix']).toBeUndefined(); - expect(variation.source.data['associated_product_ids']).toBeUndefined(); + expect(variation.source.data['configurable-matrix']).toEqual(matrix); + expect(variation.source.data['associated_product_ids']).toEqual(ids); expect(variation.source.data['configurable-matrix-serialized']).toEqual(resultMatrix); expect(variation.source.data['associated_product_ids_serialized']).toEqual(resultIds); }); @@ -164,8 +164,8 @@ define([ variation.serializeData(); - expect(variation.source.data['configurable-matrix']).toBeUndefined(); - expect(variation.source.data['associated_product_ids']).toBeUndefined(); + expect(variation.source.data['configurable-matrix']).toEqual(matrix); + expect(variation.source.data['associated_product_ids']).toEqual(ids); expect(variation.source.data['configurable-matrix-serialized']).toEqual(resultMatrix); expect(variation.source.data['associated_product_ids_serialized']).toEqual(resultIds); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Paypal/frontend/js/view/payment/method-renderer/in-context/checkout-express.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Paypal/frontend/js/view/payment/method-renderer/in-context/checkout-express.test.js index 395dd96822e..c2a20c8339c 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Paypal/frontend/js/view/payment/method-renderer/in-context/checkout-express.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Paypal/frontend/js/view/payment/method-renderer/in-context/checkout-express.test.js @@ -25,17 +25,7 @@ define([ 'Magento_Checkout/js/model/payment/additional-validators': { validate: jasmine.createSpy().and.returnValue(true) }, - 'Magento_Ui/js/model/messageList': { - addErrorMessage: jasmine.createSpy(), - addSuccessMessage: jasmine.createSpy() - }, - 'paypalInContextExpressCheckout': { - checkout: { - initXO: jasmine.createSpy(), - closeFlow: jasmine.createSpy(), - startFlow: jasmine.createSpy() - } - }, + 'Magento_Paypal/js/in-context/express-checkout-smart-buttons': jasmine.createSpy(), 'Magento_Customer/js/customer-data': { invalidate: jasmine.createSpy() } @@ -43,6 +33,15 @@ define([ obj; beforeAll(function (done) { + window.checkoutConfig = { + quoteData: { + entityId: 1 + }, + formKey: 'formKey' + }; + window.customerData = { + id: 1 + }; injector.mock(mocks); injector.require( ['Magento_Paypal/js/view/payment/method-renderer/in-context/checkout-express'], @@ -51,9 +50,11 @@ define([ provider: 'provName', name: 'test', index: 'test', - selectPaymentMethod: jasmine.createSpy() + item: { + method: 'payflow_express_bml' + }, + clientConfig: {} }); - done(); }); }); @@ -65,77 +66,11 @@ define([ } catch (e) {} }); - describe('"click" method checks', function () { - it('check success request', function () { - mocks['Magento_Paypal/js/action/set-payment-method'].and.callFake(function () { - var promise = $.Deferred(); - - promise.resolve(); - - return promise; - }); - spyOn(jQuery, 'get').and.callFake(function () { - var promise = $.Deferred(); - - promise.resolve({ - url: 'url' - }); - - return promise; - }); - - obj.clientConfig.click(new Event('event')); - - expect(mocks.paypalInContextExpressCheckout.checkout.initXO).toHaveBeenCalled(); - expect(mocks.paypalInContextExpressCheckout.checkout.startFlow).toHaveBeenCalledWith('url'); - expect(mocks.paypalInContextExpressCheckout.checkout.closeFlow).not.toHaveBeenCalled(); - expect(mocks['Magento_Customer/js/customer-data'].invalidate).toHaveBeenCalled(); - }); - - it('check request with error message', function () { - mocks['Magento_Paypal/js/action/set-payment-method'].and.callFake(function () { - var promise = $.Deferred(); - - promise.resolve(); - - return promise; - }); - spyOn(jQuery, 'get').and.callFake(function () { - var promise = $.Deferred(); - - promise.resolve({ - message: { - text: 'Text', - type: 'error' - } - }); - - return promise; - }); - - obj.clientConfig.click(new Event('event')); - - expect(mocks['Magento_Ui/js/model/messageList'].addErrorMessage).toHaveBeenCalledWith({ - message: 'Text' - }); - expect(mocks.paypalInContextExpressCheckout.checkout.initXO).toHaveBeenCalled(); - expect(mocks.paypalInContextExpressCheckout.checkout.closeFlow).toHaveBeenCalled(); - expect(mocks['Magento_Customer/js/customer-data'].invalidate).toHaveBeenCalled(); - }); - - it('check on fail request', function () { - mocks['Magento_Paypal/js/action/set-payment-method'].and.callFake(function () { - var promise = $.Deferred(); - - promise.reject(); - - return promise; - }); - spyOn(jQuery, 'get'); - - obj.clientConfig.click(new Event('event')); + describe('check smart button initialization', function () { + it('express-checkout-smart-buttons is initialized', function () { - expect(jQuery.get).not.toHaveBeenCalled(); + obj.renderPayPalButtons(); + expect(mocks['Magento_Paypal/js/in-context/express-checkout-smart-buttons']).toHaveBeenCalled(); }); }); }); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Paypal/frontend/js/view/payment/method-renderer/paypal-express-abstract.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Paypal/frontend/js/view/payment/method-renderer/paypal-express-abstract.test.js index 9950c89ce3c..29a2e8db914 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Paypal/frontend/js/view/payment/method-renderer/paypal-express-abstract.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Paypal/frontend/js/view/payment/method-renderer/paypal-express-abstract.test.js @@ -65,7 +65,7 @@ define([ name: 'test', index: 'test', item: { - method: 'paypal_express_bml' + method: 'payflow_express_bml' } }); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/adminhtml/js/form/insert.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/adminhtml/js/form/insert.test.js new file mode 100644 index 00000000000..dbe76f0dc26 --- /dev/null +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/adminhtml/js/form/insert.test.js @@ -0,0 +1,45 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +/* eslint-disable max-nested-callbacks */ +define([ + 'jquery', + 'Magento_Ui/js/form/components/insert' +], function ($, Insert) { + 'use strict'; + + describe('Magento_Ui/js/form/components/insert', function () { + var obj, params; + + beforeEach(function () { + params = { + isRendered: false, + autoRender: false + }; + obj = new Insert(params); + }); + + describe('"onRender" method', function () { + it('Check method call with not JSON response', function () { + var data = '<Not JSON>'; + + obj.onRender(data); + + expect(obj.content()).toBe(data); + expect(obj.isRendered).toBeTruthy(); + expect(obj.startRender).toBeFalsy(); + }); + + it('Check method call with ajaxExpired JSON', function () { + var data = '{"ajaxExpired": 1, "ajaxRedirect": "#test"}'; + + obj.onRender(data); + + expect(obj.content()).toBe(''); + expect(window.location.href).toContain('#test'); + }); + }); + }); +}); diff --git a/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule8/revisions/disabling_tables/db_schema.xml b/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule8/revisions/disabling_tables/db_schema.xml new file mode 100644 index 00000000000..5ea5816b1df --- /dev/null +++ b/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule8/revisions/disabling_tables/db_schema.xml @@ -0,0 +1,35 @@ +<?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="reference_table" resource="default"> + <column xsi:type="tinyint" name="tinyint_ref" padding="7" nullable="false" identity="true" unsigned="false"/> + <column xsi:type="tinyint" name="tinyint_without_padding" default="0" nullable="false" unsigned="false"/> + <column xsi:type="bigint" name="bigint_without_padding" default="0" nullable="false" unsigned="false"/> + <column xsi:type="smallint" name="smallint_without_padding" default="0" nullable="false" unsigned="false"/> + <column xsi:type="int" name="integer_without_padding" default="0" nullable="false" unsigned="false"/> + <column xsi:type="smallint" name="smallint_with_big_padding" padding="254" default="0" nullable="false" + unsigned="false"/> + <constraint xsi:type="primary" referenceId="tinyint_primary"> + <column name="tinyint_ref"/> + </constraint> + <index referenceId="COMPLEX_INDEX" indexType="btree"> + <column name="tinyint_without_padding"/> + <column name="bigint_without_padding"/> + </index> + </table> + <table name="auto_increment_test" resource="default"> + <column xsi:type="int" name="int_auto_increment_with_nullable" identity="true" padding="12" unsigned="true" + nullable="true"/> + <column xsi:type="smallint" name="int_disabled_auto_increment" default="0" identity="false" padding="12" + unsigned="true" nullable="true"/> + <constraint xsi:type="unique" referenceId="AUTO_INCREMENT_TEST_INT_AUTO_INCREMENT_WITH_NULLABLE"> + <column name="int_auto_increment_with_nullable"/> + </constraint> + </table> +</schema> diff --git a/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule8/revisions/setup_install_with_converting/InstallSchema.php b/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule8/revisions/setup_install_with_converting/InstallSchema.php new file mode 100644 index 00000000000..c1eaff264df --- /dev/null +++ b/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule8/revisions/setup_install_with_converting/InstallSchema.php @@ -0,0 +1,334 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\TestSetupDeclarationModule8\Setup; + +use Magento\Framework\DB\Adapter\AdapterInterface; +use Magento\Framework\DB\Ddl\Table; +use Magento\Framework\Setup\InstallSchemaInterface; +use Magento\Framework\Setup\ModuleContextInterface; +use Magento\Framework\Setup\SchemaSetupInterface; + +/** + * Install schema script for the TestSetupDeclarationModule8 module. + */ +class InstallSchema implements InstallSchemaInterface +{ + /** + * The name of the main table of Module8. + */ + const MAIN_TABLE = 'module8_test_main_table'; + + /** + * The name of the second table of Module8. + */ + const SECOND_TABLE = 'module8_test_second_table'; + + /** + * The name of the second table of Module8. + */ + const TEMP_TABLE = 'module8_test_install_temp_table'; + + /** + * @inheritdoc + * @throws \Zend_Db_Exception + */ + public function install(SchemaSetupInterface $setup, ModuleContextInterface $context) + { + $setup->startSetup(); + + $this->createTables($setup); + + $setup->endSetup(); + } + + /** + * Create tables. + * + * @param SchemaSetupInterface $installer + * @throws \Zend_Db_Exception + */ + private function createTables(SchemaSetupInterface $installer) + { + $mainTableName = $installer->getTable(self::MAIN_TABLE); + $this->dropTableIfExists($installer, $mainTableName); + $mainTable = $installer->getConnection()->newTable($mainTableName); + $mainTable->setComment('Main Test Table for Module8'); + $this->addColumnsToMainTable($mainTable); + $this->addIndexesToMainTable($mainTable); + $installer->getConnection()->createTable($mainTable); + + $secondTableName = $installer->getTable(self::SECOND_TABLE); + $this->dropTableIfExists($installer, $secondTableName); + $secondTable = $installer->getConnection()->newTable($secondTableName); + $secondTable->setComment('Second Test Table for Module8'); + $this->addColumnsToSecondTable($secondTable); + $this->addIndexesToSecondTable($secondTable); + $this->addConstraintsToSecondTable($secondTable); + $installer->getConnection()->createTable($secondTable); + + $this->createSimpleTable($installer, self::TEMP_TABLE); + } + + /** + * Drop existing tables. + * + * @param SchemaSetupInterface $installer + * @param string $table + */ + private function dropTableIfExists($installer, $table) + { + $connection = $installer->getConnection(); + if ($connection->isTableExists($installer->getTable($table))) { + $connection->dropTable( + $installer->getTable($table) + ); + } + } + + /** + * Add tables to main table. + * + * @param Table $table + * @throws \Zend_Db_Exception + */ + private function addColumnsToMainTable($table) + { + $table + ->addColumn( + 'module8_email_contact_id', + Table::TYPE_INTEGER, + 10, + [ + 'primary' => true, + 'identity' => true, + 'unsigned' => true, + 'nullable' => false + ], + 'Email Contact ID' + )->addColumn( + 'module8_contact_group_id', + Table::TYPE_INTEGER, + 10, + [ + 'unsigned' => true, + 'nullable' => false + ], + 'Contact Group ID' + )->addColumn( + 'module8_is_guest', + Table::TYPE_SMALLINT, + null, + [ + 'unsigned' => true, + 'nullable' => true + ], + 'Is Guest' + )->addColumn( + 'module8_contact_id', + Table::TYPE_TEXT, + 15, + [ + 'unsigned' => true, + 'nullable' => true + ], + 'Contact ID' + )->addColumn( + 'module8_content', + Table::TYPE_TEXT, + 15, + [ + 'nullable' => false, + ], + 'Content' + ); + } + + /** + * Add indexes to main table. + * + * @param Table $table + * @throws \Zend_Db_Exception + */ + private function addIndexesToMainTable($table) + { + $table + ->addIndex( + 'MODULE8_INSTALL_INDEX_1', + ['module8_email_contact_id'] + )->addIndex( + 'MODULE8_INSTALL_UNIQUE_INDEX_2', + ['module8_email_contact_id', 'module8_is_guest'], + ['type' => AdapterInterface::INDEX_TYPE_UNIQUE] + )->addIndex( + 'MODULE8_INSTALL_INDEX_3', + ['module8_is_guest'] + )->addIndex( + 'MODULE8_INSTALL_INDEX_4', + ['module8_contact_id'] + )->addIndex( + 'MODULE8_INSTALL_INDEX_TEMP', + ['module8_content'] + )->addIndex( + 'MODULE8_INSTALL_UNIQUE_INDEX_TEMP', + ['module8_contact_group_id'], + ['type' => AdapterInterface::INDEX_TYPE_UNIQUE] + ); + } + + /** + * Add tables to second table. + * + * @param Table $table + * @throws \Zend_Db_Exception + */ + private function addColumnsToSecondTable($table) + { + $table + ->addColumn( + 'module8_entity_id', + Table::TYPE_INTEGER, + 10, + [ + 'primary' => true, + 'identity' => true, + 'unsigned' => true, + 'nullable' => false + ], + 'Entity ID' + )->addColumn( + 'module8_contact_id', + Table::TYPE_INTEGER, + null, + [], + 'Contact ID' + )->addColumn( + 'module8_address', + Table::TYPE_TEXT, + 15, + [ + 'nullable' => false, + ], + 'Address' + )->addColumn( + 'module8_counter_with_multiline_comment', + Table::TYPE_SMALLINT, + null, + [ + 'unsigned' => true, + 'nullable' => true, + 'default' => 0 + ], + 'Empty + Counter + Multiline + Comment' + )->addColumn( + 'module8_second_address', + Table::TYPE_TEXT, + 15, + [ + 'unsigned' => true, + 'nullable' => true + ], + 'Second Address' + )->addColumn( + 'module8_temp_column', + Table::TYPE_TEXT, + 15, + [ + 'unsigned' => true, + 'nullable' => true + ], + 'Temp column for remove' + ); + } + + /** + * Add indexes to second table. + * + * @param Table $table + * @throws \Zend_Db_Exception + */ + private function addIndexesToSecondTable($table) + { + $table + ->addIndex( + 'MODULE8_INSTALL_SECOND_TABLE_INDEX_1', + ['module8_entity_id'] + )->addIndex( + 'MODULE8_INSTALL_SECOND_TABLE_INDEX_2', + ['module8_address'] + )->addIndex( + 'MODULE8_INSTALL_SECOND_TABLE_INDEX_3_TEMP', + ['module8_second_address'] + ); + } + + /** + * Add constraints to second table. + * + * @param Table $table + * @throws \Zend_Db_Exception + */ + private function addConstraintsToSecondTable($table) + { + $table + ->addForeignKey( + 'MODULE8_INSTALL_FK_ENTITY_ID_TEST_MAIN_TABLE_EMAIL_CONTACT_ID', + 'module8_entity_id', + self::MAIN_TABLE, + 'module8_email_contact_id' + )->addForeignKey( + 'MODULE8_INSTALL_FK_ADDRESS_TEST_MAIN_TABLE_CONTACT_ID', + 'module8_address', + self::MAIN_TABLE, + 'module8_contact_id' + )->addForeignKey( + 'MODULE8_INSTALL_FK_ADDRESS_TEST_MAIN_TABLE_MODULE8_CONTENT_TEMP', + 'module8_address', + self::MAIN_TABLE, + 'module8_content' + ); + } + + /** + * Create a simple table. + * + * @param SchemaSetupInterface $setup + * @param $tableName + * @throws \Zend_Db_Exception + */ + private function createSimpleTable(SchemaSetupInterface $setup, $tableName): void + { + $table = $setup->getConnection()->newTable($tableName); + $table + ->addColumn( + 'module8_entity_id', + Table::TYPE_INTEGER, + null, + [ + 'primary' => true, + 'identity' => true, + 'nullable' => false, + 'unsigned' => true, + ], + 'Entity ID' + )->addColumn( + 'module8_counter', + Table::TYPE_INTEGER, + null, + [ + 'unsigned' => true, + 'nullable' => true, + 'default' => 100 + ], + 'Counter' + ); + $setup->getConnection()->createTable($table); + } +} diff --git a/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule8/revisions/setup_install_with_converting/UpgradeSchema.php b/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule8/revisions/setup_install_with_converting/UpgradeSchema.php new file mode 100644 index 00000000000..2dc8667a75d --- /dev/null +++ b/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule8/revisions/setup_install_with_converting/UpgradeSchema.php @@ -0,0 +1,242 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\TestSetupDeclarationModule8\Setup; + +use Magento\Framework\DB\Adapter\AdapterInterface; +use Magento\Framework\DB\Ddl\Table; +use Magento\Framework\Setup\ModuleContextInterface; +use Magento\Framework\Setup\SchemaSetupInterface; +use Magento\Framework\Setup\UpgradeSchemaInterface; + +/** + * Upgrade schema script for the TestSetupDeclarationModule8 module. + */ +class UpgradeSchema implements UpgradeSchemaInterface +{ + /** + * The name of the main table of the Module8. + */ + const UPDATE_TABLE = 'module8_test_update_table'; + + /** + * The name of the temporary table of the Module8. + */ + const TEMP_TABLE = 'module8_test_temp_table'; + + /** + * @inheritdoc + * @throws \Zend_Db_Exception + */ + public function upgrade(SchemaSetupInterface $setup, ModuleContextInterface $context) + { + $setup->startSetup(); + + if (version_compare($context->getVersion(), '1.0.1', '<')) { + $tableName = $setup->getTable(self::UPDATE_TABLE); + $table = $setup->getConnection()->newTable($tableName); + $table->setComment('Update Test Table for Module8'); + + $this->addColumns($setup, $table); + $this->addIndexes($table); + $this->addConstraints($table); + $setup->getConnection()->createTable($table); + + $this->createSimpleTable($setup, $setup->getTable(self::TEMP_TABLE)); + } + + if (version_compare($context->getVersion(), '1.0.2', '<')) { + $connection = $setup->getConnection(); + $connection + ->dropTable( + InstallSchema::TEMP_TABLE + ); + $connection + ->dropColumn( + InstallSchema::SECOND_TABLE, + 'module8_temp_column' + ); + $connection + ->dropForeignKey( + InstallSchema::SECOND_TABLE, + 'MODULE8_INSTALL_FK_ADDRESS_TEST_MAIN_TABLE_MODULE8_CONTENT_TEMP' + ); + $connection + ->dropIndex( + InstallSchema::MAIN_TABLE, + 'MODULE8_INSTALL_INDEX_TEMP' + ); + $connection + ->dropIndex( + InstallSchema::MAIN_TABLE, + 'MODULE8_INSTALL_UNIQUE_INDEX_TEMP' + ); + } + + $setup->endSetup(); + } + + /** + * Create columns for tables. + * + * @param SchemaSetupInterface $setup + * @param Table $table + * @throws \Zend_Db_Exception + */ + private function addColumns(SchemaSetupInterface $setup, Table $table): void + { + $table + ->addColumn( + 'module8_entity_id', + Table::TYPE_INTEGER, + 10, + [ + 'primary' => true, + 'unsigned' => true, + 'nullable' => false + ], + 'Entity ID' + )->addColumn( + 'module8_entity_row_id', + Table::TYPE_INTEGER, + null, + [ + 'unsigned' => true, + 'nullable' => false + ] + )->addColumn( + 'module8_is_guest', + Table::TYPE_SMALLINT, + null, + [ + 'unsigned' => true, + 'nullable' => true + ], + 'Is Guest' + )->addColumn( + 'module8_guest_browser_id', + Table::TYPE_SMALLINT, + null, + [ + 'unsigned' => true, + 'nullable' => true + ], + 'Guest Browser ID' + )->addColumn( + 'module8_column_for_remove', + Table::TYPE_SMALLINT, + null, + [ + 'unsigned' => true, + 'nullable' => true + ], + 'For remove' + ); + + $setup->getConnection()->addColumn( + InstallSchema::MAIN_TABLE, + 'module8_update_column', + [ + 'type' => Table::TYPE_INTEGER, + 'nullable' => false, + 'comment' => 'Module_8 Update Column', + ] + ); + } + + /** + * Add indexes. + * + * @param Table $table + * @throws \Zend_Db_Exception + */ + private function addIndexes(Table $table): void + { + $table + ->addIndex( + 'MODULE8_UPDATE_IS_GUEST_INDEX', + [ + 'module8_is_guest' + ] + )->addIndex( + 'MODULE8_UPDATE_UNIQUE_INDEX_TEMP', + [ + 'module8_entity_id', + 'module8_is_guest', + + ], + ['type' => AdapterInterface::INDEX_TYPE_UNIQUE] + )->addIndex( + 'MODULE8_UPDATE_TEMP_INDEX', + [ + 'module8_column_for_remove', + 'module8_guest_browser_id' + ] + ); + } + + /** + * Add constraints. + * + * @param Table $table + * @throws \Zend_Db_Exception + */ + private function addConstraints(Table $table): void + { + $table + ->addForeignKey( + 'MODULE8_UPDATE_FK_MODULE8_IS_GUEST', + 'module8_is_guest', + InstallSchema::MAIN_TABLE, + 'module8_is_guest', + Table::ACTION_CASCADE + )->addForeignKey( + 'MODULE8_UPDATE_FK_TEMP', + 'module8_column_for_remove', + InstallSchema::MAIN_TABLE, + 'module8_is_guest', + Table::ACTION_CASCADE + ); + } + + /** + * Create a simple table. + * + * @param SchemaSetupInterface $setup + * @param $tableName + * @throws \Zend_Db_Exception + */ + private function createSimpleTable(SchemaSetupInterface $setup, $tableName): void + { + $table = $setup->getConnection()->newTable($tableName); + $table + ->addColumn( + 'module8_entity_id', + Table::TYPE_INTEGER, + null, + [ + 'primary' => true, + 'identity' => true, + 'nullable' => false, + 'unsigned' => true, + ], + 'Entity ID' + )->addColumn( + 'module8_counter', + Table::TYPE_INTEGER, + null, + [ + 'unsigned' => true, + 'nullable' => true, + 'default' => 100 + ], + 'Counter' + ); + $setup->getConnection()->createTable($table); + } +} diff --git a/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule8/revisions/setup_install_with_converting/module.xml b/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule8/revisions/setup_install_with_converting/module.xml new file mode 100644 index 00000000000..b6a57fcb476 --- /dev/null +++ b/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule8/revisions/setup_install_with_converting/module.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:Module/etc/module.xsd"> + <module name="Magento_TestSetupDeclarationModule8" setup_version="1.0.2"/> +</config> diff --git a/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule9/etc/module.xml b/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule9/etc/module.xml new file mode 100644 index 00000000000..e472f951b08 --- /dev/null +++ b/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule9/etc/module.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:Module/etc/module.xsd"> + <module name="Magento_TestSetupDeclarationModule9" setup_version="1.0.0" /> +</config> diff --git a/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule9/fixture/declarative_installer/disabling_tables.php b/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule9/fixture/declarative_installer/disabling_tables.php new file mode 100644 index 00000000000..633185390ae --- /dev/null +++ b/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule9/fixture/declarative_installer/disabling_tables.php @@ -0,0 +1,14 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +return [ + 'auto_increment_test' => 'CREATE TABLE `auto_increment_test` ( + `int_auto_increment_with_nullable` int(12) unsigned NOT NULL AUTO_INCREMENT, + `int_disabled_auto_increment` smallint(12) unsigned DEFAULT \'0\', + UNIQUE KEY `AUTO_INCREMENT_TEST_INT_AUTO_INCREMENT_WITH_NULLABLE` (`int_auto_increment_with_nullable`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8' +]; diff --git a/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule9/registration.php b/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule9/registration.php new file mode 100644 index 00000000000..f021af556bb --- /dev/null +++ b/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule9/registration.php @@ -0,0 +1,13 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Framework\Component\ComponentRegistrar; + +$registrar = new ComponentRegistrar(); +if ($registrar->getPath(ComponentRegistrar::MODULE, 'Magento_TestSetupDeclarationModule9') === null) { + ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Magento_TestSetupDeclarationModule9', __DIR__); +} diff --git a/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule9/revisions/disabling_tables/db_schema.xml b/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule9/revisions/disabling_tables/db_schema.xml new file mode 100644 index 00000000000..c22c41b7a5c --- /dev/null +++ b/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule9/revisions/disabling_tables/db_schema.xml @@ -0,0 +1,11 @@ +<?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="reference_table" disabled="true"/> +</schema> diff --git a/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule9/revisions/setup_install_with_converting/InstallSchema.php b/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule9/revisions/setup_install_with_converting/InstallSchema.php new file mode 100644 index 00000000000..d78a8e25230 --- /dev/null +++ b/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule9/revisions/setup_install_with_converting/InstallSchema.php @@ -0,0 +1,134 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\TestSetupDeclarationModule9\Setup; + +use Magento\Framework\DB\Adapter\AdapterInterface; +use Magento\Framework\DB\Ddl\Table; +use Magento\Framework\Setup\InstallSchemaInterface; +use Magento\Framework\Setup\ModuleContextInterface; +use Magento\Framework\Setup\SchemaSetupInterface; + +/** + * Install schema script for the TestSetupDeclarationModule9 module. + */ +class InstallSchema implements InstallSchemaInterface +{ + /** + * The name of the main table of Module9. + */ + const MAIN_TABLE = 'module9_test_main_table'; + + /** + * @inheritdoc + * @throws \Zend_Db_Exception + */ + public function install(SchemaSetupInterface $setup, ModuleContextInterface $context) + { + $setup->startSetup(); + + $this->createTables($setup); + + $setup->endSetup(); + } + + /** + * Create tables. + * + * @param SchemaSetupInterface $installer + * @throws \Zend_Db_Exception + */ + private function createTables(SchemaSetupInterface $installer) + { + $mainTableName = $installer->getTable(self::MAIN_TABLE); + $this->dropTableIfExists($installer, $mainTableName); + $mainTable = $installer->getConnection()->newTable($mainTableName); + $mainTable->setComment('Main Test Table for Module9'); + $this->addColumnsToMainTable($mainTable); + $this->addIndexesToMainTable($mainTable); + $installer->getConnection()->createTable($mainTable); + } + + /** + * Drop existing tables. + * + * @param SchemaSetupInterface $installer + * @param string $table + */ + private function dropTableIfExists($installer, $table) + { + $connection = $installer->getConnection(); + if ($connection->isTableExists($installer->getTable($table))) { + $connection->dropTable( + $installer->getTable($table) + ); + } + } + + /** + * Add tables to main table. + * + * @param Table $table + * @throws \Zend_Db_Exception + */ + private function addColumnsToMainTable($table) + { + $table + ->addColumn( + 'module9_email_contact_id', + Table::TYPE_INTEGER, + 10, + [ + 'primary' => true, + 'identity' => true, + 'unsigned' => true, + 'nullable' => false + ], + 'Entity ID' + )->addColumn( + 'module9_is_guest', + Table::TYPE_SMALLINT, + null, + [ + 'unsigned' => true, + 'nullable' => true + ], + 'Is Guest' + )->addColumn( + 'module9_guest_id', + Table::TYPE_INTEGER, + null, + [ + 'unsigned' => true + ], + 'Guest ID' + ) + ->addColumn( + 'module9_created_at', + Table::TYPE_DATE, + null, + [], + 'Created At' + ); + } + + /** + * Add indexes to main table. + * + * @param Table $table + * @throws \Zend_Db_Exception + */ + private function addIndexesToMainTable($table) + { + $table + ->addIndex( + 'MODULE9_INSTALL_UNIQUE_INDEX_1', + ['module9_email_contact_id', 'module9_guest_id'], + ['type' => AdapterInterface::INDEX_TYPE_UNIQUE] + ); + } +} diff --git a/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule9/revisions/setup_install_with_converting/UpgradeSchema.php b/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule9/revisions/setup_install_with_converting/UpgradeSchema.php new file mode 100644 index 00000000000..0a493496999 --- /dev/null +++ b/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule9/revisions/setup_install_with_converting/UpgradeSchema.php @@ -0,0 +1,173 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\TestSetupDeclarationModule9\Setup; + +use Magento\Framework\DB\Ddl\Table; +use Magento\Framework\Setup\ModuleContextInterface; +use Magento\Framework\Setup\SchemaSetupInterface; +use Magento\Framework\Setup\UpgradeSchemaInterface; +use Magento\TestSetupDeclarationModule8\Setup\InstallSchema as Module8InstallSchema; +use Magento\TestSetupDeclarationModule8\Setup\UpgradeSchema as Module8UpgradeSchema; + +/** + * Upgrade schema script for the TestSetupDeclarationModule9 module. + */ +class UpgradeSchema implements UpgradeSchemaInterface +{ + /** + * The name of the main table of Module9. + */ + const REPLICA_TABLE = 'module9_test_update_replica_table'; + + /** + * @inheritdoc + * @throws \Zend_Db_Exception + */ + public function upgrade(SchemaSetupInterface $setup, ModuleContextInterface $context) + { + $setup->startSetup(); + + if (version_compare($context->getVersion(), '2.0.0', '<')) { + $this->addColumns($setup); + $this->addIndexes($setup); + $this->addConstraints($setup); + $this->removeColumns($setup); + $this->removeIndexes($setup); + $this->removeConstraints($setup); + $this->removeTables($setup); + $replicaTable = $setup->getConnection() + ->createTableByDdl(Module8InstallSchema::SECOND_TABLE, self::REPLICA_TABLE); + $setup->getConnection()->createTable($replicaTable); + } + + $setup->endSetup(); + } + + /** + * Create columns for tables. + * + * @param SchemaSetupInterface $setup + */ + private function addColumns(SchemaSetupInterface $setup): void + { + $setup->getConnection()->addColumn( + InstallSchema::MAIN_TABLE, + 'module9_update_column', + [ + 'type' => Table::TYPE_INTEGER, + 'nullable' => false, + 'comment' => 'Module_9 Update Column', + ] + ); + + $setup->getConnection()->addColumn( + Module8InstallSchema::MAIN_TABLE, + 'module9_update_column', + [ + 'type' => Table::TYPE_INTEGER, + 'nullable' => false, + 'comment' => 'Module_9 Update Column', + ] + ); + } + + /** + * Add indexes. + * + * @param SchemaSetupInterface $setup + */ + private function addIndexes(SchemaSetupInterface $setup): void + { + $setup->getConnection() + ->addIndex( + Module8UpgradeSchema::UPDATE_TABLE, + 'MODULE9_UPDATE_MODULE8_GUEST_BROWSER_ID', + [ + 'module8_guest_browser_id' + ] + ); + } + + /** + * Add constraints. + * + * @param SchemaSetupInterface $setup + */ + private function addConstraints(SchemaSetupInterface $setup): void + { + $setup->getConnection() + ->addForeignKey( + 'MODULE9_UPDATE_FK_MODULE9_IS_GUEST', + InstallSchema::MAIN_TABLE, + 'module9_is_guest', + Module8InstallSchema::MAIN_TABLE, + 'module8_is_guest', + Table::ACTION_CASCADE + ); + } + + /** + * Remove columns. + * + * @param SchemaSetupInterface $setup + */ + private function removeColumns(SchemaSetupInterface $setup): void + { + $setup->getConnection() + ->dropColumn( + Module8UpgradeSchema::UPDATE_TABLE, + 'module8_column_for_remove' + ); + } + + /** + * Remove indexes. + * + * @param SchemaSetupInterface $setup + */ + private function removeIndexes(SchemaSetupInterface $setup): void + { + $connection = $setup->getConnection(); + $connection + ->dropIndex( + Module8InstallSchema::SECOND_TABLE, + 'MODULE8_INSTALL_SECOND_TABLE_INDEX_3_TEMP' + ); + } + + /** + * Remove constraints. + * + * @param SchemaSetupInterface $setup + */ + private function removeConstraints(SchemaSetupInterface $setup): void + { + $setup->getConnection() + ->dropForeignKey( + Module8InstallSchema::SECOND_TABLE, + 'MODULE8_INSTALL_FK_ADDRESS_TEST_MAIN_TABLE_CONTACT_ID' + )->dropIndex( + Module8UpgradeSchema::UPDATE_TABLE, + 'MODULE8_UPDATE_UNIQUE_INDEX_TEMP' + ); + } + + /** + * Remove tables. + * + * @param SchemaSetupInterface $setup + */ + private function removeTables(SchemaSetupInterface $setup): void + { + $setup->getConnection() + ->dropTable( + Module8UpgradeSchema::TEMP_TABLE + ); + } +} diff --git a/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule9/revisions/setup_install_with_converting/module.xml b/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule9/revisions/setup_install_with_converting/module.xml new file mode 100644 index 00000000000..a553b82d781 --- /dev/null +++ b/dev/tests/setup-integration/_files/Magento/TestSetupDeclarationModule9/revisions/setup_install_with_converting/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_TestSetupDeclarationModule9" setup_version="2.0.0"> + <sequence> + <module name="Magento_TestSetupDeclarationModule8"/> + </sequence> + </module> +</config> diff --git a/dev/tests/setup-integration/testsuite/Magento/Developer/Console/Command/SetupInstallTest.php b/dev/tests/setup-integration/testsuite/Magento/Developer/Console/Command/SetupInstallTest.php new file mode 100644 index 00000000000..cf137233ead --- /dev/null +++ b/dev/tests/setup-integration/testsuite/Magento/Developer/Console/Command/SetupInstallTest.php @@ -0,0 +1,126 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Developer\Console\Command; + +use Magento\Framework\Component\ComponentRegistrar; +use Magento\TestFramework\Deploy\CliCommand; +use Magento\TestFramework\Deploy\TestModuleManager; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\SetupTestCase; + +/** + * Test for Install command. + */ +class SetupInstallTest extends SetupTestCase +{ + /** + * @var TestModuleManager + */ + private $moduleManager; + + /** + * @var CliCommand + */ + private $cliCommand; + + /** + * @var ComponentRegistrar + */ + private $componentRegistrar; + + /** + * @inheritdoc + */ + public function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->cliCommand = $objectManager->get(CliCommand::class); + $this->moduleManager = $objectManager->get(TestModuleManager::class); + $this->componentRegistrar = $objectManager->create( + ComponentRegistrar::class + ); + } + + /** + * @moduleName Magento_TestSetupDeclarationModule8 + * @moduleName Magento_TestSetupDeclarationModule9 + * @throws \Exception + */ + public function testInstallWithConverting() + { + $modules = [ + 'Magento_TestSetupDeclarationModule8', + 'Magento_TestSetupDeclarationModule9', + ]; + + foreach ($modules as $moduleName) { + $this->moduleManager->updateRevision( + $moduleName, + 'setup_install_with_converting', + 'InstallSchema.php', + 'Setup' + ); + $this->moduleManager->updateRevision( + $moduleName, + 'setup_install_with_converting', + 'UpgradeSchema.php', + 'Setup' + ); + + $this->moduleManager->updateRevision( + $moduleName, + 'setup_install_with_converting', + 'module.xml', + 'etc' + ); + } + + $this->cliCommand->install($modules, ['convert-old-scripts' => true]); + + foreach ($modules as $moduleName) { + $modulePath = $this->componentRegistrar->getPath('module', $moduleName); + $schemaFileName = $modulePath + . DIRECTORY_SEPARATOR + . \Magento\Framework\Module\Dir::MODULE_ETC_DIR + . DIRECTORY_SEPARATOR + . 'db_schema.xml'; + $generatedSchema = $this->getSchemaDocument($schemaFileName); + + $expectedSchemaFileName = dirname(__DIR__, 2) + . DIRECTORY_SEPARATOR + . implode( + DIRECTORY_SEPARATOR, + [ + '_files', + 'SetupInstall', + str_replace('Magento_', '', $moduleName), + 'db_schema.xml' + ] + ); + $expectedSchema = $this->getSchemaDocument($expectedSchemaFileName); + + $this->assertEquals($expectedSchema->saveXML(), $generatedSchema->saveXML()); + } + } + + /** + * Convert file content in the DOM document. + * + * @param $schemaFileName + * @return \DOMDocument + */ + private function getSchemaDocument($schemaFileName): \DOMDocument + { + $schemaDocument = new \DOMDocument(); + $schemaDocument->preserveWhiteSpace = false; + $schemaDocument->formatOutput = true; + $schemaDocument->loadXML(file_get_contents($schemaFileName)); + + return $schemaDocument; + } +} diff --git a/dev/tests/setup-integration/testsuite/Magento/Developer/Console/Command/SetupUpgradeTest.php b/dev/tests/setup-integration/testsuite/Magento/Developer/Console/Command/SetupUpgradeTest.php new file mode 100644 index 00000000000..932662b58f3 --- /dev/null +++ b/dev/tests/setup-integration/testsuite/Magento/Developer/Console/Command/SetupUpgradeTest.php @@ -0,0 +1,173 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Developer\Console\Command; + +use Magento\Framework\Component\ComponentRegistrar; +use Magento\TestFramework\Deploy\CliCommand; +use Magento\TestFramework\Deploy\TestModuleManager; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\SetupTestCase; + +/** + * Test for Upgrade command. + */ +class SetupUpgradeTest extends SetupTestCase +{ + /** + * @var TestModuleManager + */ + private $moduleManager; + + /** + * @var CliCommand + */ + private $cliCommand; + + /** + * @var ComponentRegistrar + */ + private $componentRegistrar; + + /** + * @inheritdoc + */ + public function setUp() + { + $objectManager = Bootstrap::getObjectManager(); + $this->cliCommand = $objectManager->get(CliCommand::class); + $this->moduleManager = $objectManager->get(TestModuleManager::class); + $this->componentRegistrar = $objectManager->create( + ComponentRegistrar::class + ); + } + + /** + * @moduleName Magento_TestSetupDeclarationModule8 + * @moduleName Magento_TestSetupDeclarationModule9 + * @throws \Exception + */ + public function testUpgradeWithConverting() + { + $modules = [ + 'Magento_TestSetupDeclarationModule8', + 'Magento_TestSetupDeclarationModule9', + ]; + + foreach ($modules as $moduleName) { + $this->moduleManager->updateRevision( + $moduleName, + 'setup_install_with_converting', + 'InstallSchema.php', + 'Setup' + ); + } + + $this->cliCommand->install($modules, ['convert-old-scripts' => true]); + foreach ($modules as $moduleName) { + $this->assertInstallScriptChanges($moduleName); + } + + foreach ($modules as $moduleName) { + $this->moduleManager->updateRevision( + $moduleName, + 'setup_install_with_converting', + 'UpgradeSchema.php', + 'Setup' + ); + + $this->moduleManager->updateRevision( + $moduleName, + 'setup_install_with_converting', + 'module.xml', + 'etc' + ); + } + + $this->cliCommand->upgrade(['convert-old-scripts' => true]); + + foreach ($modules as $moduleName) { + $this->assertUpgradeScriptChanges($moduleName); + } + } + + /** + * Convert file content in the DOM document. + * + * @param string $schemaFileName + * @return \DOMDocument + */ + private function getSchemaDocument(string $schemaFileName): \DOMDocument + { + $schemaDocument = new \DOMDocument(); + $schemaDocument->preserveWhiteSpace = false; + $schemaDocument->formatOutput = true; + $schemaDocument->loadXML(file_get_contents($schemaFileName)); + + return $schemaDocument; + } + + /** + * @param string $moduleName + */ + private function assertInstallScriptChanges(string $moduleName): void + { + $generatedSchema = $this->getGeneratedSchema($moduleName); + $expectedSchema = $this->getSchemaDocument($this->getSchemaFixturePath($moduleName, 'install')); + + $this->assertEquals($expectedSchema->saveXML(), $generatedSchema->saveXML()); + } + + /** + * @param string $moduleName + */ + private function assertUpgradeScriptChanges(string $moduleName): void + { + $generatedSchema = $this->getGeneratedSchema($moduleName); + $expectedSchema = $this->getSchemaDocument($this->getSchemaFixturePath($moduleName, 'upgrade')); + + $this->assertEquals($expectedSchema->saveXML(), $generatedSchema->saveXML()); + } + + /** + * @param string $moduleName + * @return \DOMDocument + */ + private function getGeneratedSchema(string $moduleName): \DOMDocument + { + $modulePath = $this->componentRegistrar->getPath('module', $moduleName); + $schemaFileName = $modulePath + . DIRECTORY_SEPARATOR + . \Magento\Framework\Module\Dir::MODULE_ETC_DIR + . DIRECTORY_SEPARATOR + . 'db_schema.xml'; + + return $this->getSchemaDocument($schemaFileName); + } + + /** + * @param string $moduleName + * @param string $suffix + * @return string + */ + private function getSchemaFixturePath(string $moduleName, string $suffix): string + { + $schemaFixturePath = dirname(__DIR__, 2) + . DIRECTORY_SEPARATOR + . implode( + DIRECTORY_SEPARATOR, + [ + '_files', + 'SetupUpgrade', + str_replace('Magento_', '', $moduleName), + 'db_schema_' . $suffix . '.xml' + ] + ); + + return $schemaFixturePath; + } +} diff --git a/dev/tests/setup-integration/testsuite/Magento/Developer/_files/SetupInstall/TestSetupDeclarationModule8/db_schema.xml b/dev/tests/setup-integration/testsuite/Magento/Developer/_files/SetupInstall/TestSetupDeclarationModule8/db_schema.xml new file mode 100644 index 00000000000..cdc71980bf5 --- /dev/null +++ b/dev/tests/setup-integration/testsuite/Magento/Developer/_files/SetupInstall/TestSetupDeclarationModule8/db_schema.xml @@ -0,0 +1,114 @@ +<?xml version="1.0"?> +<schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd"> + <table name="module8_test_main_table" resource="default" engine="innodb" comment="Main Test Table for Module8"> + <column xsi:type="int" name="module8_email_contact_id" padding="10" unsigned="true" nullable="false" + identity="true" comment="Email Contact ID"/> + <column xsi:type="int" name="module8_contact_group_id" padding="10" unsigned="true" nullable="false" + identity="false" comment="Contact Group ID"/> + <column xsi:type="smallint" name="module8_is_guest" padding="5" unsigned="true" nullable="true" + identity="false" comment="Is Guest"/> + <column xsi:type="varchar" name="module8_contact_id" nullable="true" length="15" comment="Contact ID"/> + <column xsi:type="varchar" name="module8_content" nullable="false" length="15" comment="Content"/> + <column xsi:type="int" name="module8_update_column" padding="11" unsigned="false" nullable="false" + identity="false" comment="Module_8 Update Column"/> + <constraint xsi:type="primary" referenceId="PRIMARY"> + <column name="module8_email_contact_id"/> + </constraint> + <constraint xsi:type="unique" referenceId="MODULE8_INSTALL_UNIQUE_INDEX_2"> + <column name="module8_email_contact_id"/> + <column name="module8_is_guest"/> + </constraint> + <constraint xsi:type="unique" referenceId="MODULE8_INSTALL_UNIQUE_INDEX_TEMP" disabled="true"> + <column name="module8_contact_group_id"/> + </constraint> + <index referenceId="MODULE8_INSTALL_INDEX_1" indexType="btree"> + <column name="module8_email_contact_id"/> + </index> + <index referenceId="MODULE8_INSTALL_INDEX_3" indexType="btree"> + <column name="module8_is_guest"/> + </index> + <index referenceId="MODULE8_INSTALL_INDEX_4" indexType="btree"> + <column name="module8_contact_id"/> + </index> + <index referenceId="MODULE8_INSTALL_INDEX_TEMP" indexType="btree" disabled="true"> + <column name="module8_content"/> + </index> + </table> + <table name="module8_test_second_table" resource="default" engine="innodb" comment="Second Test Table for Module8"> + <column xsi:type="int" name="module8_entity_id" padding="10" unsigned="true" nullable="false" identity="true" + comment="Entity ID"/> + <column xsi:type="int" name="module8_contact_id" padding="11" unsigned="false" nullable="true" + identity="false" comment="Contact ID"/> + <column xsi:type="varchar" name="module8_address" nullable="false" length="15" comment="Address"/> + <column xsi:type="smallint" name="module8_counter_with_multiline_comment" padding="5" unsigned="true" + nullable="true" identity="false" default="0" + comment="Empty Counter Multiline Comment"/> + <column xsi:type="varchar" name="module8_second_address" nullable="true" length="15" comment="Second Address"/> + <column xsi:type="varchar" name="module8_temp_column" nullable="true" length="15" + comment="Temp column for remove" disabled="true"/> + <constraint xsi:type="primary" referenceId="PRIMARY"> + <column name="module8_entity_id"/> + </constraint> + <constraint xsi:type="foreign" referenceId="MODULE8_INSTALL_FK_ENTITY_ID_TEST_MAIN_TABLE_EMAIL_CONTACT_ID" + table="module8_test_second_table" column="module8_entity_id" + referenceTable="module8_test_main_table" referenceColumn="module8_email_contact_id" + onDelete="NO ACTION"/> + <constraint xsi:type="foreign" referenceId="MODULE8_INSTALL_FK_ADDRESS_TEST_MAIN_TABLE_CONTACT_ID" + table="module8_test_second_table" column="module8_address" referenceTable="module8_test_main_table" + referenceColumn="module8_contact_id" onDelete="NO ACTION"/> + <constraint xsi:type="foreign" referenceId="MODULE8_INSTALL_FK_ADDRESS_TEST_MAIN_TABLE_MODULE8_CONTENT_TEMP" + table="module8_test_second_table" column="module8_address" referenceTable="module8_test_main_table" + referenceColumn="module8_content" onDelete="NO ACTION" disabled="true"/> + <index referenceId="MODULE8_INSTALL_SECOND_TABLE_INDEX_1" indexType="btree"> + <column name="module8_entity_id"/> + </index> + <index referenceId="MODULE8_INSTALL_SECOND_TABLE_INDEX_2" indexType="btree"> + <column name="module8_address"/> + </index> + <index referenceId="MODULE8_INSTALL_SECOND_TABLE_INDEX_3_TEMP" indexType="btree"> + <column name="module8_second_address"/> + </index> + </table> + <table name="module8_test_update_table" resource="default" engine="innodb" comment="Update Test Table for Module8"> + <column xsi:type="int" name="module8_entity_id" padding="10" unsigned="true" nullable="false" identity="false" + comment="Entity ID"/> + <column xsi:type="int" name="module8_entity_row_id" padding="10" unsigned="true" nullable="false" + identity="false" comment="Module8_entity_row_id"/> + <column xsi:type="smallint" name="module8_is_guest" padding="5" unsigned="true" nullable="true" + identity="false" comment="Is Guest"/> + <column xsi:type="smallint" name="module8_guest_browser_id" padding="5" unsigned="true" nullable="true" + identity="false" comment="Guest Browser ID"/> + <column xsi:type="smallint" name="module8_column_for_remove" padding="5" unsigned="true" nullable="true" + identity="false" comment="For remove"/> + <constraint xsi:type="primary" referenceId="PRIMARY"> + <column name="module8_entity_id"/> + </constraint> + <constraint xsi:type="foreign" referenceId="MODULE8_UPDATE_FK_MODULE8_IS_GUEST" + table="module8_test_update_table" column="module8_is_guest" referenceTable="module8_test_main_table" + referenceColumn="module8_is_guest" onDelete="CASCADE"/> + <constraint xsi:type="foreign" referenceId="MODULE8_UPDATE_FK_TEMP" table="module8_test_update_table" + column="module8_column_for_remove" referenceTable="module8_test_main_table" + referenceColumn="module8_is_guest" onDelete="CASCADE"/> + <constraint xsi:type="unique" referenceId="MODULE8_UPDATE_UNIQUE_INDEX_TEMP"> + <column name="module8_entity_id"/> + <column name="module8_is_guest"/> + </constraint> + <index referenceId="MODULE8_UPDATE_IS_GUEST_INDEX" indexType="btree"> + <column name="module8_is_guest"/> + </index> + <index referenceId="MODULE8_UPDATE_TEMP_INDEX" indexType="btree"> + <column name="module8_column_for_remove"/> + <column name="module8_guest_browser_id"/> + </index> + </table> + <table name="module8_test_temp_table" resource="default" engine="innodb" comment="module8_test_temp_table"> + <column xsi:type="int" name="module8_entity_id" padding="10" unsigned="true" nullable="false" identity="true" + comment="Entity ID"/> + <column xsi:type="int" name="module8_counter" padding="10" unsigned="true" nullable="true" identity="false" + default="100" comment="Counter"/> + <constraint xsi:type="primary" referenceId="PRIMARY"> + <column name="module8_entity_id"/> + </constraint> + </table> +</schema> diff --git a/dev/tests/setup-integration/testsuite/Magento/Developer/_files/SetupInstall/TestSetupDeclarationModule9/db_schema.xml b/dev/tests/setup-integration/testsuite/Magento/Developer/_files/SetupInstall/TestSetupDeclarationModule9/db_schema.xml new file mode 100644 index 00000000000..3ded03c9e79 --- /dev/null +++ b/dev/tests/setup-integration/testsuite/Magento/Developer/_files/SetupInstall/TestSetupDeclarationModule9/db_schema.xml @@ -0,0 +1,68 @@ +<?xml version="1.0"?> +<schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd"> + <table name="module9_test_main_table" resource="default" engine="innodb" comment="Main Test Table for Module9"> + <column xsi:type="int" name="module9_email_contact_id" padding="10" unsigned="true" nullable="false" + identity="true" comment="Entity ID"/> + <column xsi:type="smallint" name="module9_is_guest" padding="5" unsigned="true" nullable="true" + identity="false" comment="Is Guest"/> + <column xsi:type="int" name="module9_guest_id" padding="10" unsigned="true" nullable="true" identity="false" + comment="Guest ID"/> + <column xsi:type="date" name="module9_created_at" comment="Created At"/> + <column xsi:type="int" name="module9_update_column" padding="11" unsigned="false" nullable="false" + identity="false" comment="Module_9 Update Column"/> + <constraint xsi:type="primary" referenceId="PRIMARY"> + <column name="module9_email_contact_id"/> + </constraint> + <constraint xsi:type="unique" referenceId="MODULE9_INSTALL_UNIQUE_INDEX_1"> + <column name="module9_email_contact_id"/> + <column name="module9_guest_id"/> + </constraint> + <constraint xsi:type="foreign" referenceId="MODULE9_UPDATE_FK_MODULE9_IS_GUEST" table="module9_test_main_table" + column="module9_is_guest" referenceTable="module8_test_main_table" + referenceColumn="module8_is_guest" onDelete="CASCADE"/> + </table> + <table name="module8_test_main_table" resource="default"> + <column xsi:type="int" name="module9_update_column" padding="11" unsigned="false" nullable="false" + identity="false" comment="Module_9 Update Column"/> + </table> + <table name="module8_test_update_table" resource="default"> + <column name="module8_column_for_remove" disabled="true"/> + <constraint xsi:type="foreign" referenceId="MODULE8_UPDATE_FK_TEMP" disabled="true"/> + <index referenceId="MODULE9_UPDATE_MODULE8_GUEST_BROWSER_ID" indexType="btree"> + <column name="module8_guest_browser_id"/> + </index> + <index referenceId="MODULE8_UPDATE_UNIQUE_INDEX_TEMP" disabled="true"/> + </table> + <table name="module8_test_second_table" resource="default"> + <constraint xsi:type="foreign" referenceId="MODULE8_INSTALL_FK_ADDRESS_TEST_MAIN_TABLE_CONTACT_ID" + disabled="true"/> + <index referenceId="MODULE8_INSTALL_SECOND_TABLE_INDEX_3_TEMP" disabled="true"/> + </table> + <table name="module8_test_temp_table" disabled="true" resource="default"/> + <table name="module9_test_update_replica_table" resource="default" engine="innodb" + comment="Module9 Test Update Replica Table"> + <column xsi:type="int" name="module8_entity_id" padding="10" unsigned="true" nullable="false" identity="true" + comment="Module8 Entity Id"/> + <column xsi:type="int" name="module8_contact_id" padding="11" unsigned="false" nullable="true" + identity="false" comment="Module8 Contact Id"/> + <column xsi:type="varchar" name="module8_address" nullable="false" length="15" comment="Module8 Address"/> + <column xsi:type="smallint" name="module8_counter_with_multiline_comment" padding="5" unsigned="true" + nullable="true" identity="false" default="0" comment="Module8 Counter With Multiline Comment"/> + <column xsi:type="varchar" name="module8_second_address" nullable="true" length="15" + comment="Module8 Second Address"/> + <constraint xsi:type="primary" referenceId="PRIMARY"> + <column name="module8_entity_id"/> + </constraint> + <constraint xsi:type="foreign" referenceId="FK_8914AF398964FAFB4ED2E382866ABBF4" + table="module9_test_update_replica_table" column="module8_entity_id" + referenceTable="module8_test_main_table" referenceColumn="module8_email_contact_id" + onDelete="NO ACTION"/> + <index referenceId="MODULE9_TEST_UPDATE_REPLICA_TABLE_MODULE8_ENTITY_ID" indexType="btree"> + <column name="module8_entity_id"/> + </index> + <index referenceId="MODULE9_TEST_UPDATE_REPLICA_TABLE_MODULE8_ADDRESS" indexType="btree"> + <column name="module8_address"/> + </index> + </table> +</schema> diff --git a/dev/tests/setup-integration/testsuite/Magento/Developer/_files/SetupUpgrade/TestSetupDeclarationModule8/db_schema_install.xml b/dev/tests/setup-integration/testsuite/Magento/Developer/_files/SetupUpgrade/TestSetupDeclarationModule8/db_schema_install.xml new file mode 100644 index 00000000000..2da9901cf96 --- /dev/null +++ b/dev/tests/setup-integration/testsuite/Magento/Developer/_files/SetupUpgrade/TestSetupDeclarationModule8/db_schema_install.xml @@ -0,0 +1,81 @@ +<?xml version="1.0"?> +<schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd"> + <table name="module8_test_main_table" resource="default" engine="innodb" comment="Main Test Table for Module8"> + <column xsi:type="int" name="module8_email_contact_id" padding="10" unsigned="true" nullable="false" + identity="true" comment="Email Contact ID"/> + <column xsi:type="int" name="module8_contact_group_id" padding="10" unsigned="true" nullable="false" + identity="false" comment="Contact Group ID"/> + <column xsi:type="smallint" name="module8_is_guest" padding="5" unsigned="true" nullable="true" + identity="false" comment="Is Guest"/> + <column xsi:type="varchar" name="module8_contact_id" nullable="true" length="15" comment="Contact ID"/> + <column xsi:type="varchar" name="module8_content" nullable="false" length="15" comment="Content"/> + <constraint xsi:type="primary" referenceId="PRIMARY"> + <column name="module8_email_contact_id"/> + </constraint> + <constraint xsi:type="unique" referenceId="MODULE8_INSTALL_UNIQUE_INDEX_2"> + <column name="module8_email_contact_id"/> + <column name="module8_is_guest"/> + </constraint> + <constraint xsi:type="unique" referenceId="MODULE8_INSTALL_UNIQUE_INDEX_TEMP"> + <column name="module8_contact_group_id"/> + </constraint> + <index referenceId="MODULE8_INSTALL_INDEX_1" indexType="btree"> + <column name="module8_email_contact_id"/> + </index> + <index referenceId="MODULE8_INSTALL_INDEX_3" indexType="btree"> + <column name="module8_is_guest"/> + </index> + <index referenceId="MODULE8_INSTALL_INDEX_4" indexType="btree"> + <column name="module8_contact_id"/> + </index> + <index referenceId="MODULE8_INSTALL_INDEX_TEMP" indexType="btree"> + <column name="module8_content"/> + </index> + </table> + <table name="module8_test_second_table" resource="default" engine="innodb" comment="Second Test Table for Module8"> + <column xsi:type="int" name="module8_entity_id" padding="10" unsigned="true" nullable="false" identity="true" + comment="Entity ID"/> + <column xsi:type="int" name="module8_contact_id" padding="11" unsigned="false" nullable="true" + identity="false" comment="Contact ID"/> + <column xsi:type="varchar" name="module8_address" nullable="false" length="15" comment="Address"/> + <column xsi:type="smallint" name="module8_counter_with_multiline_comment" padding="5" unsigned="true" + nullable="true" identity="false" default="0" + comment="Empty Counter Multiline Comment"/> + <column xsi:type="varchar" name="module8_second_address" nullable="true" length="15" comment="Second Address"/> + <column xsi:type="varchar" name="module8_temp_column" nullable="true" length="15" + comment="Temp column for remove"/> + <constraint xsi:type="primary" referenceId="PRIMARY"> + <column name="module8_entity_id"/> + </constraint> + <constraint xsi:type="foreign" referenceId="MODULE8_INSTALL_FK_ENTITY_ID_TEST_MAIN_TABLE_EMAIL_CONTACT_ID" + table="module8_test_second_table" column="module8_entity_id" + referenceTable="module8_test_main_table" referenceColumn="module8_email_contact_id" + onDelete="NO ACTION"/> + <constraint xsi:type="foreign" referenceId="MODULE8_INSTALL_FK_ADDRESS_TEST_MAIN_TABLE_CONTACT_ID" + table="module8_test_second_table" column="module8_address" referenceTable="module8_test_main_table" + referenceColumn="module8_contact_id" onDelete="NO ACTION"/> + <constraint xsi:type="foreign" referenceId="MODULE8_INSTALL_FK_ADDRESS_TEST_MAIN_TABLE_MODULE8_CONTENT_TEMP" + table="module8_test_second_table" column="module8_address" referenceTable="module8_test_main_table" + referenceColumn="module8_content" onDelete="NO ACTION"/> + <index referenceId="MODULE8_INSTALL_SECOND_TABLE_INDEX_1" indexType="btree"> + <column name="module8_entity_id"/> + </index> + <index referenceId="MODULE8_INSTALL_SECOND_TABLE_INDEX_2" indexType="btree"> + <column name="module8_address"/> + </index> + <index referenceId="MODULE8_INSTALL_SECOND_TABLE_INDEX_3_TEMP" indexType="btree"> + <column name="module8_second_address"/> + </index> + </table> + <table name="module8_test_install_temp_table" resource="default" engine="innodb" + comment="module8_test_install_temp_table"> + <column xsi:type="int" name="module8_entity_id" padding="10" unsigned="true" nullable="false" identity="true" + comment="Entity ID"/> + <column xsi:type="int" name="module8_counter" padding="10" unsigned="true" nullable="true" identity="false" + default="100" comment="Counter"/> + <constraint xsi:type="primary" referenceId="PRIMARY"> + <column name="module8_entity_id"/> + </constraint> + </table> +</schema> diff --git a/dev/tests/setup-integration/testsuite/Magento/Developer/_files/SetupUpgrade/TestSetupDeclarationModule8/db_schema_upgrade.xml b/dev/tests/setup-integration/testsuite/Magento/Developer/_files/SetupUpgrade/TestSetupDeclarationModule8/db_schema_upgrade.xml new file mode 100644 index 00000000000..6deed3105f2 --- /dev/null +++ b/dev/tests/setup-integration/testsuite/Magento/Developer/_files/SetupUpgrade/TestSetupDeclarationModule8/db_schema_upgrade.xml @@ -0,0 +1,124 @@ +<?xml version="1.0"?> +<schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd"> + <table name="module8_test_main_table" resource="default" engine="innodb" comment="Main Test Table for Module8"> + <column xsi:type="int" name="module8_email_contact_id" padding="10" unsigned="true" nullable="false" + identity="true" comment="Email Contact ID"/> + <column xsi:type="int" name="module8_contact_group_id" padding="10" unsigned="true" nullable="false" + identity="false" comment="Contact Group ID"/> + <column xsi:type="smallint" name="module8_is_guest" padding="5" unsigned="true" nullable="true" + identity="false" comment="Is Guest"/> + <column xsi:type="varchar" name="module8_contact_id" nullable="true" length="15" comment="Contact ID"/> + <column xsi:type="varchar" name="module8_content" nullable="false" length="15" comment="Content"/> + <constraint xsi:type="primary" referenceId="PRIMARY"> + <column name="module8_email_contact_id"/> + </constraint> + <constraint xsi:type="unique" referenceId="MODULE8_INSTALL_UNIQUE_INDEX_2"> + <column name="module8_email_contact_id"/> + <column name="module8_is_guest"/> + </constraint> + <constraint xsi:type="unique" referenceId="MODULE8_INSTALL_UNIQUE_INDEX_TEMP" disabled="true"> + <column name="module8_contact_group_id"/> + </constraint> + <index referenceId="MODULE8_INSTALL_INDEX_1" indexType="btree"> + <column name="module8_email_contact_id"/> + </index> + <index referenceId="MODULE8_INSTALL_INDEX_3" indexType="btree"> + <column name="module8_is_guest"/> + </index> + <index referenceId="MODULE8_INSTALL_INDEX_4" indexType="btree"> + <column name="module8_contact_id"/> + </index> + <index referenceId="MODULE8_INSTALL_INDEX_TEMP" indexType="btree" disabled="true"> + <column name="module8_content"/> + </index> + <column xsi:type="int" name="module8_update_column" padding="11" unsigned="false" nullable="false" + identity="false" comment="Module_8 Update Column"/> + </table> + <table name="module8_test_second_table" resource="default" engine="innodb" comment="Second Test Table for Module8"> + <column xsi:type="int" name="module8_entity_id" padding="10" unsigned="true" nullable="false" identity="true" + comment="Entity ID"/> + <column xsi:type="int" name="module8_contact_id" padding="11" unsigned="false" nullable="true" + identity="false" comment="Contact ID"/> + <column xsi:type="varchar" name="module8_address" nullable="false" length="15" comment="Address"/> + <column xsi:type="smallint" name="module8_counter_with_multiline_comment" padding="5" unsigned="true" + nullable="true" identity="false" default="0" + comment="Empty Counter Multiline Comment"/> + <column xsi:type="varchar" name="module8_second_address" nullable="true" length="15" comment="Second Address"/> + <column xsi:type="varchar" name="module8_temp_column" nullable="true" length="15" + comment="Temp column for remove" disabled="true"/> + <constraint xsi:type="primary" referenceId="PRIMARY"> + <column name="module8_entity_id"/> + </constraint> + <constraint xsi:type="foreign" referenceId="MODULE8_INSTALL_FK_ENTITY_ID_TEST_MAIN_TABLE_EMAIL_CONTACT_ID" + table="module8_test_second_table" column="module8_entity_id" + referenceTable="module8_test_main_table" referenceColumn="module8_email_contact_id" + onDelete="NO ACTION"/> + <constraint xsi:type="foreign" referenceId="MODULE8_INSTALL_FK_ADDRESS_TEST_MAIN_TABLE_CONTACT_ID" + table="module8_test_second_table" column="module8_address" referenceTable="module8_test_main_table" + referenceColumn="module8_contact_id" onDelete="NO ACTION"/> + <constraint xsi:type="foreign" referenceId="MODULE8_INSTALL_FK_ADDRESS_TEST_MAIN_TABLE_MODULE8_CONTENT_TEMP" + table="module8_test_second_table" column="module8_address" referenceTable="module8_test_main_table" + referenceColumn="module8_content" onDelete="NO ACTION" disabled="true"/> + <index referenceId="MODULE8_INSTALL_SECOND_TABLE_INDEX_1" indexType="btree"> + <column name="module8_entity_id"/> + </index> + <index referenceId="MODULE8_INSTALL_SECOND_TABLE_INDEX_2" indexType="btree"> + <column name="module8_address"/> + </index> + <index referenceId="MODULE8_INSTALL_SECOND_TABLE_INDEX_3_TEMP" indexType="btree"> + <column name="module8_second_address"/> + </index> + </table> + <table name="module8_test_install_temp_table" resource="default" engine="innodb" + comment="module8_test_install_temp_table" disabled="true"> + <column xsi:type="int" name="module8_entity_id" padding="10" unsigned="true" nullable="false" identity="true" + comment="Entity ID"/> + <column xsi:type="int" name="module8_counter" padding="10" unsigned="true" nullable="true" identity="false" + default="100" comment="Counter"/> + <constraint xsi:type="primary" referenceId="PRIMARY"> + <column name="module8_entity_id"/> + </constraint> + </table> + <table name="module8_test_update_table" resource="default" engine="innodb" comment="Update Test Table for Module8"> + <column xsi:type="int" name="module8_entity_id" padding="10" unsigned="true" nullable="false" identity="false" + comment="Entity ID"/> + <column xsi:type="int" name="module8_entity_row_id" padding="10" unsigned="true" nullable="false" + identity="false" comment="Module8_entity_row_id"/> + <column xsi:type="smallint" name="module8_is_guest" padding="5" unsigned="true" nullable="true" + identity="false" comment="Is Guest"/> + <column xsi:type="smallint" name="module8_guest_browser_id" padding="5" unsigned="true" nullable="true" + identity="false" comment="Guest Browser ID"/> + <column xsi:type="smallint" name="module8_column_for_remove" padding="5" unsigned="true" nullable="true" + identity="false" comment="For remove"/> + <constraint xsi:type="primary" referenceId="PRIMARY"> + <column name="module8_entity_id"/> + </constraint> + <constraint xsi:type="foreign" referenceId="MODULE8_UPDATE_FK_MODULE8_IS_GUEST" + table="module8_test_update_table" column="module8_is_guest" referenceTable="module8_test_main_table" + referenceColumn="module8_is_guest" onDelete="CASCADE"/> + <constraint xsi:type="foreign" referenceId="MODULE8_UPDATE_FK_TEMP" table="module8_test_update_table" + column="module8_column_for_remove" referenceTable="module8_test_main_table" + referenceColumn="module8_is_guest" onDelete="CASCADE"/> + <constraint xsi:type="unique" referenceId="MODULE8_UPDATE_UNIQUE_INDEX_TEMP"> + <column name="module8_entity_id"/> + <column name="module8_is_guest"/> + </constraint> + <index referenceId="MODULE8_UPDATE_IS_GUEST_INDEX" indexType="btree"> + <column name="module8_is_guest"/> + </index> + <index referenceId="MODULE8_UPDATE_TEMP_INDEX" indexType="btree"> + <column name="module8_column_for_remove"/> + <column name="module8_guest_browser_id"/> + </index> + </table> + <table name="module8_test_temp_table" resource="default" engine="innodb" comment="module8_test_temp_table"> + <column xsi:type="int" name="module8_entity_id" padding="10" unsigned="true" nullable="false" identity="true" + comment="Entity ID"/> + <column xsi:type="int" name="module8_counter" padding="10" unsigned="true" nullable="true" identity="false" + default="100" comment="Counter"/> + <constraint xsi:type="primary" referenceId="PRIMARY"> + <column name="module8_entity_id"/> + </constraint> + </table> +</schema> diff --git a/dev/tests/setup-integration/testsuite/Magento/Developer/_files/SetupUpgrade/TestSetupDeclarationModule9/db_schema_install.xml b/dev/tests/setup-integration/testsuite/Magento/Developer/_files/SetupUpgrade/TestSetupDeclarationModule9/db_schema_install.xml new file mode 100644 index 00000000000..2ac2cc607f0 --- /dev/null +++ b/dev/tests/setup-integration/testsuite/Magento/Developer/_files/SetupUpgrade/TestSetupDeclarationModule9/db_schema_install.xml @@ -0,0 +1,20 @@ +<?xml version="1.0"?> +<schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd"> + <table name="module9_test_main_table" resource="default" engine="innodb" comment="Main Test Table for Module9"> + <column xsi:type="int" name="module9_email_contact_id" padding="10" unsigned="true" nullable="false" + identity="true" comment="Entity ID"/> + <column xsi:type="smallint" name="module9_is_guest" padding="5" unsigned="true" nullable="true" + identity="false" comment="Is Guest"/> + <column xsi:type="int" name="module9_guest_id" padding="10" unsigned="true" nullable="true" identity="false" + comment="Guest ID"/> + <column xsi:type="date" name="module9_created_at" comment="Created At"/> + <constraint xsi:type="primary" referenceId="PRIMARY"> + <column name="module9_email_contact_id"/> + </constraint> + <constraint xsi:type="unique" referenceId="MODULE9_INSTALL_UNIQUE_INDEX_1"> + <column name="module9_email_contact_id"/> + <column name="module9_guest_id"/> + </constraint> + </table> +</schema> diff --git a/dev/tests/setup-integration/testsuite/Magento/Developer/_files/SetupUpgrade/TestSetupDeclarationModule9/db_schema_upgrade.xml b/dev/tests/setup-integration/testsuite/Magento/Developer/_files/SetupUpgrade/TestSetupDeclarationModule9/db_schema_upgrade.xml new file mode 100644 index 00000000000..b522224ca07 --- /dev/null +++ b/dev/tests/setup-integration/testsuite/Magento/Developer/_files/SetupUpgrade/TestSetupDeclarationModule9/db_schema_upgrade.xml @@ -0,0 +1,77 @@ +<?xml version="1.0"?> +<schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd"> + <table name="module9_test_main_table" resource="default" engine="innodb" comment="Main Test Table for Module9"> + <column xsi:type="int" name="module9_email_contact_id" padding="10" unsigned="true" nullable="false" + identity="true" comment="Entity ID"/> + <column xsi:type="smallint" name="module9_is_guest" padding="5" unsigned="true" nullable="true" + identity="false" comment="Is Guest"/> + <column xsi:type="int" name="module9_guest_id" padding="10" unsigned="true" nullable="true" identity="false" + comment="Guest ID"/> + <column xsi:type="date" name="module9_created_at" comment="Created At"/> + <constraint xsi:type="primary" referenceId="PRIMARY"> + <column name="module9_email_contact_id"/> + </constraint> + <constraint xsi:type="unique" referenceId="MODULE9_INSTALL_UNIQUE_INDEX_1"> + <column name="module9_email_contact_id"/> + <column name="module9_guest_id"/> + </constraint> + <column xsi:type="int" name="module9_update_column" padding="11" unsigned="false" nullable="false" + identity="false" comment="Module_9 Update Column"/> + <constraint xsi:type="foreign" referenceId="MODULE9_UPDATE_FK_MODULE9_IS_GUEST" table="module9_test_main_table" + column="module9_is_guest" referenceTable="module8_test_main_table" + referenceColumn="module8_is_guest" onDelete="CASCADE"/> + </table> + <table name="module8_test_main_table" resource="default"> + <column xsi:type="int" name="module9_update_column" padding="11" unsigned="false" nullable="false" + identity="false" comment="Module_9 Update Column"/> + </table> + <table name="module8_test_update_table" resource="default"> + <column name="module8_column_for_remove" disabled="true"/> + <constraint xsi:type="foreign" referenceId="MODULE8_UPDATE_FK_TEMP" disabled="true"/> + <index referenceId="MODULE9_UPDATE_MODULE8_GUEST_BROWSER_ID" indexType="btree"> + <column name="module8_guest_browser_id"/> + </index> + <index referenceId="MODULE8_UPDATE_UNIQUE_INDEX_TEMP" disabled="true"/> + </table> + <table name="module8_test_second_table" resource="default"> + <constraint xsi:type="foreign" referenceId="MODULE8_INSTALL_FK_ADDRESS_TEST_MAIN_TABLE_CONTACT_ID" + disabled="true"/> + <index referenceId="MODULE8_INSTALL_SECOND_TABLE_INDEX_3_TEMP" disabled="true"/> + </table> + <table name="module8_test_temp_table" disabled="true" resource="default"/> + <table name="module9_test_update_replica_table" resource="default" engine="innodb" + comment="Module9 Test Update Replica Table"> + <column xsi:type="int" name="module8_entity_id" padding="10" unsigned="true" nullable="false" identity="true" + comment="Module8 Entity Id"/> + <column xsi:type="int" name="module8_contact_id" padding="11" unsigned="false" nullable="true" + identity="false" comment="Module8 Contact Id"/> + <column xsi:type="varchar" name="module8_address" nullable="false" length="15" comment="Module8 Address"/> + <column xsi:type="smallint" name="module8_counter_with_multiline_comment" padding="5" unsigned="true" + nullable="true" identity="false" default="0" comment="Module8 Counter With Multiline Comment"/> + <column xsi:type="varchar" name="module8_second_address" nullable="true" length="15" + comment="Module8 Second Address"/> + <constraint xsi:type="primary" referenceId="PRIMARY"> + <column name="module8_entity_id"/> + </constraint> + <constraint xsi:type="foreign" referenceId="FK_F205D8789B56A8E75BBFC0C68C041E98" + table="module9_test_update_replica_table" column="module8_address" + referenceTable="module8_test_main_table" referenceColumn="module8_content" onDelete="NO ACTION"/> + <constraint xsi:type="foreign" referenceId="FK_C7075560727757663A51EC925F4032C9" + table="module9_test_update_replica_table" column="module8_address" + referenceTable="module8_test_main_table" referenceColumn="module8_contact_id" onDelete="NO ACTION"/> + <constraint xsi:type="foreign" referenceId="FK_8914AF398964FAFB4ED2E382866ABBF4" + table="module9_test_update_replica_table" column="module8_entity_id" + referenceTable="module8_test_main_table" referenceColumn="module8_email_contact_id" + onDelete="NO ACTION"/> + <index referenceId="MODULE9_TEST_UPDATE_REPLICA_TABLE_MODULE8_ENTITY_ID" indexType="btree"> + <column name="module8_entity_id"/> + </index> + <index referenceId="MODULE9_TEST_UPDATE_REPLICA_TABLE_MODULE8_ADDRESS" indexType="btree"> + <column name="module8_address"/> + </index> + <index referenceId="MODULE9_TEST_UPDATE_REPLICA_TABLE_MODULE8_SECOND_ADDRESS" indexType="btree"> + <column name="module8_second_address"/> + </index> + </table> +</schema> diff --git a/dev/tests/setup-integration/testsuite/Magento/Setup/DeclarativeInstallerTest.php b/dev/tests/setup-integration/testsuite/Magento/Setup/DeclarativeInstallerTest.php index f6497e8e4b1..6097348d4fa 100644 --- a/dev/tests/setup-integration/testsuite/Magento/Setup/DeclarativeInstallerTest.php +++ b/dev/tests/setup-integration/testsuite/Magento/Setup/DeclarativeInstallerTest.php @@ -29,7 +29,7 @@ class DeclarativeInstallerTest extends SetupTestCase /** * @var CliCommand */ - private $cliCommad; + private $cliCommand; /** * @var SchemaDiff @@ -51,11 +51,14 @@ class DeclarativeInstallerTest extends SetupTestCase */ private $describeTable; + /** + * @inheritdoc + */ public function setUp() { $objectManager = Bootstrap::getObjectManager(); $this->moduleManager = $objectManager->get(TestModuleManager::class); - $this->cliCommad = $objectManager->get(CliCommand::class); + $this->cliCommand = $objectManager->get(CliCommand::class); $this->describeTable = $objectManager->get(DescribeTable::class); $this->schemaDiff = $objectManager->get(SchemaDiff::class); $this->schemaConfig = $objectManager->get(SchemaConfigInterface::class); @@ -68,7 +71,7 @@ public function setUp() */ public function testInstallation() { - $this->cliCommad->install( + $this->cliCommand->install( ['Magento_TestSetupDeclarationModule1'] ); @@ -104,10 +107,11 @@ private function compareStructures() /** * @moduleName Magento_TestSetupDeclarationModule1 * @dataProviderFromFile Magento/TestSetupDeclarationModule1/fixture/declarative_installer/column_modification.php + * @throws \Exception */ public function testInstallationWithColumnsModification() { - $this->cliCommad->install( + $this->cliCommand->install( ['Magento_TestSetupDeclarationModule1'] ); @@ -119,7 +123,7 @@ public function testInstallationWithColumnsModification() 'etc' ); - $this->cliCommad->install( + $this->cliCommand->install( ['Magento_TestSetupDeclarationModule1'] ); @@ -157,14 +161,15 @@ private function updateDbSchemaRevision($revisionName) /** * @moduleName Magento_TestSetupDeclarationModule1 * @dataProviderFromFile Magento/TestSetupDeclarationModule1/fixture/declarative_installer/column_removal.php + * @throws \Exception */ public function testInstallationWithColumnsRemoval() { - $this->cliCommad->install( + $this->cliCommand->install( ['Magento_TestSetupDeclarationModule1'] ); $this->updateDbSchemaRevision('column_removals'); - $this->cliCommad->install( + $this->cliCommand->install( ['Magento_TestSetupDeclarationModule1'] ); @@ -195,14 +200,15 @@ private function getTrimmedData() /** * @moduleName Magento_TestSetupDeclarationModule1 * @dataProviderFromFile Magento/TestSetupDeclarationModule1/fixture/declarative_installer/constraint_modification.php + * @throws \Exception */ public function testInstallationWithConstraintsModification() { - $this->cliCommad->install( + $this->cliCommand->install( ['Magento_TestSetupDeclarationModule1'] ); $this->updateDbSchemaRevision('constraint_modifications'); - $this->cliCommad->upgrade(); + $this->cliCommand->upgrade(); $diff = $this->schemaDiff->diff( $this->schemaConfig->getDeclarationConfig(), @@ -216,10 +222,11 @@ public function testInstallationWithConstraintsModification() /** * @moduleName Magento_TestSetupDeclarationModule1 * @dataProviderFromFile Magento/TestSetupDeclarationModule1/fixture/declarative_installer/table_removal.php + * @throws \Exception */ public function testInstallationWithDroppingTables() { - $this->cliCommad->install( + $this->cliCommand->install( ['Magento_TestSetupDeclarationModule1'] ); @@ -231,7 +238,7 @@ public function testInstallationWithDroppingTables() 'etc' ); - $this->cliCommad->upgrade(); + $this->cliCommand->upgrade(); $diff = $this->schemaDiff->diff( $this->schemaConfig->getDeclarationConfig(), @@ -245,6 +252,7 @@ public function testInstallationWithDroppingTables() /** * @moduleName Magento_TestSetupDeclarationModule1 * @dataProviderFromFile Magento/TestSetupDeclarationModule1/fixture/declarative_installer/rollback.php + * @throws \Exception */ public function testInstallWithCodeBaseRollback() { @@ -255,7 +263,7 @@ public function testInstallWithCodeBaseRollback() 'db_schema.xml', 'etc' ); - $this->cliCommad->install( + $this->cliCommand->install( ['Magento_TestSetupDeclarationModule1'] ); $beforeRollback = $this->describeTable->describeShard('default'); @@ -268,7 +276,7 @@ public function testInstallWithCodeBaseRollback() 'etc' ); - $this->cliCommad->upgrade(); + $this->cliCommand->upgrade(); $afterRollback = $this->describeTable->describeShard('default'); self::assertEquals($this->getData()['after'], $afterRollback); } @@ -276,6 +284,7 @@ public function testInstallWithCodeBaseRollback() /** * @moduleName Magento_TestSetupDeclarationModule1 * @dataProviderFromFile Magento/TestSetupDeclarationModule1/fixture/declarative_installer/table_rename.php + * @throws \Exception */ public function testTableRename() { @@ -287,7 +296,7 @@ public function testTableRename() 'db_schema.xml', 'etc' ); - $this->cliCommad->install( + $this->cliCommand->install( ['Magento_TestSetupDeclarationModule1'] ); $before = $this->describeTable->describeShard('default'); @@ -305,7 +314,7 @@ public function testTableRename() 'etc' ); - $this->cliCommad->upgrade(); + $this->cliCommand->upgrade(); $after = $this->describeTable->describeShard('default'); self::assertEquals($this->getData()['after'], $after['some_table_renamed']); $select = $adapter->select() @@ -315,10 +324,11 @@ public function testTableRename() /** * @moduleName Magento_TestSetupDeclarationModule8 + * @throws \Exception */ public function testForeignKeyReferenceId() { - $this->cliCommad->install( + $this->cliCommand->install( ['Magento_TestSetupDeclarationModule8'] ); $this->moduleManager->updateRevision( @@ -327,7 +337,7 @@ public function testForeignKeyReferenceId() 'db_schema.xml', 'etc' ); - $this->cliCommad->upgrade(); + $this->cliCommand->upgrade(); $tableStatements = $this->describeTable->describeShard('default'); $tableSql = $tableStatements['dependent']; $this->assertRegExp('/CONSTRAINT\s`DEPENDENT_PAGE_ID_ON_TEST_TABLE_PAGE_ID`/', $tableSql); @@ -337,10 +347,11 @@ public function testForeignKeyReferenceId() /** * @moduleName Magento_TestSetupDeclarationModule1 * @moduleName Magento_TestSetupDeclarationModule8 + * @throws \Exception */ public function testDisableIndexByExternalModule() { - $this->cliCommad->install( + $this->cliCommand->install( ['Magento_TestSetupDeclarationModule1', 'Magento_TestSetupDeclarationModule8'] ); $this->moduleManager->updateRevision( @@ -367,7 +378,7 @@ public function testDisableIndexByExternalModule() 'module.xml', 'etc' ); - $this->cliCommad->upgrade(); + $this->cliCommand->upgrade(); $tableStatements = $this->describeTable->describeShard('default'); $tableSql = $tableStatements['test_table']; $this->assertNotRegExp( @@ -376,4 +387,36 @@ public function testDisableIndexByExternalModule() 'Index is not being disabled by external module' ); } + + /** + * @moduleName Magento_TestSetupDeclarationModule8 + * @moduleName Magento_TestSetupDeclarationModule9 + * @dataProviderFromFile Magento/TestSetupDeclarationModule9/fixture/declarative_installer/disabling_tables.php + * @throws \Exception + */ + public function testInstallationWithDisablingTables() + { + $modules = [ + 'Magento_TestSetupDeclarationModule8', + 'Magento_TestSetupDeclarationModule9', + ]; + + foreach ($modules as $moduleName) { + $this->moduleManager->updateRevision( + $moduleName, + 'disabling_tables', + 'db_schema.xml', + 'etc' + ); + } + $this->cliCommand->install($modules); + + $diff = $this->schemaDiff->diff( + $this->schemaConfig->getDeclarationConfig(), + $this->schemaConfig->getDbConfig() + ); + self::assertNull($diff->getAll()); + $shardData = $this->describeTable->describeShard(Sharding::DEFAULT_CONNECTION); + self::assertEquals($this->getData(), $shardData); + } } diff --git a/dev/tests/static/framework/Magento/CodeMessDetector/Rule/Design/CookieAndSessionMisuse.php b/dev/tests/static/framework/Magento/CodeMessDetector/Rule/Design/CookieAndSessionMisuse.php new file mode 100644 index 00000000000..ee56158a545 --- /dev/null +++ b/dev/tests/static/framework/Magento/CodeMessDetector/Rule/Design/CookieAndSessionMisuse.php @@ -0,0 +1,184 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\CodeMessDetector\Rule\Design; + +use PDepend\Source\AST\ASTClass; +use PHPMD\AbstractNode; +use PHPMD\AbstractRule; +use PHPMD\Node\ClassNode; +use PHPMD\Rule\ClassAware; + +/** + * Session and Cookies must be used only in HTML Presentation layer. + */ +class CookieAndSessionMisuse extends AbstractRule implements ClassAware +{ + /** + * Is given class a controller? + * + * @param \ReflectionClass $class + * @return bool + */ + private function isController(\ReflectionClass $class): bool + { + return $class->isSubclassOf(\Magento\Framework\App\ActionInterface::class); + } + + /** + * Is given class a block? + * + * @param \ReflectionClass $class + * @return bool + */ + private function isBlock(\ReflectionClass $class): bool + { + return $class->isSubclassOf(\Magento\Framework\View\Element\BlockInterface::class); + } + + /** + * Is given class an HTML UI data provider? + * + * @param \ReflectionClass $class + * @return bool + */ + private function isUiDataProvider(\ReflectionClass $class): bool + { + return $class->isSubclassOf( + \Magento\Framework\View\Element\UiComponent\DataProvider\DataProviderInterface::class + ); + } + + /** + * Is given class an HTML UI Document? + * + * @param \ReflectionClass $class + * @return bool + */ + private function isUiDocument(\ReflectionClass $class): bool + { + return $class->isSubclassOf(\Magento\Framework\View\Element\UiComponent\DataProvider\Document::class) + || $class->getName() === \Magento\Framework\View\Element\UiComponent\DataProvider\Document::class; + } + + /** + * Is given class a plugin for controllers? + * + * @param \ReflectionClass $class + * @return bool + */ + private function isControllerPlugin(\ReflectionClass $class): bool + { + foreach ($class->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) { + if (preg_match('/^(after|around|before).+/i', $method->getName())) { + try { + $argument = $method->getParameters()[0]->getClass(); + } catch (\Throwable $exception) { + //Non-existing class (autogenerated perhaps) or doesn't have an argument. + continue; + } + if ($argument) { + $isAction = $argument->isSubclassOf(\Magento\Framework\App\ActionInterface::class) + || $argument->getName() === \Magento\Framework\App\ActionInterface::class; + if ($isAction) { + return true; + } + } + } + } + + return false; + } + + /** + * Is given class a plugin for blocks? + * + * @param \ReflectionClass $class + * @return bool + */ + private function isBlockPlugin(\ReflectionClass $class): bool + { + foreach ($class->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) { + if (preg_match('/^(after|around|before).+/i', $method->getName())) { + try { + $argument = $method->getParameters()[0]->getClass(); + } catch (\Throwable $exception) { + //Non-existing class (autogenerated perhaps) or doesn't have an argument. + continue; + } + if ($argument) { + $isBlock = $argument->isSubclassOf(\Magento\Framework\View\Element\BlockInterface::class) + || $argument->getName() === \Magento\Framework\View\Element\BlockInterface::class; + if ($isBlock) { + return true; + } + } + } + } + + return false; + } + + /** + * Whether given class depends on classes to pay attention to. + * + * @param \ReflectionClass $class + * @return bool + */ + private function doesUseRestrictedClasses(\ReflectionClass $class): bool + { + $constructor = $class->getConstructor(); + if ($constructor) { + foreach ($constructor->getParameters() as $argument) { + try { + if ($class = $argument->getClass()) { + if ($class->isSubclassOf(\Magento\Framework\Session\SessionManagerInterface::class) + || $class->getName() === \Magento\Framework\Session\SessionManagerInterface::class + || $class->isSubclassOf(\Magento\Framework\Stdlib\Cookie\CookieReaderInterface::class) + || $class->getName() === \Magento\Framework\Stdlib\Cookie\CookieReaderInterface::class + ) { + return true; + } + } + } catch (\ReflectionException $exception) { + //Failed to load the argument's class information + continue; + } + } + } + + return false; + } + + /** + * @inheritdoc + * + * @param ClassNode|ASTClass $node + */ + public function apply(AbstractNode $node) + { + try { + $class = new \ReflectionClass($node->getFullQualifiedName()); + } catch (\Throwable $exception) { + //Failed to load class, nothing we can do + return; + } + + if ($this->doesUseRestrictedClasses($class)) { + if (!$this->isController($class) + && !$this->isBlock($class) + && !$this->isUiDataProvider($class) + && !$this->isUiDocument($class) + && !$this->isControllerPlugin($class) + && !$this->isBlockPlugin($class) + ) { + $this->addViolation($node, [$node->getFullQualifiedName()]); + } + } + } +} diff --git a/dev/tests/static/framework/Magento/CodeMessDetector/Rule/Design/RequestAwareBlockMethod.php b/dev/tests/static/framework/Magento/CodeMessDetector/Rule/Design/RequestAwareBlockMethod.php deleted file mode 100644 index 9ce891da718..00000000000 --- a/dev/tests/static/framework/Magento/CodeMessDetector/Rule/Design/RequestAwareBlockMethod.php +++ /dev/null @@ -1,53 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -declare(strict_types=1); - -namespace Magento\CodeMessDetector\Rule\Design; - -use PHPMD\AbstractNode; -use PHPMD\AbstractRule; -use PHPMD\Node\ClassNode; -use PHPMD\Node\MethodNode; -use PDepend\Source\AST\ASTMethod; -use PHPMD\Rule\MethodAware; - -/** - * Detect direct request usages. - */ -class RequestAwareBlockMethod extends AbstractRule implements MethodAware -{ - /** - * @inheritDoc - * - * @param ASTMethod|MethodNode $method - */ - public function apply(AbstractNode $method) - { - $definedIn = $method->getParentType(); - try { - $isBlock = ($definedIn instanceof ClassNode) - && is_subclass_of( - $definedIn->getFullQualifiedName(), - \Magento\Framework\View\Element\AbstractBlock::class - ); - } catch (\Throwable $exception) { - //Failed to load classes. - return; - } - - if ($isBlock) { - $nodes = $method->findChildrenOfType('PropertyPostfix') + $method->findChildrenOfType('MethodPostfix'); - foreach ($nodes as $node) { - $name = mb_strtolower($node->getFirstChildOfType('Identifier')->getImage()); - if ($name === '_request' || $name === 'getrequest') { - $this->addViolation($method, [$method->getFullQualifiedName()]); - break; - } - } - } - } -} diff --git a/dev/tests/static/framework/Magento/CodeMessDetector/resources/rulesets/design.xml b/dev/tests/static/framework/Magento/CodeMessDetector/resources/rulesets/design.xml index 100e08276e6..73354c46d76 100644 --- a/dev/tests/static/framework/Magento/CodeMessDetector/resources/rulesets/design.xml +++ b/dev/tests/static/framework/Magento/CodeMessDetector/resources/rulesets/design.xml @@ -58,32 +58,30 @@ class PostOrder implements ActionInterface ]]> </example> </rule> - <rule name="RequestAwareBlockMethod" - class="Magento\CodeMessDetector\Rule\Design\RequestAwareBlockMethod" - message="{0} uses request object directly. Add user input validation and suppress this warning."> + <rule name="CookieAndSessionMisuse" + class="Magento\CodeMessDetector\Rule\Design\CookieAndSessionMisuse" + message= "The class {0} uses sessions or cookies while not being a part of HTML Presentation layer"> <description> <![CDATA[ -Blocks must not depend on being used with certain controllers. -If you use request object in a block directly you must validate all user input inside the block. +Sessions and cookies must only be used in classes directly responsible for HTML presentation because Web APIs do not +rely on cookies and sessions. If you need to get current user use Magento\Authorization\Model\UserContextInterface ]]> </description> <priority>2</priority> <properties /> <example> <![CDATA[ -class MyOrder extends AbstractBlock +class OrderProcessor { + public function __construct(SessionManagerInterface $session) { + $this->session = $session; + } - ....... - - public function getOrder() + public function place(OrderInterface $order) { - $orderId = $this->getRequest()->getParam('order_id'); - //Validate customer having such order. - if (!$this->hasOrder($this->getCustomerId(), $orderId)) { - ...deny access... - } - ..... + //Will not be present if processing a WebAPI request + $currentOrder = $this->session->get('current_order'); + ... } } ]]> diff --git a/dev/tests/static/framework/Magento/Sniffs/Annotation/AnnotationFormatValidator.php b/dev/tests/static/framework/Magento/Sniffs/Annotation/AnnotationFormatValidator.php index 899a65c3753..49acd039a09 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Annotation/AnnotationFormatValidator.php +++ b/dev/tests/static/framework/Magento/Sniffs/Annotation/AnnotationFormatValidator.php @@ -168,8 +168,8 @@ private function validateLongDescriptionFormat( ) : void { $tokens = $phpcsFile->getTokens(); $longPtr = $phpcsFile->findNext($emptyTypeTokens, $shortPtrEnd + 1, $commentEndPtr - 1, true); - if (strtolower($tokens[$longPtr]['content']) === '{@inheritdoc}') { - $error = '{@inheritdoc} imports only short description, annotation must have long description'; + if (strtolower($tokens[$longPtr]['content']) === '@inheritdoc') { + $error = '@inheritdoc imports only short description, annotation must have long description'; $phpcsFile->addFixableError($error, $longPtr, 'MethodAnnotation'); } if ($longPtr !== false && $tokens[$longPtr]['code'] === T_DOC_COMMENT_STRING) { diff --git a/dev/tests/static/framework/Magento/Sniffs/Arrays/ShortArraySyntaxSniff.php b/dev/tests/static/framework/Magento/Sniffs/Arrays/ShortArraySyntaxSniff.php index 9013f12b6b4..5ce1ac333cc 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Arrays/ShortArraySyntaxSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/Arrays/ShortArraySyntaxSniff.php @@ -5,13 +5,16 @@ */ namespace Magento\Sniffs\Arrays; -use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Sniffs\Sniff; +/** + * Validate short array syntax is used. + */ class ShortArraySyntaxSniff implements Sniff { /** - * {@inheritdoc} + * @inheritdoc */ public function register() { @@ -19,7 +22,7 @@ public function register() } /** - * {@inheritdoc} + * @inheritdoc */ public function process(File $sourceFile, $stackPtr) { diff --git a/dev/tests/static/framework/Magento/Sniffs/EchoTags/ShortEchoSyntaxSniff.php b/dev/tests/static/framework/Magento/Sniffs/EchoTags/ShortEchoSyntaxSniff.php index 694905cb37a..9d0d58950c4 100644 --- a/dev/tests/static/framework/Magento/Sniffs/EchoTags/ShortEchoSyntaxSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/EchoTags/ShortEchoSyntaxSniff.php @@ -9,10 +9,13 @@ use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Util\Tokens; +/** + * Validate short echo syntax is used. + */ class ShortEchoSyntaxSniff implements Sniff { /** - * {@inheritdoc} + * @inheritdoc */ public function register() { @@ -20,7 +23,7 @@ public function register() } /** - * {@inheritdoc} + * @inheritdoc */ public function process(File $phpcsFile, $stackPtr) { diff --git a/dev/tests/static/framework/Magento/Sniffs/Files/LineLengthSniff.php b/dev/tests/static/framework/Magento/Sniffs/Files/LineLengthSniff.php index 2abcf0531b0..528baeedf04 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Files/LineLengthSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/Files/LineLengthSniff.php @@ -20,7 +20,7 @@ class LineLengthSniff extends FilesLineLengthSniff protected $previousLineContent = ''; /** - * {@inheritdoc} + * @inheritdoc */ protected function checkLineLength($phpcsFile, $stackPtr, $lineContent) { diff --git a/dev/tests/static/framework/Magento/Sniffs/LanguageConstructs/LanguageConstructsSniff.php b/dev/tests/static/framework/Magento/Sniffs/LanguageConstructs/LanguageConstructsSniff.php index a3a49adf62a..575b3954231 100644 --- a/dev/tests/static/framework/Magento/Sniffs/LanguageConstructs/LanguageConstructsSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/LanguageConstructs/LanguageConstructsSniff.php @@ -48,7 +48,7 @@ class LanguageConstructsSniff implements Sniff protected $directOutput = 'DirectOutput'; /** - * {@inheritdoc} + * @inheritdoc */ public function register() { @@ -60,7 +60,7 @@ public function register() } /** - * {@inheritdoc} + * @inheritdoc */ public function process(File $phpcsFile, $stackPtr) { diff --git a/dev/tests/static/framework/Magento/Sniffs/Less/AvoidIdSniff.php b/dev/tests/static/framework/Magento/Sniffs/Less/AvoidIdSniff.php index 49339054f87..4f94e335e03 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Less/AvoidIdSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/Less/AvoidIdSniff.php @@ -13,7 +13,7 @@ * * Ensure that id selector is not used * - * @link http://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#types + * @link https://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#types */ class AvoidIdSniff implements Sniff { diff --git a/dev/tests/static/framework/Magento/Sniffs/Less/BracesFormattingSniff.php b/dev/tests/static/framework/Magento/Sniffs/Less/BracesFormattingSniff.php index 435eea118af..61e2e69eeb8 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Less/BracesFormattingSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/Less/BracesFormattingSniff.php @@ -15,7 +15,7 @@ * Ensure there is a single blank line after the closing brace of a class definition * * @see Squiz_Sniffs_CSS_ClassDefinitionClosingBraceSpaceSniff - * @link http://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#braces + * @link https://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#braces */ class BracesFormattingSniff implements Sniff { @@ -27,7 +27,7 @@ class BracesFormattingSniff implements Sniff public $supportedTokenizers = [TokenizerSymbolsInterface::TOKENIZER_CSS]; /** - * {@inheritdoc} + * @inheritdoc */ public function register() { @@ -35,7 +35,7 @@ public function register() } /** - * {@inheritdoc} + * @inheritdoc */ public function process(File $phpcsFile, $stackPtr) { diff --git a/dev/tests/static/framework/Magento/Sniffs/Less/ClassNamingSniff.php b/dev/tests/static/framework/Magento/Sniffs/Less/ClassNamingSniff.php index 798c13ff269..8c55edc13af 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Less/ClassNamingSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/Less/ClassNamingSniff.php @@ -17,8 +17,7 @@ * - start with a letter (except helper classes); * - words should be separated with dash '-'; * - * @link http://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#standard-classes - * + * @link https://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#standard-classes */ class ClassNamingSniff implements Sniff { @@ -35,7 +34,7 @@ class ClassNamingSniff implements Sniff public $supportedTokenizers = [TokenizerSymbolsInterface::TOKENIZER_CSS]; /** - * {@inheritdoc} + * @inheritdoc */ public function register() { @@ -43,7 +42,7 @@ public function register() } /** - * {@inheritdoc} + * @inheritdoc */ public function process(File $phpcsFile, $stackPtr) { diff --git a/dev/tests/static/framework/Magento/Sniffs/Less/ColonSpacingSniff.php b/dev/tests/static/framework/Magento/Sniffs/Less/ColonSpacingSniff.php index 39753544cf2..b5f61a64327 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Less/ColonSpacingSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/Less/ColonSpacingSniff.php @@ -14,8 +14,7 @@ * * Ensure that single quotes are used * - * @link http://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#properties-colon-indents - * + * @link https://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#properties-colon-indents */ class ColonSpacingSniff implements Sniff { @@ -27,7 +26,7 @@ class ColonSpacingSniff implements Sniff public $supportedTokenizers = [TokenizerSymbolsInterface::TOKENIZER_CSS]; /** - * {@inheritdoc} + * @inheritdoc */ public function register() { @@ -35,7 +34,7 @@ public function register() } /** - * {@inheritdoc} + * @inheritdoc */ public function process(File $phpcsFile, $stackPtr) { diff --git a/dev/tests/static/framework/Magento/Sniffs/Less/ColourDefinitionSniff.php b/dev/tests/static/framework/Magento/Sniffs/Less/ColourDefinitionSniff.php index 83f2e8f5d94..83e3da698f0 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Less/ColourDefinitionSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/Less/ColourDefinitionSniff.php @@ -13,8 +13,7 @@ * * Ensure that hexadecimal values are used for variables not for properties * - * @link http://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#hexadecimal-notation - * + * @link https://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#hexadecimal-notation */ class ColourDefinitionSniff implements Sniff { @@ -26,7 +25,7 @@ class ColourDefinitionSniff implements Sniff public $supportedTokenizers = [TokenizerSymbolsInterface::TOKENIZER_CSS]; /** - * {@inheritdoc} + * @inheritdoc */ public function register() { @@ -34,7 +33,7 @@ public function register() } /** - * {@inheritdoc} + * @inheritdoc */ public function process(File $phpcsFile, $stackPtr) { diff --git a/dev/tests/static/framework/Magento/Sniffs/Less/CombinatorIndentationSniff.php b/dev/tests/static/framework/Magento/Sniffs/Less/CombinatorIndentationSniff.php index d27c853ab78..a8ce4cfe547 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Less/CombinatorIndentationSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/Less/CombinatorIndentationSniff.php @@ -13,8 +13,7 @@ * * Ensure that spaces are used before and after combinators * - * @link http://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#combinator-indents - * + * @link https://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#combinator-indents */ class CombinatorIndentationSniff implements Sniff { @@ -26,7 +25,7 @@ class CombinatorIndentationSniff implements Sniff public $supportedTokenizers = [TokenizerSymbolsInterface::TOKENIZER_CSS]; /** - * {@inheritdoc} + * @inheritdoc */ public function register() { @@ -34,7 +33,7 @@ public function register() } /** - * {@inheritdoc} + * @inheritdoc */ public function process(File $phpcsFile, $stackPtr) { diff --git a/dev/tests/static/framework/Magento/Sniffs/Less/CommentLevelsSniff.php b/dev/tests/static/framework/Magento/Sniffs/Less/CommentLevelsSniff.php index abf15dd3748..41c970d4cb4 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Less/CommentLevelsSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/Less/CommentLevelsSniff.php @@ -15,8 +15,7 @@ * First, second and third level comments should have two spaces after "//". * Inline comments should have one space after "//". * - * @link http://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#comments - * + * @link https://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#comments */ class CommentLevelsSniff implements Sniff { @@ -42,7 +41,7 @@ class CommentLevelsSniff implements Sniff public $supportedTokenizers = [TokenizerSymbolsInterface::TOKENIZER_CSS]; /** - * {@inheritdoc} + * @inheritdoc */ public function register() { @@ -50,7 +49,7 @@ public function register() } /** - * {@inheritdoc} + * @inheritdoc */ public function process(File $phpcsFile, $stackPtr) { diff --git a/dev/tests/static/framework/Magento/Sniffs/Less/ImportantPropertySniff.php b/dev/tests/static/framework/Magento/Sniffs/Less/ImportantPropertySniff.php index 54bd8b70d5c..db03440699f 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Less/ImportantPropertySniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/Less/ImportantPropertySniff.php @@ -13,8 +13,7 @@ * * Ensure that single quotes are used * - * @link http://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#important-property - * + * @link https://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#important-property */ class ImportantPropertySniff implements Sniff { @@ -26,7 +25,7 @@ class ImportantPropertySniff implements Sniff public $supportedTokenizers = [TokenizerSymbolsInterface::TOKENIZER_CSS]; /** - * {@inheritdoc} + * @inheritdoc */ public function register() { @@ -34,7 +33,7 @@ public function register() } /** - * {@inheritdoc} + * @inheritdoc */ public function process(File $phpcsFile, $stackPtr) { diff --git a/dev/tests/static/framework/Magento/Sniffs/Less/IndentationSniff.php b/dev/tests/static/framework/Magento/Sniffs/Less/IndentationSniff.php index 3ff268acf0c..f0f994131e6 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Less/IndentationSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/Less/IndentationSniff.php @@ -14,7 +14,7 @@ * Ensures styles are indented 4 spaces. * * @see Squiz_Sniffs_CSS_IndentationSniff - * @link http://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#indentation + * @link https://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#indentation */ class IndentationSniff implements Sniff { @@ -46,7 +46,7 @@ class IndentationSniff implements Sniff private $styleCodesToSkip = [T_ASPERAND, T_COLON, T_OPEN_PARENTHESIS, T_CLOSE_PARENTHESIS]; /** - * {@inheritdoc} + * @inheritdoc */ public function register() { @@ -54,7 +54,7 @@ public function register() } /** - * {@inheritdoc} + * @inheritdoc * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) */ diff --git a/dev/tests/static/framework/Magento/Sniffs/Less/PropertiesLineBreakSniff.php b/dev/tests/static/framework/Magento/Sniffs/Less/PropertiesLineBreakSniff.php index 3f33d0b2c69..e9bd1f7942e 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Less/PropertiesLineBreakSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/Less/PropertiesLineBreakSniff.php @@ -13,8 +13,7 @@ * * Start each property declaration in a new line * - * @link http://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#properties-line-break - * + * @link https://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#properties-line-break */ class PropertiesLineBreakSniff implements Sniff { @@ -26,7 +25,7 @@ class PropertiesLineBreakSniff implements Sniff public $supportedTokenizers = [TokenizerSymbolsInterface::TOKENIZER_CSS]; /** - * {@inheritdoc} + * @inheritdoc */ public function register() { @@ -34,7 +33,7 @@ public function register() } /** - * {@inheritdoc} + * @inheritdoc */ public function process(File $phpcsFile, $stackPtr) { diff --git a/dev/tests/static/framework/Magento/Sniffs/Less/PropertiesSortingSniff.php b/dev/tests/static/framework/Magento/Sniffs/Less/PropertiesSortingSniff.php index e59b5da37d0..a39974f2b38 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Less/PropertiesSortingSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/Less/PropertiesSortingSniff.php @@ -13,8 +13,7 @@ * * Ensure that properties are sorted alphabetically * - * @link http://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#sorting - * + * @link https://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#sorting */ class PropertiesSortingSniff implements Sniff { @@ -43,7 +42,7 @@ class PropertiesSortingSniff implements Sniff ]; /** - * {@inheritdoc} + * @inheritdoc */ public function register() { @@ -57,7 +56,7 @@ public function register() } /** - * {@inheritdoc} + * @inheritdoc */ public function process(File $phpcsFile, $stackPtr) { diff --git a/dev/tests/static/framework/Magento/Sniffs/Less/QuotesSniff.php b/dev/tests/static/framework/Magento/Sniffs/Less/QuotesSniff.php index 92ea5b42065..8127d267704 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Less/QuotesSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/Less/QuotesSniff.php @@ -13,8 +13,7 @@ * * Ensure that single quotes are used * - * @link http://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#quotes - * + * @link https://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#quotes */ class QuotesSniff implements Sniff { @@ -26,7 +25,7 @@ class QuotesSniff implements Sniff public $supportedTokenizers = [TokenizerSymbolsInterface::TOKENIZER_CSS]; /** - * {@inheritdoc} + * @inheritdoc */ public function register() { @@ -34,7 +33,7 @@ public function register() } /** - * {@inheritdoc} + * @inheritdoc */ public function process(File $phpcsFile, $stackPtr) { diff --git a/dev/tests/static/framework/Magento/Sniffs/Less/SelectorDelimiterSniff.php b/dev/tests/static/framework/Magento/Sniffs/Less/SelectorDelimiterSniff.php index 46607866698..99b91b63d88 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Less/SelectorDelimiterSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/Less/SelectorDelimiterSniff.php @@ -14,8 +14,7 @@ * Ensure that a line break exists after each selector delimiter. * No spaces should be before or after delimiters. * - * @link http://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#selector-delimiters - * + * @link https://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#selector-delimiters */ class SelectorDelimiterSniff implements Sniff { @@ -27,7 +26,7 @@ class SelectorDelimiterSniff implements Sniff public $supportedTokenizers = [TokenizerSymbolsInterface::TOKENIZER_CSS]; /** - * {@inheritdoc} + * @inheritdoc */ public function register() { @@ -35,7 +34,7 @@ public function register() } /** - * {@inheritdoc} + * @inheritdoc */ public function process(File $phpcsFile, $stackPtr) { @@ -50,6 +49,8 @@ public function process(File $phpcsFile, $stackPtr) } /** + * Parenthesis validation. + * * @param File $phpcsFile * @param int $stackPtr * @param array $tokens diff --git a/dev/tests/static/framework/Magento/Sniffs/Less/SemicolonSpacingSniff.php b/dev/tests/static/framework/Magento/Sniffs/Less/SemicolonSpacingSniff.php index c1cd6cde334..de30d9cdbf4 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Less/SemicolonSpacingSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/Less/SemicolonSpacingSniff.php @@ -13,8 +13,7 @@ * * Property should have a semicolon at the end of line * - * @link http://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#end-of-the-property-line - * + * @link https://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#end-of-the-property-line */ class SemicolonSpacingSniff implements Sniff { @@ -44,7 +43,7 @@ class SemicolonSpacingSniff implements Sniff private $styleCodesToSkip = [T_ASPERAND, T_COLON, T_OPEN_PARENTHESIS, T_CLOSE_PARENTHESIS]; /** - * {@inheritdoc} + * @inheritdoc */ public function register() { @@ -52,7 +51,7 @@ public function register() } /** - * {@inheritdoc} + * @inheritdoc */ public function process(File $phpcsFile, $stackPtr) { @@ -72,6 +71,8 @@ public function process(File $phpcsFile, $stackPtr) } /** + * Semicolon validation. + * * @param File $phpcsFile * @param int $stackPtr * @param array $tokens @@ -90,6 +91,8 @@ private function validateSemicolon(File $phpcsFile, $stackPtr, array $tokens, $s } /** + * Spaces validation. + * * @param File $phpcsFile * @param int $stackPtr * @param array $tokens diff --git a/dev/tests/static/framework/Magento/Sniffs/Less/TokenizerSymbolsInterface.php b/dev/tests/static/framework/Magento/Sniffs/Less/TokenizerSymbolsInterface.php index 9da92913b40..db6a2fe1169 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Less/TokenizerSymbolsInterface.php +++ b/dev/tests/static/framework/Magento/Sniffs/Less/TokenizerSymbolsInterface.php @@ -7,7 +7,6 @@ /** * Interface TokenizerSymbolsInterface - * */ interface TokenizerSymbolsInterface { diff --git a/dev/tests/static/framework/Magento/Sniffs/Less/TypeSelectorConcatenationSniff.php b/dev/tests/static/framework/Magento/Sniffs/Less/TypeSelectorConcatenationSniff.php index 88a275baec2..16ebb71c728 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Less/TypeSelectorConcatenationSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/Less/TypeSelectorConcatenationSniff.php @@ -13,8 +13,7 @@ * * Ensure that selector in one line, concatenation is not used * - * @link http://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#formatting-1 - * + * @link https://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#formatting-1 */ class TypeSelectorConcatenationSniff implements Sniff { @@ -34,7 +33,7 @@ class TypeSelectorConcatenationSniff implements Sniff ]; /** - * {@inheritdoc} + * @inheritdoc */ public function register() { @@ -42,7 +41,7 @@ public function register() } /** - * {@inheritdoc} + * @inheritdoc */ public function process(File $phpcsFile, $stackPtr) { diff --git a/dev/tests/static/framework/Magento/Sniffs/Less/TypeSelectorsSniff.php b/dev/tests/static/framework/Magento/Sniffs/Less/TypeSelectorsSniff.php index 3983cf54e05..3cd0f5d1679 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Less/TypeSelectorsSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/Less/TypeSelectorsSniff.php @@ -18,8 +18,7 @@ * - Type selectors must be lowercase * - Write selector in one line, do not use concatenation * - * @link http://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#selectors-naming - * + * @link https://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#selectors-naming */ class TypeSelectorsSniff implements Sniff { @@ -56,7 +55,7 @@ class TypeSelectorsSniff implements Sniff public $supportedTokenizers = [TokenizerSymbolsInterface::TOKENIZER_CSS]; /** - * {@inheritdoc} + * @inheritdoc */ public function register() { @@ -64,7 +63,7 @@ public function register() } /** - * {@inheritdoc} + * @inheritdoc */ public function process(File $phpcsFile, $stackPtr) { diff --git a/dev/tests/static/framework/Magento/Sniffs/Less/VariablesSniff.php b/dev/tests/static/framework/Magento/Sniffs/Less/VariablesSniff.php index 8a559da88e6..433c256c5bd 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Less/VariablesSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/Less/VariablesSniff.php @@ -16,9 +16,8 @@ * they should be located in the module file, in the beginning of the general comment. * - All variable names must be lowercase * - * @link http://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#local-variables - * @link http://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#naming - * + * @link https://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#local-variables + * @link https://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#naming */ class VariablesSniff implements Sniff { @@ -30,7 +29,7 @@ class VariablesSniff implements Sniff public $supportedTokenizers = [TokenizerSymbolsInterface::TOKENIZER_CSS]; /** - * {@inheritdoc} + * @inheritdoc */ public function register() { @@ -38,7 +37,7 @@ public function register() } /** - * {@inheritdoc} + * @inheritdoc */ public function process(File $phpcsFile, $stackPtr) { diff --git a/dev/tests/static/framework/Magento/Sniffs/Less/ZeroUnitsSniff.php b/dev/tests/static/framework/Magento/Sniffs/Less/ZeroUnitsSniff.php index 1b4fb53c450..a19a0a8eb70 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Less/ZeroUnitsSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/Less/ZeroUnitsSniff.php @@ -14,9 +14,8 @@ * Ensure that units for 0 is not specified * Omit leading "0"s in values, use dot instead * - * @link http://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#and-units - * @link http://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#floating-values - * + * @link https://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#and-units + * @link https://devdocs.magento.com/guides/v2.0/coding-standards/code-standard-less.html#floating-values */ class ZeroUnitsSniff implements Sniff { @@ -43,7 +42,7 @@ class ZeroUnitsSniff implements Sniff public $supportedTokenizers = [TokenizerSymbolsInterface::TOKENIZER_CSS]; /** - * {@inheritdoc} + * @inheritdoc */ public function register() { @@ -51,7 +50,7 @@ public function register() } /** - * {@inheritdoc} + * @inheritdoc */ public function process(File $phpcsFile, $stackPtr) { diff --git a/dev/tests/static/framework/Magento/Sniffs/NamingConventions/InterfaceNameSniff.php b/dev/tests/static/framework/Magento/Sniffs/NamingConventions/InterfaceNameSniff.php index 1618beb665d..6a7a0d2b143 100644 --- a/dev/tests/static/framework/Magento/Sniffs/NamingConventions/InterfaceNameSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/NamingConventions/InterfaceNameSniff.php @@ -8,12 +8,15 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; +/** + * Validates that interface name ends with "Interface" suffix. + */ class InterfaceNameSniff implements Sniff { const INTERFACE_SUFFIX = 'Interface'; /** - * {@inheritdoc} + * @inheritdoc */ public function register() { @@ -21,7 +24,7 @@ public function register() } /** - * {@inheritdoc} + * @inheritdoc */ public function process(File $sourceFile, $stackPtr) { diff --git a/dev/tests/static/framework/Magento/Sniffs/NamingConventions/ReservedWordsSniff.php b/dev/tests/static/framework/Magento/Sniffs/NamingConventions/ReservedWordsSniff.php index e3cfdf53243..b81c250338e 100644 --- a/dev/tests/static/framework/Magento/Sniffs/NamingConventions/ReservedWordsSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/NamingConventions/ReservedWordsSniff.php @@ -8,6 +8,9 @@ use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; +/** + * Validates that class name is not reserved word. + */ class ReservedWordsSniff implements Sniff { /** @@ -35,7 +38,7 @@ class ReservedWordsSniff implements Sniff ]; /** - * {@inheritdoc} + * @inheritdoc */ public function register() { @@ -94,7 +97,7 @@ protected function validateClass(File $sourceFile, $stackPtr) } /** - * {@inheritdoc} + * @inheritdoc */ public function process(File $sourceFile, $stackPtr) { diff --git a/dev/tests/static/framework/Magento/Sniffs/Security/ExecutableRegExSniff.php b/dev/tests/static/framework/Magento/Sniffs/Security/ExecutableRegExSniff.php index 0482f574f28..54cf1d462c3 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Security/ExecutableRegExSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/Security/ExecutableRegExSniff.php @@ -52,7 +52,7 @@ class ExecutableRegExSniff implements Sniff ]; /** - * {@inheritdoc} + * @inheritdoc */ public function register() { @@ -60,7 +60,7 @@ public function register() } /** - * {@inheritdoc} + * @inheritdoc */ public function process(File $phpcsFile, $stackPtr) { diff --git a/dev/tests/static/framework/Magento/Sniffs/Strings/StringPositionSniff.php b/dev/tests/static/framework/Magento/Sniffs/Strings/StringPositionSniff.php index d98a55a6f3e..aea1ddc1efa 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Strings/StringPositionSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/Strings/StringPositionSniff.php @@ -106,7 +106,7 @@ class StringPositionSniff implements Sniff ]; /** - * {@inheritdoc} + * @inheritdoc */ public function register() { @@ -114,7 +114,7 @@ public function register() } /** - * {@inheritdoc} + * @inheritdoc */ public function process(File $phpcsFile, $stackPtr) { diff --git a/dev/tests/static/framework/Magento/Sniffs/Translation/ConstantUsageSniff.php b/dev/tests/static/framework/Magento/Sniffs/Translation/ConstantUsageSniff.php index b1e358e4048..50fddb240b3 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Translation/ConstantUsageSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/Translation/ConstantUsageSniff.php @@ -5,8 +5,8 @@ */ namespace Magento\Sniffs\Translation; -use PHP_CodeSniffer\Sniffs\Sniff; use PHP_CodeSniffer\Files\File; +use PHP_CodeSniffer\Sniffs\Sniff; /** * Make sure that constants are not used as the first argument of translation function. @@ -21,7 +21,7 @@ class ConstantUsageSniff implements Sniff protected $previousLineContent = ''; /** - * {@inheritDoc} + * @inheritdoc */ public function register() { @@ -31,7 +31,11 @@ public function register() /** * Copied from \Generic_Sniffs_Files_LineLengthSniff, minor changes made * - * {@inheritDoc} + * {@inheritdoc} + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile + * @param int $stackPtr + * @return void|int */ public function process(File $phpcsFile, $stackPtr) { diff --git a/dev/tests/static/framework/Magento/Sniffs/Whitespace/EmptyLineMissedSniff.php b/dev/tests/static/framework/Magento/Sniffs/Whitespace/EmptyLineMissedSniff.php index de3cfc50bbb..8e34727533b 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Whitespace/EmptyLineMissedSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/Whitespace/EmptyLineMissedSniff.php @@ -14,7 +14,7 @@ class EmptyLineMissedSniff implements Sniff { /** - * {@inheritdoc} + * @inheritdoc */ public function register() { @@ -22,7 +22,7 @@ public function register() } /** - * {@inheritdoc} + * @inheritdoc */ public function process(File $phpcsFile, $stackPtr) { @@ -37,6 +37,8 @@ public function process(File $phpcsFile, $stackPtr) } /** + * Execute empty line missed check. + * * @param File $phpcsFile * @param int $stackPtr * @param array $tokens diff --git a/dev/tests/static/framework/Magento/Sniffs/Whitespace/MultipleEmptyLinesSniff.php b/dev/tests/static/framework/Magento/Sniffs/Whitespace/MultipleEmptyLinesSniff.php index f276426efad..c862b019ae1 100644 --- a/dev/tests/static/framework/Magento/Sniffs/Whitespace/MultipleEmptyLinesSniff.php +++ b/dev/tests/static/framework/Magento/Sniffs/Whitespace/MultipleEmptyLinesSniff.php @@ -14,7 +14,7 @@ class MultipleEmptyLinesSniff implements Sniff { /** - * {@inheritdoc} + * @inheritdoc */ public function register() { @@ -22,7 +22,7 @@ public function register() } /** - * {@inheritdoc} + * @inheritdoc */ public function process(File $phpcsFile, $stackPtr) { diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/ClassesTest.php b/dev/tests/static/testsuite/Magento/Test/Integrity/ClassesTest.php index 6d627574a3a..b5a4e41b632 100644 --- a/dev/tests/static/testsuite/Magento/Test/Integrity/ClassesTest.php +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/ClassesTest.php @@ -194,7 +194,7 @@ private function assertClassesExist(array $classes, string $path): void foreach ($classes as $class) { $class = trim($class, '\\'); try { - if (strrchr($class, '\\') === false and !Classes::isVirtual($class)) { + if (strrchr($class, '\\') === false && !Classes::isVirtual($class)) { $badUsages[] = $class; continue; } else { diff --git a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/copyright/blacklist.php b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/copyright/blacklist.php index 4ff5d001389..242e4ebb22a 100644 --- a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/copyright/blacklist.php +++ b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/copyright/blacklist.php @@ -8,5 +8,6 @@ '/pub\/opt\/magento\/var/', '/COPYING\.txt/', '/setup\/src\/Zend\/Mvc\/Controller\/LazyControllerAbstractFactory\.php/', - '/app\/code\/(?!Magento)[^\/]*/' + '/app\/code\/(?!Magento)[^\/]*/', + '#dev/tests/setup-integration/testsuite/Magento/Developer/_files/\S*\.xml$#', ]; diff --git a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/security/unsecure_php_functions.php b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/security/unsecure_php_functions.php index 1c23f8d8ccf..10c0da47cb2 100644 --- a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/security/unsecure_php_functions.php +++ b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/security/unsecure_php_functions.php @@ -63,6 +63,16 @@ 'type' => 'module', 'name' => 'Magento_AsynchronousOperations', 'path' => 'Model/ResourceModel/System/Message/Collection/Synchronized/Plugin.php' + ], + [ + 'type' => 'module', + 'name' => 'Magento_AuthorizenetAcceptjs', + 'path' => 'Gateway/Validator/TransactionHashValidator.php' + ], + [ + 'type' => 'module', + 'name' => 'Magento_Authorizenet', + 'path' => 'Model/Directpost/Response.php' ] ] ], diff --git a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt index 9c40f33f27a..837fef7a193 100644 --- a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt +++ b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt @@ -207,3 +207,4 @@ Magento/InventoryConfigurableProductIndexer/Indexer Magento/InventoryGroupedProductIndexer/Indexer Magento/Customer/Model/FileUploaderDataResolver.php Magento/Customer/Model/Customer/DataProvider.php +Magento/InventoryShippingAdminUi/Ui/DataProvider diff --git a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpmd/ruleset.xml b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpmd/ruleset.xml index 2d9eb7478ce..7a402818eb0 100644 --- a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpmd/ruleset.xml +++ b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpmd/ruleset.xml @@ -48,6 +48,6 @@ <!-- Magento Specific Rules --> <rule ref="Magento/CodeMessDetector/resources/rulesets/design.xml/FinalImplementation" /> <rule ref="Magento/CodeMessDetector/resources/rulesets/design.xml/AllPurposeAction" /> - <rule ref="Magento/CodeMessDetector/resources/rulesets/design.xml/RequestAwareBlockMethod" /> + <rule ref="Magento/CodeMessDetector/resources/rulesets/design.xml/CookieAndSessionMisuse" /> </ruleset> diff --git a/dev/tests/unit/phpunit.xml.dist b/dev/tests/unit/phpunit.xml.dist index 102c9c41505..94500ff7bdc 100644 --- a/dev/tests/unit/phpunit.xml.dist +++ b/dev/tests/unit/phpunit.xml.dist @@ -12,8 +12,10 @@ beStrictAboutTestsThatDoNotTestAnything="false" bootstrap="./framework/bootstrap.php" > - <testsuite name="Magento Unit Tests"> + <testsuite name="Magento_Unit_Tests_App_Code"> <directory suffix="Test.php">../../../app/code/*/*/Test/Unit</directory> + </testsuite> + <testsuite name="Magento_Unit_Tests_Other"> <directory suffix="Test.php">../../../lib/internal/*/*/Test/Unit</directory> <directory suffix="Test.php">../../../lib/internal/*/*/*/Test/Unit</directory> <directory suffix="Test.php">../../../setup/src/*/*/Test/Unit</directory> diff --git a/lib/internal/LinLibertineFont/ChangeLog.txt b/lib/internal/LinLibertineFont/ChangeLog.txt index 8dc2c56567a..83b8792e71e 100644 --- a/lib/internal/LinLibertineFont/ChangeLog.txt +++ b/lib/internal/LinLibertineFont/ChangeLog.txt @@ -952,7 +952,7 @@ Changes to version 0.5.8 regular(|) & italic(/) (20040315) Changes to version 0.5.7 regular(|) & italic(/) (20040315) -N is now 66pt wider -^ {Ascicircum} is now better -- {exclamdown} is now availible +- {exclamdown} is now available - {currency} has been added | "-" hyphen is the same as softhyphen. length is now 510pt -bars have been made diff --git a/lib/internal/Magento/Framework/App/DeploymentConfig.php b/lib/internal/Magento/Framework/App/DeploymentConfig.php index 615c295675a..40b03b068d6 100644 --- a/lib/internal/Magento/Framework/App/DeploymentConfig.php +++ b/lib/internal/Magento/Framework/App/DeploymentConfig.php @@ -70,6 +70,11 @@ public function get($key = null, $defaultValue = null) if ($key === null) { return $this->flatData; } + + if (array_key_exists($key, $this->flatData) && $this->flatData[$key] === null) { + return ''; + } + return $this->flatData[$key] ?? $defaultValue; } @@ -146,6 +151,8 @@ private function load() } /** + * Array keys conversion + * * Convert associative array of arbitrary depth to a flat associative array with concatenated key path as keys * each level of array is accessible by path key * diff --git a/lib/internal/Magento/Framework/App/ScopeDefault.php b/lib/internal/Magento/Framework/App/ScopeDefault.php index 2ea62387145..e62d19f9ffb 100644 --- a/lib/internal/Magento/Framework/App/ScopeDefault.php +++ b/lib/internal/Magento/Framework/App/ScopeDefault.php @@ -17,7 +17,7 @@ class ScopeDefault implements ScopeInterface */ public function getCode() { - return 'default'; + return ''; } /** @@ -27,7 +27,7 @@ public function getCode() */ public function getId() { - return 1; + return 0; } /** diff --git a/lib/internal/Magento/Framework/Communication/Config/Reader/XmlReader/Converter.php b/lib/internal/Magento/Framework/Communication/Config/Reader/XmlReader/Converter.php index 8fcbd74c884..b79ba49a24d 100644 --- a/lib/internal/Magento/Framework/Communication/Config/Reader/XmlReader/Converter.php +++ b/lib/internal/Magento/Framework/Communication/Config/Reader/XmlReader/Converter.php @@ -124,20 +124,22 @@ protected function extractTopics($config) $requestSchema, $responseSchema ); + $isSynchronous = $this->extractTopicIsSynchronous($topicNode); if ($serviceMethod) { $output[$topicName] = $this->reflectionGenerator->generateTopicConfigForServiceMethod( $topicName, $serviceMethod[ConfigParser::TYPE_NAME], $serviceMethod[ConfigParser::METHOD_NAME], - $handlers + $handlers, + $isSynchronous ); } elseif ($requestSchema && $responseSchema) { $output[$topicName] = [ Config::TOPIC_NAME => $topicName, - Config::TOPIC_IS_SYNCHRONOUS => true, + Config::TOPIC_IS_SYNCHRONOUS => $isSynchronous, Config::TOPIC_REQUEST => $requestSchema, Config::TOPIC_REQUEST_TYPE => Config::TOPIC_REQUEST_TYPE_CLASS, - Config::TOPIC_RESPONSE => $responseSchema, + Config::TOPIC_RESPONSE => ($isSynchronous) ? $responseSchema: null, Config::TOPIC_HANDLERS => $handlers ]; } elseif ($requestSchema) { @@ -258,4 +260,20 @@ protected function parseServiceMethod($serviceMethod, $topicName) ); return $parsedServiceMethod; } + + /** + * Extract is_synchronous topic value. + * + * @param \DOMNode $topicNode + * @return bool + */ + private function extractTopicIsSynchronous($topicNode): bool + { + $attributeName = Config::TOPIC_IS_SYNCHRONOUS; + $topicAttributes = $topicNode->attributes; + if (!$topicAttributes->getNamedItem($attributeName)) { + return true; + } + return $this->booleanUtils->toBoolean($topicAttributes->getNamedItem($attributeName)->nodeValue); + } } diff --git a/lib/internal/Magento/Framework/Communication/Config/ReflectionGenerator.php b/lib/internal/Magento/Framework/Communication/Config/ReflectionGenerator.php index d1bc62464f2..7ef84f1c43b 100644 --- a/lib/internal/Magento/Framework/Communication/Config/ReflectionGenerator.php +++ b/lib/internal/Magento/Framework/Communication/Config/ReflectionGenerator.php @@ -42,7 +42,10 @@ public function extractMethodMetadata($className, $methodName) $result = [ Config::SCHEMA_METHOD_PARAMS => [], Config::SCHEMA_METHOD_RETURN_TYPE => $this->methodsMap->getMethodReturnType($className, $methodName), - Config::SCHEMA_METHOD_HANDLER => [Config::HANDLER_TYPE => $className, Config::HANDLER_METHOD => $methodName] + Config::SCHEMA_METHOD_HANDLER => [ + Config::HANDLER_TYPE => $className, + Config::HANDLER_METHOD => $methodName + ] ]; $paramsMeta = $this->methodsMap->getMethodParams($className, $methodName); foreach ($paramsMeta as $paramPosition => $paramMeta) { @@ -63,16 +66,27 @@ public function extractMethodMetadata($className, $methodName) * @param string $serviceType * @param string $serviceMethod * @param array|null $handlers + * @param bool|null $isSynchronous * @return array */ - public function generateTopicConfigForServiceMethod($topicName, $serviceType, $serviceMethod, $handlers = []) - { + public function generateTopicConfigForServiceMethod( + $topicName, + $serviceType, + $serviceMethod, + $handlers = [], + $isSynchronous = null + ) { $methodMetadata = $this->extractMethodMetadata($serviceType, $serviceMethod); $returnType = $methodMetadata[Config::SCHEMA_METHOD_RETURN_TYPE]; $returnType = ($returnType != 'void' && $returnType != 'null') ? $returnType : null; + if (!isset($isSynchronous)) { + $isSynchronous = $returnType ? true : false; + } else { + $returnType = ($isSynchronous) ? $returnType : null; + } return [ Config::TOPIC_NAME => $topicName, - Config::TOPIC_IS_SYNCHRONOUS => $returnType ? true : false, + Config::TOPIC_IS_SYNCHRONOUS => $isSynchronous, Config::TOPIC_REQUEST => $methodMetadata[Config::SCHEMA_METHOD_PARAMS], Config::TOPIC_REQUEST_TYPE => Config::TOPIC_REQUEST_TYPE_METHOD, Config::TOPIC_RESPONSE => $returnType, @@ -85,7 +99,8 @@ public function generateTopicConfigForServiceMethod($topicName, $serviceType, $s * Generate topic name based on service type and method name. * * Perform the following conversion: - * \Magento\Customer\Api\RepositoryInterface + getById => magento.customer.api.repositoryInterface.getById + * \Magento\Customer\Api\RepositoryInterface + getById => + * magento.customer.api.repositoryInterface.getById * * @param string $typeName * @param string $methodName diff --git a/lib/internal/Magento/Framework/Communication/etc/communication.xsd b/lib/internal/Magento/Framework/Communication/etc/communication.xsd index 12ee56371ce..678d89f30c5 100644 --- a/lib/internal/Magento/Framework/Communication/etc/communication.xsd +++ b/lib/internal/Magento/Framework/Communication/etc/communication.xsd @@ -40,6 +40,7 @@ <xs:attribute type="schemaType" name="schema" use="optional"/> <xs:attribute type="xs:string" name="request" use="optional"/> <xs:attribute type="xs:string" name="response" use="optional"/> + <xs:attribute type="xs:boolean" name="is_synchronous" use="optional"/> </xs:complexType> <xs:complexType name="handlerType"> <xs:attribute type="xs:string" name="name" use="required"/> diff --git a/lib/internal/Magento/Framework/DB/Adapter/AdapterInterface.php b/lib/internal/Magento/Framework/DB/Adapter/AdapterInterface.php index 5c9bc9c2fb2..f654fd263f6 100644 --- a/lib/internal/Magento/Framework/DB/Adapter/AdapterInterface.php +++ b/lib/internal/Magento/Framework/DB/Adapter/AdapterInterface.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Framework\DB\Adapter; use Magento\Framework\DB\Ddl\Table; @@ -365,6 +366,7 @@ public function getIndexList($tableName, $schemaName = null); /** * Add new Foreign Key to table + * * If Foreign Key with same name is exist - it will be deleted * * @param string $fkName @@ -373,7 +375,6 @@ public function getIndexList($tableName, $schemaName = null); * @param string $refTableName * @param string $refColumnName * @param string $onDelete - * @param string $onUpdate * @param boolean $purge trying remove invalid data * @param string $schemaName * @param string $refSchemaName @@ -484,6 +485,7 @@ public function insert($table, array $bind); /** * Inserts a table row with specified data + * * Special for Zero values to identity column * * @param string $table @@ -502,9 +504,9 @@ public function insertForce($table, array $bind); * If the $where parameter is an array of multiple clauses, they will be joined by AND, with each clause wrapped in * parenthesis. If you wish to use an OR, you must give a single clause that is an instance of {@see Zend_Db_Expr} * - * @param mixed $table The table to update. - * @param array $bind Column-value pairs. - * @param mixed $where UPDATE WHERE clause(s). + * @param mixed $table The table to update. + * @param array $bind Column-value pairs. + * @param mixed $where UPDATE WHERE clause(s). * @return int The number of affected rows. */ public function update($table, array $bind, $where = ''); @@ -512,8 +514,8 @@ public function update($table, array $bind, $where = ''); /** * Deletes table rows based on a WHERE clause. * - * @param mixed $table The table to update. - * @param mixed $where DELETE WHERE clause(s). + * @param mixed $table The table to update. + * @param mixed $where DELETE WHERE clause(s). * @return int The number of affected rows. */ public function delete($table, $where = ''); @@ -521,31 +523,33 @@ public function delete($table, $where = ''); /** * Prepares and executes an SQL statement with bound data. * - * @param mixed $sql The SQL statement with placeholders. + * @param mixed $sql The SQL statement with placeholders. * May be a string or \Magento\Framework\DB\Select. - * @param mixed $bind An array of data or data itself to bind to the placeholders. + * @param mixed $bind An array of data or data itself to bind to the placeholders. * @return \Zend_Db_Statement_Interface */ public function query($sql, $bind = []); /** * Fetches all SQL result rows as a sequential array. + * * Uses the current fetchMode for the adapter. * - * @param string|\Magento\Framework\DB\Select $sql An SQL SELECT statement. - * @param mixed $bind Data to bind into SELECT placeholders. - * @param mixed $fetchMode Override current fetch mode. + * @param string|\Magento\Framework\DB\Select $sql An SQL SELECT statement. + * @param mixed $bind Data to bind into SELECT placeholders. + * @param mixed $fetchMode Override current fetch mode. * @return array */ public function fetchAll($sql, $bind = [], $fetchMode = null); /** * Fetches the first row of the SQL result. + * * Uses the current fetchMode for the adapter. * * @param string|\Magento\Framework\DB\Select $sql An SQL SELECT statement. * @param mixed $bind Data to bind into SELECT placeholders. - * @param mixed $fetchMode Override current fetch mode. + * @param mixed $fetchMode Override current fetch mode. * @return array */ public function fetchRow($sql, $bind = [], $fetchMode = null); @@ -622,9 +626,9 @@ public function quote($value, $type = null); * // $safe = "WHERE date < '2005-01-02'" * </code> * - * @param string $text The text with a placeholder. - * @param mixed $value The value to quote. - * @param string $type OPTIONAL SQL datatype + * @param string $text The text with a placeholder. + * @param mixed $value The value to quote. + * @param string $type OPTIONAL SQL datatype * @param integer $count OPTIONAL count of placeholders to replace * @return string An SQL-safe quoted value placed into the original text. */ @@ -633,7 +637,7 @@ public function quoteInto($text, $value, $type = null, $count = null); /** * Quotes an identifier. * - * Accepts a string representing a qualified indentifier. For Example: + * Accepts a string representing a qualified identifier. For Example: * <code> * $adapter->quoteIdentifier('myschema.mytable') * </code> @@ -721,7 +725,8 @@ public function disallowDdlCache(); /** * Reset cached DDL data from cache - * if table name is null - reset all cached DDL data + * + * If table name is null - reset all cached DDL data * * @param string $tableName * @param string $schemaName OPTIONAL @@ -741,6 +746,7 @@ public function saveDdlCache($tableCacheKey, $ddlType, $data); /** * Load DDL data from cache + * * Return false if cache does not exists * * @param string $tableCacheKey the table cache key @@ -784,6 +790,7 @@ public function prepareSqlCondition($fieldName, $condition); /** * Prepare value for save in column + * * Return converted to column data type value * * @param array $column the column describe array @@ -813,6 +820,7 @@ public function getIfNullSql($expression, $value = 0); /** * Generate fragment of SQL, that combine together (concatenate) the results from data array + * * All arguments in data must be quoted * * @param array $data @@ -823,6 +831,7 @@ public function getConcatSql(array $data, $separator = null); /** * Generate fragment of SQL that returns length of character string + * * The string argument must be quoted * * @param string $string @@ -931,6 +940,7 @@ public function getDateExtractSql($date, $unit); /** * Retrieve valid table name + * * Check table name length and allowed symbols * * @param string $tableName @@ -950,6 +960,7 @@ public function getTriggerName($tableName, $time, $event); /** * Retrieve valid index name + * * Check index name length and allowed symbols * * @param string $tableName @@ -961,6 +972,7 @@ public function getIndexName($tableName, $fields, $indexType = ''); /** * Retrieve valid foreign key name + * * Check foreign key name length and allowed symbols * * @param string $priTableName @@ -1047,6 +1059,7 @@ public function supportStraightJoin(); /** * Adds order by random to select object + * * Possible using integer field for optimization * * @param \Magento\Framework\DB\Select $select @@ -1074,6 +1087,7 @@ public function getPrimaryKeyName($tableName, $schemaName = null); /** * Converts fetched blob into raw binary PHP data. + * * Some DB drivers return blobs as hex-coded strings, so we need to process them. * * @param mixed $value @@ -1114,6 +1128,8 @@ public function dropTrigger($triggerName, $schemaName = null); public function getTables($likeCondition = null); /** + * Generates case SQL fragment + * * Generate fragment of SQL, that check value against multiple condition cases * and return different result depends on them * diff --git a/lib/internal/Magento/Framework/DB/Adapter/Pdo/Mysql.php b/lib/internal/Magento/Framework/DB/Adapter/Pdo/Mysql.php index 86266ec23fe..90186707177 100644 --- a/lib/internal/Magento/Framework/DB/Adapter/Pdo/Mysql.php +++ b/lib/internal/Magento/Framework/DB/Adapter/Pdo/Mysql.php @@ -2915,6 +2915,7 @@ public function endSetup() * - array("gteq" => $greaterOrEqualValue) * - array("lteq" => $lessOrEqualValue) * - array("finset" => $valueInSet) + * - array("nfinset" => $valueNotInSet) * - array("regexp" => $regularExpression) * - array("seq" => $stringValue) * - array("sneq" => $stringValue) @@ -2944,6 +2945,7 @@ public function prepareSqlCondition($fieldName, $condition) 'gteq' => "{{fieldName}} >= ?", 'lteq' => "{{fieldName}} <= ?", 'finset' => "FIND_IN_SET(?, {{fieldName}})", + 'nfinset' => "NOT FIND_IN_SET(?, {{fieldName}})", 'regexp' => "{{fieldName}} REGEXP ?", 'from' => "{{fieldName}} >= ?", 'to' => "{{fieldName}} <= ?", diff --git a/lib/internal/Magento/Framework/Data/Collection.php b/lib/internal/Magento/Framework/Data/Collection.php index 9c789e81913..dbafc9734e0 100644 --- a/lib/internal/Magento/Framework/Data/Collection.php +++ b/lib/internal/Magento/Framework/Data/Collection.php @@ -7,6 +7,7 @@ use Magento\Framework\Data\Collection\EntityFactoryInterface; use Magento\Framework\Option\ArrayInterface; +use Magento\Framework\Exception\InputException; /** * Data collection @@ -234,12 +235,20 @@ protected function _setIsLoaded($flag = true) * Get current collection page * * @param int $displacement + * @throws \Magento\Framework\Exception\InputException * @return int */ public function getCurPage($displacement = 0) { if ($this->_curPage + $displacement < 1) { return 1; + } elseif ($this->_curPage > $this->getLastPageNumber() && $displacement === 0) { + throw new InputException( + __( + 'currentPage value %1 specified is greater than the %2 page(s) available.', + [$this->_curPage, $this->getLastPageNumber()] + ) + ); } elseif ($this->_curPage + $displacement > $this->getLastPageNumber()) { return $this->getLastPageNumber(); } else { diff --git a/lib/internal/Magento/Framework/Data/Form/Element/AbstractElement.php b/lib/internal/Magento/Framework/Data/Form/Element/AbstractElement.php index a8451e43ade..3638ff921fa 100644 --- a/lib/internal/Magento/Framework/Data/Form/Element/AbstractElement.php +++ b/lib/internal/Magento/Framework/Data/Form/Element/AbstractElement.php @@ -201,6 +201,8 @@ public function setType($type) } /** + * Set form. + * * @param AbstractForm $form * @return $this */ @@ -238,6 +240,7 @@ public function getHtmlAttributes() 'onchange', 'disabled', 'readonly', + 'autocomplete', 'tabindex', 'placeholder', 'data-form-part', @@ -326,6 +329,8 @@ public function getRenderer() } /** + * Get Ui Id. + * * @param null|string $suffix * @return string */ diff --git a/lib/internal/Magento/Framework/Data/Form/Element/Date.php b/lib/internal/Magento/Framework/Data/Form/Element/Date.php index c519ecfed4c..897617e560b 100644 --- a/lib/internal/Magento/Framework/Data/Form/Element/Date.php +++ b/lib/internal/Magento/Framework/Data/Form/Element/Date.php @@ -151,7 +151,7 @@ public function getValueInstance() */ public function getElementHtml() { - $this->addClass('admin__control-text input-text'); + $this->addClass('admin__control-text input-text input-date'); $dateFormat = $this->getDateFormat() ?: $this->getFormat(); $timeFormat = $this->getTimeFormat(); if (empty($dateFormat)) { diff --git a/lib/internal/Magento/Framework/Data/Test/Unit/CollectionTest.php b/lib/internal/Magento/Framework/Data/Test/Unit/CollectionTest.php index 5608959c110..80c256d8553 100644 --- a/lib/internal/Magento/Framework/Data/Test/Unit/CollectionTest.php +++ b/lib/internal/Magento/Framework/Data/Test/Unit/CollectionTest.php @@ -5,6 +5,9 @@ */ namespace Magento\Framework\Data\Test\Unit; +/** + * Class for Collection test. + */ class CollectionTest extends \PHPUnit\Framework\TestCase { /** @@ -12,6 +15,9 @@ class CollectionTest extends \PHPUnit\Framework\TestCase */ protected $_model; + /** + * Set up. + */ protected function setUp() { $this->_model = new \Magento\Framework\Data\Collection( @@ -19,6 +25,11 @@ protected function setUp() ); } + /** + * Test for method removeAllItems. + * + * @return void + */ public function testRemoveAllItems() { $this->_model->addItem(new \Magento\Framework\DataObject()); @@ -30,6 +41,7 @@ public function testRemoveAllItems() /** * Test loadWithFilter() + * * @return void */ public function testLoadWithFilter() @@ -42,6 +54,8 @@ public function testLoadWithFilter() } /** + * Test for method etItemObjectClass + * * @dataProvider setItemObjectClassDataProvider */ public function testSetItemObjectClass($class) @@ -51,6 +65,8 @@ public function testSetItemObjectClass($class) } /** + * Data provider. + * * @return array */ public function setItemObjectClassDataProvider() @@ -59,6 +75,8 @@ public function setItemObjectClassDataProvider() } /** + * Test for method setItemObjectClass with exception. + * * @expectedException \InvalidArgumentException * @expectedExceptionMessage Incorrect_ClassName does not extend \Magento\Framework\DataObject */ @@ -67,12 +85,22 @@ public function testSetItemObjectClassException() $this->_model->setItemObjectClass('Incorrect_ClassName'); } + /** + * Test for method addFilter. + * + * @return void + */ public function testAddFilter() { $this->_model->addFilter('field1', 'value'); $this->assertEquals('field1', $this->_model->getFilter('field1')->getData('field')); } + /** + * Test for method getFilters. + * + * @return void + */ public function testGetFilters() { $this->_model->addFilter('field1', 'value'); @@ -81,12 +109,22 @@ public function testGetFilters() $this->assertEquals('field2', $this->_model->getFilter(['field1', 'field2'])[1]->getData('field')); } + /** + * Test for method get non existion filters. + * + * @return void + */ public function testGetNonExistingFilters() { $this->assertEmpty($this->_model->getFilter([])); $this->assertEmpty($this->_model->getFilter('non_existing_filter')); } + /** + * Test for lag. + * + * @return void + */ public function testFlag() { $this->_model->setFlag('flag_name', 'flag_value'); @@ -95,12 +133,35 @@ public function testFlag() $this->assertNull($this->_model->getFlag('non_existing_flag')); } + /** + * Test for method getCurPage. + * + * @return void + */ public function testGetCurPage() { - $this->_model->setCurPage(10); + $this->_model->setCurPage(1); $this->assertEquals(1, $this->_model->getCurPage()); } + /** + * Test for getCurPage with exception. + * + * @expectedException \Magento\Framework\Exception\StateException + * @return void + */ + public function testGetCurPageWithException() + { + $this->_model->setCurPage(10); + $this->expectException(\Magento\Framework\Exception\InputException::class); + $this->_model->getCurPage(); + } + + /** + * Test for method possibleFlowWithItem. + * + * @return void + */ public function testPossibleFlowWithItem() { $firstItemMock = $this->createPartialMock( @@ -168,6 +229,11 @@ public function testPossibleFlowWithItem() $this->assertEquals([], $this->_model->getItems()); } + /** + * Test for method eachCallsMethodOnEachItemWithNoArgs. + * + * @return void + */ public function testEachCallsMethodOnEachItemWithNoArgs() { for ($i = 0; $i < 3; $i++) { @@ -177,7 +243,12 @@ public function testEachCallsMethodOnEachItemWithNoArgs() } $this->_model->each('testCallback'); } - + + /** + * Test for method eachCallsMethodOnEachItemWithArgs. + * + * @return void + */ public function testEachCallsMethodOnEachItemWithArgs() { for ($i = 0; $i < 3; $i++) { @@ -188,6 +259,11 @@ public function testEachCallsMethodOnEachItemWithArgs() $this->_model->each('testCallback', ['a', 'b', 'c']); } + /** + * Test for method callsClosureWithEachItemAndNoArgs. + * + * @return void + */ public function testCallsClosureWithEachItemAndNoArgs() { for ($i = 0; $i < 3; $i++) { @@ -200,6 +276,11 @@ public function testCallsClosureWithEachItemAndNoArgs() }); } + /** + * Test for method callsClosureWithEachItemAndArgs. + * + * @return void + */ public function testCallsClosureWithEachItemAndArgs() { for ($i = 0; $i < 3; $i++) { @@ -212,6 +293,11 @@ public function testCallsClosureWithEachItemAndArgs() }, ['a', 'b', 'c']); } + /** + * Test for method callsCallableArrayWithEachItemNoArgs. + * + * @return void + */ public function testCallsCallableArrayWithEachItemNoArgs() { $mockCallbackObject = $this->getMockBuilder('DummyEachCallbackInstance') @@ -230,6 +316,11 @@ public function testCallsCallableArrayWithEachItemNoArgs() $this->_model->each([$mockCallbackObject, 'testObjCallback']); } + /** + * Test for method callsCallableArrayWithEachItemAndArgs. + * + * @return void + */ public function testCallsCallableArrayWithEachItemAndArgs() { $mockCallbackObject = $this->getMockBuilder('DummyEachCallbackInstance') diff --git a/lib/internal/Magento/Framework/Data/Test/Unit/Form/Element/AbstractElementTest.php b/lib/internal/Magento/Framework/Data/Test/Unit/Form/Element/AbstractElementTest.php index e29b1dcf441..a85c1f4aa45 100644 --- a/lib/internal/Magento/Framework/Data/Test/Unit/Form/Element/AbstractElementTest.php +++ b/lib/internal/Magento/Framework/Data/Test/Unit/Form/Element/AbstractElementTest.php @@ -195,6 +195,7 @@ public function testGetHtmlAttributes() 'onchange', 'disabled', 'readonly', + 'autocomplete', 'tabindex', 'placeholder', 'data-form-part', diff --git a/lib/internal/Magento/Framework/Encryption/Encryptor.php b/lib/internal/Magento/Framework/Encryption/Encryptor.php index 676feac5ed0..791e6d72b95 100644 --- a/lib/internal/Magento/Framework/Encryption/Encryptor.php +++ b/lib/internal/Magento/Framework/Encryption/Encryptor.php @@ -9,6 +9,7 @@ namespace Magento\Framework\Encryption; use Magento\Framework\App\DeploymentConfig; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Encryption\Adapter\EncryptionAdapterInterface; use Magento\Framework\Encryption\Helper\Security; use Magento\Framework\Math\Random; @@ -115,20 +116,28 @@ class Encryptor implements EncryptorInterface */ private $random; + /** + * @var KeyValidator + */ + private $keyValidator; + /** * Encryptor constructor. * @param Random $random * @param DeploymentConfig $deploymentConfig + * @param KeyValidator|null $keyValidator */ public function __construct( Random $random, - DeploymentConfig $deploymentConfig + DeploymentConfig $deploymentConfig, + KeyValidator $keyValidator = null ) { $this->random = $random; // load all possible keys $this->keys = preg_split('/\s+/s', trim((string)$deploymentConfig->get(self::PARAM_CRYPT_KEY))); $this->keyVersion = count($this->keys) - 1; + $this->keyValidator = $keyValidator ?: ObjectManager::getInstance()->get(KeyValidator::class); } /** @@ -374,8 +383,12 @@ public function decrypt($data) */ public function validateKey($key) { - if (preg_match('/\s/s', $key)) { - throw new \Exception((string)new \Magento\Framework\Phrase('The encryption key format is invalid.')); + if (!$this->keyValidator->isValid($key)) { + throw new \Exception( + (string)new \Magento\Framework\Phrase( + 'Encryption key must be 32 character string without any white space.' + ) + ); } } diff --git a/lib/internal/Magento/Framework/Encryption/KeyValidator.php b/lib/internal/Magento/Framework/Encryption/KeyValidator.php new file mode 100644 index 00000000000..79d592bec2a --- /dev/null +++ b/lib/internal/Magento/Framework/Encryption/KeyValidator.php @@ -0,0 +1,33 @@ +<?php +/** + * Protocol validator + * + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\Encryption; + +use Magento\Framework\Config\ConfigOptionsListConstants; + +/** + * Encryption Key Validator + */ +class KeyValidator +{ + /** + * Validate encryption key + * + * Validate that encryption key is exactly 32 characters long and has + * no trailing spaces, no invisible characters (tabs, new lines, etc.) + * + * @param string $value + * @return bool + */ + public function isValid($value) : bool + { + return strlen($value) === ConfigOptionsListConstants::STORE_KEY_RANDOM_STRING_SIZE + && preg_match('/^\S+$/', $value); + } +} diff --git a/lib/internal/Magento/Framework/Encryption/Test/Unit/EncryptorTest.php b/lib/internal/Magento/Framework/Encryption/Test/Unit/EncryptorTest.php index 98bb1c5676d..709a432e73b 100644 --- a/lib/internal/Magento/Framework/Encryption/Test/Unit/EncryptorTest.php +++ b/lib/internal/Magento/Framework/Encryption/Test/Unit/EncryptorTest.php @@ -8,10 +8,11 @@ namespace Magento\Framework\Encryption\Test\Unit; -use Magento\Framework\Encryption\Adapter\Mcrypt; use Magento\Framework\Encryption\Adapter\SodiumChachaIetf; use Magento\Framework\Encryption\Encryptor; use Magento\Framework\Encryption\Crypt; +use Magento\Framework\Encryption\KeyValidator; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; class EncryptorTest extends \PHPUnit\Framework\TestCase { @@ -21,62 +22,75 @@ class EncryptorTest extends \PHPUnit\Framework\TestCase /** * @var \Magento\Framework\Encryption\Encryptor */ - protected $_model; + private $encryptor; /** * @var \PHPUnit_Framework_MockObject_MockObject */ - protected $_randomGenerator; + private $randomGeneratorMock; + + /** + * @var KeyValidator|\PHPUnit_Framework_MockObject_MockObject + */ + private $keyValidatorMock; protected function setUp() { - $this->_randomGenerator = $this->createMock(\Magento\Framework\Math\Random::class); + $this->randomGeneratorMock = $this->createMock(\Magento\Framework\Math\Random::class); $deploymentConfigMock = $this->createMock(\Magento\Framework\App\DeploymentConfig::class); $deploymentConfigMock->expects($this->any()) ->method('get') ->with(Encryptor::PARAM_CRYPT_KEY) ->will($this->returnValue(self::CRYPT_KEY_1)); - $this->_model = new \Magento\Framework\Encryption\Encryptor($this->_randomGenerator, $deploymentConfigMock); + $this->keyValidatorMock = $this->createMock(KeyValidator::class); + $this->encryptor = (new ObjectManager($this))->getObject( + \Magento\Framework\Encryption\Encryptor::class, + [ + 'random' => $this->randomGeneratorMock, + 'deploymentConfig' => $deploymentConfigMock, + 'keyValidator' => $this->keyValidatorMock + ] + ); } public function testGetHashNoSalt() { - $this->_randomGenerator->expects($this->never())->method('getRandomString'); + $this->randomGeneratorMock->expects($this->never())->method('getRandomString'); $expected = '5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8'; - $actual = $this->_model->getHash('password'); + $actual = $this->encryptor->getHash('password'); $this->assertEquals($expected, $actual); } public function testGetHashSpecifiedSalt() { - $this->_randomGenerator->expects($this->never())->method('getRandomString'); + $this->randomGeneratorMock->expects($this->never())->method('getRandomString'); $expected = '13601bda4ea78e55a07b98866d2be6be0744e3866f13c00c811cab608a28f322:salt:1'; - $actual = $this->_model->getHash('password', 'salt'); + $actual = $this->encryptor->getHash('password', 'salt'); $this->assertEquals($expected, $actual); } public function testGetHashRandomSaltDefaultLength() { $salt = '-----------random_salt----------'; - $this->_randomGenerator + $this->randomGeneratorMock ->expects($this->once()) ->method('getRandomString') ->with(32) ->will($this->returnValue($salt)); $expected = 'a1c7fc88037b70c9be84d3ad12522c7888f647915db78f42eb572008422ba2fa:' . $salt . ':1'; - $actual = $this->_model->getHash('password', true); + $actual = $this->encryptor->getHash('password', true); $this->assertEquals($expected, $actual); } public function testGetHashRandomSaltSpecifiedLength() { - $this->_randomGenerator + $this->randomGeneratorMock ->expects($this->once()) ->method('getRandomString') ->with(11) ->will($this->returnValue('random_salt')); $expected = '4c5cab8dd00137d11258f8f87b93fd17bd94c5026fc52d3c5af911dd177a2611:random_salt:1'; - $actual = $this->_model->getHash('password', 11); + $actual = $this->encryptor->getHash('password', 11); $this->assertEquals($expected, $actual); } @@ -89,7 +103,7 @@ public function testGetHashRandomSaltSpecifiedLength() */ public function testValidateHash($password, $hash, $expected) { - $actual = $this->_model->validateHash($password, $hash); + $actual = $this->encryptor->validateHash($password, $hash); $this->assertEquals($expected, $actual); } @@ -118,7 +132,7 @@ public function testEncryptWithEmptyKey($key) ->method('get') ->with(Encryptor::PARAM_CRYPT_KEY) ->will($this->returnValue($key)); - $model = new Encryptor($this->_randomGenerator, $deploymentConfigMock); + $model = new Encryptor($this->randomGeneratorMock, $deploymentConfigMock); $value = 'arbitrary_string'; $this->assertEquals($value, $model->encrypt($value)); } @@ -143,7 +157,7 @@ public function testDecryptWithEmptyKey($key) ->method('get') ->with(Encryptor::PARAM_CRYPT_KEY) ->will($this->returnValue($key)); - $model = new Encryptor($this->_randomGenerator, $deploymentConfigMock); + $model = new Encryptor($this->randomGeneratorMock, $deploymentConfigMock); $value = 'arbitrary_string'; $this->assertEquals('', $model->decrypt($value)); } @@ -161,7 +175,7 @@ public function testEncrypt() // sample data to encrypt $data = 'Mares eat oats and does eat oats, but little lambs eat ivy.'; - $actual = $this->_model->encrypt($data); + $actual = $this->encryptor->encrypt($data); // Extract the initialization vector and encrypted data $parts = explode(':', $actual, 3); @@ -175,9 +189,9 @@ public function testEncrypt() public function testDecrypt() { $message = 'Mares eat oats and does eat oats, but little lambs eat ivy.'; - $encrypted = $this->_model->encrypt($message); + $encrypted = $this->encryptor->encrypt($message); - $this->assertEquals($message, $this->_model->decrypt($encrypted)); + $this->assertEquals($message, $this->encryptor->decrypt($encrypted)); } public function testLegacyDecrypt() @@ -186,7 +200,7 @@ public function testLegacyDecrypt() $data = '0:2:z3a4ACpkU35W6pV692U4ueCVQP0m0v0p:' . 'DhEG8/uKGGq92ZusqrGb6X/9+2Ng0QZ9z2UZwljgJbs5/A3LaSnqcK0oI32yjHY49QJi+Z7q1EKu2yVqB8EMpA=='; - $actual = $this->_model->decrypt($data); + $actual = $this->encryptor->decrypt($data); // Extract the initialization vector and encrypted data $parts = explode(':', $data, 4); @@ -209,9 +223,9 @@ public function testEncryptDecryptNewKeyAdded() ->method('get') ->with(Encryptor::PARAM_CRYPT_KEY) ->will($this->returnValue(self::CRYPT_KEY_1 . "\n" . self::CRYPT_KEY_2)); - $model1 = new Encryptor($this->_randomGenerator, $deploymentConfigMock); + $model1 = new Encryptor($this->randomGeneratorMock, $deploymentConfigMock); // simulate an encryption key is being added - $model2 = new Encryptor($this->_randomGenerator, $deploymentConfigMock); + $model2 = new Encryptor($this->randomGeneratorMock, $deploymentConfigMock); // sample data to encrypt $data = 'Mares eat oats and does eat oats, but little lambs eat ivy.'; @@ -224,7 +238,8 @@ public function testEncryptDecryptNewKeyAdded() public function testValidateKey() { - $this->_model->validateKey(self::CRYPT_KEY_1); + $this->keyValidatorMock->method('isValid')->willReturn(true); + $this->encryptor->validateKey(self::CRYPT_KEY_1); } /** @@ -232,7 +247,8 @@ public function testValidateKey() */ public function testValidateKeyInvalid() { - $this->_model->validateKey('----- '); + $this->keyValidatorMock->method('isValid')->willReturn(false); + $this->encryptor->validateKey('----- '); } /** @@ -262,7 +278,7 @@ public function useSpecifiedHashingAlgoDataProvider() */ public function testGetHashMustUseSpecifiedHashingAlgo($password, $salt, $hashAlgo, $expected) { - $hash = $this->_model->getHash($password, $salt, $hashAlgo); + $hash = $this->encryptor->getHash($password, $salt, $hashAlgo); $this->assertEquals($expected, $hash); } } diff --git a/lib/internal/Magento/Framework/Encryption/Test/Unit/KeyValidatorTest.php b/lib/internal/Magento/Framework/Encryption/Test/Unit/KeyValidatorTest.php new file mode 100644 index 00000000000..85faa0aa467 --- /dev/null +++ b/lib/internal/Magento/Framework/Encryption/Test/Unit/KeyValidatorTest.php @@ -0,0 +1,54 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Framework\Encryption\Test\Unit; + +use Magento\Framework\Encryption\KeyValidator; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; + +class KeyValidatorTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var KeyValidator + */ + private $keyValidator; + + protected function setUp() + { + $this->keyValidator = (new ObjectManager($this))->getObject(KeyValidator::class); + } + + /** + * @param $key + * @param bool $expected + * @dataProvider isValidDataProvider + */ + public function testIsValid($key, $expected = true) + { + $this->assertEquals($expected, $this->keyValidator->isValid($key)); + } + + public function isValidDataProvider() : array + { + return [ + '32 numbers' => ['12345678901234567890123456789012'], + '32 characters' => ['aBcdeFghIJKLMNOPQRSTUvwxYzabcdef'], + '32 special characters' => ['!@#$%^&*()_+~`:;"<>,.?/|*&^%$#@!'], + '32 combination' =>['1234eFghI1234567^&*(890123456789'], + 'empty string' => ['', false], + 'leading space' => [' 1234567890123456789012345678901', false], + 'tailing space' => ['1234567890123456789012345678901 ', false], + 'space in the middle' => ['12345678901 23456789012345678901', false], + 'tab in the middle' => ['12345678901 23456789012345678', false], + 'return in the middle' => ['12345678901 + 23456789012345678901', false], + '31 characters' => ['1234567890123456789012345678901', false], + '33 characters' => ['123456789012345678901234567890123', false], + ]; + } +} diff --git a/lib/internal/Magento/Framework/Escaper.php b/lib/internal/Magento/Framework/Escaper.php index 19a9c0c1788..c4150851ec4 100644 --- a/lib/internal/Magento/Framework/Escaper.php +++ b/lib/internal/Magento/Framework/Escaper.php @@ -46,20 +46,6 @@ class Escaper */ private $escapeAsUrlAttributes = ['href']; - /** - * @param \Magento\Framework\ZendEscaper|null $escaper - * @param \Psr\Log\LoggerInterface|null $logger - */ - public function __construct( - \Magento\Framework\ZendEscaper $escaper = null, - \Psr\Log\LoggerInterface $logger = null - ) { - $this->escaper = $escaper ?? \Magento\Framework\App\ObjectManager::getInstance() - ->get(\Magento\Framework\ZendEscaper::class); - $this->logger = $logger ?? \Magento\Framework\App\ObjectManager::getInstance() - ->get(\Psr\Log\LoggerInterface::class); - } - /** * Escape string for HTML context. * @@ -99,7 +85,7 @@ function ($errorNumber, $errorString) { ); } catch (\Exception $e) { restore_error_handler(); - $this->logger->critical($e); + $this->getLogger()->critical($e); } restore_error_handler(); @@ -229,7 +215,7 @@ private function escapeAttributeValue($name, $value) public function escapeHtmlAttr($string, $escapeSingleQuote = true) { if ($escapeSingleQuote) { - return $this->escaper->escapeHtmlAttr((string) $string); + return $this->getEscaper()->escapeHtmlAttr((string) $string); } return htmlspecialchars((string)$string, ENT_COMPAT, 'UTF-8', false); } @@ -254,7 +240,7 @@ public function escapeUrl($string) */ public function encodeUrlParam($string) { - return $this->escaper->escapeUrl($string); + return $this->getEscaper()->escapeUrl($string); } /** @@ -293,7 +279,7 @@ function ($matches) { */ public function escapeCss($string) { - return $this->escaper->escapeCss($string); + return $this->getEscaper()->escapeCss($string); } /** @@ -368,6 +354,36 @@ public function escapeQuote($data, $addSlashes = false) return htmlspecialchars($data, ENT_QUOTES, null, false); } + /** + * Get escaper + * + * @return \Magento\Framework\ZendEscaper + * @deprecated 100.2.0 + */ + private function getEscaper() + { + if ($this->escaper == null) { + $this->escaper = \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Magento\Framework\ZendEscaper::class); + } + return $this->escaper; + } + + /** + * Get logger + * + * @return \Psr\Log\LoggerInterface + * @deprecated 100.2.0 + */ + private function getLogger() + { + if ($this->logger == null) { + $this->logger = \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Psr\Log\LoggerInterface::class); + } + return $this->logger; + } + /** * Filter prohibited tags. * @@ -382,7 +398,7 @@ private function filterProhibitedTags(array $allowedTags): array ); if (!empty($notAllowedTags)) { - $this->logger->critical( + $this->getLogger()->critical( 'The following tag(s) are not allowed: ' . implode(', ', $notAllowedTags) ); $allowedTags = array_diff($allowedTags, $this->notAllowedTags); diff --git a/lib/internal/Magento/Framework/Filesystem/Directory/ReadInterface.php b/lib/internal/Magento/Framework/Filesystem/Directory/ReadInterface.php index 61108c64dda..85d41b69326 100644 --- a/lib/internal/Magento/Framework/Filesystem/Directory/ReadInterface.php +++ b/lib/internal/Magento/Framework/Filesystem/Directory/ReadInterface.php @@ -89,6 +89,7 @@ public function isDirectory($path = null); * * @param string $path * @return \Magento\Framework\Filesystem\File\ReadInterface + * @throws \Magento\Framework\Exception\FileSystemException */ public function openFile($path); diff --git a/lib/internal/Magento/Framework/Filter/Template.php b/lib/internal/Magento/Framework/Filter/Template.php index 3e5f9bcf0bd..a56a4a3edf1 100644 --- a/lib/internal/Magento/Framework/Filter/Template.php +++ b/lib/internal/Magento/Framework/Filter/Template.php @@ -293,7 +293,7 @@ public function templateDirective($construction) { // Processing of {template config_path=... [...]} statement $templateParameters = $this->getParameters($construction[2]); - if (!isset($templateParameters['config_path']) or !$this->getTemplateProcessor()) { + if (!isset($templateParameters['config_path']) || !$this->getTemplateProcessor()) { // Not specified template or not set include processor $replacedValue = '{Error in template processing}'; } else { diff --git a/lib/internal/Magento/Framework/Filter/Test/Unit/StripTagsTest.php b/lib/internal/Magento/Framework/Filter/Test/Unit/StripTagsTest.php index f0dc0d9bd78..2c42eb3d1c8 100644 --- a/lib/internal/Magento/Framework/Filter/Test/Unit/StripTagsTest.php +++ b/lib/internal/Magento/Framework/Filter/Test/Unit/StripTagsTest.php @@ -12,8 +12,7 @@ class StripTagsTest extends \PHPUnit\Framework\TestCase */ public function testStripTags() { - $escaper = $this->createMock(\Magento\Framework\Escaper::class); - $stripTags = new \Magento\Framework\Filter\StripTags($escaper); + $stripTags = new \Magento\Framework\Filter\StripTags(new \Magento\Framework\Escaper()); $this->assertEquals('three', $stripTags->filter('<two>three</two>')); } } diff --git a/lib/internal/Magento/Framework/GraphQl/Config.php b/lib/internal/Magento/Framework/GraphQl/Config.php index 75b6c64e9d2..ec22b742b1d 100644 --- a/lib/internal/Magento/Framework/GraphQl/Config.php +++ b/lib/internal/Magento/Framework/GraphQl/Config.php @@ -48,12 +48,7 @@ public function __construct( } /** - * Get a data object with data pertaining to a GraphQL type's structural makeup. - * - * @param string $configElementName - * @return ConfigElementInterface - * @throws \LogicException - * @SuppressWarnings(PHPMD.UnusedLocalVariable) + * @inheritdoc */ public function getConfigElement(string $configElementName) : ConfigElementInterface { @@ -67,7 +62,7 @@ public function getConfigElement(string $configElementName) : ConfigElementInter $fieldsInQuery = $this->queryFields->getFieldsUsedInQuery(); if (isset($data['fields'])) { if (!empty($fieldsInQuery)) { - foreach ($data['fields'] as $fieldName => $fieldConfig) { + foreach (array_keys($data['fields']) as $fieldName) { if (!isset($fieldsInQuery[$fieldName])) { unset($data['fields'][$fieldName]); } @@ -81,18 +76,20 @@ public function getConfigElement(string $configElementName) : ConfigElementInter } /** - * Return all type names declared in a GraphQL schema's configuration. - * - * @return string[] + * @inheritdoc */ - public function getDeclaredTypeNames() : array + public function getDeclaredTypes() : array { $types = []; foreach ($this->configData->get(null) as $item) { - if (isset($item['type']) && $item['type'] == 'graphql_type') { - $types[] = $item['name']; + if (isset($item['type'])) { + $types[] = [ + 'name' => $item['name'], + 'type' => $item['type'], + ]; } } + return $types; } } diff --git a/lib/internal/Magento/Framework/GraphQl/Config/Element/Enum.php b/lib/internal/Magento/Framework/GraphQl/Config/Element/Enum.php index b1210e986b7..994ae489af1 100644 --- a/lib/internal/Magento/Framework/GraphQl/Config/Element/Enum.php +++ b/lib/internal/Magento/Framework/GraphQl/Config/Element/Enum.php @@ -37,7 +37,7 @@ class Enum implements ConfigElementInterface public function __construct( string $name, array $values, - string $description = "" + string $description ) { $this->name = $name; $this->values = $values; diff --git a/lib/internal/Magento/Framework/GraphQl/Config/Element/FieldsFactory.php b/lib/internal/Magento/Framework/GraphQl/Config/Element/FieldsFactory.php new file mode 100644 index 00000000000..ca6b67eac3d --- /dev/null +++ b/lib/internal/Magento/Framework/GraphQl/Config/Element/FieldsFactory.php @@ -0,0 +1,62 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\GraphQl\Config\Element; + +/** + * Fields object factory + */ +class FieldsFactory +{ + /** + * @var ArgumentFactory + */ + private $argumentFactory; + + /** + * @var FieldFactory + */ + private $fieldFactory; + + /** + * @param ArgumentFactory $argumentFactory + * @param FieldFactory $fieldFactory + */ + public function __construct( + ArgumentFactory $argumentFactory, + FieldFactory $fieldFactory + ) { + $this->argumentFactory = $argumentFactory; + $this->fieldFactory = $fieldFactory; + } + + /** + * Create a fields object from a configured array with optional arguments. + * + * Field data must contain name and type. Other values are optional and include required, itemType, description, + * and resolver. Arguments array must be in the format of [$argumentData['name'] => $argumentData]. + * + * @param array $fieldsData + * @return Field[] + */ + public function createFromConfigData( + array $fieldsData + ) : array { + $fields = []; + foreach ($fieldsData as $fieldData) { + $arguments = []; + foreach ($fieldData['arguments'] as $argumentData) { + $arguments[$argumentData['name']] = $this->argumentFactory->createFromConfigData($argumentData); + } + $fields[$fieldData['name']] = $this->fieldFactory->createFromConfigData( + $fieldData, + $arguments + ); + } + return $fields; + } +} diff --git a/lib/internal/Magento/Framework/GraphQl/Config/Element/Input.php b/lib/internal/Magento/Framework/GraphQl/Config/Element/Input.php new file mode 100644 index 00000000000..8e86f701672 --- /dev/null +++ b/lib/internal/Magento/Framework/GraphQl/Config/Element/Input.php @@ -0,0 +1,74 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\GraphQl\Config\Element; + +/** + * Class representing 'input' GraphQL config element. + */ +class Input implements TypeInterface +{ + /** + * @var string + */ + private $name; + + /** + * @var Field[] + */ + private $fields; + + /** + * @var string + */ + private $description; + + /** + * @param string $name + * @param Field[] $fields + * @param string $description + */ + public function __construct( + string $name, + array $fields, + string $description + ) { + $this->name = $name; + $this->fields = $fields; + $this->description = $description; + } + + /** + * Get the type name. + * + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * Get a list of fields that make up the possible return or input values of a type. + * + * @return Field[] + */ + public function getFields(): array + { + return $this->fields; + } + + /** + * Get a human-readable description of the type. + * + * @return string + */ + public function getDescription(): string + { + return $this->description; + } +} diff --git a/lib/internal/Magento/Framework/GraphQl/Config/Element/InputFactory.php b/lib/internal/Magento/Framework/GraphQl/Config/Element/InputFactory.php new file mode 100644 index 00000000000..0e7ccb831a5 --- /dev/null +++ b/lib/internal/Magento/Framework/GraphQl/Config/Element/InputFactory.php @@ -0,0 +1,79 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\GraphQl\Config\Element; + +use Magento\Framework\GraphQl\Config\ConfigElementFactoryInterface; +use Magento\Framework\GraphQl\Config\ConfigElementInterface; +use Magento\Framework\ObjectManagerInterface; + +/** + * Factory for config elements of 'input' type. + */ +class InputFactory implements ConfigElementFactoryInterface +{ + /** + * @var ObjectManagerInterface + */ + private $objectManager; + + /** + * @var FieldsFactory + */ + private $fieldsFactory; + + /** + * @param ObjectManagerInterface $objectManager + * @param FieldsFactory $fieldsFactory + */ + public function __construct( + ObjectManagerInterface $objectManager, + FieldsFactory $fieldsFactory + ) { + $this->objectManager = $objectManager; + $this->fieldsFactory = $fieldsFactory; + } + + /** + * Instantiate an object representing 'input' GraphQL config element. + * + * @param array $data + * @return ConfigElementInterface + */ + public function createFromConfigData(array $data): ConfigElementInterface + { + $fields = isset($data['fields']) ? $this->fieldsFactory->createFromConfigData($data['fields']) : []; + + return $this->create( + $data, + $fields + ); + } + + /** + * Create input type object based off array of configured GraphQL InputType data. + * + * Type data must contain name and the type's fields. Optional data includes description. + * + * @param array $typeData + * @param array $fields + * @return Input + */ + private function create( + array $typeData, + array $fields + ): Input { + return $this->objectManager->create( + Input::class, + [ + 'name' => $typeData['name'], + 'fields' => $fields, + 'description' => isset($typeData['description']) ? $typeData['description'] : '' + ] + ); + } +} diff --git a/lib/internal/Magento/Framework/GraphQl/Config/Element/InterfaceType.php b/lib/internal/Magento/Framework/GraphQl/Config/Element/InterfaceType.php index 320199c14a6..73ebd42acfb 100644 --- a/lib/internal/Magento/Framework/GraphQl/Config/Element/InterfaceType.php +++ b/lib/internal/Magento/Framework/GraphQl/Config/Element/InterfaceType.php @@ -8,7 +8,7 @@ namespace Magento\Framework\GraphQl\Config\Element; /** - * Describes the configured data for a GraphQL interface type. + * Class representing 'interface' GraphQL config element. */ class InterfaceType implements TypeInterface { @@ -42,7 +42,7 @@ public function __construct( string $name, string $typeResolver, array $fields, - string $description = "" + string $description ) { $this->name = $name; $this->fields = $fields; diff --git a/lib/internal/Magento/Framework/GraphQl/Config/Element/Type.php b/lib/internal/Magento/Framework/GraphQl/Config/Element/Type.php index 24ff439db03..20d017cc710 100644 --- a/lib/internal/Magento/Framework/GraphQl/Config/Element/Type.php +++ b/lib/internal/Magento/Framework/GraphQl/Config/Element/Type.php @@ -8,7 +8,7 @@ namespace Magento\Framework\GraphQl\Config\Element; /** - * Describes all the configured data of an Output or Input type in GraphQL. + * Class representing 'type' GraphQL config element. */ class Type implements TypeInterface { diff --git a/lib/internal/Magento/Framework/GraphQl/Config/Element/TypeFactory.php b/lib/internal/Magento/Framework/GraphQl/Config/Element/TypeFactory.php index c5f3187b048..5dd477a0508 100644 --- a/lib/internal/Magento/Framework/GraphQl/Config/Element/TypeFactory.php +++ b/lib/internal/Magento/Framework/GraphQl/Config/Element/TypeFactory.php @@ -22,28 +22,20 @@ class TypeFactory implements ConfigElementFactoryInterface private $objectManager; /** - * @var ArgumentFactory + * @var FieldsFactory */ - private $argumentFactory; - - /** - * @var FieldFactory - */ - private $fieldFactory; + private $fieldsFactory; /** * @param ObjectManagerInterface $objectManager - * @param ArgumentFactory $argumentFactory - * @param FieldFactory $fieldFactory + * @param FieldsFactory $fieldsFactory */ public function __construct( ObjectManagerInterface $objectManager, - ArgumentFactory $argumentFactory, - FieldFactory $fieldFactory + FieldsFactory $fieldsFactory ) { $this->objectManager = $objectManager; - $this->argumentFactory = $argumentFactory; - $this->fieldFactory = $fieldFactory; + $this->fieldsFactory = $fieldsFactory; } /** @@ -54,18 +46,8 @@ public function __construct( */ public function createFromConfigData(array $data): ConfigElementInterface { - $fields = []; - $data['fields'] = isset($data['fields']) ? $data['fields'] : []; - foreach ($data['fields'] as $field) { - $arguments = []; - foreach ($field['arguments'] as $argument) { - $arguments[$argument['name']] = $this->argumentFactory->createFromConfigData($argument); - } - $fields[$field['name']] = $this->fieldFactory->createFromConfigData( - $field, - $arguments - ); - } + $fields = isset($data['fields']) ? $this->fieldsFactory->createFromConfigData($data['fields']) : []; + return $this->create( $data, $fields @@ -73,10 +55,10 @@ public function createFromConfigData(array $data): ConfigElementInterface } /** - * Create type object based off array of configured GraphQL Output/InputType data. + * Create type object based off array of configured GraphQL Type data. * * Type data must contain name and the type's fields. Optional data includes 'implements' (i.e. the interfaces - * implemented by the types), and description. An InputType cannot implement an interface. + * implemented by the types), and description. * * @param array $typeData * @param array $fields diff --git a/lib/internal/Magento/Framework/GraphQl/ConfigInterface.php b/lib/internal/Magento/Framework/GraphQl/ConfigInterface.php index c2670967f1d..f7d6cf49e18 100644 --- a/lib/internal/Magento/Framework/GraphQl/ConfigInterface.php +++ b/lib/internal/Magento/Framework/GraphQl/ConfigInterface.php @@ -25,9 +25,11 @@ interface ConfigInterface public function getConfigElement(string $configElementName) : ConfigElementInterface; /** - * Return all type names from a GraphQL schema's configuration. + * Return all type names declared in a GraphQL schema's configuration and their type. * - * @return string[] + * Format is ['name' => 'example value', 'type' = 'example value'] + * + * @return array $types */ - public function getDeclaredTypeNames() : array; + public function getDeclaredTypes() : array; } diff --git a/lib/internal/Magento/Framework/GraphQl/Query/Fields.php b/lib/internal/Magento/Framework/GraphQl/Query/Fields.php index d0bc9591265..a34c0a9d421 100644 --- a/lib/internal/Magento/Framework/GraphQl/Query/Fields.php +++ b/lib/internal/Magento/Framework/GraphQl/Query/Fields.php @@ -24,9 +24,11 @@ class Fields * Set Query for extracting list of fields. * * @param string $query + * @param array|null $variables + * * @return void */ - public function setQuery($query) + public function setQuery($query, array $variables = null) { $queryFields = []; try { @@ -41,6 +43,9 @@ public function setQuery($query) ] ] ); + if (isset($variables)) { + $queryFields = array_merge($queryFields, $this->extractVariables($variables)); + } } catch (\Exception $e) { // If a syntax error is encountered do not collect fields } @@ -62,4 +67,24 @@ public function getFieldsUsedInQuery() { return $this->fieldsUsedInQuery; } + + /** + * Extract and return list of all used fields in GraphQL query's variables + * + * @param array $variables + * + * @return string[] + */ + private function extractVariables(array $variables): array + { + $fields = []; + foreach ($variables as $key => $value) { + if (is_array($value)) { + $fields = array_merge($fields, $this->extractVariables($value)); + } + $fields[$key] = $key; + } + + return $fields; + } } diff --git a/lib/internal/Magento/Framework/GraphQl/Query/IntrospectionConfiguration.php b/lib/internal/Magento/Framework/GraphQl/Query/IntrospectionConfiguration.php new file mode 100644 index 00000000000..2fdb3df5f6d --- /dev/null +++ b/lib/internal/Magento/Framework/GraphQl/Query/IntrospectionConfiguration.php @@ -0,0 +1,42 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\GraphQl\Query; + +use Magento\Framework\App\DeploymentConfig; + +/** + * Class for fetching the availability of introspection queries + */ +class IntrospectionConfiguration +{ + private const CONFIG_PATH_DISABLE_INTROSPECTION = 'graphql/disable_introspection'; + + /** + * @var DeploymentConfig + */ + private $deploymentConfig; + + /** + * @param DeploymentConfig $deploymentConfig + */ + public function __construct( + DeploymentConfig $deploymentConfig + ) { + $this->deploymentConfig = $deploymentConfig; + } + + /** + * Check the the environment config to determine if introspection should be disabled. + * + * @return bool + */ + public function isIntrospectionDisabled(): bool + { + return (bool)$this->deploymentConfig->get(self::CONFIG_PATH_DISABLE_INTROSPECTION); + } +} diff --git a/lib/internal/Magento/Framework/GraphQl/Query/QueryComplexityLimiter.php b/lib/internal/Magento/Framework/GraphQl/Query/QueryComplexityLimiter.php index 5730156ca5b..2b9ce9b01b5 100644 --- a/lib/internal/Magento/Framework/GraphQl/Query/QueryComplexityLimiter.php +++ b/lib/internal/Magento/Framework/GraphQl/Query/QueryComplexityLimiter.php @@ -33,16 +33,24 @@ class QueryComplexityLimiter */ private $queryComplexity; + /** + * @var IntrospectionConfiguration + */ + private $introspectionConfig; + /** * @param int $queryDepth * @param int $queryComplexity + * @param IntrospectionConfiguration $introspectionConfig */ public function __construct( int $queryDepth, - int $queryComplexity + int $queryComplexity, + IntrospectionConfiguration $introspectionConfig ) { $this->queryDepth = $queryDepth; $this->queryComplexity = $queryComplexity; + $this->introspectionConfig = $introspectionConfig; } /** @@ -53,7 +61,9 @@ public function __construct( public function execute(): void { DocumentValidator::addRule(new QueryComplexity($this->queryComplexity)); - DocumentValidator::addRule(new DisableIntrospection()); + DocumentValidator::addRule( + new DisableIntrospection((int) $this->introspectionConfig->isIntrospectionDisabled()) + ); DocumentValidator::addRule(new QueryDepth($this->queryDepth)); } } diff --git a/lib/internal/Magento/Framework/GraphQl/Query/QueryProcessor.php b/lib/internal/Magento/Framework/GraphQl/Query/QueryProcessor.php index a6ad10dded8..0a0dba36ef0 100644 --- a/lib/internal/Magento/Framework/GraphQl/Query/QueryProcessor.php +++ b/lib/internal/Magento/Framework/GraphQl/Query/QueryProcessor.php @@ -69,7 +69,7 @@ public function process( $operationName )->toArray( $this->exceptionFormatter->shouldShowDetail() ? - \GraphQL\Error\Debug::INCLUDE_DEBUG_MESSAGE | \GraphQL\Error\Debug::INCLUDE_TRACE : false + \GraphQL\Error\Debug::INCLUDE_DEBUG_MESSAGE : false ); } } diff --git a/lib/internal/Magento/Framework/GraphQl/Query/Resolver/Argument/FieldEntityAttributesPool.php b/lib/internal/Magento/Framework/GraphQl/Query/Resolver/Argument/FieldEntityAttributesPool.php index e7d14a81b9d..bd9de206ccd 100644 --- a/lib/internal/Magento/Framework/GraphQl/Query/Resolver/Argument/FieldEntityAttributesPool.php +++ b/lib/internal/Magento/Framework/GraphQl/Query/Resolver/Argument/FieldEntityAttributesPool.php @@ -38,7 +38,7 @@ public function getEntityAttributesForEntityFromField(string $fieldName) : array if (isset($this->attributesInstances[$fieldName])) { return $this->attributesInstances[$fieldName]->getEntityAttributes(); } else { - throw new \LogicException(sprintf('There is no attrribute class assigned to field %1', $fieldName)); + throw new \LogicException(sprintf('There is no attribute class assigned to field %1', $fieldName)); } } } diff --git a/lib/internal/Magento/Framework/GraphQl/Schema/SchemaGenerator.php b/lib/internal/Magento/Framework/GraphQl/Schema/SchemaGenerator.php index 63fef73186b..250b80defa6 100644 --- a/lib/internal/Magento/Framework/GraphQl/Schema/SchemaGenerator.php +++ b/lib/internal/Magento/Framework/GraphQl/Schema/SchemaGenerator.php @@ -8,9 +8,8 @@ namespace Magento\Framework\GraphQl\Schema; use Magento\Framework\GraphQl\ConfigInterface; -use Magento\Framework\GraphQl\Schema\SchemaGeneratorInterface; -use Magento\Framework\GraphQl\Schema\Type\Output\OutputMapper; use Magento\Framework\GraphQl\Schema; +use Magento\Framework\GraphQl\Schema\Type\TypeRegistry; use Magento\Framework\GraphQl\SchemaFactory; /** @@ -24,47 +23,46 @@ class SchemaGenerator implements SchemaGeneratorInterface private $schemaFactory; /** - * @var OutputMapper + * @var ConfigInterface */ - private $outputMapper; + private $config; /** - * @var ConfigInterface + * @var TypeRegistry */ - private $config; + private $typeRegistry; /** * @param SchemaFactory $schemaFactory - * @param OutputMapper $outputMapper * @param ConfigInterface $config + * @param TypeRegistry $typeRegistry */ public function __construct( SchemaFactory $schemaFactory, - OutputMapper $outputMapper, - ConfigInterface $config + ConfigInterface $config, + TypeRegistry $typeRegistry ) { $this->schemaFactory = $schemaFactory; - $this->outputMapper = $outputMapper; $this->config = $config; + $this->typeRegistry = $typeRegistry; } /** - * {@inheritdoc} + * @inheritdoc */ public function generate() : Schema { $schema = $this->schemaFactory->create( [ - 'query' => $this->outputMapper->getOutputType('Query'), - 'mutation' => $this->outputMapper->getOutputType('Mutation'), + 'query' => $this->typeRegistry->get('Query'), + 'mutation' => $this->typeRegistry->get('Mutation'), 'typeLoader' => function ($name) { - return $this->outputMapper->getOutputType($name); + return $this->typeRegistry->get($name); }, 'types' => function () { - //all types should be generated only on introspection $typesImplementors = []; - foreach ($this->config->getDeclaredTypeNames() as $name) { - $typesImplementors [] = $this->outputMapper->getOutputType($name); + foreach ($this->config->getDeclaredTypes() as $type) { + $typesImplementors [] = $this->typeRegistry->get($type['name']); } return $typesImplementors; } diff --git a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Input/InputFactory.php b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Input/InputFactory.php deleted file mode 100644 index cbbd97cfdb8..00000000000 --- a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Input/InputFactory.php +++ /dev/null @@ -1,60 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -namespace Magento\Framework\GraphQl\Schema\Type\Input; - -use Magento\Framework\GraphQl\Config\ConfigElementInterface; -use Magento\Framework\GraphQl\Schema\Type\InputTypeInterface; -use Magento\Framework\ObjectManagerInterface; - -class InputFactory -{ - /** - * @var ObjectManagerInterface - */ - private $objectManager; - - /** - * @var string - */ - private $prototypes; - - /** - * @var array - */ - private $typeRegistry; - - /** - * @param ObjectManagerInterface $objectManager - * @param array $prototypes - */ - public function __construct( - ObjectManagerInterface $objectManager, - array $prototypes - ) { - $this->objectManager = $objectManager; - $this->prototypes = $prototypes; - } - - /** - * @param ConfigElementInterface $configElement - * @return InputTypeInterface - */ - public function create(ConfigElementInterface $configElement) : InputTypeInterface - { - if (!isset($this->typeRegistry[$configElement->getName()])) { - $this->typeRegistry[$configElement->getName()] = - $this->objectManager->create( - $this->prototypes[get_class($configElement)], - [ - 'configElement' => $configElement - ] - ); - } - return $this->typeRegistry[$configElement->getName()]; - } -} diff --git a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Input/InputMapper.php b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Input/InputMapper.php index d806c0b3e68..d1f48dada2c 100644 --- a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Input/InputMapper.php +++ b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Input/InputMapper.php @@ -9,27 +9,15 @@ use Magento\Framework\GraphQl\Config\Data\WrappedTypeProcessor; use Magento\Framework\GraphQl\Config\Element\Argument; -use Magento\Framework\GraphQl\ConfigInterface; +use Magento\Framework\GraphQl\Exception\GraphQlInputException; use Magento\Framework\GraphQl\Schema\Type\ScalarTypes; -use Magento\Framework\GraphQl\Schema\TypeFactory; +use Magento\Framework\GraphQl\Schema\Type\TypeRegistry; +/** + * Prepare argument's metadata for GraphQL schema generation + */ class InputMapper { - /** - * @var InputFactory - */ - private $inputFactory; - - /** - * @var ConfigInterface - */ - private $config; - - /** - * @var TypeFactory - */ - private $typeFactory; - /** * @var ScalarTypes */ @@ -41,24 +29,23 @@ class InputMapper private $wrappedTypeProcessor; /** - * @param InputFactory $inputFactory - * @param ConfigInterface $config - * @param TypeFactory $typeFactory + * @var TypeRegistry + */ + private $typeRegistry; + + /** * @param ScalarTypes $scalarTypes * @param WrappedTypeProcessor $wrappedTypeProcessor + * @param TypeRegistry $typeRegistry */ public function __construct( - InputFactory $inputFactory, - ConfigInterface $config, - TypeFactory $typeFactory, ScalarTypes $scalarTypes, - WrappedTypeProcessor $wrappedTypeProcessor + WrappedTypeProcessor $wrappedTypeProcessor, + TypeRegistry $typeRegistry ) { - $this->inputFactory = $inputFactory; - $this->config = $config; - $this->typeFactory = $typeFactory; $this->scalarTypes = $scalarTypes; $this->wrappedTypeProcessor = $wrappedTypeProcessor; + $this->typeRegistry = $typeRegistry; } /** @@ -66,6 +53,7 @@ public function __construct( * * @param Argument $argument * @return array + * @throws GraphQlInputException */ public function getRepresentation(Argument $argument) : array { @@ -73,8 +61,7 @@ public function getRepresentation(Argument $argument) : array if ($this->scalarTypes->isScalarType($typeName)) { $instance = $this->wrappedTypeProcessor->processScalarWrappedType($argument); } else { - $configElement = $this->config->getConfigElement($typeName); - $instance = $this->inputFactory->create($configElement); + $instance = $this->typeRegistry->get($typeName); $instance = $this->wrappedTypeProcessor->processWrappedType($argument, $instance); } diff --git a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Input/InputObjectType.php b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Input/InputObjectType.php index ae2d07ade2a..fa0327f79bc 100644 --- a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Input/InputObjectType.php +++ b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Input/InputObjectType.php @@ -8,21 +8,16 @@ namespace Magento\Framework\GraphQl\Schema\Type\Input; use Magento\Framework\GraphQl\Config\Data\WrappedTypeProcessor; -use Magento\Framework\GraphQl\Config\Element\Type as TypeConfigElement; -use Magento\Framework\GraphQl\ConfigInterface; +use Magento\Framework\GraphQl\Config\Element\Input as InputConfigElement; +use Magento\Framework\GraphQl\Exception\GraphQlInputException; use Magento\Framework\GraphQl\Schema\Type\ScalarTypes; -use Magento\Framework\GraphQl\Schema\TypeFactory; +use Magento\Framework\GraphQl\Schema\Type\TypeRegistry; /** * Class InputObjectType */ class InputObjectType extends \Magento\Framework\GraphQl\Schema\Type\InputObjectType { - /** - * @var TypeFactory - */ - private $typeFactory; - /** * @var ScalarTypes */ @@ -34,36 +29,27 @@ class InputObjectType extends \Magento\Framework\GraphQl\Schema\Type\InputObject private $wrappedTypeProcessor; /** - * @var InputFactory + * @var TypeRegistry */ - private $inputFactory; + private $typeRegistry; /** - * @var ConfigInterface - */ - public $graphQlConfig; - - /** - * @param TypeConfigElement $configElement - * @param TypeFactory $typeFactory + * @param InputConfigElement $configElement * @param ScalarTypes $scalarTypes * @param WrappedTypeProcessor $wrappedTypeProcessor - * @param InputFactory $inputFactory - * @param ConfigInterface $graphQlConfig + * @param TypeRegistry $typeRegistry + * @throws GraphQlInputException */ public function __construct( - TypeConfigElement $configElement, - TypeFactory $typeFactory, + InputConfigElement $configElement, ScalarTypes $scalarTypes, WrappedTypeProcessor $wrappedTypeProcessor, - InputFactory $inputFactory, - ConfigInterface $graphQlConfig + TypeRegistry $typeRegistry ) { - $this->typeFactory = $typeFactory; $this->scalarTypes = $scalarTypes; $this->wrappedTypeProcessor = $wrappedTypeProcessor; - $this->inputFactory = $inputFactory; - $this->graphQlConfig = $graphQlConfig; + $this->typeRegistry = $typeRegistry; + $config = [ 'name' => $configElement->getName(), 'description' => $configElement->getDescription() @@ -75,8 +61,7 @@ public function __construct( if ($field->getTypeName() == $configElement->getName()) { $type = $this; } else { - $fieldConfigElement = $this->graphQlConfig->getConfigElement($field->getTypeName()); - $type = $this->inputFactory->create($fieldConfigElement); + $type = $this->typeRegistry->get($field->getTypeName()); } $type = $this->wrappedTypeProcessor->processWrappedType($field, $type); } diff --git a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/Fields.php b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/Fields.php index b54cd4d8ca2..034a5702090 100644 --- a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/Fields.php +++ b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/Fields.php @@ -16,7 +16,6 @@ use Magento\Framework\GraphQl\Schema\Type\Output\OutputMapper; use Magento\Framework\GraphQl\Schema\Type\OutputTypeInterface; use Magento\Framework\GraphQl\Schema\Type\ScalarTypes; -use Magento\Framework\GraphQl\Schema\TypeFactory; use Magento\Framework\ObjectManagerInterface; use Magento\Framework\GraphQl\Schema\Type\ResolveInfoFactory; @@ -40,11 +39,6 @@ class Fields implements FormatterInterface */ private $inputMapper; - /** - * @var TypeFactory - */ - private $typeFactory; - /** * @var ScalarTypes */ @@ -64,7 +58,6 @@ class Fields implements FormatterInterface * @param ObjectManagerInterface $objectManager * @param OutputMapper $outputMapper * @param InputMapper $inputMapper - * @param TypeFactory $typeFactory * @param ScalarTypes $scalarTypes * @param WrappedTypeProcessor $wrappedTypeProcessor * @param ResolveInfoFactory $resolveInfoFactory @@ -73,7 +66,6 @@ public function __construct( ObjectManagerInterface $objectManager, OutputMapper $outputMapper, InputMapper $inputMapper, - TypeFactory $typeFactory, ScalarTypes $scalarTypes, WrappedTypeProcessor $wrappedTypeProcessor, ResolveInfoFactory $resolveInfoFactory @@ -81,14 +73,13 @@ public function __construct( $this->objectManager = $objectManager; $this->outputMapper = $outputMapper; $this->inputMapper = $inputMapper; - $this->typeFactory = $typeFactory; $this->scalarTypes = $scalarTypes; $this->wrappedTypeProcessor = $wrappedTypeProcessor; $this->resolveInfoFactory = $resolveInfoFactory; } /** - * {@inheritDoc} + * @inheritdoc */ public function format(TypeInterface $configElement, OutputTypeInterface $outputType): array { diff --git a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/OutputFactory.php b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/OutputFactory.php deleted file mode 100644 index 81dad11774b..00000000000 --- a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/OutputFactory.php +++ /dev/null @@ -1,65 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -namespace Magento\Framework\GraphQl\Schema\Type\Output; - -use Magento\Framework\GraphQl\Config\ConfigElementInterface; -use Magento\Framework\GraphQl\Schema\Type\OutputTypeInterface; -use Magento\Framework\ObjectManagerInterface; - -/** - * Factory for 'output type' objects compatible with GraphQL schema generator. - */ -class OutputFactory -{ - /** - * @var ObjectManagerInterface - */ - private $objectManager; - - /** - * @var string - */ - private $prototypes; - - /** - * @var array - */ - private $typeRegistry; - - /** - * @param ObjectManagerInterface $objectManager - * @param array $prototypes - */ - public function __construct( - ObjectManagerInterface $objectManager, - array $prototypes - ) { - $this->objectManager = $objectManager; - $this->prototypes = $prototypes; - } - - /** - * Create output type. - * - * @param ConfigElementInterface $configElement - * @return OutputTypeInterface - */ - public function create(ConfigElementInterface $configElement) : OutputTypeInterface - { - if (!isset($this->typeRegistry[$configElement->getName()])) { - $this->typeRegistry[$configElement->getName()] = - $this->objectManager->create( - $this->prototypes[get_class($configElement)], - [ - 'configElement' => $configElement - ] - ); - } - return $this->typeRegistry[$configElement->getName()]; - } -} diff --git a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/OutputMapper.php b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/OutputMapper.php index b7f4b8a1f60..046eeb5b1f9 100644 --- a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/OutputMapper.php +++ b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/OutputMapper.php @@ -7,50 +7,28 @@ namespace Magento\Framework\GraphQl\Schema\Type\Output; -use Magento\Framework\GraphQl\ConfigInterface; use Magento\Framework\GraphQl\Schema\Type\OutputTypeInterface; -use Magento\Framework\GraphQl\Schema\TypeFactory; +use Magento\Framework\GraphQl\Schema\Type\TypeRegistry; use Magento\Framework\GraphQl\Exception\GraphQlInputException; use Magento\Framework\Phrase; /** - * Map type names to their output type/interface classes. + * Map type names to their output type/interface/enum classes. */ class OutputMapper { /** - * @var OutputFactory + * @var TypeRegistry */ - private $outputFactory; + private $typeRegistry; /** - * @var OutputTypeInterface[] - */ - private $outputTypes; - - /** - * @var TypeFactory - */ - private $typeFactory; - - /** - * @var ConfigInterface - */ - private $config; - - /** - * @param OutputFactory $outputFactory - * @param TypeFactory $typeFactory - * @param ConfigInterface $config + * @param TypeRegistry $typeRegistry */ public function __construct( - OutputFactory $outputFactory, - TypeFactory $typeFactory, - ConfigInterface $config + TypeRegistry $typeRegistry ) { - $this->outputFactory = $outputFactory; - $this->config = $config; - $this->typeFactory = $typeFactory; + $this->typeRegistry = $typeRegistry; } /** @@ -62,16 +40,13 @@ public function __construct( */ public function getOutputType($typeName) { - if (!isset($this->outputTypes[$typeName])) { - $configElement = $this->config->getConfigElement($typeName); - $this->outputTypes[$typeName] = $this->outputFactory->create($configElement); - if (!($this->outputTypes[$typeName] instanceof OutputTypeInterface)) { - throw new GraphQlInputException( - new Phrase("Type '{$typeName}' was requested but is not declared in the GraphQL schema.") - ); - } - } + $outputType = $this->typeRegistry->get($typeName); - return $this->outputTypes[$typeName]; + if (!$outputType instanceof OutputTypeInterface) { + throw new GraphQlInputException( + new Phrase("Type '{$typeName}' was requested but is not declared in the GraphQL schema.") + ); + } + return $outputType; } } diff --git a/lib/internal/Magento/Framework/GraphQl/Schema/Type/TypeRegistry.php b/lib/internal/Magento/Framework/GraphQl/Schema/Type/TypeRegistry.php new file mode 100644 index 00000000000..cde8b6b3e44 --- /dev/null +++ b/lib/internal/Magento/Framework/GraphQl/Schema/Type/TypeRegistry.php @@ -0,0 +1,95 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\GraphQl\Schema\Type; + +use Magento\Framework\GraphQl\ConfigInterface; +use Magento\Framework\GraphQl\Exception\GraphQlInputException; +use Magento\Framework\GraphQl\Schema\TypeInterface; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\Phrase; + +/** + * GraphQL type object registry + */ +class TypeRegistry +{ + /** + * @var ObjectManagerInterface + */ + private $objectManager; + + /** + * @var ConfigInterface + */ + private $config; + + /** + * Key is config class name, value is related type class name + * + * @var array + */ + private $configToTypeMap; + + /** + * @var TypeInterface[] + */ + private $types; + + /** + * @param ObjectManagerInterface $objectManager + * @param ConfigInterface $config + * @param array $configToTypeMap + */ + public function __construct( + ObjectManagerInterface $objectManager, + ConfigInterface $config, + array $configToTypeMap + ) { + $this->objectManager = $objectManager; + $this->config = $config; + $this->configToTypeMap = $configToTypeMap; + } + + /** + * Get GraphQL type object by type name + * + * @param string $typeName + * @return TypeInterface|InputTypeInterface|OutputTypeInterface + * @throws GraphQlInputException + */ + public function get(string $typeName): TypeInterface + { + if (!isset($this->types[$typeName])) { + $configElement = $this->config->getConfigElement($typeName); + + $configElementClass = get_class($configElement); + if (!isset($this->configToTypeMap[$configElementClass])) { + throw new GraphQlInputException( + new Phrase( + "No mapping to Webonyx type is declared for '%1' config element type.", + [$configElementClass] + ) + ); + } + + $this->types[$typeName] = $this->objectManager->create( + $this->configToTypeMap[$configElementClass], + [ + 'configElement' => $configElement, + ] + ); + + if (!($this->types[$typeName] instanceof TypeInterface)) { + throw new GraphQlInputException( + new Phrase("Type '{$typeName}' was requested but is not declared in the GraphQL schema.") + ); + } + } + return $this->types[$typeName]; + } +} diff --git a/lib/internal/Magento/Framework/HTTP/Adapter/Curl.php b/lib/internal/Magento/Framework/HTTP/Adapter/Curl.php index 15f09b85052..0e51c64661a 100644 --- a/lib/internal/Magento/Framework/HTTP/Adapter/Curl.php +++ b/lib/internal/Magento/Framework/HTTP/Adapter/Curl.php @@ -11,6 +11,9 @@ */ namespace Magento\Framework\HTTP\Adapter; +/** + * Curl http adapter + */ class Curl implements \Zend_Http_Client_Adapter_Interface { /** @@ -139,8 +142,8 @@ public function setConfig($config = []) /** * Connect to the remote server * - * @param string $host - * @param int $port + * @param string $host + * @param int $port * @param boolean $secure * @return $this * @SuppressWarnings(PHPMD.UnusedFormalParameter) @@ -167,6 +170,7 @@ public function write($method, $url, $http_ver = '1.1', $headers = [], $body = ' // set url to post to curl_setopt($this->_getResource(), CURLOPT_URL, $url); + curl_setopt($this->_getResource(), CURLOPT_HTTP_VERSION, $http_ver); curl_setopt($this->_getResource(), CURLOPT_RETURNTRANSFER, true); if ($method == \Zend_Http_Client::POST) { curl_setopt($this->_getResource(), CURLOPT_POST, true); @@ -273,7 +277,7 @@ public function getInfo($opt = 0) } /** - * curl_multi_* requests support + * Curl_multi_* requests support * * @param array $urls * @param array $options diff --git a/lib/internal/Magento/Framework/Interception/Config/CacheManager.php b/lib/internal/Magento/Framework/Interception/Config/CacheManager.php index a754215bbe7..fd612370d07 100644 --- a/lib/internal/Magento/Framework/Interception/Config/CacheManager.php +++ b/lib/internal/Magento/Framework/Interception/Config/CacheManager.php @@ -98,7 +98,7 @@ public function saveCompiled(string $key, array $data) */ public function clean(string $key) { - $this->cache->clean(\Zend_Cache::CLEANING_MODE_MATCHING_TAG, [$key]); + $this->cache->remove($key); } /** diff --git a/lib/internal/Magento/Framework/Lock/Backend/Cache.php b/lib/internal/Magento/Framework/Lock/Backend/Cache.php new file mode 100644 index 00000000000..61818cbb8c5 --- /dev/null +++ b/lib/internal/Magento/Framework/Lock/Backend/Cache.php @@ -0,0 +1,52 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\Lock\Backend; + +use Magento\Framework\Cache\FrontendInterface; + +/** + * Implementation of the lock manager on the basis of the caching system. + */ +class Cache implements \Magento\Framework\Lock\LockManagerInterface +{ + /** + * @var FrontendInterface + */ + private $cache; + + /** + * @param FrontendInterface $cache + */ + public function __construct(FrontendInterface $cache) + { + $this->cache = $cache; + } + /** + * @inheritdoc + */ + public function lock(string $name, int $timeout = -1): bool + { + return $this->cache->save('1', $name, [], $timeout); + } + + /** + * @inheritdoc + */ + public function unlock(string $name): bool + { + return $this->cache->remove($name); + } + + /** + * @inheritdoc + */ + public function isLocked(string $name): bool + { + return (bool)$this->cache->test($name); + } +} diff --git a/lib/internal/Magento/Framework/Lock/Backend/Database.php b/lib/internal/Magento/Framework/Lock/Backend/Database.php index ce6aeca66d6..efdba63e7a0 100644 --- a/lib/internal/Magento/Framework/Lock/Backend/Database.php +++ b/lib/internal/Magento/Framework/Lock/Backend/Database.php @@ -15,7 +15,7 @@ use Magento\Framework\Phrase; /** - * LockManager using the DB locks + * Implementation of the lock manager on the basis of MySQL. */ class Database implements \Magento\Framework\Lock\LockManagerInterface { @@ -62,9 +62,13 @@ public function __construct( * @return bool * @throws InputException * @throws AlreadyExistsException + * @throws \Zend_Db_Statement_Exception */ public function lock(string $name, int $timeout = -1): bool { + if (!$this->deploymentConfig->isDbAvailable()) { + return true; + }; $name = $this->addPrefix($name); /** @@ -75,7 +79,7 @@ public function lock(string $name, int $timeout = -1): bool if ($this->currentLock) { throw new AlreadyExistsException( new Phrase( - 'Current connection is already holding lock for $1, only single lock allowed', + 'Current connection is already holding lock for %1, only single lock allowed', [$this->currentLock] ) ); @@ -99,9 +103,14 @@ public function lock(string $name, int $timeout = -1): bool * @param string $name lock name * @return bool * @throws InputException + * @throws \Zend_Db_Statement_Exception */ public function unlock(string $name): bool { + if (!$this->deploymentConfig->isDbAvailable()) { + return true; + }; + $name = $this->addPrefix($name); $result = (bool)$this->resource->getConnection()->query( @@ -122,9 +131,14 @@ public function unlock(string $name): bool * @param string $name lock name * @return bool * @throws InputException + * @throws \Zend_Db_Statement_Exception */ public function isLocked(string $name): bool { + if (!$this->deploymentConfig->isDbAvailable()) { + return false; + }; + $name = $this->addPrefix($name); return (bool)$this->resource->getConnection()->query( diff --git a/lib/internal/Magento/Framework/Lock/Test/Unit/Backend/DatabaseTest.php b/lib/internal/Magento/Framework/Lock/Test/Unit/Backend/DatabaseTest.php index db3add62ff5..e2a95030bbd 100644 --- a/lib/internal/Magento/Framework/Lock/Test/Unit/Backend/DatabaseTest.php +++ b/lib/internal/Magento/Framework/Lock/Test/Unit/Backend/DatabaseTest.php @@ -7,8 +7,13 @@ namespace Magento\Framework\Lock\Test\Unit\Backend; +use Magento\Framework\App\DeploymentConfig; use Magento\Framework\Lock\Backend\Database; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +/** + * @inheritdoc + */ class DatabaseTest extends \PHPUnit\Framework\TestCase { /** @@ -27,7 +32,7 @@ class DatabaseTest extends \PHPUnit\Framework\TestCase private $statement; /** - * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager + * @var ObjectManager */ private $objectManager; @@ -36,6 +41,14 @@ class DatabaseTest extends \PHPUnit\Framework\TestCase */ private $database; + /** + * @var DeploymentConfig|\PHPUnit_Framework_MockObject_MockObject + */ + private $deploymentConfig; + + /** + * @inheritdoc + */ protected function setUp() { $this->connection = $this->getMockBuilder(\Magento\Framework\DB\Adapter\AdapterInterface::class) @@ -56,17 +69,32 @@ protected function setUp() ->method('query') ->willReturn($this->statement); - $this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->objectManager = new ObjectManager($this); + $this->deploymentConfig = $this->getMockBuilder(DeploymentConfig::class) + ->disableOriginalConstructor() + ->getMock(); /** @var Database $database */ $this->database = $this->objectManager->getObject( Database::class, - ['resource' => $this->resource] + [ + 'resource' => $this->resource, + 'deploymentConfig' => $this->deploymentConfig, + ] ); } + /** + * @throws \Magento\Framework\Exception\AlreadyExistsException + * @throws \Magento\Framework\Exception\InputException + * @throws \Zend_Db_Statement_Exception + */ public function testLock() { + $this->deploymentConfig + ->method('isDbAvailable') + ->with() + ->willReturn(true); $this->statement->expects($this->once()) ->method('fetchColumn') ->willReturn(true); @@ -75,18 +103,32 @@ public function testLock() } /** + * @throws \Magento\Framework\Exception\AlreadyExistsException + * @throws \Magento\Framework\Exception\InputException + * @throws \Zend_Db_Statement_Exception * @expectedException \Magento\Framework\Exception\InputException */ public function testlockWithTooLongName() { + $this->deploymentConfig + ->method('isDbAvailable') + ->with() + ->willReturn(true); $this->database->lock('BbXbyf9rIY5xuAVdviQJmh76FyoeeVHTDpcjmcImNtgpO4Hnz4xk76ZGEyYALvrQu'); } /** + * @throws \Magento\Framework\Exception\AlreadyExistsException + * @throws \Magento\Framework\Exception\InputException + * @throws \Zend_Db_Statement_Exception * @expectedException \Magento\Framework\Exception\AlreadyExistsException */ public function testlockWithAlreadyAcquiredLockInSameSession() { + $this->deploymentConfig + ->method('isDbAvailable') + ->with() + ->willReturn(true); $this->statement->expects($this->any()) ->method('fetchColumn') ->willReturn(true); @@ -94,4 +136,47 @@ public function testlockWithAlreadyAcquiredLockInSameSession() $this->database->lock('testLock'); $this->database->lock('differentLock'); } + + /** + * @throws \Magento\Framework\Exception\AlreadyExistsException + * @throws \Magento\Framework\Exception\InputException + * @throws \Zend_Db_Statement_Exception + */ + public function testLockWithUnavailableDeploymentConfig() + { + $this->deploymentConfig + ->expects($this->atLeast(1)) + ->method('isDbAvailable') + ->with() + ->willReturn(false); + $this->assertTrue($this->database->lock('testLock')); + } + + /** + * @throws \Magento\Framework\Exception\InputException + * @throws \Zend_Db_Statement_Exception + */ + public function testUnlockWithUnavailableDeploymentConfig() + { + $this->deploymentConfig + ->expects($this->atLeast(1)) + ->method('isDbAvailable') + ->with() + ->willReturn(false); + $this->assertTrue($this->database->unlock('testLock')); + } + + /** + * @throws \Magento\Framework\Exception\InputException + * @throws \Zend_Db_Statement_Exception + */ + public function testIsLockedWithUnavailableDB() + { + $this->deploymentConfig + ->expects($this->atLeast(1)) + ->method('isDbAvailable') + ->with() + ->willReturn(false); + $this->assertFalse($this->database->isLocked('testLock')); + } } diff --git a/lib/internal/Magento/Framework/Mail/Message.php b/lib/internal/Magento/Framework/Mail/Message.php index 6f156e42dfd..71da6e673bf 100644 --- a/lib/internal/Magento/Framework/Mail/Message.php +++ b/lib/internal/Magento/Framework/Mail/Message.php @@ -8,6 +8,9 @@ use Zend\Mime\Mime; use Zend\Mime\Part; +/** + * Class Message for email transportation + */ class Message implements MailMessageInterface { /** @@ -34,7 +37,7 @@ public function __construct($charset = 'utf-8') } /** - * {@inheritdoc} + * @inheritdoc * * @deprecated * @see \Magento\Framework\Mail\Message::setBodyText @@ -47,7 +50,7 @@ public function setMessageType($type) } /** - * {@inheritdoc} + * @inheritdoc * * @deprecated * @see \Magento\Framework\Mail\Message::setBodyText @@ -63,7 +66,7 @@ public function setBody($body) } /** - * {@inheritdoc} + * @inheritdoc */ public function setSubject($subject) { @@ -72,7 +75,7 @@ public function setSubject($subject) } /** - * {@inheritdoc} + * @inheritdoc */ public function getSubject() { @@ -80,7 +83,7 @@ public function getSubject() } /** - * {@inheritdoc} + * @inheritdoc */ public function getBody() { @@ -88,16 +91,29 @@ public function getBody() } /** - * {@inheritdoc} + * @inheritdoc + * + * @deprecated This function is missing the from name. The + * setFromAddress() function sets both from address and from name. + * @see setFromAddress() */ public function setFrom($fromAddress) { - $this->zendMessage->setFrom($fromAddress); + $this->setFromAddress($fromAddress, null); + return $this; + } + + /** + * @inheritdoc + */ + public function setFromAddress($fromAddress, $fromName = null) + { + $this->zendMessage->setFrom($fromAddress, $fromName); return $this; } /** - * {@inheritdoc} + * @inheritdoc */ public function addTo($toAddress) { @@ -106,7 +122,7 @@ public function addTo($toAddress) } /** - * {@inheritdoc} + * @inheritdoc */ public function addCc($ccAddress) { @@ -115,7 +131,7 @@ public function addCc($ccAddress) } /** - * {@inheritdoc} + * @inheritdoc */ public function addBcc($bccAddress) { @@ -124,7 +140,7 @@ public function addBcc($bccAddress) } /** - * {@inheritdoc} + * @inheritdoc */ public function setReplyTo($replyToAddress) { @@ -133,7 +149,7 @@ public function setReplyTo($replyToAddress) } /** - * {@inheritdoc} + * @inheritdoc */ public function getRawMessage() { @@ -157,7 +173,7 @@ private function createHtmlMimeFromString($htmlBody) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBodyHtml($html) { @@ -166,7 +182,7 @@ public function setBodyHtml($html) } /** - * {@inheritdoc} + * @inheritdoc */ public function setBodyText($text) { diff --git a/lib/internal/Magento/Framework/Mail/Template/TransportBuilder.php b/lib/internal/Magento/Framework/Mail/Template/TransportBuilder.php index eac5d74c6e3..a7bb96122a8 100644 --- a/lib/internal/Magento/Framework/Mail/Template/TransportBuilder.php +++ b/lib/internal/Magento/Framework/Mail/Template/TransportBuilder.php @@ -17,6 +17,8 @@ use Magento\Framework\Phrase; /** + * TransportBuilder + * * @api * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ @@ -175,13 +177,30 @@ public function setReplyTo($email, $name = null) /** * Set mail from address * + * @deprecated This function sets the from address for the first store only. + * new function setFromByStore introduced to allow setting of from address + * based on store. + * @see setFromByStore() + * * @param string|array $from * @return $this */ public function setFrom($from) { - $result = $this->_senderResolver->resolve($from); - $this->message->setFrom($result['email'], $result['name']); + return $this->setFromByStore($from, null); + } + + /** + * Set mail from address by store + * + * @param string|array $from + * @param string|int $store + * @return $this + */ + public function setFromByStore($from, $store = null) + { + $result = $this->_senderResolver->resolve($from, $store); + $this->message->setFromAddress($result['email'], $result['name']); return $this; } diff --git a/lib/internal/Magento/Framework/Mail/Template/TransportBuilderByStore.php b/lib/internal/Magento/Framework/Mail/Template/TransportBuilderByStore.php index 785c93824a5..85b1b181d4f 100644 --- a/lib/internal/Magento/Framework/Mail/Template/TransportBuilderByStore.php +++ b/lib/internal/Magento/Framework/Mail/Template/TransportBuilderByStore.php @@ -8,6 +8,13 @@ use Magento\Framework\Mail\MessageInterface; +/** + * Class TransportBuilderByStore + * + * @deprecated The ability to set From address based on store is now available + * in the \Magento\Framework\Mail\Template\TransportBuilder class + * @see \Magento\Framework\Mail\Template\TransportBuilder::setFromByStore + */ class TransportBuilderByStore { /** @@ -47,7 +54,7 @@ public function __construct( public function setFromByStore($from, $store) { $result = $this->senderResolver->resolve($from, $store); - $this->message->setFrom($result['email'], $result['name']); + $this->message->setFromAddress($result['email'], $result['name']); return $this; } diff --git a/lib/internal/Magento/Framework/Mail/Test/Unit/Template/TransportBuilderByStoreTest.php b/lib/internal/Magento/Framework/Mail/Test/Unit/Template/TransportBuilderByStoreTest.php index 80df2887a3a..a28dbcd291b 100644 --- a/lib/internal/Magento/Framework/Mail/Test/Unit/Template/TransportBuilderByStoreTest.php +++ b/lib/internal/Magento/Framework/Mail/Test/Unit/Template/TransportBuilderByStoreTest.php @@ -55,8 +55,8 @@ public function testSetFromByStore() ->with($sender, $store) ->willReturn($sender); $this->messageMock->expects($this->once()) - ->method('setFrom') - ->with('from@example.com', 'name') + ->method('setFromAddress') + ->with($sender['email'], $sender['name']) ->willReturnSelf(); $this->model->setFromByStore($sender, $store); diff --git a/lib/internal/Magento/Framework/Mail/Test/Unit/Template/TransportBuilderTest.php b/lib/internal/Magento/Framework/Mail/Test/Unit/Template/TransportBuilderTest.php index 596640480c8..b476eecd7f5 100644 --- a/lib/internal/Magento/Framework/Mail/Test/Unit/Template/TransportBuilderTest.php +++ b/lib/internal/Magento/Framework/Mail/Test/Unit/Template/TransportBuilderTest.php @@ -167,19 +167,20 @@ public function getTransportDataProvider() /** * @return void */ - public function testSetFrom() + public function testSetFromByStore() { $sender = ['email' => 'from@example.com', 'name' => 'name']; + $store = 1; $this->senderResolverMock->expects($this->once()) ->method('resolve') - ->with($sender) + ->with($sender, $store) ->willReturn($sender); $this->messageMock->expects($this->once()) - ->method('setFrom') - ->with('from@example.com', 'name') + ->method('setFromAddress') + ->with($sender['email'], $sender['name']) ->willReturnSelf(); - $this->builder->setFrom($sender); + $this->builder->setFromByStore($sender, $store); } /** diff --git a/lib/internal/Magento/Framework/Message/Manager.php b/lib/internal/Magento/Framework/Message/Manager.php index c3b5701057d..4ef1754b7e5 100644 --- a/lib/internal/Magento/Framework/Message/Manager.php +++ b/lib/internal/Magento/Framework/Message/Manager.php @@ -11,6 +11,8 @@ /** * Message manager model + * + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Manager implements ManagerInterface @@ -226,7 +228,7 @@ public function addUniqueMessages(array $messages, $group = null) $items = $this->getMessages(false, $group)->getItems(); foreach ($messages as $message) { - if ($message instanceof MessageInterface and !in_array($message, $items, false)) { + if ($message instanceof MessageInterface && !in_array($message, $items, false)) { $this->addMessage($message, $group); } } diff --git a/lib/internal/Magento/Framework/MessageQueue/MessageProcessor.php b/lib/internal/Magento/Framework/MessageQueue/MessageProcessor.php index d71a527c9cb..ad0201cf56e 100644 --- a/lib/internal/Magento/Framework/MessageQueue/MessageProcessor.php +++ b/lib/internal/Magento/Framework/MessageQueue/MessageProcessor.php @@ -12,6 +12,11 @@ */ class MessageProcessor implements MessageProcessorInterface { + /** + * Maximum number of transaction retries + */ + const MAX_TRANSACTION_RETRIES = 10; + /** * @var \Magento\Framework\MessageQueue\MessageStatusProcessor */ @@ -22,6 +27,11 @@ class MessageProcessor implements MessageProcessorInterface */ private $resource; + /** + * @var int + */ + private $retryCount = 0; + /** * @param MessageStatusProcessor $messageStatusProcessor * @param ResourceConnection $resource @@ -53,8 +63,19 @@ public function process( } catch (ConnectionLostException $e) { $this->resource->getConnection()->rollBack(); } catch (\Exception $e) { + $retry = false; $this->resource->getConnection()->rollBack(); - $this->messageStatusProcessor->rejectMessages($queue, $messages); + if (strpos($e->getMessage(), 'Error while sending QUERY packet') !== false + && $this->retryCount < self::MAX_TRANSACTION_RETRIES + ) { + $retry = true; + $this->retryCount++; + $this->resource->closeConnection(); + $this->process($queue, $configuration, $messages, $messagesToAcknowledge, $mergedMessages); + } + if (!$retry) { + $this->messageStatusProcessor->rejectMessages($queue, $messages); + } } } diff --git a/lib/internal/Magento/Framework/Option/ArrayPool.php b/lib/internal/Magento/Framework/Option/ArrayPool.php index 5ac349d99b8..11e1b46ff03 100644 --- a/lib/internal/Magento/Framework/Option/ArrayPool.php +++ b/lib/internal/Magento/Framework/Option/ArrayPool.php @@ -28,13 +28,14 @@ public function __construct(\Magento\Framework\ObjectManagerInterface $objectMan * * @param string $model * @throws \InvalidArgumentException - * @return \Magento\Framework\Option\ArrayInterface + * @return \Magento\Framework\Data\OptionSourceInterface */ public function get($model) { $modelInstance = $this->_objectManager->get($model); - if (false == $modelInstance instanceof \Magento\Framework\Option\ArrayInterface) { - throw new \InvalidArgumentException($model . 'doesn\'t implement \Magento\Framework\Option\ArrayInterface'); + if (false == $modelInstance instanceof \Magento\Framework\Data\OptionSourceInterface) { + throw new \InvalidArgumentException($model + . 'doesn\'t implement \Magento\Framework\Data\OptionSourceInterface'); } return $modelInstance; } diff --git a/lib/internal/Magento/Framework/Reflection/Test/Unit/Fixture/UseClasses/SampleOne.php b/lib/internal/Magento/Framework/Reflection/Test/Unit/Fixture/UseClasses/SampleOne.php new file mode 100644 index 00000000000..6382f4b2470 --- /dev/null +++ b/lib/internal/Magento/Framework/Reflection/Test/Unit/Fixture/UseClasses/SampleOne.php @@ -0,0 +1,13 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\Reflection\Test\Unit\Fixture\UseClasses; + +class SampleOne +{ + +} diff --git a/lib/internal/Magento/Framework/Reflection/Test/Unit/Fixture/UseClasses/SampleOne/SampleThree.php b/lib/internal/Magento/Framework/Reflection/Test/Unit/Fixture/UseClasses/SampleOne/SampleThree.php new file mode 100644 index 00000000000..5384bfa6a67 --- /dev/null +++ b/lib/internal/Magento/Framework/Reflection/Test/Unit/Fixture/UseClasses/SampleOne/SampleThree.php @@ -0,0 +1,13 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\Reflection\Test\Unit\Fixture\UseClasses\SampleOne; + +class SampleThree +{ + +} diff --git a/lib/internal/Magento/Framework/Reflection/Test/Unit/Fixture/UseClasses/SampleTwo.php b/lib/internal/Magento/Framework/Reflection/Test/Unit/Fixture/UseClasses/SampleTwo.php new file mode 100644 index 00000000000..02e2c7da30c --- /dev/null +++ b/lib/internal/Magento/Framework/Reflection/Test/Unit/Fixture/UseClasses/SampleTwo.php @@ -0,0 +1,13 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\Reflection\Test\Unit\Fixture\UseClasses; + +class SampleTwo +{ + +} diff --git a/lib/internal/Magento/Framework/Reflection/Test/Unit/Fixture/UseClasses/SampleTwo/SampleFour.php b/lib/internal/Magento/Framework/Reflection/Test/Unit/Fixture/UseClasses/SampleTwo/SampleFour.php new file mode 100644 index 00000000000..160d4c51322 --- /dev/null +++ b/lib/internal/Magento/Framework/Reflection/Test/Unit/Fixture/UseClasses/SampleTwo/SampleFour.php @@ -0,0 +1,13 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\Reflection\Test\Unit\Fixture\UseClasses\SampleTwo; + +class SampleFour +{ + +} diff --git a/lib/internal/Magento/Framework/Reflection/Test/Unit/Fixture/UseSample.php b/lib/internal/Magento/Framework/Reflection/Test/Unit/Fixture/UseSample.php new file mode 100644 index 00000000000..88c1b460206 --- /dev/null +++ b/lib/internal/Magento/Framework/Reflection/Test/Unit/Fixture/UseSample.php @@ -0,0 +1,16 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\Reflection\Test\Unit\Fixture; + +use Magento\Framework\Reflection\Test\Unit\Fixture\UseClasses\SampleOne; +use Magento\Framework\Reflection\Test\Unit\Fixture\UseClasses\SampleTwo as Sample2; + +class UseSample +{ + // ... +} diff --git a/lib/internal/Magento/Framework/Reflection/Test/Unit/TypeProcessorTest.php b/lib/internal/Magento/Framework/Reflection/Test/Unit/TypeProcessorTest.php index 86a4693d9e5..1a8702c0e1c 100644 --- a/lib/internal/Magento/Framework/Reflection/Test/Unit/TypeProcessorTest.php +++ b/lib/internal/Magento/Framework/Reflection/Test/Unit/TypeProcessorTest.php @@ -8,9 +8,18 @@ use Magento\Framework\Exception\SerializationException; use Magento\Framework\Reflection\Test\Unit\Fixture\TSample; +use Magento\Framework\Reflection\Test\Unit\Fixture\TSampleInterface; +use Magento\Framework\Reflection\Test\Unit\Fixture\UseClasses\SampleOne; +use Magento\Framework\Reflection\Test\Unit\Fixture\UseClasses\SampleOne\SampleThree; +use Magento\Framework\Reflection\Test\Unit\Fixture\UseClasses\SampleTwo; +use Magento\Framework\Reflection\Test\Unit\Fixture\UseClasses\SampleTwo\SampleFour; +use Magento\Framework\Reflection\Test\Unit\Fixture\UseSample; use Magento\Framework\Reflection\TypeProcessor; use Zend\Code\Reflection\ClassReflection; +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class TypeProcessorTest extends \PHPUnit\Framework\TestCase { /** @@ -278,7 +287,7 @@ public function arrayParamTypeDataProvider() { return [ ['method name' => 'addData', 'type' => 'array[]'], - ['method name' => 'addObjectList', 'type' => 'TSampleInterface[]'] + ['method name' => 'addObjectList', 'type' => '\\' . TSampleInterface::class . '[]'] ]; } @@ -354,4 +363,182 @@ public function testGetReturnTypeWithoutReturnTag() $methodReflection = $classReflection->getMethod('getName'); $this->typeProcessor->getGetterReturnType($methodReflection); } + + /** + * Simple and complex data provider + * + * @return array + */ + public function simpleAndComplexDataProvider(): array + { + return [ + ['string', true], + ['array', true], + ['int', true], + ['SomeClass', false], + ['\\My\\Namespace\\Model\\Class', false], + ['Some\\Other\\Class', false], + ]; + } + + /** + * Test simple type detection method + * + * @dataProvider simpleAndComplexDataProvider + * @param string $type + * @param bool $expectedValue + */ + public function testIsSimpleType(string $type, bool $expectedValue) + { + self::assertEquals($expectedValue, $this->typeProcessor->isSimpleType($type)); + } + + /** + * Simple and complex data provider + * + * @return array + */ + public function basicClassNameProvider(): array + { + return [ + ['SomeClass[]', 'SomeClass'], + ['\\My\\Namespace\\Model\\Class[]', '\\My\\Namespace\\Model\\Class'], + ['Some\\Other\\Class[]', 'Some\\Other\\Class'], + ['SomeClass', 'SomeClass'], + ['\\My\\Namespace\\Model\\Class', '\\My\\Namespace\\Model\\Class'], + ['Some\\Other\\Class', 'Some\\Other\\Class'], + ]; + } + + /** + * Extract basic class name + * + * @dataProvider basicClassNameProvider + * @param string $type + * @param string $expectedValue + */ + public function testBasicClassName(string $type, string $expectedValue) + { + self::assertEquals($expectedValue, $this->typeProcessor->getBasicClassName($type)); + } + + /** + * Fully qualified class names data provider + * + * @return array + */ + public function isFullyQualifiedClassNamesDataProvider(): array + { + return [ + ['SomeClass', false], + ['\\My\\Namespace\\Model\\Class', true], + ['Some\\Other\\Class', false], + ]; + } + + /** + * Test fully qualified class name detector + * + * @dataProvider isFullyQualifiedClassNamesDataProvider + * @param string $type + * @param bool $expectedValue + */ + public function testIsFullyQualifiedClassName(string $type, bool $expectedValue) + { + self::assertEquals($expectedValue, $this->typeProcessor->isFullyQualifiedClassName($type)); + } + + /** + * Test alias mapping + */ + public function testGetAliasMapping() + { + $sourceClass = new ClassReflection(UseSample::class); + $aliasMap = $this->typeProcessor->getAliasMapping($sourceClass); + + self::assertEquals([ + 'SampleOne' => SampleOne::class, + 'Sample2' => SampleTwo::class, + ], $aliasMap); + } + + /** + * Resolve fully qualified class names data provider + * + * @return array + */ + public function resolveFullyQualifiedClassNamesDataProvider(): array + { + return [ + [UseSample::class, 'string', 'string'], + [UseSample::class, 'string[]', 'string[]'], + + [UseSample::class, 'SampleOne', '\\' . SampleOne::class], + [UseSample::class, 'Sample2', '\\' . SampleTwo::class], + [ + UseSample::class, + '\\Magento\\Framework\\Reflection\\Test\\Unit\\Fixture\\UseClasses\\SampleOne', + '\\' . SampleOne::class + ], + [ + UseSample::class, + '\\Magento\\Framework\\Reflection\\Test\\Unit\\Fixture\\UseClasses\\SampleTwo', + '\\' . SampleTwo::class + ], + [UseSample::class, 'UseClasses\\SampleOne', '\\' . SampleOne::class], + [UseSample::class, 'UseClasses\\SampleTwo', '\\' . SampleTwo::class], + + [UseSample::class, 'SampleOne[]', '\\' . SampleOne::class . '[]'], + [UseSample::class, 'Sample2[]', '\\' . SampleTwo::class . '[]'], + [ + UseSample::class, + '\\Magento\\Framework\\Reflection\\Test\\Unit\\Fixture\\UseClasses\\SampleOne[]', + '\\' . SampleOne::class . '[]' + ], + [ + UseSample::class, + '\\Magento\\Framework\\Reflection\\Test\\Unit\\Fixture\\UseClasses\\SampleTwo[]', + '\\' . SampleTwo::class . '[]' + ], + [UseSample::class, 'UseClasses\\SampleOne[]', '\\' . SampleOne::class . '[]'], + [UseSample::class, 'UseClasses\\SampleTwo[]', '\\' . SampleTwo::class . '[]'], + + [UseSample::class, 'SampleOne\SampleThree', '\\' . SampleThree::class], + [UseSample::class, 'SampleOne\SampleThree[]', '\\' . SampleThree::class . '[]'], + + [UseSample::class, 'Sample2\SampleFour', '\\' . SampleFour::class], + [UseSample::class, 'Sample2\SampleFour[]', '\\' . SampleFour::class . '[]'], + + [UseSample::class, 'Sample2\NotExisting', 'Sample2\NotExisting'], + [UseSample::class, 'Sample2\NotExisting[]', 'Sample2\NotExisting[]'], + + [ + UseSample::class, + '\\Magento\\Framework\\Reflection\\Test\\Unit\\Fixture\\UseClasses\\NotExisting', + '\\Magento\\Framework\\Reflection\\Test\\Unit\\Fixture\\UseClasses\\NotExisting' + ], + [ + UseSample::class, + '\\Magento\\Framework\\Reflection\\Test\\Unit\\Fixture\\UseClasses\\NotExisting[]', + '\\Magento\\Framework\\Reflection\\Test\\Unit\\Fixture\\UseClasses\\NotExisting[]' + ], + ]; + } + + /** + * Resolve fully qualified class names + * + * @dataProvider resolveFullyQualifiedClassNamesDataProvider + * @param string $className + * @param string $type + * @param string $expectedValue + * @throws \ReflectionException + */ + public function testResolveFullyQualifiedClassNames(string $className, string $type, string $expectedValue) + { + $sourceClass = new ClassReflection($className); + $fullyQualified = $this->typeProcessor->resolveFullyQualifiedClassName($sourceClass, $type); + + self::assertEquals($expectedValue, $fullyQualified); + } } diff --git a/lib/internal/Magento/Framework/Reflection/TypeProcessor.php b/lib/internal/Magento/Framework/Reflection/TypeProcessor.php index d7206032c68..9571fa53547 100644 --- a/lib/internal/Magento/Framework/Reflection/TypeProcessor.php +++ b/lib/internal/Magento/Framework/Reflection/TypeProcessor.php @@ -512,7 +512,7 @@ public function processSimpleAndAnyType($value, $type) public function getParamType(ParameterReflection $param) { $type = $param->detectType(); - if ($type == 'null') { + if ($type === 'null') { throw new \LogicException(sprintf( '@param annotation is incorrect for the parameter "%s" in the method "%s:%s".' . ' First declared type should not be null. E.g. string|null', @@ -521,14 +521,149 @@ public function getParamType(ParameterReflection $param) $param->getDeclaringFunction()->name )); } - if ($type == 'array') { + if ($type === 'array') { // try to determine class, if it's array of objects $paramDocBlock = $this->getParamDocBlockTag($param); $paramTypes = $paramDocBlock->getTypes(); $paramType = array_shift($paramTypes); + + $paramType = $this->resolveFullyQualifiedClassName($param->getDeclaringClass(), $paramType); + return strpos($paramType, '[]') !== false ? $paramType : "{$paramType}[]"; } - return $type; + + return $this->resolveFullyQualifiedClassName($param->getDeclaringClass(), $type); + } + + /** + * Get alias mapping for source class + * + * @param ClassReflection $sourceClass + * @return array + */ + public function getAliasMapping(ClassReflection $sourceClass): array + { + $sourceFileName = $sourceClass->getDeclaringFile(); + $aliases = []; + foreach ($sourceFileName->getUses() as $use) { + if ($use['as'] !== null) { + $aliases[$use['as']] = $use['use']; + } else { + $pos = strrpos($use['use'], '\\'); + + $aliasName = substr($use['use'], $pos + 1); + $aliases[$aliasName] = $use['use']; + } + } + + return $aliases; + } + + /** + * Return true if the passed type is a simple type + * + * Eg.: + * Return true with; array, string, ... + * Return false with: SomeClassName + * + * @param string $typeName + * @return bool + */ + public function isSimpleType(string $typeName): bool + { + return strtolower($typeName) === $typeName; + } + + /** + * Get basic type for a class name + * + * Eg.: + * SomeClassName[] => SomeClassName + * + * @param string $className + * @return string + */ + public function getBasicClassName(string $className): string + { + $pos = strpos($className, '['); + return ($pos === false) ? $className : substr($className, 0, $pos); + } + + /** + * Return true if it is a FQ class name + * + * Eg.: + * SomeClassName => false + * \My\NameSpace\SomeClassName => true + * + * @param string $className + * @return bool + */ + public function isFullyQualifiedClassName(string $className): bool + { + return strpos($className, '\\') === 0; + } + + /** + * Get aliased class name + * + * @param string $className + * @param string $namespace + * @param array $aliases + * @return string + */ + private function getAliasedClassName(string $className, string $namespace, array $aliases): string + { + $pos = strpos($className, '\\'); + if ($pos === false) { + $namespacePrefix = $className; + $partialClassName = ''; + } else { + $namespacePrefix = substr($className, 0, $pos); + $partialClassName = substr($className, $pos); + } + + if (isset($aliases[$namespacePrefix])) { + return $aliases[$namespacePrefix] . $partialClassName; + } + + return $namespace . '\\' . $className; + } + + /** + * Resolve fully qualified type name in the class alias context + * + * @param ClassReflection $sourceClass + * @param string $typeName + * @return string + */ + public function resolveFullyQualifiedClassName(ClassReflection $sourceClass, string $typeName): string + { + $typeName = trim($typeName); + + // Simple way to understand it is a basic type or a class name + if ($this->isSimpleType($typeName)) { + return $typeName; + } + + $basicTypeName = $this->getBasicClassName($typeName); + + // Already a FQN class name + if ($this->isFullyQualifiedClassName($basicTypeName)) { + return '\\' . substr($typeName, 1); + } + + $isArray = $this->isArrayType($typeName); + $aliases = $this->getAliasMapping($sourceClass); + + $namespace = $sourceClass->getNamespaceName(); + $fqClassName = '\\' . $this->getAliasedClassName($basicTypeName, $namespace, $aliases); + + if (interface_exists($fqClassName) || class_exists($fqClassName)) { + return $fqClassName . ($isArray ? '[]' : ''); + } + + return $typeName; } /** diff --git a/lib/internal/Magento/Framework/Search/Adapter/Mysql/Query/Builder/Match.php b/lib/internal/Magento/Framework/Search/Adapter/Mysql/Query/Builder/Match.php index ea165e0cf9f..51e7ea9be0c 100644 --- a/lib/internal/Magento/Framework/Search/Adapter/Mysql/Query/Builder/Match.php +++ b/lib/internal/Magento/Framework/Search/Adapter/Mysql/Query/Builder/Match.php @@ -26,7 +26,7 @@ class Match implements QueryInterface /** * @var string */ - const SPECIAL_CHARACTERS = '+~/\\<>\'":*$#@()!,.?`=%&^'; + const SPECIAL_CHARACTERS = '-+~/\\<>\'":*$#@()!,.?`=%&^'; const MINIMAL_CHARACTER_LENGTH = 3; diff --git a/lib/internal/Magento/Framework/Serialize/README.md b/lib/internal/Magento/Framework/Serialize/README.md index 5af8fb7f71b..d900f89208a 100644 --- a/lib/internal/Magento/Framework/Serialize/README.md +++ b/lib/internal/Magento/Framework/Serialize/README.md @@ -3,6 +3,7 @@ **Serialize** library provides interface *SerializerInterface* and multiple implementations: * *Json* - default implementation. Uses PHP native json_encode/json_decode functions; + * *JsonHexTag* - default implementation. Uses PHP native json_encode/json_decode functions with `JSON_HEX_TAG` option enabled; * *Serialize* - less secure than *Json*, but gives higher performance on big arrays. Uses PHP native serialize/unserialize functions, does not unserialize objects on PHP 7. Using *Serialize* implementation directly is discouraged, always use *SerializerInterface*, using *Serialize* implementation may lead to security vulnerabilities. \ No newline at end of file diff --git a/lib/internal/Magento/Framework/Serialize/Serializer/Json.php b/lib/internal/Magento/Framework/Serialize/Serializer/Json.php index e352d0c2d71..7ce9756ff24 100644 --- a/lib/internal/Magento/Framework/Serialize/Serializer/Json.php +++ b/lib/internal/Magento/Framework/Serialize/Serializer/Json.php @@ -16,27 +16,27 @@ class Json implements SerializerInterface { /** - * {@inheritDoc} + * @inheritDoc * @since 100.2.0 */ public function serialize($data) { $result = json_encode($data); if (false === $result) { - throw new \InvalidArgumentException('Unable to serialize value.'); + throw new \InvalidArgumentException("Unable to serialize value. Error: " . json_last_error_msg()); } return $result; } /** - * {@inheritDoc} + * @inheritDoc * @since 100.2.0 */ public function unserialize($string) { $result = json_decode($string, true); if (json_last_error() !== JSON_ERROR_NONE) { - throw new \InvalidArgumentException('Unable to unserialize value.'); + throw new \InvalidArgumentException("Unable to unserialize value. Error: " . json_last_error_msg()); } return $result; } diff --git a/lib/internal/Magento/Framework/Serialize/Serializer/JsonHexTag.php b/lib/internal/Magento/Framework/Serialize/Serializer/JsonHexTag.php new file mode 100644 index 00000000000..4a5406ff3fd --- /dev/null +++ b/lib/internal/Magento/Framework/Serialize/Serializer/JsonHexTag.php @@ -0,0 +1,35 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Framework\Serialize\Serializer; + +use Magento\Framework\Serialize\SerializerInterface; + +/** + * Serialize data to JSON with the JSON_HEX_TAG option enabled + * (All < and > are converted to \u003C and \u003E), + * unserialize JSON encoded data + * + * @api + * @since 100.2.0 + */ +class JsonHexTag extends Json implements SerializerInterface +{ + /** + * @inheritDoc + * @since 100.2.0 + */ + public function serialize($data): string + { + $result = json_encode($data, JSON_HEX_TAG); + if (false === $result) { + throw new \InvalidArgumentException('Unable to serialize value.'); + } + return $result; + } +} diff --git a/lib/internal/Magento/Framework/Serialize/Test/Unit/Serializer/JsonHexTagTest.php b/lib/internal/Magento/Framework/Serialize/Test/Unit/Serializer/JsonHexTagTest.php new file mode 100644 index 00000000000..c867dced0fc --- /dev/null +++ b/lib/internal/Magento/Framework/Serialize/Test/Unit/Serializer/JsonHexTagTest.php @@ -0,0 +1,118 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Framework\Serialize\Test\Unit\Serializer; + +use Magento\Framework\DataObject; +use Magento\Framework\Serialize\Serializer\JsonHexTag; + +class JsonHexTagTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\Framework\Serialize\Serializer\Json + */ + private $json; + + protected function setUp() + { + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->json = $objectManager->getObject(JsonHexTag::class); + } + + /** + * @param string|int|float|bool|array|null $value + * @param string $expected + * @dataProvider serializeDataProvider + */ + public function testSerialize($value, $expected) + { + $this->assertEquals( + $expected, + $this->json->serialize($value) + ); + } + + public function serializeDataProvider() + { + $dataObject = new DataObject(['something']); + return [ + ['', '""'], + ['string', '"string"'], + [null, 'null'], + [false, 'false'], + [['a' => 'b', 'd' => 123], '{"a":"b","d":123}'], + [123, '123'], + [10.56, '10.56'], + [$dataObject, '{}'], + ['< >', '"\u003C \u003E"'], + ]; + } + + /** + * @param string $value + * @param string|int|float|bool|array|null $expected + * @dataProvider unserializeDataProvider + */ + public function testUnserialize($value, $expected) + { + $this->assertEquals( + $expected, + $this->json->unserialize($value) + ); + } + + /** + * @return array + */ + public function unserializeDataProvider(): array + { + return [ + ['""', ''], + ['"string"', 'string'], + ['null', null], + ['false', false], + ['{"a":"b","d":123}', ['a' => 'b', 'd' => 123]], + ['123', 123], + ['10.56', 10.56], + ['{}', []], + ['"\u003C \u003E"', '< >'], + ]; + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Unable to serialize value. + */ + public function testSerializeException() + { + $this->json->serialize(STDOUT); + } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Unable to unserialize value. + * @dataProvider unserializeExceptionDataProvider + */ + public function testUnserializeException($value) + { + $this->json->unserialize($value); + } + + /** + * @return array + */ + public function unserializeExceptionDataProvider(): array + { + return [ + [''], + [false], + [null], + ['{'] + ]; + } +} diff --git a/lib/internal/Magento/Framework/Session/SessionManager.php b/lib/internal/Magento/Framework/Session/SessionManager.php index b53c83acb48..c7d201676b2 100644 --- a/lib/internal/Magento/Framework/Session/SessionManager.php +++ b/lib/internal/Magento/Framework/Session/SessionManager.php @@ -12,6 +12,7 @@ /** * Session Manager * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) */ class SessionManager implements SessionManagerInterface { @@ -36,7 +37,7 @@ class SessionManager implements SessionManagerInterface /** * Validator * - * @var \Magento\Framework\Session\ValidatorInterface + * @var ValidatorInterface */ protected $validator; @@ -50,28 +51,28 @@ class SessionManager implements SessionManagerInterface /** * SID resolver * - * @var \Magento\Framework\Session\SidResolverInterface + * @var SidResolverInterface */ protected $sidResolver; /** * Session config * - * @var \Magento\Framework\Session\Config\ConfigInterface + * @var Config\ConfigInterface */ protected $sessionConfig; /** * Save handler * - * @var \Magento\Framework\Session\SaveHandlerInterface + * @var SaveHandlerInterface */ protected $saveHandler; /** * Storage * - * @var \Magento\Framework\Session\StorageInterface + * @var StorageInterface */ protected $storage; @@ -92,6 +93,11 @@ class SessionManager implements SessionManagerInterface */ private $appState; + /** + * @var SessionStartChecker + */ + private $sessionStartChecker; + /** * @param \Magento\Framework\App\Request\Http $request * @param SidResolverInterface $sidResolver @@ -102,7 +108,10 @@ class SessionManager implements SessionManagerInterface * @param \Magento\Framework\Stdlib\CookieManagerInterface $cookieManager * @param \Magento\Framework\Stdlib\Cookie\CookieMetadataFactory $cookieMetadataFactory * @param \Magento\Framework\App\State $appState + * @param SessionStartChecker|null $sessionStartChecker * @throws \Magento\Framework\Exception\SessionException + * + * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( \Magento\Framework\App\Request\Http $request, @@ -113,7 +122,8 @@ public function __construct( StorageInterface $storage, \Magento\Framework\Stdlib\CookieManagerInterface $cookieManager, \Magento\Framework\Stdlib\Cookie\CookieMetadataFactory $cookieMetadataFactory, - \Magento\Framework\App\State $appState + \Magento\Framework\App\State $appState, + SessionStartChecker $sessionStartChecker = null ) { $this->request = $request; $this->sidResolver = $sidResolver; @@ -124,11 +134,15 @@ public function __construct( $this->cookieManager = $cookieManager; $this->cookieMetadataFactory = $cookieMetadataFactory; $this->appState = $appState; + $this->sessionStartChecker = $sessionStartChecker ?: \Magento\Framework\App\ObjectManager::getInstance()->get( + SessionStartChecker::class + ); $this->start(); } /** - * This method needs to support sessions with APC enabled + * This method needs to support sessions with APC enabled. + * * @return void */ public function writeClose() @@ -163,47 +177,49 @@ public function __call($method, $args) */ public function start() { - if (!$this->isSessionExists()) { - \Magento\Framework\Profiler::start('session_start'); - - try { - $this->appState->getAreaCode(); - } catch (\Magento\Framework\Exception\LocalizedException $e) { - throw new \Magento\Framework\Exception\SessionException( - new \Magento\Framework\Phrase( - 'Area code not set: Area code must be set before starting a session.' - ), - $e - ); - } + if ($this->sessionStartChecker->check()) { + if (!$this->isSessionExists()) { + \Magento\Framework\Profiler::start('session_start'); + + try { + $this->appState->getAreaCode(); + } catch (\Magento\Framework\Exception\LocalizedException $e) { + throw new \Magento\Framework\Exception\SessionException( + new \Magento\Framework\Phrase( + 'Area code not set: Area code must be set before starting a session.' + ), + $e + ); + } - // Need to apply the config options so they can be ready by session_start - $this->initIniOptions(); - $this->registerSaveHandler(); - if (isset($_SESSION['new_session_id'])) { - // Not fully expired yet. Could be lost cookie by unstable network. - session_commit(); - session_id($_SESSION['new_session_id']); - } - $sid = $this->sidResolver->getSid($this); - // potential custom logic for session id (ex. switching between hosts) - $this->setSessionId($sid); - session_start(); - if (isset($_SESSION['destroyed']) - && $_SESSION['destroyed'] < time() - $this->sessionConfig->getCookieLifetime() - ) { - $this->destroy(['clear_storage' => true]); - } + // Need to apply the config options so they can be ready by session_start + $this->initIniOptions(); + $this->registerSaveHandler(); + if (isset($_SESSION['new_session_id'])) { + // Not fully expired yet. Could be lost cookie by unstable network. + session_commit(); + session_id($_SESSION['new_session_id']); + } + $sid = $this->sidResolver->getSid($this); + // potential custom logic for session id (ex. switching between hosts) + $this->setSessionId($sid); + session_start(); + if (isset($_SESSION['destroyed']) + && $_SESSION['destroyed'] < time() - $this->sessionConfig->getCookieLifetime() + ) { + $this->destroy(['clear_storage' => true]); + } - $this->validator->validate($this); - $this->renewCookie($sid); + $this->validator->validate($this); + $this->renewCookie($sid); - register_shutdown_function([$this, 'writeClose']); + register_shutdown_function([$this, 'writeClose']); - $this->_addHost(); - \Magento\Framework\Profiler::stop('session_start'); + $this->_addHost(); + \Magento\Framework\Profiler::stop('session_start'); + } + $this->storage->init(isset($_SESSION) ? $_SESSION : []); } - $this->storage->init(isset($_SESSION) ? $_SESSION : []); return $this; } diff --git a/lib/internal/Magento/Framework/Session/SessionStartChecker.php b/lib/internal/Magento/Framework/Session/SessionStartChecker.php new file mode 100644 index 00000000000..9cc32268d57 --- /dev/null +++ b/lib/internal/Magento/Framework/Session/SessionStartChecker.php @@ -0,0 +1,38 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Framework\Session; + +/** + * Class to check if session can be started or not. + */ +class SessionStartChecker +{ + /** + * @var bool + */ + private $checkSapi; + + /** + * @param bool $checkSapi + */ + public function __construct(bool $checkSapi = true) + { + $this->checkSapi = $checkSapi; + } + + /** + * Can session be started or not. + * + * @return bool + */ + public function check() : bool + { + return !($this->checkSapi && PHP_SAPI === 'cli'); + } +} diff --git a/lib/internal/Magento/Framework/Setup/Declaration/Schema/etc/constraints/constraint.xsd b/lib/internal/Magento/Framework/Setup/Declaration/Schema/etc/constraints/constraint.xsd index 3eed77c37ca..c379452d65d 100644 --- a/lib/internal/Magento/Framework/Setup/Declaration/Schema/etc/constraints/constraint.xsd +++ b/lib/internal/Magento/Framework/Setup/Declaration/Schema/etc/constraints/constraint.xsd @@ -9,7 +9,7 @@ <xs:include schemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/operations.xsd" /> <xs:attributeGroup name="baseConstraint"> - <xs:attributeGroup ref="basicOperations" /> + <xs:attribute name="disabled" type="xs:boolean" /> <xs:attribute name="referenceId" type="referenceIdType" use="required" /> </xs:attributeGroup> </xs:schema> diff --git a/lib/internal/Magento/Framework/Setup/Declaration/Schema/etc/schema.xsd b/lib/internal/Magento/Framework/Setup/Declaration/Schema/etc/schema.xsd index a2f8611c4bd..e3c54413f81 100644 --- a/lib/internal/Magento/Framework/Setup/Declaration/Schema/etc/schema.xsd +++ b/lib/internal/Magento/Framework/Setup/Declaration/Schema/etc/schema.xsd @@ -84,6 +84,7 @@ <xs:element name="constraint" /> <xs:element name="index" type="index" /> </xs:choice> + <xs:attributeGroup ref="basicOperations" /> <xs:attribute name="name" type="xs:string" use="required" /> <xs:attribute name="resource" type="resourceType" /> <xs:attribute name="engine" type="engineType" /> diff --git a/lib/internal/Magento/Framework/Setup/SchemaListener.php b/lib/internal/Magento/Framework/Setup/SchemaListener.php index c6407a2569a..aabd7dedc91 100644 --- a/lib/internal/Magento/Framework/Setup/SchemaListener.php +++ b/lib/internal/Magento/Framework/Setup/SchemaListener.php @@ -12,6 +12,9 @@ /** * Listen for all changes and record them in order to reuse later. + * + * @SuppressWarnings(PHPMD.TooManyPublicMethods) + * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) */ class SchemaListener { @@ -61,7 +64,8 @@ class SchemaListener 'SCALE' => 'scale', 'UNSIGNED' => 'unsigned', 'IDENTITY' => 'identity', - 'PRIMARY' => 'primary' + 'PRIMARY' => 'primary', + 'COMMENT' => 'comment', ]; /** @@ -71,7 +75,6 @@ class SchemaListener 'COLUMN_POSITION', 'COLUMN_TYPE', 'PRIMARY_POSITION', - 'COMMENT' ]; /** @@ -90,8 +93,6 @@ class SchemaListener private $handlers; /** - * Constructor. - * * @param array $definitionMappers * @param array $handlers */ @@ -132,7 +133,9 @@ private function castColumnDefinition($definition, $columnName) $definition = $this->doColumnMapping($definition); $definition['name'] = strtolower($columnName); $definitionType = $definition['type'] === 'int' ? 'integer' : $definition['type']; + $columnComment = $definition['comment'] ?? null; $definition = $this->definitionMappers[$definitionType]->convertToDefinition($definition); + $definition['comment'] = $columnComment; if (isset($definition['default']) && $definition['default'] === false) { $definition['default'] = null; //uniform default values } @@ -214,7 +217,7 @@ private function doColumnMapping(array $definition) * @param string $columnName * @param array $definition * @param string $primaryKeyName - * @param null $onCreate + * @param string|null $onCreate */ public function addColumn($tableName, $columnName, $definition, $primaryKeyName = 'PRIMARY', $onCreate = null) { @@ -448,6 +451,7 @@ private function prepareColumns($tableName, array $tableColumns) * @param array $foreignKeys * @param array $indexes * @param string $tableName + * @param string $engine */ private function prepareConstraintsAndIndexes(array $foreignKeys, array $indexes, $tableName, $engine) { @@ -478,11 +482,16 @@ private function prepareConstraintsAndIndexes(array $foreignKeys, array $indexes * Create table. * * @param Table $table + * @throws \Zend_Db_Exception */ public function createTable(Table $table) { $engine = strtolower($table->getOption('type')); - $this->tables[$this->getModuleName()][strtolower($table->getName())]['engine'] = $engine; + $this->tables[$this->getModuleName()][strtolower($table->getName())] = + [ + 'engine' => $engine, + 'comment' => $table->getComment(), + ]; $this->prepareColumns($table->getName(), $table->getColumns()); $this->prepareConstraintsAndIndexes($table->getForeignKeys(), $table->getIndexes(), $table->getName(), $engine); } @@ -510,7 +519,7 @@ public function toogleIgnore($flag) /** * Drop table. * - * @param $tableName + * @param string $tableName */ public function dropTable($tableName) { diff --git a/lib/internal/Magento/Framework/Setup/SchemaPersistor.php b/lib/internal/Magento/Framework/Setup/SchemaPersistor.php index f3af56b8ac2..51f61f1dde1 100644 --- a/lib/internal/Magento/Framework/Setup/SchemaPersistor.php +++ b/lib/internal/Magento/Framework/Setup/SchemaPersistor.php @@ -6,7 +6,7 @@ namespace Magento\Framework\Setup; use Magento\Framework\Component\ComponentRegistrar; -use Magento\Framework\Shell; +use Magento\Framework\Setup\Declaration\Schema\Sharding; /** * Persist listened schema to db_schema.xml file. @@ -24,8 +24,6 @@ class SchemaPersistor private $xmlPersistor; /** - * Constructor. - * * @param ComponentRegistrar $componentRegistrar * @param XmlPersistor $xmlPersistor */ @@ -64,32 +62,88 @@ public function persist(SchemaListener $schemaListener) continue; } $schemaPatch = sprintf('%s/etc/db_schema.xml', $path); - if (file_exists($schemaPatch)) { - $dom = new \SimpleXMLElement(file_get_contents($schemaPatch)); - } else { - $dom = $this->initEmptyDom(); - } + $dom = $this->processTables($schemaPatch, $tablesData); + $this->persistModule($dom, $schemaPatch); + } + } + + /** + * Convert tables data into XML document. + * + * @param string $schemaPatch + * @param array $tablesData + * @return \SimpleXMLElement + */ + private function processTables(string $schemaPatch, array $tablesData): \SimpleXMLElement + { + if (file_exists($schemaPatch)) { + $dom = new \SimpleXMLElement(file_get_contents($schemaPatch)); + } else { + $dom = $this->initEmptyDom(); + } + $defaultAttributesValues = [ + 'resource' => Sharding::DEFAULT_CONNECTION, + ]; - foreach ($tablesData as $tableName => $tableData) { - $tableData = $this->handleDefinition($tableData); + foreach ($tablesData as $tableName => $tableData) { + $tableData = $this->handleDefinition($tableData); + $table = $dom->xpath("//table[@name='" . $tableName . "']"); + if (!$table) { $table = $dom->addChild('table'); $table->addAttribute('name', $tableName); - $table->addAttribute('resource', $tableData['resource']); - if (isset($tableData['engine']) && $tableData['engine'] !== null) { - $table->addAttribute('engine', $tableData['engine']); - } + } else { + $table = reset($table); + } - $this->processColumns($tableData, $table); - $this->processConstraints($tableData, $table); - $this->processIndexes($tableData, $table); + $attributeNames = ['disabled', 'resource', 'engine', 'comment']; + foreach ($attributeNames as $attributeName) { + $this->updateElementAttribute( + $table, + $attributeName, + $tableData, + $defaultAttributesValues[$attributeName] ?? null + ); } - $this->persistModule($dom, $schemaPatch); + $this->processColumns($tableData, $table); + $this->processConstraints($tableData, $table); + $this->processIndexes($tableData, $table); + } + + return $dom; + } + + /** + * Update element attribute value or create new attribute. + * + * @param \SimpleXMLElement $element + * @param string $attributeName + * @param array $elementData + * @param string|null $defaultValue + */ + private function updateElementAttribute( + \SimpleXMLElement $element, + string $attributeName, + array $elementData, + ?string $defaultValue = null + ) { + $attributeValue = $elementData[$attributeName] ?? $defaultValue; + if ($attributeValue !== null) { + if (is_bool($attributeValue)) { + $attributeValue = $this->castBooleanToString($attributeValue); + } + + if ($element->attributes()[$attributeName]) { + $element->attributes()->$attributeName = $attributeValue; + } else { + $element->addAttribute($attributeName, $attributeValue); + } } } /** * If disabled attribute is set to false it remove it at all. + * * Also handle other generic attributes. * * @param array $definition @@ -124,24 +178,30 @@ private function castBooleanToString($boolean) */ private function processColumns(array $tableData, \SimpleXMLElement $table) { - if (isset($tableData['columns'])) { - foreach ($tableData['columns'] as $columnData) { - $columnData = $this->handleDefinition($columnData); - $domColumn = $table->addChild('column'); - $domColumn->addAttribute('xsi:type', $columnData['xsi:type'], 'xsi'); - unset($columnData['xsi:type']); - - foreach ($columnData as $attributeKey => $attributeValue) { - if ($attributeValue === null) { - continue; - } - - if (is_bool($attributeValue)) { - $attributeValue = $this->castBooleanToString($attributeValue); - } + if (!isset($tableData['columns'])) { + return $table; + } - $domColumn->addAttribute($attributeKey, $attributeValue); + foreach ($tableData['columns'] as $columnName => $columnData) { + $columnData = $this->handleDefinition($columnData); + $domColumn = $table->xpath("column[@name='" . $columnName . "']"); + if (!$domColumn) { + $domColumn = $table->addChild('column'); + if (!empty($columnData['xsi:type'])) { + $domColumn->addAttribute('xsi:type', $columnData['xsi:type'], 'xsi'); } + $domColumn->addAttribute('name', $columnName); + } else { + $domColumn = reset($domColumn); + } + + $attributeNames = array_diff(array_keys($columnData), ['name', 'xsi:type']); + foreach ($attributeNames as $attributeName) { + $this->updateElementAttribute( + $domColumn, + $attributeName, + $columnData + ); } } @@ -160,14 +220,29 @@ private function processIndexes(array $tableData, \SimpleXMLElement $table) if (isset($tableData['indexes'])) { foreach ($tableData['indexes'] as $indexName => $indexData) { $indexData = $this->handleDefinition($indexData); - $domIndex = $table->addChild('index'); - $domIndex->addAttribute('name', $indexName); - if (isset($indexData['disabled']) && $indexData['disabled']) { - $domIndex->addAttribute('disabled', true); - } else { - $domIndex->addAttribute('indexType', $indexData['indexType']); + $domIndex = $table->xpath("index[@referenceId='" . $indexName . "']"); + if (!$domIndex) { + $domIndex = $this->getUniqueIndexByName($table, $indexName); + } + + if (!$domIndex) { + $domIndex = $table->addChild('index'); + $domIndex->addAttribute('referenceId', $indexName); + } elseif (is_array($domIndex)) { + $domIndex = reset($domIndex); + } + $attributeNames = array_diff(array_keys($indexData), ['referenceId', 'columns', 'name']); + foreach ($attributeNames as $attributeName) { + $this->updateElementAttribute( + $domIndex, + $attributeName, + $indexData + ); + } + + if (!empty($indexData['columns'])) { foreach ($indexData['columns'] as $column) { $columnXml = $domIndex->addChild('column'); $columnXml->addAttribute('name', $column); @@ -188,37 +263,48 @@ private function processIndexes(array $tableData, \SimpleXMLElement $table) */ private function processConstraints(array $tableData, \SimpleXMLElement $table) { - if (isset($tableData['constraints'])) { - foreach ($tableData['constraints'] as $constraintType => $constraints) { - if ($constraintType === 'foreign') { - foreach ($constraints as $name => $constraintData) { - $constraintData = $this->handleDefinition($constraintData); - $constraintDom = $table->addChild('constraint'); - $constraintDom->addAttribute('xsi:type', $constraintType, 'xsi'); - $constraintDom->addAttribute('name', $name); - - foreach ($constraintData as $attributeKey => $attributeValue) { - $constraintDom->addAttribute($attributeKey, $attributeValue); - } - } + if (!isset($tableData['constraints'])) { + return $table; + } + + foreach ($tableData['constraints'] as $constraintType => $constraints) { + foreach ($constraints as $constraintName => $constraintData) { + $constraintData = $this->handleDefinition($constraintData); + $domConstraint = $table->xpath("constraint[@referenceId='" . $constraintName . "']"); + if (!$domConstraint) { + $domConstraint = $table->addChild('constraint'); + $domConstraint->addAttribute('xsi:type', $constraintType, 'xsi'); + $domConstraint->addAttribute('referenceId', $constraintName); } else { - foreach ($constraints as $name => $constraintData) { - $constraintData = $this->handleDefinition($constraintData); - $constraintDom = $table->addChild('constraint'); - $constraintDom->addAttribute('xsi:type', $constraintType, 'xsi'); - $constraintDom->addAttribute('name', $name); - $constraintData['columns'] = $constraintData['columns'] ?? []; - - if (isset($constraintData['disabled'])) { - $constraintDom->addAttribute('disabled', (bool) $constraintData['disabled']); - } - - foreach ($constraintData['columns'] as $column) { - $columnXml = $constraintDom->addChild('column'); - $columnXml->addAttribute('name', $column); - } + $domConstraint = reset($domConstraint); + } + + $attributeNames = array_diff( + array_keys($constraintData), + ['referenceId', 'xsi:type', 'disabled', 'columns', 'name', 'type'] + ); + foreach ($attributeNames as $attributeName) { + $this->updateElementAttribute( + $domConstraint, + $attributeName, + $constraintData + ); + } + + if (!empty($constraintData['columns'])) { + foreach ($constraintData['columns'] as $column) { + $columnXml = $domConstraint->addChild('column'); + $columnXml->addAttribute('name', $column); } } + + if (!empty($constraintData['disabled'])) { + $this->updateElementAttribute( + $domConstraint, + 'disabled', + $constraintData + ); + } } } @@ -236,4 +322,26 @@ private function persistModule(\SimpleXMLElement $simpleXmlElementDom, $path) { $this->xmlPersistor->persist($simpleXmlElementDom, $path); } + + /** + * Retrieve unique index declaration by name. + * + * @param \SimpleXMLElement $table + * @param string $indexName + * @return \SimpleXMLElement|null + */ + private function getUniqueIndexByName(\SimpleXMLElement $table, string $indexName): ?\SimpleXMLElement + { + $indexElement = null; + $constraint = $table->xpath("constraint[@referenceId='" . $indexName . "']"); + if ($constraint) { + $constraint = reset($constraint); + $type = $constraint->attributes('xsi', true)->type; + if ($type == 'unique') { + $indexElement = $constraint; + } + } + + return $indexElement; + } } diff --git a/lib/internal/Magento/Framework/Setup/Test/Unit/SchemaListenerTest.php b/lib/internal/Magento/Framework/Setup/Test/Unit/SchemaListenerTest.php index 4e34b3aebbf..cfde80b12ee 100644 --- a/lib/internal/Magento/Framework/Setup/Test/Unit/SchemaListenerTest.php +++ b/lib/internal/Magento/Framework/Setup/Test/Unit/SchemaListenerTest.php @@ -127,6 +127,7 @@ public function testCreateTable() : void 'default' => 'CURRENT_TIMESTAMP', 'disabled' => false, 'onCreate' => null, + 'comment' => 'Column with type timestamp init update', ], 'integer' => [ @@ -139,6 +140,7 @@ public function testCreateTable() : void 'default' => null, 'disabled' => false, 'onCreate' => null, + 'comment' => 'Integer' ], 'decimal' => [ @@ -151,6 +153,7 @@ public function testCreateTable() : void 'default' => null, 'disabled' => false, 'onCreate' => null, + 'comment' => 'Decimal' ], ], $tables['First_Module']['new_table']['columns'] diff --git a/lib/internal/Magento/Framework/Setup/Test/Unit/SchemaPersistorTest.php b/lib/internal/Magento/Framework/Setup/Test/Unit/SchemaPersistorTest.php index cc88af15a26..f65e6c910dc 100644 --- a/lib/internal/Magento/Framework/Setup/Test/Unit/SchemaPersistorTest.php +++ b/lib/internal/Magento/Framework/Setup/Test/Unit/SchemaPersistorTest.php @@ -152,13 +152,13 @@ public function schemaListenerTablesDataProvider() : array <column xmlns:xsi="xsi" xsi:type="integer" name="first_column" nullable="1" unsigned="0"/> <column xmlns:xsi="xsi" xsi:type="date" name="second_column" nullable="0"/> - <constraint xmlns:xsi="xsi" xsi:type="foreign" name="some_foreign_constraint" + <constraint xmlns:xsi="xsi" xsi:type="foreign" referenceId="some_foreign_constraint" referenceTable="table" referenceColumn="column" table="first_table" column="first_column"/> - <constraint xmlns:xsi="xsi" xsi:type="primary" name="PRIMARY"> + <constraint xmlns:xsi="xsi" xsi:type="primary" referenceId="PRIMARY"> <column name="second_column"/> </constraint> - <index name="TEST_INDEX" indexType="btree"> + <index referenceId="TEST_INDEX" indexType="btree"> <column name="first_column"/> </index> </table> diff --git a/lib/internal/Magento/Framework/Test/Unit/EscaperTest.php b/lib/internal/Magento/Framework/Test/Unit/EscaperTest.php index 1599cae073f..e406994b54c 100644 --- a/lib/internal/Magento/Framework/Test/Unit/EscaperTest.php +++ b/lib/internal/Magento/Framework/Test/Unit/EscaperTest.php @@ -30,9 +30,12 @@ class EscaperTest extends \PHPUnit\Framework\TestCase protected function setUp() { + $this->escaper = new Escaper(); $this->zendEscaper = new \Magento\Framework\ZendEscaper(); $this->loggerMock = $this->getMockForAbstractClass(\Psr\Log\LoggerInterface::class); - $this->escaper = new Escaper($this->zendEscaper, $this->loggerMock); + $objectManagerHelper = new ObjectManager($this); + $objectManagerHelper->setBackwardCompatibleProperty($this->escaper, 'escaper', $this->zendEscaper); + $objectManagerHelper->setBackwardCompatibleProperty($this->escaper, 'logger', $this->loggerMock); } /** diff --git a/lib/internal/Magento/Framework/Test/Unit/UrlTest.php b/lib/internal/Magento/Framework/Test/Unit/UrlTest.php index 939a9c48443..046a9d63fc8 100644 --- a/lib/internal/Magento/Framework/Test/Unit/UrlTest.php +++ b/lib/internal/Magento/Framework/Test/Unit/UrlTest.php @@ -141,7 +141,7 @@ protected function getUrlModel($arguments = []) $modelProperty->setValue($model, $this->urlModifier); $zendEscaper = new \Magento\Framework\ZendEscaper(); - $escaper = $objectManager->getObject(\Magento\Framework\Escaper::class); + $escaper = new \Magento\Framework\Escaper(); $objectManager->setBackwardCompatibleProperty($escaper, 'escaper', $zendEscaper); $objectManager->setBackwardCompatibleProperty($model, 'escaper', $escaper); diff --git a/lib/internal/Magento/Framework/View/Asset/Repository.php b/lib/internal/Magento/Framework/View/Asset/Repository.php index 654d80382f4..19c9ddd1e91 100644 --- a/lib/internal/Magento/Framework/View/Asset/Repository.php +++ b/lib/internal/Magento/Framework/View/Asset/Repository.php @@ -126,6 +126,7 @@ public function __construct( * @return $this * * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.NPathComplexity) */ public function updateDesignParams(array &$params) { @@ -146,7 +147,12 @@ public function updateDesignParams(array &$params) } if ($theme) { - $params['themeModel'] = $this->getThemeProvider()->getThemeByFullPath($area . '/' . $theme); + if (is_numeric($theme)) { + $params['themeModel'] = $this->getThemeProvider()->getThemeById($theme); + } else { + $params['themeModel'] = $this->getThemeProvider()->getThemeByFullPath($area . '/' . $theme); + } + if (!$params['themeModel']) { throw new \UnexpectedValueException("Could not find theme '$theme' for area '$area'"); } @@ -167,6 +173,8 @@ public function updateDesignParams(array &$params) } /** + * Get theme provider + * * @return ThemeProviderInterface */ private function getThemeProvider() @@ -440,6 +448,8 @@ public static function extractModule($fileId) } /** + * Get repository files map + * * @param string $fileId * @param array $params * @return RepositoryMap diff --git a/lib/internal/Magento/Framework/View/Element/UiComponent/Context.php b/lib/internal/Magento/Framework/View/Element/UiComponent/Context.php index e472a9c9eff..fbb84712b2a 100644 --- a/lib/internal/Magento/Framework/View/Element/UiComponent/Context.php +++ b/lib/internal/Magento/Framework/View/Element/UiComponent/Context.php @@ -5,7 +5,9 @@ */ namespace Magento\Framework\View\Element\UiComponent; +use Magento\Framework\App\ObjectManager; use Magento\Framework\App\RequestInterface; +use Magento\Framework\AuthorizationInterface; use Magento\Framework\UrlInterface; use Magento\Framework\View\Element\UiComponent\ContentType\ContentTypeFactory; use Magento\Framework\View\Element\UiComponent\Control\ActionPoolFactory; @@ -94,6 +96,11 @@ class Context implements ContextInterface */ protected $uiComponentFactory; + /** + * @var AuthorizationInterface + */ + private $authorization; + /** * @param PageLayoutInterface $pageLayout * @param RequestInterface $request @@ -104,7 +111,8 @@ class Context implements ContextInterface * @param Processor $processor * @param UiComponentFactory $uiComponentFactory * @param DataProviderInterface|null $dataProvider - * @param string|null $namespace + * @param string $namespace + * @param AuthorizationInterface|null $authorization * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -117,7 +125,8 @@ public function __construct( Processor $processor, UiComponentFactory $uiComponentFactory, DataProviderInterface $dataProvider = null, - $namespace = null + $namespace = null, + AuthorizationInterface $authorization = null ) { $this->namespace = $namespace; $this->request = $request; @@ -129,6 +138,9 @@ public function __construct( $this->urlBuilder = $urlBuilder; $this->processor = $processor; $this->uiComponentFactory = $uiComponentFactory; + $this->authorization = $authorization ?: ObjectManager::getInstance()->get( + AuthorizationInterface::class + ); $this->setAcceptType(); } @@ -280,6 +292,9 @@ public function addButtons(array $buttons, UiComponentInterface $component) uasort($buttons, [$this, 'sortButtons']); foreach ($buttons as $buttonId => $buttonData) { + if (isset($buttonData['aclResource']) && !$this->authorization->isAllowed($buttonData['aclResource'])) { + continue; + } if (isset($buttonData['url'])) { $buttonData['url'] = $this->getUrl($buttonData['url']); } diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Asset/RepositoryTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Asset/RepositoryTest.php index a8beb380c51..5654563f879 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/Asset/RepositoryTest.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/Asset/RepositoryTest.php @@ -164,6 +164,50 @@ public function testUpdateDesignParams($params, $result) $this->assertEquals($result, $params); } + /** + * @return void + */ + public function testUpdateDesignParamsWithThemePath() + { + $params = ['area' => 'AREA']; + $result = ['area' => 'AREA', 'themeModel' => 'Theme', 'module' => false, 'locale' => null]; + + $this->designMock + ->expects($this->once()) + ->method('getConfigurationDesignTheme') + ->willReturn('themePath'); + + $this->themeProvider + ->expects($this->once()) + ->method('getThemeByFullPath') + ->willReturn('Theme'); + + $this->repository->updateDesignParams($params); + $this->assertEquals($result, $params); + } + + /** + * @return void + */ + public function testUpdateDesignParamsWithThemeId() + { + $params = ['area' => 'AREA']; + $result = ['area' => 'AREA', 'themeModel' => 'Theme', 'module' => false, 'locale' => null]; + + $this->designMock + ->expects($this->once()) + ->method('getConfigurationDesignTheme') + ->willReturn('1'); + + $this->themeProvider + ->expects($this->once()) + ->method('getThemeById') + ->willReturn('Theme'); + + $this->repository->updateDesignParams($params); + $this->assertEquals($result, $params); + } + /** * @return array */ diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Element/UiComponent/ContextTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Element/UiComponent/ContextTest.php index b7301c4cad5..75c7fc24854 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/Element/UiComponent/ContextTest.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/Element/UiComponent/ContextTest.php @@ -11,7 +11,11 @@ use Magento\Framework\View\Element\UiComponent\Context; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +use Magento\Framework\View\Element\UiComponent\Control\ActionPoolInterface; +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class ContextTest extends \PHPUnit\Framework\TestCase { /** @@ -19,6 +23,16 @@ class ContextTest extends \PHPUnit\Framework\TestCase */ protected $context; + /** + * @var ActionPoolInterface + */ + private $actionPool; + + /** + * @var \Magento\Framework\AuthorizationInterface + */ + private $authorization; + protected function setUp() { $pageLayout = $this->getMockBuilder(\Magento\Framework\View\LayoutInterface::class)->getMock(); @@ -33,6 +47,10 @@ protected function setUp() $this->getMockBuilder(\Magento\Framework\View\Element\UiComponent\Control\ActionPoolFactory::class) ->disableOriginalConstructor() ->getMock(); + $this->actionPool = $this->getMockBuilder(ActionPoolInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $actionPoolFactory->method('create')->willReturn($this->actionPool); $contentTypeFactory = $this->getMockBuilder(\Magento\Framework\View\Element\UiComponent\ContentType\ContentTypeFactory::class) ->disableOriginalConstructor() @@ -43,6 +61,9 @@ protected function setUp() $this->getMockBuilder(\Magento\Framework\View\Element\UiComponentFactory::class) ->disableOriginalConstructor() ->getMock(); + $this->authorization = $this->getMockBuilder(\Magento\Framework\AuthorizationInterface::class) + ->disableOriginalConstructor() + ->getMock(); $objectManagerHelper = new ObjectManagerHelper($this); $this->context = $objectManagerHelper->getObject( @@ -55,11 +76,62 @@ protected function setUp() 'contentTypeFactory' => $contentTypeFactory, 'urlBuilder' => $urlBuilder, 'processor' => $processor, - 'uiComponentFactory' => $uiComponentFactory + 'uiComponentFactory' => $uiComponentFactory, + 'authorization' => $this->authorization, ] ); } + public function testAddButtonWithoutAclResource() + { + $component = $this->getMockBuilder(\Magento\Framework\View\Element\UiComponentInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->actionPool->expects($this->once())->method('add'); + $this->authorization->expects($this->never())->method('isAllowed'); + + $this->context->addButtons([ + 'button_1' => [ + 'name' => 'button_1', + ], + ], $component); + } + + public function testAddButtonWithAclResourceAllowed() + { + $component = $this->getMockBuilder(\Magento\Framework\View\Element\UiComponentInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->actionPool->expects($this->once())->method('add'); + $this->authorization->expects($this->once())->method('isAllowed')->willReturn(true); + + $this->context->addButtons([ + 'button_1' => [ + 'name' => 'button_1', + 'aclResource' => 'Magento_Framwork::acl', + ], + ], $component); + } + + public function testAddButtonWithAclResourceDenied() + { + $component = $this->getMockBuilder(\Magento\Framework\View\Element\UiComponentInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->actionPool->expects($this->never())->method('add'); + $this->authorization->expects($this->once())->method('isAllowed')->willReturn(false); + + $this->context->addButtons([ + 'button_1' => [ + 'name' => 'button_1', + 'aclResource' => 'Magento_Framwork::acl', + ], + ], $component); + } + /** * @dataProvider addComponentDefinitionDataProvider * @param array $components diff --git a/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php b/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php index 26102f008c7..a9b553f6dd6 100644 --- a/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php +++ b/lib/internal/Magento/Framework/Webapi/ServiceInputProcessor.php @@ -165,17 +165,25 @@ public function process($serviceClassName, $serviceMethodName, array $inputArray } /** + * Retrieve constructor data + * * @param string $className * @param array $data * @return array * @throws \ReflectionException + * @throws \Magento\Framework\Exception\LocalizedException */ private function getConstructorData(string $className, array $data): array { $preferenceClass = $this->config->getPreference($className); $class = new ClassReflection($preferenceClass ?: $className); - $constructor = $class->getConstructor(); + try { + $constructor = $class->getMethod('__construct'); + } catch (\ReflectionException $e) { + $constructor = null; + } + if ($constructor === null) { return []; } @@ -184,7 +192,15 @@ private function getConstructorData(string $className, array $data): array $parameters = $constructor->getParameters(); foreach ($parameters as $parameter) { if (isset($data[$parameter->getName()])) { - $res[$parameter->getName()] = $data[$parameter->getName()]; + $parameterType = $this->typeProcessor->getParamType($parameter); + + try { + $res[$parameter->getName()] = $this->convertValue($data[$parameter->getName()], $parameterType); + } catch (\ReflectionException $e) { + // Parameter was not correclty declared or the class is uknown. + // By not returing the contructor value, we will automatically fall back to the "setters" way. + continue; + } } } diff --git a/lib/internal/Magento/Framework/Webapi/ServiceOutputProcessor.php b/lib/internal/Magento/Framework/Webapi/ServiceOutputProcessor.php index cdb6ed799aa..224421d6561 100644 --- a/lib/internal/Magento/Framework/Webapi/ServiceOutputProcessor.php +++ b/lib/internal/Magento/Framework/Webapi/ServiceOutputProcessor.php @@ -7,8 +7,11 @@ use Magento\Framework\Api\AbstractExtensibleObject; use Magento\Framework\Api\ExtensibleDataObjectConverter; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Reflection\DataObjectProcessor; use Magento\Framework\Reflection\MethodsMap; +use Magento\Framework\Reflection\TypeProcessor; +use Zend\Code\Reflection\ClassReflection; /** * Data object converter @@ -27,16 +30,24 @@ class ServiceOutputProcessor implements ServicePayloadConverterInterface */ protected $methodsMapProcessor; + /** + * @var TypeProcessor|null + */ + private $typeProcessor; + /** * @param DataObjectProcessor $dataObjectProcessor * @param MethodsMap $methodsMapProcessor + * @param TypeProcessor|null $typeProcessor */ public function __construct( DataObjectProcessor $dataObjectProcessor, - MethodsMap $methodsMapProcessor + MethodsMap $methodsMapProcessor, + TypeProcessor $typeProcessor = null ) { $this->dataObjectProcessor = $dataObjectProcessor; $this->methodsMapProcessor = $methodsMapProcessor; + $this->typeProcessor = $typeProcessor ?: ObjectManager::getInstance()->get(TypeProcessor::class); } /** @@ -57,6 +68,12 @@ public function process($data, $serviceClassName, $serviceMethodName) { /** @var string $dataType */ $dataType = $this->methodsMapProcessor->getMethodReturnType($serviceClassName, $serviceMethodName); + + if (class_exists($serviceClassName) || interface_exists($serviceClassName)) { + $sourceClass = new ClassReflection($serviceClassName); + $dataType = $this->typeProcessor->resolveFullyQualifiedClassName($sourceClass, $dataType); + } + return $this->convertValue($data, $dataType); } diff --git a/lib/web/css/source/components/_modals.less b/lib/web/css/source/components/_modals.less index a8e8cebde3a..396930cce6d 100644 --- a/lib/web/css/source/components/_modals.less +++ b/lib/web/css/source/components/_modals.less @@ -102,7 +102,7 @@ &.confirm { .modal-inner-wrap { - .lib-css(width, @modal-popup-confirm__width); + .lib-css(max-width, @modal-popup-confirm__width); .modal-content { padding-right: 7rem; diff --git a/lib/web/css/source/lib/_forms.less b/lib/web/css/source/lib/_forms.less index 531cf3d34ad..a22ec5f52db 100644 --- a/lib/web/css/source/lib/_forms.less +++ b/lib/web/css/source/lib/_forms.less @@ -300,6 +300,8 @@ input[type="checkbox"] { .lib-form-element-choice(@_type: input-checkbox); + position: relative; + top: 2px; } input[type="radio"] { diff --git a/lib/web/css/source/lib/_resets.less b/lib/web/css/source/lib/_resets.less index 8228a07ef92..11501d4a7fe 100644 --- a/lib/web/css/source/lib/_resets.less +++ b/lib/web/css/source/lib/_resets.less @@ -105,6 +105,13 @@ .lib-css(box-shadow, @focus__box-shadow); } } + + input[type="radio"], + input[type="checkbox"] { + &:focus { + box-shadow: none; + } + } } // diff --git a/lib/web/css/source/lib/variables/_buttons.less b/lib/web/css/source/lib/variables/_buttons.less index 8c31c161438..8729eca6aca 100644 --- a/lib/web/css/source/lib/variables/_buttons.less +++ b/lib/web/css/source/lib/variables/_buttons.less @@ -57,14 +57,14 @@ @button-primary__gradient: false; @button-primary__gradient-direction: false; -@button-primary__background: @color-blue1; -@button-primary__border: 1px solid @color-blue1; +@button-primary__background: @theme__color__primary; +@button-primary__border: 1px solid @theme__color__primary; @button-primary__color: @color-white; @button-primary__gradient-color-start: false; @button-primary__gradient-color-end: false; -@button-primary__hover__background: @color-blue2; -@button-primary__hover__border: 1px solid @color-blue2; +@button-primary__hover__background: @theme__color__primary-alt; +@button-primary__hover__border: 1px solid @theme__color__primary-alt; @button-primary__hover__color: @button-primary__color; @button-primary__hover__gradient-color-start: false; @button-primary__hover__gradient-color-end: false; diff --git a/lib/web/css/source/lib/variables/_colors.less b/lib/web/css/source/lib/variables/_colors.less index 01e0db03adc..9c694468e9f 100644 --- a/lib/web/css/source/lib/variables/_colors.less +++ b/lib/web/css/source/lib/variables/_colors.less @@ -78,7 +78,7 @@ @color-sky-blue1: #68a8e0; @color-pink1: #fae5e5; -@color-dark-pink1: #800080; +@color-dark-pink1: #800080; // Legacy pink @color-brownie1: #6f4400; @color-brownie-light1: #c07600; @@ -92,6 +92,10 @@ // Color nesting // --------------------------------------------- +@theme__color__primary: @color-blue1; +@theme__color__primary-alt: @color-blue2; +@theme__color__secondary: @color-orange-red1; + @primary__color: @color-gray20; @primary__color__dark: darken(@primary__color, 35%); // #000 @primary__color__darker: darken(@primary__color, 13.5%); // #111 @@ -104,5 +108,5 @@ @page__background-color: @color-white; @panel__background-color: darken(@page__background-color, 6%); -@active__color: @color-orange-red1; +@active__color: @theme__color__secondary; @error__color: @color-red10; diff --git a/lib/web/css/source/lib/variables/_navigation.less b/lib/web/css/source/lib/variables/_navigation.less index 2a4aa11014d..3ef17425474 100644 --- a/lib/web/css/source/lib/variables/_navigation.less +++ b/lib/web/css/source/lib/variables/_navigation.less @@ -23,7 +23,7 @@ @navigation-level0-item__text-decoration: none; @navigation-level0-item__active__background: ''; -@navigation-level0-item__active__border-color: @color-orange-red1; +@navigation-level0-item__active__border-color: @active__color; @navigation-level0-item__active__border-style: solid; @navigation-level0-item__active__border-width: 0 0 0 8px; @navigation-level0-item__active__color: ''; @@ -47,7 +47,7 @@ @submenu-item__active__background: ''; @submenu-item__active__border: 8px; -@submenu-item__active__border-color: @color-orange-red1; +@submenu-item__active__border-color: @active__color; @submenu-item__active__border-style: solid; @submenu-item__active__border-width: 0 0 0 @submenu-item__active__border; @submenu-item__active__color: ''; @@ -77,7 +77,7 @@ @navigation-desktop-level0-item__hover__text-decoration: @navigation-desktop-level0-item__text-decoration; @navigation-desktop-level0-item__active__background: ''; -@navigation-desktop-level0-item__active__border-color: @color-orange-red1; +@navigation-desktop-level0-item__active__border-color: @active__color; @navigation-desktop-level0-item__active__border-style: solid; @navigation-desktop-level0-item__active__border-width: 0 0 3px; @navigation-desktop-level0-item__active__color: @navigation-desktop-level0-item__hover__color; @@ -109,7 +109,7 @@ @submenu-desktop-item__hover__text-decoration: @navigation-desktop-level0-item__text-decoration; @submenu-desktop-item__active__background: ''; -@submenu-desktop-item__active__border-color: @color-orange-red1; +@submenu-desktop-item__active__border-color: @active__color; @submenu-desktop-item__active__border-style: solid; @submenu-desktop-item__active__border-width: 0 0 0 3px; @submenu-desktop-item__active__color: ''; diff --git a/lib/web/css/source/lib/variables/_rating.less b/lib/web/css/source/lib/variables/_rating.less index 2ffdab8bedd..ec60d591631 100644 --- a/lib/web/css/source/lib/variables/_rating.less +++ b/lib/web/css/source/lib/variables/_rating.less @@ -14,6 +14,6 @@ @rating-icon__letter-spacing: -10px; @rating-icon__color: @color-gray78; -@rating-icon__active__color: @color-orange-red1; +@rating-icon__active__color: @active__color; @rating-label__hide: false; diff --git a/lib/web/css/source/lib/variables/_typography.less b/lib/web/css/source/lib/variables/_typography.less index bf88990fe05..205b1fa9c7a 100644 --- a/lib/web/css/source/lib/variables/_typography.less +++ b/lib/web/css/source/lib/variables/_typography.less @@ -83,13 +83,13 @@ // Links // --------------------------------------------- -@link__color: @color-blue1; +@link__color: @theme__color__primary; @link__text-decoration: none; @link__visited__color: @link__color; @link__visited__text-decoration: none; -@link__hover__color: @color-blue2; +@link__hover__color: @theme__color__primary-alt; @link__hover__text-decoration: underline; @link__active__color: @active__color; diff --git a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js index 9374bac405c..9779be85133 100644 --- a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js +++ b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter.js @@ -350,7 +350,7 @@ define([ * @param {String} content */ setContent: function (content) { - this.get(this.getId()).execCommand('mceSetContent', false, content); + this.get(this.getId()).setContent(content); }, /** @@ -559,10 +559,12 @@ define([ var selection = editor.selection, dom = editor.dom, rng = dom.createRng(), + doc = editor.getDoc(), markerHtml, marker; - if (!selection.getContent().length) { + // Validate the range we're trying to fix is contained within the current editors document + if (!selection.getContent().length && jQuery.contains(doc, selection.getRng().startContainer)) { markerHtml = '<span id="mce_marker" data-mce-type="bookmark">\uFEFF</span>'; selection.setContent(markerHtml); marker = dom.get('mce_marker'); diff --git a/lib/web/mage/calendar.js b/lib/web/mage/calendar.js index ac154b33380..a9ccf2cf787 100644 --- a/lib/web/mage/calendar.js +++ b/lib/web/mage/calendar.js @@ -66,6 +66,9 @@ * Widget calendar */ $.widget('mage.calendar', { + options: { + autoComplete: true + }, /** * Merge global options with options passed to widget invoke @@ -379,6 +382,9 @@ .addClass('v-middle') .text('') // Remove jQuery UI datepicker generated image .append('<span>' + pickerButtonText + '</span>'); + + $(element).attr('autocomplete', this.options.autoComplete ? 'on' : 'off'); + this._setCurrentDate(element); }, diff --git a/lib/web/mage/dataPost.js b/lib/web/mage/dataPost.js index 5d052f12db8..cc56ee266e0 100644 --- a/lib/web/mage/dataPost.js +++ b/lib/web/mage/dataPost.js @@ -57,7 +57,7 @@ define([ */ postData: function (params) { var formKey = $(this.options.formKeyInputSelector).val(), - $form; + $form, input; if (formKey) { params.data['form_key'] = formKey; @@ -67,6 +67,19 @@ define([ data: params })); + if (params.files) { + $form[0].enctype = 'multipart/form-data'; + $.each(params.files, function (key, files) { + if (files instanceof FileList) { + input = document.createElement('input'); + input.type = 'file'; + input.name = key; + input.files = files; + $form[0].appendChild(input); + } + }); + } + if (params.data.confirmation) { uiConfirm({ content: params.data.confirmationMessage, diff --git a/lib/web/mage/gallery/gallery.less b/lib/web/mage/gallery/gallery.less index ebd6bdd003b..373708ac35a 100644 --- a/lib/web/mage/gallery/gallery.less +++ b/lib/web/mage/gallery/gallery.less @@ -181,8 +181,8 @@ .fotorama__active { .fotorama__dot { - background-color: @color-orange-red1; - border-color: @color-orange-red1; + background-color: @active__color; + border-color: @active__color; } } @@ -237,7 +237,7 @@ &:extend(.fotorama-print-background); backface-visibility: hidden; background-image: linear-gradient(to bottom right, rgba(255, 255, 255, 0.25), rgba(64, 64, 64, 0.1)); - border: 1px solid @color-orange-red1; + border: 1px solid @active__color; left: 0; position: absolute; top: 0; diff --git a/lib/web/mage/gallery/module/_extends.less b/lib/web/mage/gallery/module/_extends.less index cfcec776034..ab38cf40f63 100644 --- a/lib/web/mage/gallery/module/_extends.less +++ b/lib/web/mage/gallery/module/_extends.less @@ -42,7 +42,7 @@ .fotorama-focus-overlay { &:after { &:extend(.fotorama-stretch); - background-color: @color-blue2; + background-color: @theme__color__primary-alt; border-radius: inherit; content: ''; } diff --git a/lib/web/mage/translate-init.js b/lib/web/mage/translate-init.js deleted file mode 100644 index 1b9defad5e3..00000000000 --- a/lib/web/mage/translate-init.js +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -define([ - 'jquery', - 'mage/translate', - 'jquery/jquery-storageapi' -], function ($) { - 'use strict'; - - return function (pageOptions) { - var dependencies = [], - versionObj; - - $.initNamespaceStorage('mage-translation-storage'); - $.initNamespaceStorage('mage-translation-file-version'); - versionObj = $.localStorage.get('mage-translation-file-version'); - - if (versionObj.version !== pageOptions.version) { - dependencies.push( - pageOptions.dictionaryFile - ); - } - - require.config({ - deps: dependencies, - - /** - * @param {String} string - */ - callback: function (string) { - if (typeof string === 'string') { - $.mage.translate.add(JSON.parse(string)); - $.localStorage.set('mage-translation-storage', string); - $.localStorage.set( - 'mage-translation-file-version', - { - version: pageOptions.version - } - ); - } else { - $.mage.translate.add($.localStorage.get('mage-translation-storage')); - } - } - }); - }; -}); diff --git a/lib/web/mage/validation.js b/lib/web/mage/validation.js index 01ecaf7cf46..dfa35473176 100644 --- a/lib/web/mage/validation.js +++ b/lib/web/mage/validation.js @@ -1777,7 +1777,8 @@ valid = true, validateConfig = { errorElement: 'label', - ignore: '.ignore-validate' + ignore: '.ignore-validate', + hideError: false }, form, validator, classes, elementValue; @@ -1815,7 +1816,10 @@ valid = false; errors[element.get(0).name] = this.messages[className]; validator.invalid[element.get(0).name] = true; - validator.showErrors(errors); + + if (!validateConfig.hideError) { + validator.showErrors(errors); + } return valid; } diff --git a/phpserver/README.md b/phpserver/README.md index 6bb814fe5f5..563d2ed7c9f 100644 --- a/phpserver/README.md +++ b/phpserver/README.md @@ -14,7 +14,7 @@ Without a router script, that is not possible via the php built-in server. ### How to install Magento -Magento's web-based Setup Wizard runs from the `setup` subdirectory, which PHP's built-in web server cannot route. Therefore, you must install Magento using the <a href="http://devdocs.magento.com/guides/v2.0/install-gde/install/cli/install-cli.html" target="_blank">command line</a>. An example follows: +Magento's web-based Setup Wizard runs from the `setup` subdirectory, which PHP's built-in web server cannot route. Therefore, you must install Magento using the <a href="https://devdocs.magento.com/guides/v2.0/install-gde/install/cli/install-cli.html" target="_blank">command line</a>. An example follows: ``` php bin/magento setup:install --base-url=http://127.0.0.1:8082 @@ -31,7 +31,7 @@ For more informations about the installation process using the CLI, you can cons ### How to run Magento -Example usage: ```php -S 127.0.0.1:8082 -t ./pub/ ../phpserver/router.php``` +Example usage: ```php -S 127.0.0.1:8082 -t ./pub/ ./phpserver/router.php``` ### What exactly the script does diff --git a/setup/performance-toolkit/README.md b/setup/performance-toolkit/README.md index bb0a023ab8a..7bf7a1fbd47 100644 --- a/setup/performance-toolkit/README.md +++ b/setup/performance-toolkit/README.md @@ -29,7 +29,7 @@ Splitting generation and indexation processes doesn't reduce total processing ti php bin/magento setup:performance:generate-fixtures -s setup/performance-toolkit/profiles/ce/small.xml php bin/magento indexer:reindex -For more information about the available profiles and generating fixtures generation, read [Generate data for performance testing](http://devdocs.magento.com/guides/v2.2/config-guide/cli/config-cli-subcommands-perf-data.html). +For more information about the available profiles and generating fixtures generation, read [Generate data for performance testing](https://devdocs.magento.com/guides/v2.2/config-guide/cli/config-cli-subcommands-perf-data.html). For run Admin Pool in multithreading mode, please be sure, that: - "Admin Account Sharing" is enabled diff --git a/setup/src/Magento/Setup/Console/Command/InstallCommand.php b/setup/src/Magento/Setup/Console/Command/InstallCommand.php index 74c2e3b2423..cc1cca74ed6 100644 --- a/setup/src/Magento/Setup/Console/Command/InstallCommand.php +++ b/setup/src/Magento/Setup/Console/Command/InstallCommand.php @@ -183,7 +183,7 @@ protected function configure() self::INPUT_KEY_INTERACTIVE_SETUP, self::INPUT_KEY_INTERACTIVE_SETUP_SHORTCUT, InputOption::VALUE_NONE, - 'Interactive Magento instalation' + 'Interactive Magento installation' ), new InputOption( OperationsExecutor::KEY_SAFE_MODE, diff --git a/setup/src/Magento/Setup/Controller/LandingInstaller.php b/setup/src/Magento/Setup/Controller/LandingInstaller.php index 45579979b9f..49125c47fad 100644 --- a/setup/src/Magento/Setup/Controller/LandingInstaller.php +++ b/setup/src/Magento/Setup/Controller/LandingInstaller.php @@ -27,13 +27,15 @@ public function __construct(\Magento\Framework\App\ProductMetadata $productMetad } /** + * Setup index action. + * * @return array|ViewModel */ public function indexAction() { $welcomeMsg = "Welcome to Magento Admin, your online store headquarters.<br>" . "Click 'Agree and Set Up Magento' or read "; - $docRef = "http://devdocs.magento.com/guides/v1.0/install-gde/install/install-web.html"; + $docRef = "https://devdocs.magento.com/guides/v1.0/install-gde/install/install-web.html"; $agreeButtonText = "Agree and Setup Magento"; $view = new ViewModel; $view->setTerminal(true); diff --git a/setup/src/Magento/Setup/Controller/LandingUpdater.php b/setup/src/Magento/Setup/Controller/LandingUpdater.php index e144cc40c37..6ae97eec42d 100644 --- a/setup/src/Magento/Setup/Controller/LandingUpdater.php +++ b/setup/src/Magento/Setup/Controller/LandingUpdater.php @@ -27,15 +27,17 @@ public function __construct(\Magento\Framework\App\ProductMetadata $productMetad } /** + * Updater index action. + * * @return array|ViewModel */ public function indexAction() { $welcomeMsg = "Welcome to Magento Module Manager.<br>" . "Click 'Agree and Update Magento' or read "; - $docRef = "http://devdocs.magento.com/guides/v1.0/install-gde/install/install-web.html"; + $docRef = "https://devdocs.magento.com/guides/v1.0/install-gde/install/install-web.html"; $agreeButtonText = "Agree and Update Magento"; - $view = new ViewModel; + $view = new ViewModel(); $view->setTerminal(true); $view->setTemplate('/magento/setup/landing.phtml'); $view->setVariable('version', $this->productMetadata->getVersion()); diff --git a/setup/src/Magento/Setup/Model/ConfigOptionsList.php b/setup/src/Magento/Setup/Model/ConfigOptionsList.php index fa79139e733..afe1a5d9e25 100644 --- a/setup/src/Magento/Setup/Model/ConfigOptionsList.php +++ b/setup/src/Magento/Setup/Model/ConfigOptionsList.php @@ -8,6 +8,7 @@ use Magento\Framework\App\DeploymentConfig; use Magento\Framework\Config\ConfigOptionsListConstants; +use Magento\Framework\Encryption\KeyValidator; use Magento\Framework\Setup\ConfigOptionsListInterface; use Magento\Framework\Setup\Option\FlagConfigOption; use Magento\Framework\Setup\Option\SelectConfigOption; @@ -38,6 +39,11 @@ class ConfigOptionsList implements ConfigOptionsListInterface */ private $configOptionsCollection = []; + /** + * @var KeyValidator + */ + private $encryptionKeyValidator; + /** * @var array */ @@ -52,18 +58,25 @@ class ConfigOptionsList implements ConfigOptionsListInterface * * @param ConfigGenerator $configGenerator * @param DbValidator $dbValidator + * @param KeyValidator|null $encryptionKeyValidator */ - public function __construct(ConfigGenerator $configGenerator, DbValidator $dbValidator) - { + public function __construct( + ConfigGenerator $configGenerator, + DbValidator $dbValidator, + KeyValidator $encryptionKeyValidator = null + ) { $this->configGenerator = $configGenerator; $this->dbValidator = $dbValidator; + $objectManager = \Magento\Framework\App\ObjectManager::getInstance(); foreach ($this->configOptionsListClasses as $className) { - $this->configOptionsCollection[] = \Magento\Framework\App\ObjectManager::getInstance()->get($className); + $this->configOptionsCollection[] = $objectManager->get($className); } + $this->encryptionKeyValidator = $encryptionKeyValidator ?: $objectManager->get(KeyValidator::class); } /** - * {@inheritdoc} + * @inheritdoc + * * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ public function getOptions() @@ -160,7 +173,7 @@ public function getOptions() } /** - * {@inheritdoc} + * @inheritdoc */ public function createConfig(array $data, DeploymentConfig $deploymentConfig) { @@ -184,7 +197,7 @@ public function createConfig(array $data, DeploymentConfig $deploymentConfig) } /** - * {@inheritdoc} + * @inheritdoc */ public function validate(array $options, DeploymentConfig $deploymentConfig) { @@ -276,8 +289,9 @@ private function validateEncryptionKey(array $options) $errors = []; if (isset($options[ConfigOptionsListConstants::INPUT_KEY_ENCRYPTION_KEY]) - && !$options[ConfigOptionsListConstants::INPUT_KEY_ENCRYPTION_KEY]) { - $errors[] = 'Invalid encryption key'; + && !$this->encryptionKeyValidator->isValid($options[ConfigOptionsListConstants::INPUT_KEY_ENCRYPTION_KEY]) + ) { + $errors[] = 'Invalid encryption key. Encryption key must be 32 character string without any white space.'; } return $errors; diff --git a/setup/src/Magento/Setup/Model/ConfigOptionsList/Session.php b/setup/src/Magento/Setup/Model/ConfigOptionsList/Session.php index c0ec78f046e..e864a81ffcc 100644 --- a/setup/src/Magento/Setup/Model/ConfigOptionsList/Session.php +++ b/setup/src/Magento/Setup/Model/ConfigOptionsList/Session.php @@ -139,7 +139,7 @@ class Session implements ConfigOptionsListInterface ]; /** - * {@inheritdoc} + * @inheritdoc * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ public function getOptions() @@ -289,7 +289,7 @@ public function getOptions() } /** - * {@inheritdoc} + * @inheritdoc */ public function createConfig(array $options, DeploymentConfig $deploymentConfig) { @@ -320,7 +320,7 @@ public function createConfig(array $options, DeploymentConfig $deploymentConfig) } /** - * {@inheritdoc} + * @inheritdoc */ public function validate(array $options, DeploymentConfig $deploymentConfig) { @@ -340,7 +340,7 @@ public function validate(array $options, DeploymentConfig $deploymentConfig) if (isset($options[self::INPUT_KEY_SESSION_REDIS_LOG_LEVEL])) { $level = $options[self::INPUT_KEY_SESSION_REDIS_LOG_LEVEL]; - if (($level < 0) or ($level > 7)) { + if (($level < 0) || ($level > 7)) { $errors[] = "Invalid Redis log level '{$level}'. Valid range is 0-7, inclusive."; } } diff --git a/setup/src/Magento/Setup/Model/SystemPackage.php b/setup/src/Magento/Setup/Model/SystemPackage.php index 232ac1dd342..bc5f55c0b12 100755 --- a/setup/src/Magento/Setup/Model/SystemPackage.php +++ b/setup/src/Magento/Setup/Model/SystemPackage.php @@ -154,6 +154,8 @@ public function getSystemPackageVersions($systemPackageInfo) } /** + * Get installed system packages. + * * @return array * @throws \Exception * @throws \RuntimeException @@ -179,7 +181,7 @@ public function getInstalledSystemPackages() throw new \RuntimeException( 'We\'re sorry, no components are available because you cloned the Magento 2 GitHub repository. ' . 'You must manually update components as discussed in the ' . - '<a href="http://devdocs.magento.com/guides/v2.0/install-gde/install/cli/dev_options.html">' . + '<a href="https://devdocs.magento.com/guides/v2.0/install-gde/install/cli/dev_options.html">' . 'Installation Guide</a>.' ); } @@ -187,6 +189,8 @@ public function getInstalledSystemPackages() } /** + * Sort versions. + * * @param array $enterpriseVersions * @return array */ @@ -241,6 +245,8 @@ private function formatPackages($packages) } /** + * Filter enterprise versions. + * * @param string $currentCE * @param array $enterpriseVersions * @param string $maxVersion diff --git a/setup/src/Magento/Setup/Test/Unit/Model/ConfigOptionsListTest.php b/setup/src/Magento/Setup/Test/Unit/Model/ConfigOptionsListTest.php index f342a114934..d7f680309c9 100644 --- a/setup/src/Magento/Setup/Test/Unit/Model/ConfigOptionsListTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Model/ConfigOptionsListTest.php @@ -144,7 +144,7 @@ public function testValidateEmptyEncryptionKey() ConfigOptionsListConstants::INPUT_KEY_ENCRYPTION_KEY => '' ]; $this->assertEquals( - ['Invalid encryption key'], + ['Invalid encryption key. Encryption key must be 32 character string without any white space.'], $this->object->validate($options, $this->deploymentConfig) ); } diff --git a/setup/view/magento/setup/landing.phtml b/setup/view/magento/setup/landing.phtml index 581be20df93..645ae6ae9d6 100644 --- a/setup/view/magento/setup/landing.phtml +++ b/setup/view/magento/setup/landing.phtml @@ -15,7 +15,7 @@ <p class="text-welcome"> Welcome to Magento Admin, your online store headquarters. <br> - Click 'Agree and Set Up Magento' or read <a href="http://devdocs.magento.com/guides/v2.0/install-gde/install/web/install-web.html" target="_blank">Getting Started</a> to learn more. + Click 'Agree and Set Up Magento' or read <a href="https://devdocs.magento.com/guides/v2.0/install-gde/install/web/install-web.html" target="_blank">Getting Started</a> to learn more. </p> <p class="text-terms"> <a href="" ng-click="previousState()">Terms & Agreement</a> diff --git a/setup/view/magento/setup/readiness-check/progress.phtml b/setup/view/magento/setup/readiness-check/progress.phtml index eb9dd0ce9d1..d8e519aa8ac 100755 --- a/setup/view/magento/setup/readiness-check/progress.phtml +++ b/setup/view/magento/setup/readiness-check/progress.phtml @@ -121,7 +121,7 @@ Download and install the updater. </p> <p ng-show="updater.expanded">For additional assistance, see - <a href="http://devdocs.magento.com/guides/v2.0/comp-mgr/trouble/cman/updater.html" + <a href="https://devdocs.magento.com/guides/v2.0/comp-mgr/trouble/cman/updater.html" target="_blank">updater application help</a>. </p> </div> @@ -172,7 +172,7 @@ <p ng-show="cronScript.expanded" ng-bind-html="cronScript.updaterErrorMessage"> </p> <p ng-show="cronScript.expanded">For additional assistance, see - <a href="http://devdocs.magento.com/guides/v2.0/comp-mgr/trouble/cman/cron.html" + <a href="https://devdocs.magento.com/guides/v2.0/comp-mgr/trouble/cman/cron.html" target="_blank">cron scripts help</a>. </p> </div> @@ -214,7 +214,7 @@ <p ng-show="componentDependency.expanded" ng-bind-html="componentDependency.errorMessage"> </p> <p ng-show="componentDependency.expanded">For additional assistance, see - <a href="http://devdocs.magento.com/guides/v2.0/comp-mgr/trouble/cman/component-depend.html" + <a href="https://devdocs.magento.com/guides/v2.0/comp-mgr/trouble/cman/component-depend.html" target="_blank">component dependency help </a>. </p> @@ -336,7 +336,7 @@ </div> <p ng-show="componentDependency.expanded">For additional assistance, see - <a href="http://devdocs.magento.com/guides/v2.2/install-gde/trouble/php/tshoot_php-set.html" + <a href="https://devdocs.magento.com/guides/v2.2/install-gde/trouble/php/tshoot_php-set.html" target="_blank">PHP settings check help </a>. </p> @@ -392,7 +392,7 @@ <div class="readiness-check-side"> <p class="side-title">Need Help?</p> - <a href="http://devdocs.magento.com/guides/v2.2/install-gde/system-requirements.html" target="_blank">PHP Extension Help</a> + <a href="https://devdocs.magento.com/guides/v2.2/install-gde/system-requirements.html" target="_blank">PHP Extension Help</a> </div> <span class="readiness-check-icon icon-failed-round"></span> @@ -413,7 +413,7 @@ <p> The best way to resolve this is to install the correct missing extensions. The exact fix depends on our server, your host, and other system variables. <br> - Our <a href="http://devdocs.magento.com/guides/v2.2/install-gde/system-requirements.html" target="_blank">PHP extension help</a> can get you started. + Our <a href="https://devdocs.magento.com/guides/v2.2/install-gde/system-requirements.html" target="_blank">PHP extension help</a> can get you started. </p> <p> For additional assistance, contact your hosting provider. @@ -477,7 +477,7 @@ <div class="readiness-check-side"> <p class="side-title">Need Help?</p> - <a href="http://devdocs.magento.com/guides/v2.2/install-gde/prereq/file-system-perms.html" target="_blank">File Permission Help</a> + <a href="https://devdocs.magento.com/guides/v2.2/install-gde/prereq/file-system-perms.html" target="_blank">File Permission Help</a> </div> <span class="readiness-check-icon icon-failed-round"></span> @@ -500,7 +500,7 @@ The best way to resolve this is to allow write permissions for files in the following Magento directories and subdirectories. The exact fix depends on your server, your host, and other system variables. <br> - For help, see our <a href="http://devdocs.magento.com/guides/v2.2/install-gde/prereq/file-system-perms.html" target="_blank">File Permission Help</a> or call your hosting provider. + For help, see our <a href="https://devdocs.magento.com/guides/v2.2/install-gde/prereq/file-system-perms.html" target="_blank">File Permission Help</a> or call your hosting provider. </p> <ul class="list" ng-show="permissions.expanded" ng-init="showDetails=false"> <li diff --git a/setup/view/magento/setup/start-updater.phtml b/setup/view/magento/setup/start-updater.phtml index b08407b30d4..9b2a6b5598a 100644 --- a/setup/view/magento/setup/start-updater.phtml +++ b/setup/view/magento/setup/start-updater.phtml @@ -26,7 +26,7 @@ </strong> <div ng-show="type == 'upgrade'"> Before you start upgrade, you can optionally - <a href="http://devdocs.magento.com/guides/v2.1/comp-mgr/trouble/cman/maint-mode.html"> + <a href="https://devdocs.magento.com/guides/v2.1/comp-mgr/trouble/cman/maint-mode.html"> configure a custom maintenance page</a> that informs shoppers the store is offline. (Magento provides a default page.) </div> diff --git a/setup/view/magento/setup/web-configuration.phtml b/setup/view/magento/setup/web-configuration.phtml index d0307a71e4a..4018694dfd0 100644 --- a/setup/view/magento/setup/web-configuration.phtml +++ b/setup/view/magento/setup/web-configuration.phtml @@ -282,15 +282,20 @@ $hints = [ tooltip-html-unsafe="<?= $hints['encrypt_key'] ?>" tooltip-trigger="focus" tooltip-append-to-body="true" - ng-minlength="4" + ng-minlength="32" + ng-maxlength="32" + ng-pattern="/^\S+$/" required > <div class="error-container"> <span ng-show="webconfig.key.$error.required"> You must enter an encryption key. </span> - <span ng-show="webconfig.key.$error.minlength"> - Your encryption key must be longer and stronger. + <span ng-show="webconfig.key.$error.minlength + || webconfig.key.$error.maxlength + || webconfig.key.$error.pattern" + > + Encryption key must be 32 character string without any white space. </span> </div> </div> diff --git a/setup/view/styles/lib/variables/_colors.less b/setup/view/styles/lib/variables/_colors.less index 638490ac867..a72dc69ac76 100644 --- a/setup/view/styles/lib/variables/_colors.less +++ b/setup/view/styles/lib/variables/_colors.less @@ -24,7 +24,7 @@ @color-green-apple: #79a22e; @color-green-islamic: #090; @color-dark-brownie: #41362f; -@color-brown-darkie: #41362f; +@color-brown-darker: #41362f; @color-phoenix-down: #e04f00; @color-phoenix: #eb5202; @color-phoenix-almost-rise: #ef672f;