From 12f2e936d3fa433b0872886894745c41418f39b6 Mon Sep 17 00:00:00 2001 From: OlgaVasyltsun Date: Tue, 29 May 2018 14:30:00 +0300 Subject: [PATCH 01/24] MAGETWO-89407: [2.3] PayPal Payments Pro settings gets from wrong store --- .../Payflow/Service/Request/SecureToken.php | 2 +- app/code/Magento/Paypal/Model/Payflowlink.php | 3 + .../Service/Request/SecureTokenTest.php | 42 +++- .../Test/Unit/Model/PayflowlinkTest.php | 11 +- .../default_payment_configuration.php | 30 +++ ...default_payment_configuration_rollback.php | 24 ++ .../Paypal/Fixtures/process_config_data.php | 22 ++ .../Fixtures/store_payment_configuration.php | 32 +++ .../store_payment_configuration_rollback.php | 30 +++ .../website_payment_configuration.php | 31 +++ ...website_payment_configuration_rollback.php | 30 +++ .../Service/Request/SecureTokenTest.php | 214 ++++++++++++++++++ .../Paypal/_files/fixed_discount_rollback.php | 26 +++ .../Paypal/_files/quote_payflowpro.php | 16 +- .../_files/quote_payflowpro_rollback.php | 43 ++++ 15 files changed, 535 insertions(+), 21 deletions(-) create mode 100644 dev/tests/integration/testsuite/Magento/Paypal/Fixtures/default_payment_configuration.php create mode 100644 dev/tests/integration/testsuite/Magento/Paypal/Fixtures/default_payment_configuration_rollback.php create mode 100644 dev/tests/integration/testsuite/Magento/Paypal/Fixtures/process_config_data.php create mode 100644 dev/tests/integration/testsuite/Magento/Paypal/Fixtures/store_payment_configuration.php create mode 100644 dev/tests/integration/testsuite/Magento/Paypal/Fixtures/store_payment_configuration_rollback.php create mode 100644 dev/tests/integration/testsuite/Magento/Paypal/Fixtures/website_payment_configuration.php create mode 100644 dev/tests/integration/testsuite/Magento/Paypal/Fixtures/website_payment_configuration_rollback.php create mode 100644 dev/tests/integration/testsuite/Magento/Paypal/Model/Payflow/Service/Request/SecureTokenTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Paypal/_files/fixed_discount_rollback.php create mode 100644 dev/tests/integration/testsuite/Magento/Paypal/_files/quote_payflowpro_rollback.php diff --git a/app/code/Magento/Paypal/Model/Payflow/Service/Request/SecureToken.php b/app/code/Magento/Paypal/Model/Payflow/Service/Request/SecureToken.php index 9d215ca6cbe17..da5599984b701 100644 --- a/app/code/Magento/Paypal/Model/Payflow/Service/Request/SecureToken.php +++ b/app/code/Magento/Paypal/Model/Payflow/Service/Request/SecureToken.php @@ -11,7 +11,6 @@ use Magento\Paypal\Model\Payflow\Transparent; use Magento\Paypal\Model\Payflowpro; use Magento\Quote\Model\Quote; -use Magento\Sales\Model\Order\Payment; /** * Class SecureToken @@ -59,6 +58,7 @@ public function __construct( */ public function requestToken(Quote $quote) { + $this->transparent->setStore($quote->getStoreId()); $request = $this->transparent->buildBasicRequest(); $request->setTrxtype(Payflowpro::TRXTYPE_AUTH_ONLY); diff --git a/app/code/Magento/Paypal/Model/Payflowlink.php b/app/code/Magento/Paypal/Model/Payflowlink.php index 792309bd76cf9..1955ef3c67661 100644 --- a/app/code/Magento/Paypal/Model/Payflowlink.php +++ b/app/code/Magento/Paypal/Model/Payflowlink.php @@ -10,6 +10,7 @@ use Magento\Payment\Model\Method\ConfigInterfaceFactory; use Magento\Paypal\Model\Payflow\Service\Response\Handler\HandlerInterface; use Magento\Sales\Api\Data\OrderPaymentInterface; +use Magento\Sales\Model\Order; use Magento\Sales\Model\Order\Email\Sender\OrderSender; /** @@ -239,11 +240,13 @@ public function initialize($paymentAction, $stateObject) case \Magento\Paypal\Model\Config::PAYMENT_ACTION_AUTH: case \Magento\Paypal\Model\Config::PAYMENT_ACTION_SALE: $payment = $this->getInfoInstance(); + /** @var Order $order */ $order = $payment->getOrder(); $order->setCanSendNewEmailFlag(false); $payment->setAmountAuthorized($order->getTotalDue()); $payment->setBaseAmountAuthorized($order->getBaseTotalDue()); $this->_generateSecureSilentPostHash($payment); + $this->setStore($order->getStoreId()); $request = $this->_buildTokenRequest($payment); $response = $this->postRequest($request, $this->getConfig()); $this->_processTokenErrors($response, $payment); diff --git a/app/code/Magento/Paypal/Test/Unit/Model/Payflow/Service/Request/SecureTokenTest.php b/app/code/Magento/Paypal/Test/Unit/Model/Payflow/Service/Request/SecureTokenTest.php index d4a7db25cae89..d8e54ad28fcc8 100644 --- a/app/code/Magento/Paypal/Test/Unit/Model/Payflow/Service/Request/SecureTokenTest.php +++ b/app/code/Magento/Paypal/Test/Unit/Model/Payflow/Service/Request/SecureTokenTest.php @@ -10,6 +10,9 @@ use Magento\Framework\UrlInterface; use Magento\Paypal\Model\Payflow\Service\Request\SecureToken; use Magento\Paypal\Model\Payflow\Transparent; +use Magento\Paypal\Model\PayflowConfig; +use Magento\Quote\Model\Quote; +use PHPUnit_Framework_MockObject_MockObject as MockObject; /** * Test class for \Magento\Paypal\Model\Payflow\Service\Request\SecureToken @@ -19,23 +22,26 @@ class SecureTokenTest extends \PHPUnit\Framework\TestCase /** * @var SecureToken */ - protected $model; + private $model; /** - * @var \PHPUnit_Framework_MockObject_MockObject|Transparent + * @var Transparent|MockObject */ - protected $transparent; + private $transparent; /** - * @var \PHPUnit_Framework_MockObject_MockObject|Random + * @var Random|MockObject */ - protected $mathRandom; + private $mathRandom; /** - * @var \PHPUnit_Framework_MockObject_MockObject|UrlInterface + * @var UrlInterface|MockObject */ - protected $url; + private $url; + /** + * @inheritdoc + */ protected function setUp() { $this->url = $this->createMock(\Magento\Framework\UrlInterface::class); @@ -52,11 +58,29 @@ protected function setUp() public function testRequestToken() { $request = new DataObject(); + $storeId = 1; $secureTokenID = 'Sdj46hDokds09c8k2klaGJdKLl032ekR'; + $response = new DataObject([ + 'result' => '0', + 'respmsg' => 'Approved', + 'securetoken' => '80IgSbabyj0CtBDWHZZeQN3', + 'securetokenid' => $secureTokenID, + 'result_code' => '0', + ]); + + $quote = $this->getMockBuilder(Quote::class) + ->disableOriginalConstructor() + ->getMock(); + $quote->expects($this->once()) + ->method('getStoreId') + ->willReturn($storeId); $this->transparent->expects($this->once()) ->method('buildBasicRequest') ->willReturn($request); + $this->transparent->expects($this->once()) + ->method('setStore') + ->with($storeId); $this->transparent->expects($this->once()) ->method('fillCustomerContacts'); $this->transparent->expects($this->once()) @@ -64,7 +88,7 @@ public function testRequestToken() ->willReturn($this->createMock(\Magento\Paypal\Model\PayflowConfig::class)); $this->transparent->expects($this->once()) ->method('postRequest') - ->willReturn(new DataObject()); + ->willReturn($response); $this->mathRandom->expects($this->once()) ->method('getUniqueHash') @@ -73,8 +97,6 @@ public function testRequestToken() $this->url->expects($this->exactly(3)) ->method('getUrl'); - $quote = $this->createMock(\Magento\Quote\Model\Quote::class); - $this->model->requestToken($quote); $this->assertEquals($secureTokenID, $request->getSecuretokenid()); diff --git a/app/code/Magento/Paypal/Test/Unit/Model/PayflowlinkTest.php b/app/code/Magento/Paypal/Test/Unit/Model/PayflowlinkTest.php index 362615e965d1b..80c8194e07654 100644 --- a/app/code/Magento/Paypal/Test/Unit/Model/PayflowlinkTest.php +++ b/app/code/Magento/Paypal/Test/Unit/Model/PayflowlinkTest.php @@ -101,16 +101,20 @@ protected function setUp() public function testInitialize() { + $storeId = 1; $order = $this->createMock(\Magento\Sales\Model\Order::class); + $order->expects($this->exactly(2)) + ->method('getStoreId') + ->willReturn($storeId); $this->infoInstance->expects($this->any()) ->method('getOrder') - ->will($this->returnValue($order)); + ->willReturn($order); $this->infoInstance->expects($this->any()) ->method('setAdditionalInformation') - ->will($this->returnSelf()); + ->willReturnSelf(); $this->paypalConfig->expects($this->once()) ->method('getBuildNotationCode') - ->will($this->returnValue('build notation code')); + ->willReturn('build notation code'); $response = new \Magento\Framework\DataObject( [ @@ -148,6 +152,7 @@ public function testInitialize() $stateObject = new \Magento\Framework\DataObject(); $this->model->initialize(\Magento\Paypal\Model\Config::PAYMENT_ACTION_AUTH, $stateObject); + self::assertEquals($storeId, $this->model->getStore(), '{Store} should be set'); } /** diff --git a/dev/tests/integration/testsuite/Magento/Paypal/Fixtures/default_payment_configuration.php b/dev/tests/integration/testsuite/Magento/Paypal/Fixtures/default_payment_configuration.php new file mode 100644 index 0000000000000..2c1614b9317c5 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Paypal/Fixtures/default_payment_configuration.php @@ -0,0 +1,30 @@ +get(EncryptorInterface::class); + +// save payment configuration for the default scope +$configData = [ + 'payment/payflowpro/partner' => 'def_partner', + 'payment/payflowpro/vendor' => 'def_vendor', + 'payment/payflowpro/user' => $encryptor->encrypt('def_user'), + 'payment/payflowpro/pwd' => $encryptor->encrypt('def_pwd'), +]; +/** @var Config $defConfig */ +$defConfig = $objectManager->create(Config::class); +$defConfig->setScope(ScopeConfigInterface::SCOPE_TYPE_DEFAULT); +$processConfigData($defConfig, $configData); diff --git a/dev/tests/integration/testsuite/Magento/Paypal/Fixtures/default_payment_configuration_rollback.php b/dev/tests/integration/testsuite/Magento/Paypal/Fixtures/default_payment_configuration_rollback.php new file mode 100644 index 0000000000000..b9cf1707c7b6c --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Paypal/Fixtures/default_payment_configuration_rollback.php @@ -0,0 +1,24 @@ +get(WriterInterface::class); +$deleteConfigData($configWriter, $configData, ScopeConfigInterface::SCOPE_TYPE_DEFAULT, 0); diff --git a/dev/tests/integration/testsuite/Magento/Paypal/Fixtures/process_config_data.php b/dev/tests/integration/testsuite/Magento/Paypal/Fixtures/process_config_data.php new file mode 100644 index 0000000000000..2c672378fb832 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Paypal/Fixtures/process_config_data.php @@ -0,0 +1,22 @@ + $value) { + $config->setDataByPath($key, $value); + $config->save(); + } +}; + +$deleteConfigData = function (WriterInterface $writer, array $configData, string $scope, int $scopeId) { + foreach ($configData as $path) { + $writer->delete($path, $scope, $scopeId); + } +}; diff --git a/dev/tests/integration/testsuite/Magento/Paypal/Fixtures/store_payment_configuration.php b/dev/tests/integration/testsuite/Magento/Paypal/Fixtures/store_payment_configuration.php new file mode 100644 index 0000000000000..e54349e118bb4 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Paypal/Fixtures/store_payment_configuration.php @@ -0,0 +1,32 @@ +get(EncryptorInterface::class); + +$storeConfigData = [ + 'payment/payflowpro/partner' => 'store_partner', + 'payment/payflowpro/vendor' => 'store_vendor', + 'payment/payflowpro/user' => $encryptor->encrypt('store_user'), + 'payment/payflowpro/pwd' => $encryptor->encrypt('store_pwd'), +]; +/** @var Config $storeConfig */ +$storeConfig = $objectManager->create(Config::class); +$storeConfig->setScope(ScopeInterface::SCOPE_STORES); +$storeConfig->setStore('test'); +$processConfigData($storeConfig, $storeConfigData); diff --git a/dev/tests/integration/testsuite/Magento/Paypal/Fixtures/store_payment_configuration_rollback.php b/dev/tests/integration/testsuite/Magento/Paypal/Fixtures/store_payment_configuration_rollback.php new file mode 100644 index 0000000000000..08fe9ca7049f2 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Paypal/Fixtures/store_payment_configuration_rollback.php @@ -0,0 +1,30 @@ +get(WriterInterface::class); + +/** @var StoreRepositoryInterface $storeRepository */ +$storeRepository = $objectManager->get(StoreRepositoryInterface::class); +$store = $storeRepository->get('test'); +$deleteConfigData($configWriter, $configData, ScopeInterface::SCOPE_STORES, (int)$store->getId()); +require __DIR__ . '/../../Store/_files/store_rollback.php'; diff --git a/dev/tests/integration/testsuite/Magento/Paypal/Fixtures/website_payment_configuration.php b/dev/tests/integration/testsuite/Magento/Paypal/Fixtures/website_payment_configuration.php new file mode 100644 index 0000000000000..90359ccf4fe1c --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Paypal/Fixtures/website_payment_configuration.php @@ -0,0 +1,31 @@ +get(EncryptorInterface::class); +$websiteConfigData = [ + 'payment/payflowpro/partner' => 'website_partner', + 'payment/payflowpro/vendor' => 'website_vendor', + 'payment/payflowpro/user' => $encryptor->encrypt('website_user'), + 'payment/payflowpro/pwd' => $encryptor->encrypt('website_pwd'), +]; +/** @var Config $websiteConfig */ +$websiteConfig = $objectManager->create(Config::class); +$websiteConfig->setScope(ScopeInterface::SCOPE_WEBSITES); +$websiteConfig->setWebsite($websiteId); +$processConfigData($websiteConfig, $websiteConfigData); diff --git a/dev/tests/integration/testsuite/Magento/Paypal/Fixtures/website_payment_configuration_rollback.php b/dev/tests/integration/testsuite/Magento/Paypal/Fixtures/website_payment_configuration_rollback.php new file mode 100644 index 0000000000000..1558d78e2504f --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Paypal/Fixtures/website_payment_configuration_rollback.php @@ -0,0 +1,30 @@ +get(WriterInterface::class); +/** @var WebsiteRepositoryInterface $websiteRepository */ +$websiteRepository = $objectManager->get(WebsiteRepositoryInterface::class); +$website = $websiteRepository->get('test'); +$deleteConfigData($configWriter, $configData, ScopeInterface::SCOPE_WEBSITES, (int)$website->getId()); + +require __DIR__ . '/../../Store/_files/second_website_with_two_stores_rollback.php'; diff --git a/dev/tests/integration/testsuite/Magento/Paypal/Model/Payflow/Service/Request/SecureTokenTest.php b/dev/tests/integration/testsuite/Magento/Paypal/Model/Payflow/Service/Request/SecureTokenTest.php new file mode 100644 index 0000000000000..df6a1aae3fdb8 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Paypal/Model/Payflow/Service/Request/SecureTokenTest.php @@ -0,0 +1,214 @@ +objectManager = Bootstrap::getObjectManager(); + + $this->gateway = $this->getMockBuilder(Gateway::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->quoteRepository = $this->objectManager->get(CartRepositoryInterface::class); + $this->objectManager->addSharedInstance($this->gateway, Gateway::class); + + $this->mathRandom = $this->getMockBuilder(Random::class) + ->getMock(); + + $this->service = $this->objectManager->create( + SecureToken::class, + [ + 'mathRandom' => $this->mathRandom + ] + ); + } + + /** + * @inheritdoc + */ + protected function tearDown() + { + $this->objectManager->removeSharedInstance(Gateway::class); + } + + /** + * Checks a case when secure token can be obtained with credentials for the default scope. + * + * @magentoDataFixture Magento/Paypal/_files/quote_payflowpro.php + * @magentoDataFixture Magento/Paypal/Fixtures/default_payment_configuration.php + * @magentoAppArea adminhtml + */ + public function testRequestToken() + { + $quote = $this->getQuote('100000015'); + $quote->setStoreId(null); + $this->execute($quote, 'def_partner', 'def_vendor', 'def_user', 'def_pwd'); + } + + /** + * Checks a case when secure token can be obtained with credentials specified per store. + * + * @magentoDataFixture Magento/Paypal/_files/quote_payflowpro.php + * @magentoDataFixture Magento/Paypal/Fixtures/store_payment_configuration.php + * @magentoAppArea adminhtml + */ + public function testRequestTokenWithStoreConfiguration() + { + $quote = $this->getQuote('100000015'); + $store = $this->getStore('test'); + $quote->setStoreId($store->getId()); + $this->execute($quote, 'store_partner', 'store_vendor', 'store_user', 'store_pwd'); + } + + /** + * Checks a case when secure token can be obtained with credentials specified per website. + * + * @magentoDataFixture Magento/Paypal/_files/quote_payflowpro.php + * @magentoDataFixture Magento/Paypal/Fixtures/website_payment_configuration.php + * @magentoAppArea adminhtml + */ + public function testRequestTokenWithWebsiteConfiguration() + { + $quote = $this->getQuote('100000015'); + $store = $this->getStore('fixture_second_store'); + $quote->setStoreId($store->getId()); + $this->execute($quote, 'website_partner', 'website_vendor', 'website_user', 'website_pwd'); + } + + /** + * Retrieves secure token and perform test assertions. + * + * @param Quote $quote + * @param string $expPartner + * @param string $expVendor + * @param string $expUser + * @param string $expPwd + */ + private function execute(Quote $quote, string $expPartner, string $expVendor, string $expUser, string $expPwd) + { + $secureTokenId = '31f2a7c8d257c70b1c9eb9051b90e0'; + $token = '80IgSbabyj0CtBDWHZZeQN3'; + + $this->mathRandom->method('getUniqueHash') + ->willReturn($secureTokenId); + + $response = new DataObject([ + 'result' => '0', + 'respmsg' => 'Approved', + 'securetoken' => $token, + 'securetokenid' => $secureTokenId, + 'result_code' => '0', + ]); + $self = $this; + $this->gateway->method('postRequest') + /** @var DataObject $request */ + ->with(self::callback(function ($request) use ($self, $expPartner, $expVendor, $expUser, $expPwd) { + $self->performAssertion($expPartner, $request->getPartner(), '{Partner}'); + $self->performAssertion($expVendor, $request->getVendor(), '{Vendor}'); + $self->performAssertion($expUser, $request->getUser(), '{User}'); + $self->performAssertion($expPwd, $request->getPwd(), '{Password}'); + + return true; + })) + ->willReturn($response); + + $response = $this->service->requestToken($quote); + $this->performAssertion($token, $response->getData('securetoken'), '{Secure Token}'); + } + + /** + * Perform assertions test assertions. + * + * @param string $expected + * @param string $actual + * @param string $property + */ + private function performAssertion(string $expected, string $actual, string $property) + { + self::assertEquals($expected, $actual, "$property should match."); + } + + /** + * Loads quote by order increment id. + * + * @param string $orderIncrementId + * @return Quote + */ + private function getQuote(string $orderIncrementId): Quote + { + /** @var SearchCriteriaBuilder $searchCriteriaBuilder */ + $searchCriteriaBuilder = $this->objectManager->get(SearchCriteriaBuilder::class); + $searchCriteria = $searchCriteriaBuilder->addFilter('reserved_order_id', $orderIncrementId) + ->create(); + + $items = $this->quoteRepository->getList($searchCriteria) + ->getItems(); + + return array_pop($items); + } + + /** + * Loads store by provided code. + * + * @param string $code + * @return StoreInterface + */ + private function getStore(string $code): StoreInterface + { + /** @var StoreRepositoryInterface $storeRepository */ + $storeRepository = $this->objectManager->get(StoreRepositoryInterface::class); + return $storeRepository->get($code); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Paypal/_files/fixed_discount_rollback.php b/dev/tests/integration/testsuite/Magento/Paypal/_files/fixed_discount_rollback.php new file mode 100644 index 0000000000000..55ce3be29f7c7 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Paypal/_files/fixed_discount_rollback.php @@ -0,0 +1,26 @@ +get(RuleRepositoryInterface::class); +/** @var SearchCriteriaBuilder $searchCriteriaBuilder */ +$searchCriteriaBuilder = $objectManager->get(SearchCriteriaBuilder::class); +$searchCriteria = $searchCriteriaBuilder->addFilter('name', '10$ discount')->create(); +$items = $ruleRepository->getList($searchCriteria)->getItems(); + +/** @var RuleInterface $item */ +foreach ($items as $item) { + $ruleRepository->deleteById($item->getRuleId()); +} diff --git a/dev/tests/integration/testsuite/Magento/Paypal/_files/quote_payflowpro.php b/dev/tests/integration/testsuite/Magento/Paypal/_files/quote_payflowpro.php index 9b7e296d12c7d..c95f9df151608 100644 --- a/dev/tests/integration/testsuite/Magento/Paypal/_files/quote_payflowpro.php +++ b/dev/tests/integration/testsuite/Magento/Paypal/_files/quote_payflowpro.php @@ -5,6 +5,7 @@ */ use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\CatalogInventory\Api\Data\StockItemInterface; use Magento\Framework\ObjectManagerInterface; use Magento\Quote\Model\Quote; use Magento\Quote\Model\Quote\Address; @@ -55,15 +56,16 @@ ->setName('Simple ' . $i) ->setSku('simple' . $i) ->setAttributeSetId(4) - ->setStockData( - [ - 'qty' => 10, - 'is_in_stock' => 10, - 'manage_stock' => 1, - ] - ) ->setPrice(5.69 + $i * 2) ->setWeight(1); + + /** @var StockItemInterface $stockItem */ + $stockItem = $objectManager->create(StockItemInterface::class); + $stockItem->setQty(10) + ->setIsInStock(true); + $extensionAttributes = $product->getExtensionAttributes(); + $extensionAttributes->setStockItem($stockItem); + $item = $productRepository->save($product); $quote->addProduct($item, $i); } diff --git a/dev/tests/integration/testsuite/Magento/Paypal/_files/quote_payflowpro_rollback.php b/dev/tests/integration/testsuite/Magento/Paypal/_files/quote_payflowpro_rollback.php new file mode 100644 index 0000000000000..42c93f4a4c1e7 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Paypal/_files/quote_payflowpro_rollback.php @@ -0,0 +1,43 @@ +get(ProductRepositoryInterface::class); +/** @var SearchCriteriaBuilder $productSearchCriteriaBuilder */ +$productSearchCriteriaBuilder = $objectManager->create(SearchCriteriaBuilder::class); +$searchCriteria = $productSearchCriteriaBuilder->addFilter('sku', ['simple1', 'simple2', 'simple3'], 'in') + ->create(); +$productList = $productRepository->getList($searchCriteria) + ->getItems(); +if (!empty($productList)) { + foreach ($productList as $product) { + $productRepository->delete($product); + } +} + +/** @var CartRepositoryInterface $quoteRepository */ +$quoteRepository = $objectManager->get(CartRepositoryInterface::class); +/** @var SearchCriteriaBuilder $searchCriteriaBuilder */ +$searchCriteriaBuilder = $objectManager->create(SearchCriteriaBuilder::class); +$searchCriteria = $searchCriteriaBuilder->addFilter('reserved_order_id', '100000015')->create(); +$items = $quoteRepository->getList($searchCriteria)->getItems(); + +if (!empty($items)) { + $quote = array_pop($items); + $quoteRepository->delete($quote); +} From d1446bf8de1e9f0c622e9f388bbc22c10a34ce27 Mon Sep 17 00:00:00 2001 From: OlgaVasyltsun Date: Tue, 29 May 2018 15:15:49 +0300 Subject: [PATCH 02/24] MAGETWO-89407: [2.3] PayPal Payments Pro settings gets from wrong store --- .../Magento/Paypal/_files/quote_payflowpro_rollback.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/dev/tests/integration/testsuite/Magento/Paypal/_files/quote_payflowpro_rollback.php b/dev/tests/integration/testsuite/Magento/Paypal/_files/quote_payflowpro_rollback.php index 42c93f4a4c1e7..6eded10e5c555 100644 --- a/dev/tests/integration/testsuite/Magento/Paypal/_files/quote_payflowpro_rollback.php +++ b/dev/tests/integration/testsuite/Magento/Paypal/_files/quote_payflowpro_rollback.php @@ -22,8 +22,12 @@ $productSearchCriteriaBuilder = $objectManager->create(SearchCriteriaBuilder::class); $searchCriteria = $productSearchCriteriaBuilder->addFilter('sku', ['simple1', 'simple2', 'simple3'], 'in') ->create(); -$productList = $productRepository->getList($searchCriteria) - ->getItems(); +$productList = $productRepository->getList($searchCriteria)->getItems(); + +$registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Framework\Registry::class); +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + if (!empty($productList)) { foreach ($productList as $product) { $productRepository->delete($product); From 769352fef70f3b0ce8c8c96ea79bfe0c6cb5a27a Mon Sep 17 00:00:00 2001 From: Andrii Meysar Date: Tue, 29 May 2018 15:54:04 +0300 Subject: [PATCH 03/24] MAGETWO-92134: [2.2.x] Payment Failed Email is not generated --- .../Magento/Authorizenet/Model/Directpost.php | 69 +++- .../Test/Unit/Model/DirectpostTest.php | 51 ++- app/code/Magento/Checkout/Helper/Data.php | 137 +------- .../Checkout/Test/Unit/Helper/DataTest.php | 228 ++++---------- .../Express/AbstractExpress/PlaceOrder.php | 18 +- .../Magento/Paypal/Controller/Payflow.php | 19 +- .../Controller/Transparent/Response.php | 21 +- app/code/Magento/Paypal/Model/Payflowpro.php | 5 +- .../Unit/Controller/Payflow/ReturnUrlTest.php | 19 ++ .../Controller/Transparent/ResponseTest.php | 37 ++- .../Sales/Api/PaymentFailuresInterface.php | 28 ++ .../Sales/Model/Service/OrderService.php | 15 +- .../Model/Service/PaymentFailuresService.php | 295 ++++++++++++++++++ app/code/Magento/Sales/etc/di.xml | 1 + .../Controller/Payflow/SilentPostTest.php | 4 +- .../Magento/Paypal/Controller/PayflowTest.php | 86 +++-- .../Paypal/Controller/PayflowadvancedTest.php | 84 +++-- .../Service/PaymentFailuresServiceTest.php | 98 ++++++ .../testsuite/Magento/Sales/_files/order.php | 94 +++--- .../quote_with_two_products_and_customer.php | 31 ++ 20 files changed, 931 insertions(+), 409 deletions(-) create mode 100644 app/code/Magento/Sales/Api/PaymentFailuresInterface.php create mode 100644 app/code/Magento/Sales/Model/Service/PaymentFailuresService.php create mode 100644 dev/tests/integration/testsuite/Magento/Sales/Model/Service/PaymentFailuresServiceTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Sales/_files/quote_with_two_products_and_customer.php diff --git a/app/code/Magento/Authorizenet/Model/Directpost.php b/app/code/Magento/Authorizenet/Model/Directpost.php index 0f10fd633cb5b..de567a8895f7e 100644 --- a/app/code/Magento/Authorizenet/Model/Directpost.php +++ b/app/code/Magento/Authorizenet/Model/Directpost.php @@ -5,10 +5,9 @@ */ namespace Magento\Authorizenet\Model; -use Magento\Framework\HTTP\ZendClientFactory; +use Magento\Framework\App\ObjectManager; use Magento\Payment\Model\Method\ConfigInterface; use Magento\Payment\Model\Method\TransparentInterface; -use Magento\Sales\Model\Order\Email\Sender\OrderSender; /** * Authorize.net DirectPost payment method model. @@ -102,7 +101,7 @@ class Directpost extends \Magento\Authorizenet\Model\Authorizenet implements Tra protected $response; /** - * @var OrderSender + * @var \Magento\Sales\Model\Order\Email\Sender\OrderSender */ protected $orderSender; @@ -123,6 +122,16 @@ class Directpost extends \Magento\Authorizenet\Model\Authorizenet implements Tra */ private $psrLogger; + /** + * @var \Magento\Sales\Api\PaymentFailuresInterface + */ + private $paymentFailures; + + /** + * @var \Magento\Sales\Model\Order + */ + private $order; + /** * @param \Magento\Framework\Model\Context $context * @param \Magento\Framework\Registry $registry @@ -141,11 +150,12 @@ class Directpost extends \Magento\Authorizenet\Model\Authorizenet implements Tra * @param \Magento\Sales\Model\OrderFactory $orderFactory * @param \Magento\Store\Model\StoreManagerInterface $storeManager * @param \Magento\Quote\Api\CartRepositoryInterface $quoteRepository - * @param OrderSender $orderSender + * @param \Magento\Sales\Model\Order\Email\Sender\OrderSender $orderSender * @param \Magento\Sales\Api\TransactionRepositoryInterface $transactionRepository * @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource * @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection * @param array $data + * @param \Magento\Sales\Api\PaymentFailuresInterface|null $paymentFailures * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -162,7 +172,7 @@ public function __construct( \Magento\Authorizenet\Model\Directpost\Request\Factory $requestFactory, \Magento\Authorizenet\Model\Directpost\Response\Factory $responseFactory, TransactionService $transactionService, - ZendClientFactory $httpClientFactory, + \Magento\Framework\HTTP\ZendClientFactory $httpClientFactory, \Magento\Sales\Model\OrderFactory $orderFactory, \Magento\Store\Model\StoreManagerInterface $storeManager, \Magento\Quote\Api\CartRepositoryInterface $quoteRepository, @@ -170,7 +180,8 @@ public function __construct( \Magento\Sales\Api\TransactionRepositoryInterface $transactionRepository, \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null, \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, - array $data = [] + array $data = [], + \Magento\Sales\Api\PaymentFailuresInterface $paymentFailures = null ) { $this->orderFactory = $orderFactory; $this->storeManager = $storeManager; @@ -179,6 +190,8 @@ public function __construct( $this->orderSender = $orderSender; $this->transactionRepository = $transactionRepository; $this->_code = static::METHOD_CODE; + $this->paymentFailures = $paymentFailures ? : ObjectManager::getInstance() + ->get(\Magento\Sales\Api\PaymentFailuresInterface::class); parent::__construct( $context, @@ -561,13 +574,10 @@ public function process(array $responseData) $this->validateResponse(); $response = $this->getResponse(); - //operate with order - $orderIncrementId = $response->getXInvoiceNum(); $responseText = $this->dataHelper->wrapGatewayError($response->getXResponseReasonText()); $isError = false; - if ($orderIncrementId) { - /* @var $order \Magento\Sales\Model\Order */ - $order = $this->orderFactory->create()->loadByIncrementId($orderIncrementId); + if ($this->getOrderIncrementId()) { + $order = $this->getOrderFromResponse(); //check payment method $payment = $order->getPayment(); if (!$payment || $payment->getMethod() != $this->getCode()) { @@ -632,9 +642,10 @@ public function checkResponseCode() return true; case self::RESPONSE_CODE_DECLINED: case self::RESPONSE_CODE_ERROR: - throw new \Magento\Framework\Exception\LocalizedException( - $this->dataHelper->wrapGatewayError($this->getResponse()->getXResponseReasonText()) - ); + $errorMessage = $this->dataHelper->wrapGatewayError($this->getResponse()->getXResponseReasonText()); + $order = $this->getOrderFromResponse(); + $this->paymentFailures->handle((int)$order->getQuoteId(), $errorMessage); + throw new \Magento\Framework\Exception\LocalizedException($errorMessage); default: throw new \Magento\Framework\Exception\LocalizedException( __('There was a payment authorization error.') @@ -988,12 +999,40 @@ protected function getTransactionResponse($transactionId) private function getPsrLogger() { if (null === $this->psrLogger) { - $this->psrLogger = \Magento\Framework\App\ObjectManager::getInstance() + $this->psrLogger = ObjectManager::getInstance() ->get(\Psr\Log\LoggerInterface::class); } return $this->psrLogger; } + /** + * Fetch order by increment id from response. + * + * @return \Magento\Sales\Model\Order + */ + private function getOrderFromResponse(): \Magento\Sales\Model\Order + { + if (!$this->order) { + $this->order = $this->orderFactory->create(); + + if ($incrementId = $this->getOrderIncrementId()) { + $this->order = $this->order->loadByIncrementId($incrementId); + } + } + + return $this->order; + } + + /** + * Fetch order increment id from response. + * + * @return string + */ + private function getOrderIncrementId(): string + { + return $this->getResponse()->getXInvoiceNum(); + } + /** * Checks if filter action is Report Only. Transactions that trigger this filter are processed as normal, * but are also reported in the Merchant Interface as triggering this filter. diff --git a/app/code/Magento/Authorizenet/Test/Unit/Model/DirectpostTest.php b/app/code/Magento/Authorizenet/Test/Unit/Model/DirectpostTest.php index dbb6ac8333c14..95c67f67852da 100644 --- a/app/code/Magento/Authorizenet/Test/Unit/Model/DirectpostTest.php +++ b/app/code/Magento/Authorizenet/Test/Unit/Model/DirectpostTest.php @@ -5,6 +5,7 @@ */ namespace Magento\Authorizenet\Test\Unit\Model; +use Magento\Sales\Api\PaymentFailuresInterface; use Magento\Framework\Simplexml\Element; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; use Magento\Authorizenet\Model\Directpost; @@ -74,6 +75,14 @@ class DirectpostTest extends \PHPUnit\Framework\TestCase */ protected $requestFactory; + /** + * @var PaymentFailuresInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $paymentFailures; + + /** + * @inheritdoc + */ protected function setUp() { $this->scopeConfigMock = $this->getMockBuilder(\Magento\Framework\App\Config\ScopeConfigInterface::class) @@ -104,6 +113,12 @@ protected function setUp() ->setMethods(['getTransactionDetails']) ->getMock(); + $this->paymentFailures = $this->getMockBuilder( + PaymentFailuresInterface::class + ) + ->disableOriginalConstructor() + ->getMock(); + $this->requestFactory = $this->getRequestFactoryMock(); $httpClientFactoryMock = $this->getHttpClientFactoryMock(); @@ -117,7 +132,8 @@ protected function setUp() 'responseFactory' => $this->responseFactoryMock, 'transactionRepository' => $this->transactionRepositoryMock, 'transactionService' => $this->transactionServiceMock, - 'httpClientFactory' => $httpClientFactoryMock + 'httpClientFactory' => $httpClientFactoryMock, + 'paymentFailures' => $this->paymentFailures, ] ); } @@ -313,12 +329,16 @@ public function checkResponseCodeSuccessDataProvider() } /** - * @param bool $responseCode + * Checks response failures behaviour. + * + * @param int $responseCode + * @param int $failuresHandlerCalls + * @return void * * @expectedException \Magento\Framework\Exception\LocalizedException * @dataProvider checkResponseCodeFailureDataProvider */ - public function testCheckResponseCodeFailure($responseCode) + public function testCheckResponseCodeFailure(int $responseCode, int $failuresHandlerCalls): void { $reasonText = 'reason text'; @@ -333,18 +353,35 @@ public function testCheckResponseCodeFailure($responseCode) ->with($reasonText) ->willReturn(__('Gateway error: %1', $reasonText)); + $orderMock = $this->getMockBuilder(Order::class) + ->disableOriginalConstructor() + ->getMock(); + + $orderMock->expects($this->exactly($failuresHandlerCalls)) + ->method('getQuoteId') + ->willReturn(1); + + $this->paymentFailures->expects($this->exactly($failuresHandlerCalls)) + ->method('handle') + ->with(1); + + $reflection = new \ReflectionClass($this->directpost); + $order = $reflection->getProperty('order'); + $order->setAccessible(true); + $order->setValue($this->directpost, $orderMock); + $this->directpost->checkResponseCode(); } /** * @return array */ - public function checkResponseCodeFailureDataProvider() + public function checkResponseCodeFailureDataProvider(): array { return [ - ['responseCode' => Directpost::RESPONSE_CODE_DECLINED], - ['responseCode' => Directpost::RESPONSE_CODE_ERROR], - ['responseCode' => 999999] + ['responseCode' => Directpost::RESPONSE_CODE_DECLINED, 1], + ['responseCode' => Directpost::RESPONSE_CODE_ERROR, 1], + ['responseCode' => 999999, 0], ]; } diff --git a/app/code/Magento/Checkout/Helper/Data.php b/app/code/Magento/Checkout/Helper/Data.php index b3c2e17e5d678..636d4aaca21f0 100644 --- a/app/code/Magento/Checkout/Helper/Data.php +++ b/app/code/Magento/Checkout/Helper/Data.php @@ -9,6 +9,7 @@ use Magento\Quote\Model\Quote\Item\AbstractItem; use Magento\Store\Model\Store; use Magento\Store\Model\ScopeInterface; +use Magento\Sales\Api\PaymentFailuresInterface; /** * Checkout default helper @@ -52,6 +53,11 @@ class Data extends \Magento\Framework\App\Helper\AbstractHelper */ protected $priceCurrency; + /** + * @var PaymentFailuresInterface + */ + private $paymentFailures; + /** * @param \Magento\Framework\App\Helper\Context $context * @param \Magento\Store\Model\StoreManagerInterface $storeManager @@ -60,6 +66,7 @@ class Data extends \Magento\Framework\App\Helper\AbstractHelper * @param \Magento\Framework\Mail\Template\TransportBuilder $transportBuilder * @param \Magento\Framework\Translate\Inline\StateInterface $inlineTranslation * @param PriceCurrencyInterface $priceCurrency + * @param PaymentFailuresInterface|null $paymentFailures * @codeCoverageIgnore */ public function __construct( @@ -69,7 +76,8 @@ public function __construct( \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate, \Magento\Framework\Mail\Template\TransportBuilder $transportBuilder, \Magento\Framework\Translate\Inline\StateInterface $inlineTranslation, - PriceCurrencyInterface $priceCurrency + PriceCurrencyInterface $priceCurrency, + PaymentFailuresInterface $paymentFailures = null ) { $this->_storeManager = $storeManager; $this->_checkoutSession = $checkoutSession; @@ -77,6 +85,8 @@ public function __construct( $this->_transportBuilder = $transportBuilder; $this->inlineTranslation = $inlineTranslation; $this->priceCurrency = $priceCurrency; + $this->paymentFailures = $paymentFailures ? : \Magento\Framework\App\ObjectManager::getInstance() + ->get(PaymentFailuresInterface::class); parent::__construct($context); } @@ -202,126 +212,13 @@ public function getBaseSubtotalInclTax($item) * @param string $message * @param string $checkoutType * @return $this - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ - public function sendPaymentFailedEmail($checkout, $message, $checkoutType = 'onepage') - { - $this->inlineTranslation->suspend(); - - $template = $this->scopeConfig->getValue( - 'checkout/payment_failed/template', - \Magento\Store\Model\ScopeInterface::SCOPE_STORE, - $checkout->getStoreId() - ); - - $copyTo = $this->_getEmails('checkout/payment_failed/copy_to', $checkout->getStoreId()); - $copyMethod = $this->scopeConfig->getValue( - 'checkout/payment_failed/copy_method', - \Magento\Store\Model\ScopeInterface::SCOPE_STORE, - $checkout->getStoreId() - ); - $bcc = []; - if ($copyTo && $copyMethod == 'bcc') { - $bcc = $copyTo; - } - - $_receiver = $this->scopeConfig->getValue( - 'checkout/payment_failed/receiver', - \Magento\Store\Model\ScopeInterface::SCOPE_STORE, - $checkout->getStoreId() - ); - $sendTo = [ - [ - 'email' => $this->scopeConfig->getValue( - 'trans_email/ident_' . $_receiver . '/email', - \Magento\Store\Model\ScopeInterface::SCOPE_STORE, - $checkout->getStoreId() - ), - 'name' => $this->scopeConfig->getValue( - 'trans_email/ident_' . $_receiver . '/name', - \Magento\Store\Model\ScopeInterface::SCOPE_STORE, - $checkout->getStoreId() - ), - ], - ]; - - if ($copyTo && $copyMethod == 'copy') { - foreach ($copyTo as $email) { - $sendTo[] = ['email' => $email, 'name' => null]; - } - } - $shippingMethod = ''; - if ($shippingInfo = $checkout->getShippingAddress()->getShippingMethod()) { - $data = explode('_', $shippingInfo); - $shippingMethod = $data[0]; - } - - $paymentMethod = ''; - if ($paymentInfo = $checkout->getPayment()) { - $paymentMethod = $paymentInfo->getMethod(); - } - - $items = ''; - foreach ($checkout->getAllVisibleItems() as $_item) { - /* @var $_item \Magento\Quote\Model\Quote\Item */ - $items .= - $_item->getProduct()->getName() . ' x ' . $_item->getQty() . ' ' . $checkout->getStoreCurrencyCode() - . ' ' . $_item->getProduct()->getFinalPrice( - $_item->getQty() - ) . "\n"; - } - $total = $checkout->getStoreCurrencyCode() . ' ' . $checkout->getGrandTotal(); - - foreach ($sendTo as $recipient) { - $transport = $this->_transportBuilder->setTemplateIdentifier( - $template - )->setTemplateOptions( - [ - 'area' => \Magento\Backend\App\Area\FrontNameResolver::AREA_CODE, - 'store' => Store::DEFAULT_STORE_ID - ] - )->setTemplateVars( - [ - 'reason' => $message, - 'checkoutType' => $checkoutType, - 'dateAndTime' => $this->_localeDate->formatDateTime( - new \DateTime(), - \IntlDateFormatter::MEDIUM, - \IntlDateFormatter::MEDIUM - ), - 'customer' => $checkout->getCustomerFirstname() . ' ' . $checkout->getCustomerLastname(), - 'customerEmail' => $checkout->getCustomerEmail(), - 'billingAddress' => $checkout->getBillingAddress(), - 'shippingAddress' => $checkout->getShippingAddress(), - 'shippingMethod' => $this->scopeConfig->getValue( - 'carriers/' . $shippingMethod . '/title', - \Magento\Store\Model\ScopeInterface::SCOPE_STORE - ), - 'paymentMethod' => $this->scopeConfig->getValue( - 'payment/' . $paymentMethod . '/title', - \Magento\Store\Model\ScopeInterface::SCOPE_STORE - ), - 'items' => nl2br($items), - 'total' => $total, - ] - )->setFrom( - $this->scopeConfig->getValue( - 'checkout/payment_failed/identity', - \Magento\Store\Model\ScopeInterface::SCOPE_STORE, - $checkout->getStoreId() - ) - )->addTo( - $recipient['email'], - $recipient['name'] - )->addBcc( - $bcc - )->getTransport(); - - $transport->sendMessage(); - } - - $this->inlineTranslation->resume(); + public function sendPaymentFailedEmail( + \Magento\Quote\Model\Quote $checkout, + string $message, + string $checkoutType = 'onepage' + ): \Magento\Checkout\Helper\Data { + $this->paymentFailures->handle((int)$checkout->getId(), $message, $checkoutType); return $this; } diff --git a/app/code/Magento/Checkout/Test/Unit/Helper/DataTest.php b/app/code/Magento/Checkout/Test/Unit/Helper/DataTest.php index c403156dc13e9..f6f9ff78c1cb2 100644 --- a/app/code/Magento/Checkout/Test/Unit/Helper/DataTest.php +++ b/app/code/Magento/Checkout/Test/Unit/Helper/DataTest.php @@ -6,8 +6,7 @@ namespace Magento\Checkout\Test\Unit\Helper; -use \Magento\Checkout\Helper\Data; - +use Magento\Checkout\Helper\Data; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Store\Model\ScopeInterface; @@ -39,23 +38,21 @@ class DataTest extends \PHPUnit\Framework\TestCase /** * @var \PHPUnit_Framework_MockObject_MockObject */ - protected $_checkoutSession; + private $_checkoutSession; /** * @var \PHPUnit_Framework_MockObject_MockObject */ - protected $_scopeConfig; + private $_scopeConfig; /** * @var \PHPUnit_Framework_MockObject_MockObject */ - protected $_collectionFactory; + private $_eventManager; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @inheritdoc */ - protected $_eventManager; - protected function setUp() { $objectManagerHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); @@ -68,59 +65,57 @@ protected function setUp() $this->_scopeConfig = $context->getScopeConfig(); $this->_scopeConfig->expects($this->any()) ->method('getValue') - ->will( - $this->returnValueMap( + ->willReturnMap( + [ + [ + 'checkout/payment_failed/template', + ScopeInterface::SCOPE_STORE, + 8, + 'fixture_email_template_payment_failed', + ], + [ + 'checkout/payment_failed/receiver', + ScopeInterface::SCOPE_STORE, + 8, + 'sysadmin', + ], + [ + 'trans_email/ident_sysadmin/email', + ScopeInterface::SCOPE_STORE, + 8, + 'sysadmin@example.com', + ], + [ + 'trans_email/ident_sysadmin/name', + ScopeInterface::SCOPE_STORE, + 8, + 'System Administrator', + ], + [ + 'checkout/payment_failed/identity', + ScopeInterface::SCOPE_STORE, + 8, + 'noreply@example.com', + ], + [ + 'carriers/ground/title', + ScopeInterface::SCOPE_STORE, + null, + 'Ground Shipping', + ], [ - [ - 'checkout/payment_failed/template', - \Magento\Store\Model\ScopeInterface::SCOPE_STORE, - 8, - 'fixture_email_template_payment_failed' - ], - [ - 'checkout/payment_failed/receiver', - \Magento\Store\Model\ScopeInterface::SCOPE_STORE, - 8, - 'sysadmin' - ], - [ - 'trans_email/ident_sysadmin/email', - \Magento\Store\Model\ScopeInterface::SCOPE_STORE, - 8, - 'sysadmin@example.com' - ], - [ - 'trans_email/ident_sysadmin/name', - \Magento\Store\Model\ScopeInterface::SCOPE_STORE, - 8, - 'System Administrator' - ], - [ - 'checkout/payment_failed/identity', - \Magento\Store\Model\ScopeInterface::SCOPE_STORE, - 8, - 'noreply@example.com' - ], - [ - 'carriers/ground/title', - \Magento\Store\Model\ScopeInterface::SCOPE_STORE, - null, - 'Ground Shipping' - ], - [ - 'payment/fixture-payment-method/title', - \Magento\Store\Model\ScopeInterface::SCOPE_STORE, - null, - 'Check Money Order' - ], - [ - 'checkout/options/onepage_checkout_enabled', - \Magento\Store\Model\ScopeInterface::SCOPE_STORE, - null, - 'One Page Checkout' - ] - ] - ) + 'payment/fixture-payment-method/title', + ScopeInterface::SCOPE_STORE, + null, + 'Check Money Order', + ], + [ + 'checkout/options/onepage_checkout_enabled', + ScopeInterface::SCOPE_STORE, + null, + 'One Page Checkout', + ], + ] ); $this->_checkoutSession = $arguments['checkoutSession']; @@ -137,117 +132,16 @@ protected function setUp() /** * @return void - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ public function testSendPaymentFailedEmail() { - $shippingAddress = new \Magento\Framework\DataObject(['shipping_method' => 'ground_transportation']); - $billingAddress = new \Magento\Framework\DataObject(['street' => 'Fixture St']); - - $this->_transportBuilder->expects( - $this->once() - )->method( - 'setTemplateOptions' - )->with( - [ - 'area' => \Magento\Backend\App\Area\FrontNameResolver::AREA_CODE, - 'store' => \Magento\Store\Model\Store::DEFAULT_STORE_ID, - ] - )->will( - $this->returnSelf() - ); - - $this->_transportBuilder->expects( - $this->once() - )->method( - 'setTemplateIdentifier' - )->with( - 'fixture_email_template_payment_failed' - )->will( - $this->returnSelf() - ); + $quoteMock = $this->getMockBuilder(\Magento\Quote\Model\Quote::class) + ->setMethods(['getId']) + ->disableOriginalConstructor() + ->getMock(); + $quoteMock->expects($this->any())->method('getId')->willReturn(1); - $this->_transportBuilder->expects( - $this->once() - )->method( - 'setFrom' - )->with( - 'noreply@example.com' - )->will( - $this->returnSelf() - ); - - $this->_transportBuilder->expects( - $this->once() - )->method( - 'addTo' - )->with( - 'sysadmin@example.com', - 'System Administrator' - )->will( - $this->returnSelf() - ); - - $this->_transportBuilder->expects( - $this->once() - )->method( - 'setTemplateVars' - )->with( - [ - 'reason' => 'test message', - 'checkoutType' => 'onepage', - 'dateAndTime' => 'Oct 02, 2013', - 'customer' => 'John Doe', - 'customerEmail' => 'john.doe@example.com', - 'billingAddress' => $billingAddress, - 'shippingAddress' => $shippingAddress, - 'shippingMethod' => 'Ground Shipping', - 'paymentMethod' => 'Check Money Order', - 'items' => "Product One x 2 USD 10
\nProduct Two x 3 USD 60
\n", - 'total' => 'USD 70' - ] - )->will( - $this->returnSelf() - ); - - $this->_transportBuilder->expects($this->once())->method('addBcc')->will($this->returnSelf()); - $this->_transportBuilder->expects( - $this->once() - )->method( - 'getTransport' - )->will( - $this->returnValue($this->createMock(\Magento\Framework\Mail\TransportInterface::class)) - ); - - $this->_translator->expects($this->at(1))->method('suspend'); - $this->_translator->expects($this->at(1))->method('resume'); - - $productOne = $this->createMock(\Magento\Catalog\Model\Product::class); - $productOne->expects($this->once())->method('getName')->will($this->returnValue('Product One')); - $productOne->expects($this->once())->method('getFinalPrice')->with(2)->will($this->returnValue(10)); - - $productTwo = $this->createMock(\Magento\Catalog\Model\Product::class); - $productTwo->expects($this->once())->method('getName')->will($this->returnValue('Product Two')); - $productTwo->expects($this->once())->method('getFinalPrice')->with(3)->will($this->returnValue(60)); - - $quote = new \Magento\Framework\DataObject( - [ - 'store_id' => 8, - 'store_currency_code' => 'USD', - 'grand_total' => 70, - 'customer_firstname' => 'John', - 'customer_lastname' => 'Doe', - 'customer_email' => 'john.doe@example.com', - 'billing_address' => $billingAddress, - 'shipping_address' => $shippingAddress, - 'payment' => new \Magento\Framework\DataObject(['method' => 'fixture-payment-method']), - 'all_visible_items' => [ - new \Magento\Framework\DataObject(['product' => $productOne, 'qty' => 2]), - new \Magento\Framework\DataObject(['product' => $productTwo, 'qty' => 3]) - ] - ] - ); - $this->assertSame($this->_helper, $this->_helper->sendPaymentFailedEmail($quote, 'test message')); + $this->assertSame($this->_helper, $this->_helper->sendPaymentFailedEmail($quoteMock, 'test message')); } /** diff --git a/app/code/Magento/Paypal/Controller/Express/AbstractExpress/PlaceOrder.php b/app/code/Magento/Paypal/Controller/Express/AbstractExpress/PlaceOrder.php index f0fce97da512a..055af4162d5f3 100644 --- a/app/code/Magento/Paypal/Controller/Express/AbstractExpress/PlaceOrder.php +++ b/app/code/Magento/Paypal/Controller/Express/AbstractExpress/PlaceOrder.php @@ -21,6 +21,11 @@ class PlaceOrder extends \Magento\Paypal\Controller\Express\AbstractExpress */ protected $agreementsValidator; + /** + * @var \Magento\Sales\Api\PaymentFailuresInterface + */ + private $paymentFailures; + /** * @param \Magento\Framework\App\Action\Context $context * @param \Magento\Customer\Model\Session $customerSession @@ -31,6 +36,8 @@ class PlaceOrder extends \Magento\Paypal\Controller\Express\AbstractExpress * @param \Magento\Framework\Url\Helper\Data $urlHelper * @param \Magento\Customer\Model\Url $customerUrl * @param \Magento\Checkout\Api\AgreementsValidatorInterface $agreementValidator + * @param \Magento\Sales\Api\PaymentFailuresInterface|null $paymentFailures + * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( \Magento\Framework\App\Action\Context $context, @@ -41,9 +48,9 @@ public function __construct( \Magento\Framework\Session\Generic $paypalSession, \Magento\Framework\Url\Helper\Data $urlHelper, \Magento\Customer\Model\Url $customerUrl, - \Magento\Checkout\Api\AgreementsValidatorInterface $agreementValidator + \Magento\Checkout\Api\AgreementsValidatorInterface $agreementValidator, + \Magento\Sales\Api\PaymentFailuresInterface $paymentFailures = null ) { - $this->agreementsValidator = $agreementValidator; parent::__construct( $context, $customerSession, @@ -54,6 +61,11 @@ public function __construct( $urlHelper, $customerUrl ); + + $this->agreementsValidator = $agreementValidator; + $this->paymentFailures = $paymentFailures ? : $this->_objectManager->get( + \Magento\Sales\Api\PaymentFailuresInterface::class + ); } /** @@ -148,6 +160,8 @@ private function processException(\Exception $exception, string $message): void */ protected function _processPaypalApiError($exception) { + $this->paymentFailures->handle((int)$this->_getCheckoutSession()->getQuoteId(), $exception->getMessage()); + switch ($exception->getCode()) { case ApiProcessableException::API_MAX_PAYMENT_ATTEMPTS_EXCEEDED: case ApiProcessableException::API_TRANSACTION_EXPIRED: diff --git a/app/code/Magento/Paypal/Controller/Payflow.php b/app/code/Magento/Paypal/Controller/Payflow.php index ab21986bde3ba..78c0536e393ac 100644 --- a/app/code/Magento/Paypal/Controller/Payflow.php +++ b/app/code/Magento/Paypal/Controller/Payflow.php @@ -41,6 +41,11 @@ abstract class Payflow extends \Magento\Framework\App\Action\Action */ protected $_redirectBlockName = 'payflow.link.iframe'; + /** + * @var \Magento\Sales\Api\PaymentFailuresInterface + */ + private $paymentFailures; + /** * @param \Magento\Framework\App\Action\Context $context * @param \Magento\Checkout\Model\Session $checkoutSession @@ -48,6 +53,7 @@ abstract class Payflow extends \Magento\Framework\App\Action\Action * @param \Magento\Paypal\Model\PayflowlinkFactory $payflowModelFactory * @param \Magento\Paypal\Helper\Checkout $checkoutHelper * @param \Psr\Log\LoggerInterface $logger + * @param \Magento\Sales\Api\PaymentFailuresInterface|null $paymentFailures */ public function __construct( \Magento\Framework\App\Action\Context $context, @@ -55,14 +61,19 @@ public function __construct( \Magento\Sales\Model\OrderFactory $orderFactory, \Magento\Paypal\Model\PayflowlinkFactory $payflowModelFactory, \Magento\Paypal\Helper\Checkout $checkoutHelper, - \Psr\Log\LoggerInterface $logger + \Psr\Log\LoggerInterface $logger, + \Magento\Sales\Api\PaymentFailuresInterface $paymentFailures = null ) { + parent::__construct($context); + $this->_checkoutSession = $checkoutSession; $this->_orderFactory = $orderFactory; $this->_logger = $logger; $this->_payflowModelFactory = $payflowModelFactory; $this->_checkoutHelper = $checkoutHelper; - parent::__construct($context); + $this->paymentFailures = $paymentFailures ?: $this->_objectManager->get( + \Magento\Sales\Api\PaymentFailuresInterface::class + ); } /** @@ -74,6 +85,10 @@ public function __construct( protected function _cancelPayment($errorMsg = '') { $errorMsg = trim(strip_tags($errorMsg)); + $order = $this->_checkoutSession->getLastRealOrder(); + if ($order->getId()) { + $this->paymentFailures->handle((int)$order->getQuoteId(), $errorMsg); + } $gotoSection = false; $this->_checkoutHelper->cancelCurrentOrder($errorMsg); diff --git a/app/code/Magento/Paypal/Controller/Transparent/Response.php b/app/code/Magento/Paypal/Controller/Transparent/Response.php index 23ac20ca8c87b..c54dd529588b9 100644 --- a/app/code/Magento/Paypal/Controller/Transparent/Response.php +++ b/app/code/Magento/Paypal/Controller/Transparent/Response.php @@ -14,6 +14,8 @@ use Magento\Paypal\Model\Payflow\Service\Response\Transaction; use Magento\Paypal\Model\Payflow\Service\Response\Validator\ResponseValidator; use Magento\Paypal\Model\Payflow\Transparent; +use Magento\Sales\Api\PaymentFailuresInterface; +use Magento\Framework\Session\Generic as Session; /** * Class Response @@ -47,6 +49,16 @@ class Response extends \Magento\Framework\App\Action\Action */ private $transparent; + /** + * @var PaymentFailuresInterface + */ + private $paymentFailures; + + /** + * @var Session + */ + private $sessionTransparent; + /** * Constructor * @@ -56,6 +68,8 @@ class Response extends \Magento\Framework\App\Action\Action * @param ResponseValidator $responseValidator * @param LayoutFactory $resultLayoutFactory * @param Transparent $transparent + * @param Session|null $sessionTransparent + * @param PaymentFailuresInterface|null $paymentFailures */ public function __construct( Context $context, @@ -63,7 +77,9 @@ public function __construct( Transaction $transaction, ResponseValidator $responseValidator, LayoutFactory $resultLayoutFactory, - Transparent $transparent + Transparent $transparent, + Session $sessionTransparent = null, + PaymentFailuresInterface $paymentFailures = null ) { parent::__construct($context); $this->coreRegistry = $coreRegistry; @@ -71,6 +87,8 @@ public function __construct( $this->responseValidator = $responseValidator; $this->resultLayoutFactory = $resultLayoutFactory; $this->transparent = $transparent; + $this->sessionTransparent = $sessionTransparent ?: $this->_objectManager->get(Session::class); + $this->paymentFailures = $paymentFailures ?: $this->_objectManager->get(PaymentFailuresInterface::class); } /** @@ -86,6 +104,7 @@ public function execute() } catch (LocalizedException $exception) { $parameters['error'] = true; $parameters['error_msg'] = $exception->getMessage(); + $this->paymentFailures->handle((int)$this->sessionTransparent->getQuoteId(), $parameters['error_msg']); } $this->coreRegistry->register(Iframe::REGISTRY_KEY, $parameters); diff --git a/app/code/Magento/Paypal/Model/Payflowpro.php b/app/code/Magento/Paypal/Model/Payflowpro.php index 125aa0f6e65a7..16ba4efc90d6a 100644 --- a/app/code/Magento/Paypal/Model/Payflowpro.php +++ b/app/code/Magento/Paypal/Model/Payflowpro.php @@ -648,6 +648,7 @@ public function buildBasicRequest() * @param DataObject $response * @return void * @throws \Magento\Framework\Exception\LocalizedException + * @throws \Magento\Payment\Gateway\Command\CommandException * @throws \Magento\Framework\Exception\State\InvalidTransitionException */ public function processErrors(DataObject $response) @@ -659,9 +660,9 @@ public function processErrors(DataObject $response) } elseif ($response->getResultCode() != self::RESPONSE_CODE_APPROVED && $response->getResultCode() != self::RESPONSE_CODE_FRAUDSERVICE_FILTER ) { - throw new \Magento\Framework\Exception\LocalizedException(__($response->getRespmsg())); + throw new \Magento\Payment\Gateway\Command\CommandException(__($response->getRespmsg())); } elseif ($response->getOrigresult() == self::RESPONSE_CODE_DECLINED_BY_FILTER) { - throw new \Magento\Framework\Exception\LocalizedException(__($response->getRespmsg())); + throw new \Magento\Payment\Gateway\Command\CommandException(__($response->getRespmsg())); } } diff --git a/app/code/Magento/Paypal/Test/Unit/Controller/Payflow/ReturnUrlTest.php b/app/code/Magento/Paypal/Test/Unit/Controller/Payflow/ReturnUrlTest.php index e25864bbc2f3c..bd4da25cb84d0 100644 --- a/app/code/Magento/Paypal/Test/Unit/Controller/Payflow/ReturnUrlTest.php +++ b/app/code/Magento/Paypal/Test/Unit/Controller/Payflow/ReturnUrlTest.php @@ -5,6 +5,7 @@ */ namespace Magento\Paypal\Test\Unit\Controller\Payflow; +use Magento\Sales\Api\PaymentFailuresInterface; use Magento\Checkout\Block\Onepage\Success; use Magento\Checkout\Model\Session; use Magento\Framework\App\Action\Context; @@ -90,6 +91,11 @@ class ReturnUrlTest extends \PHPUnit\Framework\TestCase */ private $objectManager; + /** + * @var PaymentFailuresInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $paymentFailures; + /** * @inheritdoc */ @@ -138,6 +144,17 @@ protected function setUp() ->setMethods(['getLastRealOrderId', 'getLastRealOrder', 'restoreQuote']) ->getMock(); + $this->quote = $this->getMockBuilder(CartInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->context->expects($this->any())->method('getView')->willReturn($this->view); + $this->context->expects($this->any())->method('getRequest')->willReturn($this->request); + + $this->paymentFailures = $this->getMockBuilder(PaymentFailuresInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->context->method('getView') ->willReturn($this->view); $this->context->method('getRequest') @@ -148,6 +165,7 @@ protected function setUp() 'checkoutSession' => $this->checkoutSession, 'orderFactory' => $this->orderFactory, 'checkoutHelper' => $this->checkoutHelper, + 'paymentFailures' => $this->paymentFailures, ]); } @@ -321,6 +339,7 @@ public function testCheckAdvancedAcceptingByPaymentMethod() 'checkoutSession' => $this->checkoutSession, 'orderFactory' => $this->orderFactory, 'checkoutHelper' => $this->checkoutHelper, + 'paymentFailures' => $this->paymentFailures, ]); $returnUrl->execute(); diff --git a/app/code/Magento/Paypal/Test/Unit/Controller/Transparent/ResponseTest.php b/app/code/Magento/Paypal/Test/Unit/Controller/Transparent/ResponseTest.php index a10d103860c65..acefebb779200 100644 --- a/app/code/Magento/Paypal/Test/Unit/Controller/Transparent/ResponseTest.php +++ b/app/code/Magento/Paypal/Test/Unit/Controller/Transparent/ResponseTest.php @@ -8,16 +8,16 @@ use Magento\Framework\App\Action\Context; use Magento\Framework\App\RequestInterface; use Magento\Framework\Registry; +use Magento\Framework\Session\Generic as Session; use Magento\Framework\View\Result\Layout; use Magento\Framework\View\Result\LayoutFactory; use Magento\Paypal\Controller\Transparent\Response; use Magento\Paypal\Model\Payflow\Service\Response\Transaction; use Magento\Paypal\Model\Payflow\Service\Response\Validator\ResponseValidator; use Magento\Paypal\Model\Payflow\Transparent; +use Magento\Sales\Api\PaymentFailuresInterface; /** - * Class ResponseTest - * * Test for class \Magento\Paypal\Controller\Transparent\Response * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -53,6 +53,19 @@ class ResponseTest extends \PHPUnit\Framework\TestCase */ private $payflowFacade; + /** + * @var PaymentFailuresInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $paymentFailures; + + /** + * @var Session|\PHPUnit_Framework_MockObject_MockObject + */ + private $sessionTransparent; + + /** + * @inheritdoc + */ protected function setUp() { $this->requestMock = $this->getMockBuilder(\Magento\Framework\App\RequestInterface::class) @@ -97,6 +110,14 @@ protected function setUp() ->disableOriginalConstructor() ->setMethods([]) ->getMock(); + $this->paymentFailures = $this->getMockBuilder(PaymentFailuresInterface::class) + ->disableOriginalConstructor() + ->setMethods(['handle']) + ->getMock(); + $this->sessionTransparent = $this->getMockBuilder(Session::class) + ->disableOriginalConstructor() + ->setMethods(['getQuoteId']) + ->getMock(); $this->object = new Response( $this->contextMock, @@ -104,7 +125,9 @@ protected function setUp() $this->transactionMock, $this->responseValidatorMock, $this->resultLayoutFactoryMock, - $this->payflowFacade + $this->payflowFacade, + $this->sessionTransparent, + $this->paymentFailures ); } @@ -131,6 +154,8 @@ public function testExecute() $this->resultLayoutMock->expects($this->once()) ->method('getLayout') ->willReturn($this->getLayoutMock()); + $this->paymentFailures->expects($this->never()) + ->method('handle'); $this->assertInstanceOf(\Magento\Framework\Controller\ResultInterface::class, $this->object->execute()); } @@ -156,6 +181,12 @@ public function testExecuteWithException() $this->resultLayoutMock->expects($this->once()) ->method('getLayout') ->willReturn($this->getLayoutMock()); + $this->sessionTransparent->method('getQuoteId') + ->willReturn(1); + $this->paymentFailures->expects($this->once()) + ->method('handle') + ->with(1) + ->willReturnSelf(); $this->assertInstanceOf(\Magento\Framework\Controller\ResultInterface::class, $this->object->execute()); } diff --git a/app/code/Magento/Sales/Api/PaymentFailuresInterface.php b/app/code/Magento/Sales/Api/PaymentFailuresInterface.php new file mode 100644 index 0000000000000..485ff1ffbe248 --- /dev/null +++ b/app/code/Magento/Sales/Api/PaymentFailuresInterface.php @@ -0,0 +1,28 @@ +orderRepository = $orderRepository; $this->historyRepository = $historyRepository; @@ -76,6 +84,8 @@ public function __construct( $this->notifier = $notifier; $this->eventManager = $eventManager; $this->orderCommentSender = $orderCommentSender; + $this->paymentFailures = $paymentFailures ? : \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Magento\Sales\Api\PaymentFailuresInterface::class); } /** @@ -192,6 +202,9 @@ public function place(\Magento\Sales\Api\Data\OrderInterface $order) return $this->orderRepository->save($order); //commit } catch (\Exception $e) { + if ($e instanceof CommandException) { + $this->paymentFailures->handle((int)$order->getQuoteId(), __($e->getMessage())); + } throw $e; //rollback; } diff --git a/app/code/Magento/Sales/Model/Service/PaymentFailuresService.php b/app/code/Magento/Sales/Model/Service/PaymentFailuresService.php new file mode 100644 index 0000000000000..2e8da0dadd5b1 --- /dev/null +++ b/app/code/Magento/Sales/Model/Service/PaymentFailuresService.php @@ -0,0 +1,295 @@ + Configuration > Sales > Checkout > Payment Failed Emails configuration. + */ +class PaymentFailuresService implements PaymentFailuresInterface +{ + /** + * Store config + * + * @var ScopeConfigInterface + */ + private $scopeConfig; + + /** + * @var StateInterface + */ + private $inlineTranslation; + + /** + * @var TransportBuilder + */ + private $transportBuilder; + + /** + * @var TimezoneInterface + */ + private $localeDate; + + /** + * @var CartRepositoryInterface + */ + private $cartRepository; + + /** + * @param ScopeConfigInterface $scopeConfig + * @param StateInterface $inlineTranslation + * @param TransportBuilder $transportBuilder + * @param TimezoneInterface $localeDate + * @param CartRepositoryInterface $cartRepository + */ + public function __construct( + ScopeConfigInterface $scopeConfig, + StateInterface $inlineTranslation, + TransportBuilder $transportBuilder, + TimezoneInterface $localeDate, + CartRepositoryInterface $cartRepository + ) { + $this->scopeConfig = $scopeConfig; + $this->inlineTranslation = $inlineTranslation; + $this->transportBuilder = $transportBuilder; + $this->localeDate = $localeDate; + $this->cartRepository = $cartRepository; + } + + /** + * Sends an email about failed transaction. + * + * @param int $cartId + * @param string $message + * @param string $checkoutType + * @return PaymentFailuresInterface + */ + public function handle( + int $cartId, + string $message, + string $checkoutType = 'onepage' + ): PaymentFailuresInterface { + $this->inlineTranslation->suspend(); + $quote = $this->cartRepository->get($cartId); + + $template = $this->getConfigValue('checkout/payment_failed/template', $quote); + $receiver = $this->getConfigValue('checkout/payment_failed/receiver', $quote); + $sendTo = [ + [ + 'email' => $this->getConfigValue('trans_email/ident_' . $receiver . '/email', $quote), + 'name' => $this->getConfigValue('trans_email/ident_' . $receiver . '/name', $quote), + ], + ]; + + $copyMethod = $this->getConfigValue('checkout/payment_failed/copy_method', $quote); + $copyTo = $this->getConfigEmails($quote); + + $bcc = []; + if (!empty($copyTo)) { + switch ($copyMethod) { + case 'bcc': + $bcc = $copyTo; + break; + case 'copy': + foreach ($copyTo as $email) { + $sendTo[] = ['email' => $email, 'name' => null]; + } + break; + } + } + + foreach ($sendTo as $recipient) { + $transport = $this->transportBuilder + ->setTemplateIdentifier($template) + ->setTemplateOptions([ + 'area' => FrontNameResolver::AREA_CODE, + 'store' => Store::DEFAULT_STORE_ID, + ]) + ->setTemplateVars($this->getTemplateVars($quote, $message, $checkoutType)) + ->setFrom($this->getSendFrom($quote)) + ->addTo($recipient['email'], $recipient['name']) + ->addBcc($bcc) + ->getTransport(); + + $transport->sendMessage(); + } + + $this->inlineTranslation->resume(); + + return $this; + } + + /** + * Returns mail template variables. + * + * @param Quote $quote + * @param string $message + * @param string $checkoutType + * @return array + */ + private function getTemplateVars(Quote $quote, string $message, string $checkoutType): array + { + return [ + 'reason' => $message, + 'checkoutType' => $checkoutType, + 'dateAndTime' => $this->getLocaleDate(), + 'customer' => $this->getCustomerName($quote), + 'customerEmail' => $quote->getBillingAddress()->getEmail(), + 'billingAddress' => $quote->getBillingAddress(), + 'shippingAddress' => $quote->getShippingAddress(), + 'shippingMethod' => $this->getConfigValue( + 'carriers/' . $this->getShippingMethod($quote) . '/title', + $quote + ), + 'paymentMethod' => $this->getConfigValue( + 'payment/' . $this->getPaymentMethod($quote) . '/title', + $quote + ), + 'items' => implode('
', $this->getQuoteItems($quote)), + 'total' => $quote->getCurrency()->getStoreCurrencyCode() . ' ' . $quote->getGrandTotal(), + ]; + } + + /** + * Returns scope config value by config path. + * + * @param string $configPath + * @param Quote $quote + * @return mixed + */ + private function getConfigValue(string $configPath, Quote $quote) + { + return $this->scopeConfig->getValue( + $configPath, + ScopeInterface::SCOPE_STORE, + $quote->getStoreId() + ); + } + + /** + * Returns shipping method from quote. + * + * @param Quote $quote + * @return string + */ + private function getShippingMethod(Quote $quote) + { + $shippingMethod = ''; + if ($shippingInfo = $quote->getShippingAddress()->getShippingMethod()) { + $data = explode('_', $shippingInfo); + $shippingMethod = $data[0]; + } + + return $shippingMethod; + } + + /** + * Returns payment method title from quote. + * + * @param Quote $quote + * @return string + */ + private function getPaymentMethod(Quote $quote) + { + $paymentMethod = ''; + if ($paymentInfo = $quote->getPayment()) { + $paymentMethod = $paymentInfo->getMethod(); + } + + return $paymentMethod; + } + + /** + * Returns quote visible items. + * + * @param Quote $quote + * @return array + */ + private function getQuoteItems(Quote $quote): array + { + $items = []; + foreach ($quote->getAllVisibleItems() as $item) { + $itemData = $item->getProduct()->getName() . ' x ' . $item->getQty() . ' ' . + $quote->getCurrency()->getStoreCurrencyCode() . ' ' . + $item->getProduct()->getFinalPrice($item->getQty()); + $items[] = $itemData; + } + + return $items; + } + + /** + * Gets email values by configuration path. + * + * @param Quote $quote + * @return array|false + */ + private function getConfigEmails(Quote $quote) + { + $configData = $this->getConfigValue('checkout/payment_failed/copy_to', $quote); + if (!empty($configData)) { + return explode(',', $configData); + } + + return false; + } + + /** + * Returns sender identity. + * + * @param Quote $quote + * @return string + */ + private function getSendFrom(Quote $quote): string + { + return $this->getConfigValue('checkout/payment_failed/identity', $quote); + } + + /** + * Returns current locale date and time + * + * @return string + */ + private function getLocaleDate(): string + { + return $this->localeDate->formatDateTime( + new \DateTime(), + \IntlDateFormatter::MEDIUM, + \IntlDateFormatter::MEDIUM + ); + } + + /** + * Returns customer name. + * + * @param Quote $quote + * @return string + */ + private function getCustomerName(Quote $quote): string + { + $customer = __('Guest'); + if (!$quote->getCustomerIsGuest()) { + $customer = $quote->getCustomer()->getFirstname() . ' ' . + $quote->getCustomer()->getLastname(); + } + + return $customer; + } +} diff --git a/app/code/Magento/Sales/etc/di.xml b/app/code/Magento/Sales/etc/di.xml index ce2948983edbe..ac25cdab22f56 100644 --- a/app/code/Magento/Sales/etc/di.xml +++ b/app/code/Magento/Sales/etc/di.xml @@ -65,6 +65,7 @@ + diff --git a/dev/tests/integration/testsuite/Magento/Paypal/Controller/Payflow/SilentPostTest.php b/dev/tests/integration/testsuite/Magento/Paypal/Controller/Payflow/SilentPostTest.php index b4904c10dc22e..2bebb5bd95bb2 100644 --- a/dev/tests/integration/testsuite/Magento/Paypal/Controller/Payflow/SilentPostTest.php +++ b/dev/tests/integration/testsuite/Magento/Paypal/Controller/Payflow/SilentPostTest.php @@ -8,8 +8,8 @@ use Magento\Framework\Api\FilterBuilder; use Magento\Framework\Api\SearchCriteriaBuilder; use Magento\Framework\DataObject; -use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Logger\Monolog; +use Magento\Payment\Gateway\Command\CommandException; use Magento\Paypal\Model\Payflow\Service\Gateway; use Magento\Paypal\Model\Payflowlink; use Magento\Sales\Api\Data\OrderInterface; @@ -116,7 +116,7 @@ public function testFraudulentNotification() ->getMock(); $this->_objectManager->addSharedInstance($logger, Monolog::class); - $exception = new LocalizedException(__('Response message from PayPal gateway')); + $exception = new CommandException(__('Response message from PayPal gateway')); $logger->expects(self::once()) ->method('critical') ->with(self::equalTo($exception)); diff --git a/dev/tests/integration/testsuite/Magento/Paypal/Controller/PayflowTest.php b/dev/tests/integration/testsuite/Magento/Paypal/Controller/PayflowTest.php index 987f14c8c87e9..367f68ce25872 100644 --- a/dev/tests/integration/testsuite/Magento/Paypal/Controller/PayflowTest.php +++ b/dev/tests/integration/testsuite/Magento/Paypal/Controller/PayflowTest.php @@ -5,32 +5,74 @@ */ namespace Magento\Paypal\Controller; +use Magento\Checkout\Model\Session; +use Magento\Paypal\Model\Config; +use Magento\Quote\Model\Quote; +use Magento\Quote\Api\CartRepositoryInterface; +use Magento\Framework\Api\FilterBuilder; +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Api\OrderRepositoryInterface; + /** * @magentoDataFixture Magento/Sales/_files/order.php */ class PayflowTest extends \Magento\TestFramework\TestCase\AbstractController { + /** + * @var OrderRepositoryInterface + */ + private $orderRepository; + + /** + * @var CartRepositoryInterface + */ + private $quoteRepository; + + /** + * @var OrderInterface + */ + private $order; + + /** + * @inheritdoc + */ protected function setUp() { parent::setUp(); - $order = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(\Magento\Sales\Model\Order::class); - $order->load('100000001', 'increment_id'); - $order->getPayment()->setMethod(\Magento\Paypal\Model\Config::METHOD_PAYFLOWLINK); + /** @var FilterBuilder $filterBuilder */ + $filterBuilder = $this->_objectManager->get(FilterBuilder::class); + $filters = [ + $filterBuilder->setField(OrderInterface::INCREMENT_ID) + ->setValue('100000001') + ->create(), + ]; - $quote = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( - \Magento\Quote\Model\Quote::class - )->setStoreId( - $order->getStoreId() - )->save(); + /** @var SearchCriteriaBuilder $searchCriteriaBuilder */ + $searchCriteriaBuilder = $this->_objectManager->get(SearchCriteriaBuilder::class); + $searchCriteria = $searchCriteriaBuilder->addFilters($filters) + ->create(); - $order->setQuoteId($quote->getId()); - $order->save(); + $this->orderRepository = $this->_objectManager->get(OrderRepositoryInterface::class); + $orders = $this->orderRepository->getList($searchCriteria) + ->getItems(); - $session = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get( - \Magento\Checkout\Model\Session::class - ); - $session->setLastRealOrderId($order->getRealOrderId())->setLastQuoteId($order->getQuoteId()); + /** @var OrderInterface $order */ + $this->order = array_pop($orders); + $this->order->getPayment()->setMethod(Config::METHOD_PAYFLOWLINK); + + /** @var $quote \Magento\Quote\Model\Quote */ + $quote = $this->_objectManager->create(Quote::class)->setStoreid($this->order->getStoreid()); + + $this->quoteRepository = $this->_objectManager->get(CartRepositoryInterface::class); + $this->quoteRepository->save($quote); + + $this->order->setQuoteId($quote->getId()); + $this->orderRepository->save($this->order); + + $session = $this->_objectManager->get(Session::class); + $session->setLastRealOrderId($this->order->getRealOrderId())->setLastQuoteId($this->order->getQuoteId()); } public function testCancelPaymentActionIsContentGenerated() @@ -65,22 +107,24 @@ public function testFormActionIsContentGenerated() } /** - * @magentoDataFixture Magento/Sales/_files/order.php * @magentoConfigFixture current_store payment/paypal_payflow/active 1 * @magentoConfigFixture current_store paypal/general/business_account merchant_2012050718_biz@example.com + * @return void */ public function testCancelAction() { - $order = $this->_objectManager->create(\Magento\Sales\Model\Order::class); - $session = $this->_objectManager->get(\Magento\Checkout\Model\Session::class); + $orderId = $this->order->getEntityId(); + /** @var \Magento\Sales\Model\Order $order */ + $order = $this->orderRepository->get($orderId); + /** @var $quote \Magento\Quote\Model\Quote */ + $quote = $this->quoteRepository->get($order->getQuoteId()); - $quote = $this->_objectManager->create(\Magento\Quote\Model\Quote::class); - $quote->load('test02', 'reserved_order_id'); - $order->load('100000001', 'increment_id')->setQuoteId($quote->getId())->save(); + $session = $this->_objectManager->get(Session::class); $session->setQuoteId($quote->getId()); $session->setPaypalStandardQuoteId($quote->getId())->setLastRealOrderId('100000001'); $this->dispatch('paypal/payflow/cancelpayment'); - $order->load('100000001', 'increment_id'); + + $order = $this->_objectManager->create(OrderRepositoryInterface::class)->get($orderId); $this->assertEquals('canceled', $order->getState()); $this->assertEquals($session->getQuote()->getGrandTotal(), $quote->getGrandTotal()); $this->assertEquals($session->getQuote()->getItemsCount(), $quote->getItemsCount()); diff --git a/dev/tests/integration/testsuite/Magento/Paypal/Controller/PayflowadvancedTest.php b/dev/tests/integration/testsuite/Magento/Paypal/Controller/PayflowadvancedTest.php index ae6053a4801b0..7996acc7ed3b0 100644 --- a/dev/tests/integration/testsuite/Magento/Paypal/Controller/PayflowadvancedTest.php +++ b/dev/tests/integration/testsuite/Magento/Paypal/Controller/PayflowadvancedTest.php @@ -5,30 +5,75 @@ */ namespace Magento\Paypal\Controller; +use Magento\Checkout\Model\Session; +use Magento\Paypal\Model\Config; +use Magento\Quote\Model\Quote; +use Magento\Quote\Api\CartRepositoryInterface; +use Magento\Framework\Api\FilterBuilder; +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Api\OrderRepositoryInterface; + /** * @magentoDataFixture Magento/Sales/_files/order.php */ class PayflowadvancedTest extends \Magento\TestFramework\TestCase\AbstractController { + /** + * @var OrderRepositoryInterface + */ + private $orderRepository; + + /** + * @var CartRepositoryInterface + */ + private $quoteRepository; + + /** + * @var OrderInterface + */ + private $order; + + /** + * @inheritdoc + */ protected function setUp() { parent::setUp(); - $order = $this->_objectManager->create(\Magento\Sales\Model\Order::class); - $order->load('100000001', 'increment_id'); - $order->getPayment()->setMethod(\Magento\Paypal\Model\Config::METHOD_PAYFLOWADVANCED); + /** @var FilterBuilder $filterBuilder */ + $filterBuilder = $this->_objectManager->get(FilterBuilder::class); + $filters = [ + $filterBuilder->setField(OrderInterface::INCREMENT_ID) + ->setValue('100000001') + ->create(), + ]; + + /** @var SearchCriteriaBuilder $searchCriteriaBuilder */ + $searchCriteriaBuilder = $this->_objectManager->get(SearchCriteriaBuilder::class); + $searchCriteria = $searchCriteriaBuilder->addFilters($filters) + ->create(); + + $this->orderRepository = $this->_objectManager->get(OrderRepositoryInterface::class); + $orders = $this->orderRepository->getList($searchCriteria) + ->getItems(); + + /** @var OrderInterface $order */ + $this->order = array_pop($orders); + $this->order->getPayment()->setMethod(Config::METHOD_PAYFLOWLINK); + + /** @var $quote \Magento\Quote\Model\Quote */ + $quote = $this->_objectManager->create(Quote::class) + ->setStoreid($this->order->getStoreid()); - $quote = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( - \Magento\Quote\Model\Quote::class - )->setStoreId( - $order->getStoreId() - )->save(); + $this->quoteRepository = $this->_objectManager->get(CartRepositoryInterface::class); + $this->quoteRepository->save($quote); - $order->setQuoteId($quote->getId()); - $order->save(); + $this->order->setQuoteId($quote->getId()); + $this->orderRepository->save($this->order); - $session = $this->_objectManager->create(\Magento\Checkout\Model\Session::class); - $session->setLastRealOrderId($order->getRealOrderId())->setLastQuoteId($order->getQuoteId()); + $session = $this->_objectManager->get(Session::class); + $session->setLastRealOrderId($this->order->getRealOrderId())->setLastQuoteId($this->order->getQuoteId()); } public function testCancelPaymentActionIsContentGenerated() @@ -55,23 +100,24 @@ public function testFormActionIsContentGenerated() } /** - * @magentoDataFixture Magento/Sales/_files/order.php * @magentoConfigFixture current_store payment/paypal_payflow/active 1 * @magentoConfigFixture current_store paypal/general/business_account merchant_2012050718_biz@example.com + * @return void */ public function testCancelAction() { - $order = $this->_objectManager->create(\Magento\Sales\Model\Order::class); - $session = $this->_objectManager->get(\Magento\Checkout\Model\Session::class); + $orderId = $this->order->getEntityId(); + /** @var \Magento\Sales\Model\Order $order */ + $order = $this->orderRepository->get($orderId); + /** @var $quote \Magento\Quote\Model\Quote */ + $quote = $this->quoteRepository->get($order->getQuoteId()); - $quote = $this->_objectManager->create(\Magento\Quote\Model\Quote::class); - $quote->load('test02', 'reserved_order_id'); - $order->load('100000001', 'increment_id')->setQuoteId($quote->getId())->save(); + $session = $this->_objectManager->get(Session::class); $session->setQuoteId($quote->getId()); $session->setPaypalStandardQuoteId($quote->getId())->setLastRealOrderId('100000001'); $this->dispatch('paypal/payflow/cancelpayment'); - $order->load('100000001', 'increment_id'); + $order = $this->_objectManager->create(OrderRepositoryInterface::class)->get($orderId); $this->assertEquals('canceled', $order->getState()); $this->assertEquals($session->getQuote()->getGrandTotal(), $quote->getGrandTotal()); $this->assertEquals($session->getQuote()->getItemsCount(), $quote->getItemsCount()); diff --git a/dev/tests/integration/testsuite/Magento/Sales/Model/Service/PaymentFailuresServiceTest.php b/dev/tests/integration/testsuite/Magento/Sales/Model/Service/PaymentFailuresServiceTest.php new file mode 100644 index 0000000000000..1e334c9660d19 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/Model/Service/PaymentFailuresServiceTest.php @@ -0,0 +1,98 @@ +quote = Bootstrap::getObjectManager()->create(Quote::class); + $this->cartRepositoryMock = $this->getMockBuilder(CartRepositoryInterface::class) + ->disableOriginalConstructor() + ->setMethods(['get']) + ->getMockForAbstractClass(); + + $this->paymentFailures = Bootstrap::getObjectManager()->create( + PaymentFailuresInterface::class, + [ + 'cartRepository' => $this->cartRepositoryMock, + ] + ); + } + + /** + * @magentoDataFixture Magento/Sales/_files/quote_with_two_products_and_customer.php + * @magentoConfigFixture current_store payment/payflowpro/title Some Title Of The Method + * @magentoConfigFixture current_store carriers/freeshipping/title Some Shipping Method + * @magentoDbIsolation enabled + * @magentoAppIsolation enabled + * @return void + */ + public function testHandlerWithCustomer() + { + $errorMessage = __('Transaction declined.'); + $checkoutType = 'custom_checkout'; + + $this->quote->load('test01', 'reserved_order_id'); + $this->cartRepositoryMock->method('get') + ->with($this->quote->getId()) + ->willReturn($this->quote); + + $this->paymentFailures->handle((int)$this->quote->getId(), $errorMessage->render()); + + $paymentReflection = new \ReflectionClass($this->paymentFailures); + $templateTimeMethod = $paymentReflection->getMethod('getLocaleDate'); + $templateTimeMethod->setAccessible(true); + + $templateVarsMethod = $paymentReflection->getMethod('getTemplateVars'); + $templateVarsMethod->setAccessible(true); + + $templateVars = $templateVarsMethod->invoke($this->paymentFailures, $this->quote, $errorMessage, $checkoutType); + $expectedVars = [ + 'reason' => $errorMessage, + 'checkoutType' => $checkoutType, + 'dateAndTime' => $templateTimeMethod->invoke($this->paymentFailures), + 'customer' => 'John Smith', + 'customerEmail' => 'aaa@aaa.com', + 'paymentMethod' => 'Some Title Of The Method', + 'shippingMethod' => 'Some Shipping Method', + 'items' => 'Simple Product x 2 USD 10
Custom Design Simple Product x 1 USD 10', + 'total' => 'USD 30.0000', + 'billingAddress' => $this->quote->getBillingAddress(), + 'shippingAddress' => $this->quote->getShippingAddress(), + ]; + + $this->assertEquals($expectedVars, $templateVars); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/order.php b/dev/tests/integration/testsuite/Magento/Sales/_files/order.php index f1e2afc88b6f8..a1c5f8277762c 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/_files/order.php +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/order.php @@ -4,6 +4,13 @@ * See COPYING.txt for license details. */ +use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\Sales\Model\Order; +use Magento\Sales\Model\Order\Address as OrderAddress; +use Magento\Sales\Model\Order\Item as OrderItem; +use Magento\Sales\Model\Order\Payment; +use Magento\Store\Model\StoreManagerInterface; + require 'default_rollback.php'; require __DIR__ . '/../../../Magento/Catalog/_files/product_simple.php'; /** @var \Magento\Catalog\Model\Product $product */ @@ -12,57 +19,50 @@ $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); -$billingAddress = $objectManager->create(\Magento\Sales\Model\Order\Address::class, ['data' => $addressData]); +$billingAddress = $objectManager->create(OrderAddress::class, ['data' => $addressData]); $billingAddress->setAddressType('billing'); $shippingAddress = clone $billingAddress; $shippingAddress->setId(null)->setAddressType('shipping'); -$payment = $objectManager->create(\Magento\Sales\Model\Order\Payment::class); -$payment->setMethod('checkmo'); -$payment->setAdditionalInformation('last_trans_id', '11122'); -$payment->setAdditionalInformation('metadata', [ - 'type' => 'free', - 'fraudulent' => false -]); +/** @var Payment $payment */ +$payment = $objectManager->create(Payment::class); +$payment->setMethod('checkmo') + ->setAdditionalInformation('last_trans_id', '11122') + ->setAdditionalInformation( + 'metadata', + [ + 'type' => 'free', + 'fraudulent' => false, + ] + ); + +/** @var OrderItem $orderItem */ +$orderItem = $objectManager->create(OrderItem::class); +$orderItem->setProductId($product->getId()) + ->setQtyOrdered(2) + ->setBasePrice($product->getPrice()) + ->setPrice($product->getPrice()) + ->setRowTotal($product->getPrice()) + ->setProductType('simple'); -/** @var \Magento\Sales\Model\Order\Item $orderItem */ -$orderItem = $objectManager->create(\Magento\Sales\Model\Order\Item::class); -$orderItem->setProductId($product->getId())->setQtyOrdered(2); -$orderItem->setBasePrice($product->getPrice()); -$orderItem->setPrice($product->getPrice()); -$orderItem->setRowTotal($product->getPrice()); -$orderItem->setProductType('simple'); +/** @var Order $order */ +$order = $objectManager->create(Order::class); +$order->setIncrementId('100000001') + ->setState(Order::STATE_PROCESSING) + ->setStatus($order->getConfig()->getStateDefaultStatus(Order::STATE_PROCESSING)) + ->setSubtotal(100) + ->setGrandTotal(100) + ->setBaseSubtotal(100) + ->setBaseGrandTotal(100) + ->setCustomerIsGuest(true) + ->setCustomerEmail('customer@null.com') + ->setBillingAddress($billingAddress) + ->setShippingAddress($shippingAddress) + ->setStoreId($objectManager->get(StoreManagerInterface::class)->getStore()->getId()) + ->addItem($orderItem) + ->setPayment($payment); -/** @var \Magento\Sales\Model\Order $order */ -$order = $objectManager->create(\Magento\Sales\Model\Order::class); -$order->setIncrementId( - '100000001' -)->setState( - \Magento\Sales\Model\Order::STATE_PROCESSING -)->setStatus( - $order->getConfig()->getStateDefaultStatus(\Magento\Sales\Model\Order::STATE_PROCESSING) -)->setSubtotal( - 100 -)->setGrandTotal( - 100 -)->setBaseSubtotal( - 100 -)->setBaseGrandTotal( - 100 -)->setCustomerIsGuest( - true -)->setCustomerEmail( - 'customer@null.com' -)->setBillingAddress( - $billingAddress -)->setShippingAddress( - $shippingAddress -)->setStoreId( - $objectManager->get(\Magento\Store\Model\StoreManagerInterface::class)->getStore()->getId() -)->addItem( - $orderItem -)->setPayment( - $payment -); -$order->save(); +/** @var OrderRepositoryInterface $orderRepository */ +$orderRepository = $objectManager->create(OrderRepositoryInterface::class); +$orderRepository->save($order); diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/quote_with_two_products_and_customer.php b/dev/tests/integration/testsuite/Magento/Sales/_files/quote_with_two_products_and_customer.php new file mode 100644 index 0000000000000..b3b341c092d69 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/quote_with_two_products_and_customer.php @@ -0,0 +1,31 @@ +addProduct( + $customDesignProduct->load($customDesignProduct->getId()), + 1 +); + +$quote->getPayment()->setMethod('payflowpro'); +$quote->setIsMultiShipping('0'); +$quote->getShippingAddress()->setShippingMethod('freeshipping_freeshipping'); +$quote->setReservedOrderId('test01'); +$quote->collectTotals() + ->save(); + +/** @var \Magento\Quote\Model\QuoteIdMask $quoteIdMask */ +$quoteIdMask = Bootstrap::getObjectManager() + ->create(\Magento\Quote\Model\QuoteIdMaskFactory::class) + ->create(); +$quoteIdMask->setQuoteId($quote->getId()); +$quoteIdMask->setDataChanges(true); +$quoteIdMask->save(); From 8559900dab7a81c5dc7fd9b7396fb05aa594f8c1 Mon Sep 17 00:00:00 2001 From: Andrii Meysar Date: Tue, 29 May 2018 16:15:40 +0300 Subject: [PATCH 04/24] MAGETWO-92134: [2.2.x] Payment Failed Email is not generated --- app/code/Magento/Checkout/composer.json | 1 - 1 file changed, 1 deletion(-) diff --git a/app/code/Magento/Checkout/composer.json b/app/code/Magento/Checkout/composer.json index 5f695adc9f4b4..540565345bd9b 100644 --- a/app/code/Magento/Checkout/composer.json +++ b/app/code/Magento/Checkout/composer.json @@ -7,7 +7,6 @@ "require": { "php": "~7.1.3||~7.2.0", "magento/framework": "*", - "magento/module-backend": "*", "magento/module-catalog": "*", "magento/module-catalog-inventory": "*", "magento/module-config": "*", From 6f4e81b6ec8d685d2fdfbd65b7b64939b533fb45 Mon Sep 17 00:00:00 2001 From: Viktor Sevch Date: Wed, 30 May 2018 10:25:47 +0300 Subject: [PATCH 05/24] MAGETWO-90762: [2.3.0] Placing an order takes more time then usual --- .../GuestPaymentInformationManagement.php | 32 ++++++- .../GuestPaymentInformationManagementTest.php | 90 +++++++++++++++---- .../Quote/Model/PaymentMethodManagement.php | 24 ++--- .../Model/PaymentMethodManagementTest.php | 36 ++++---- 4 files changed, 131 insertions(+), 51 deletions(-) diff --git a/app/code/Magento/Checkout/Model/GuestPaymentInformationManagement.php b/app/code/Magento/Checkout/Model/GuestPaymentInformationManagement.php index a17cf41585649..333226b7d216f 100644 --- a/app/code/Magento/Checkout/Model/GuestPaymentInformationManagement.php +++ b/app/code/Magento/Checkout/Model/GuestPaymentInformationManagement.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Checkout\Model; @@ -10,6 +11,7 @@ use Magento\Framework\App\ResourceConnection; use Magento\Quote\Api\CartRepositoryInterface; use Magento\Framework\Exception\CouldNotSaveException; +use Magento\Quote\Model\Quote; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -135,13 +137,19 @@ public function savePaymentInformation( \Magento\Quote\Api\Data\PaymentInterface $paymentMethod, \Magento\Quote\Api\Data\AddressInterface $billingAddress = null ) { + $quoteIdMask = $this->quoteIdMaskFactory->create()->load($cartId, 'masked_id'); + /** @var Quote $quote */ + $quote = $this->cartRepository->getActive($quoteIdMask->getQuoteId()); + if ($billingAddress) { $billingAddress->setEmail($email); - $this->billingAddressManagement->assign($cartId, $billingAddress); + $quote->removeAddress($quote->getBillingAddress()->getId()); + $quote->setBillingAddress($billingAddress); + $quote->setDataChanges(true); } else { - $quoteIdMask = $this->quoteIdMaskFactory->create()->load($cartId, 'masked_id'); - $this->cartRepository->getActive($quoteIdMask->getQuoteId())->getBillingAddress()->setEmail($email); + $quote->getBillingAddress()->setEmail($email); } + $this->limitShippingCarrier($quote); $this->paymentMethodManagement->set($cartId, $paymentMethod); return true; @@ -169,4 +177,22 @@ private function getLogger() } return $this->logger; } + + /** + * Limits shipping rates request by carrier from shipping address. + * + * @param Quote $quote + * + * @return void + * @see \Magento\Shipping\Model\Shipping::collectRates + */ + private function limitShippingCarrier(Quote $quote) : void + { + $shippingAddress = $quote->getShippingAddress(); + if ($shippingAddress && $shippingAddress->getShippingMethod()) { + $shippingDataArray = explode('_', $shippingAddress->getShippingMethod()); + $shippingCarrier = array_shift($shippingDataArray); + $shippingAddress->setLimitCarrier($shippingCarrier); + } + } } diff --git a/app/code/Magento/Checkout/Test/Unit/Model/GuestPaymentInformationManagementTest.php b/app/code/Magento/Checkout/Test/Unit/Model/GuestPaymentInformationManagementTest.php index da0de5d4f0a3d..c4924ca211024 100644 --- a/app/code/Magento/Checkout/Test/Unit/Model/GuestPaymentInformationManagementTest.php +++ b/app/code/Magento/Checkout/Test/Unit/Model/GuestPaymentInformationManagementTest.php @@ -3,11 +3,15 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Checkout\Test\Unit\Model; use Magento\Framework\App\ResourceConnection; use Magento\Framework\DB\Adapter\AdapterInterface; +use Magento\Quote\Model\Quote; +use Magento\Quote\Model\Quote\Address; +use Magento\Quote\Model\QuoteIdMask; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -96,6 +100,7 @@ public function testSavePaymentInformationAndPlaceOrder() $email = 'email@magento.com'; $paymentMock = $this->createMock(\Magento\Quote\Api\Data\PaymentInterface::class); $billingAddressMock = $this->createMock(\Magento\Quote\Api\Data\AddressInterface::class); + $this->getMockForAssignBillingAddress($cartId, $billingAddressMock); $billingAddressMock->expects($this->once())->method('setEmail')->with($email)->willReturnSelf(); @@ -119,10 +124,6 @@ public function testSavePaymentInformationAndPlaceOrder() ->willReturn($adapterMockForCheckout); $adapterMockForCheckout->expects($this->once())->method('beginTransaction'); $adapterMockForCheckout->expects($this->once())->method('commit'); - - $this->billingAddressManagementMock->expects($this->once()) - ->method('assign') - ->with($cartId, $billingAddressMock); $this->paymentMethodManagementMock->expects($this->once())->method('set')->with($cartId, $paymentMock); $this->cartManagementMock->expects($this->once())->method('placeOrder')->with($cartId)->willReturn($orderId); @@ -142,6 +143,7 @@ public function testSavePaymentInformationAndPlaceOrderException() $paymentMock = $this->createMock(\Magento\Quote\Api\Data\PaymentInterface::class); $billingAddressMock = $this->createMock(\Magento\Quote\Api\Data\AddressInterface::class); + $this->getMockForAssignBillingAddress($cartId, $billingAddressMock); $billingAddressMock->expects($this->once())->method('setEmail')->with($email)->willReturnSelf(); $adapterMockForSales = $this->getMockBuilder(AdapterInterface::class) @@ -164,10 +166,7 @@ public function testSavePaymentInformationAndPlaceOrderException() ->willReturn($adapterMockForCheckout); $adapterMockForCheckout->expects($this->once())->method('beginTransaction'); $adapterMockForCheckout->expects($this->once())->method('rollback'); - - $this->billingAddressManagementMock->expects($this->once()) - ->method('assign') - ->with($cartId, $billingAddressMock); + $this->paymentMethodManagementMock->expects($this->once())->method('set')->with($cartId, $paymentMock); $exception = new \Exception(__('DB exception')); $this->cartManagementMock->expects($this->once())->method('placeOrder')->willThrowException($exception); @@ -185,11 +184,9 @@ public function testSavePaymentInformation() $email = 'email@magento.com'; $paymentMock = $this->createMock(\Magento\Quote\Api\Data\PaymentInterface::class); $billingAddressMock = $this->createMock(\Magento\Quote\Api\Data\AddressInterface::class); + $this->getMockForAssignBillingAddress($cartId, $billingAddressMock); $billingAddressMock->expects($this->once())->method('setEmail')->with($email)->willReturnSelf(); - $this->billingAddressManagementMock->expects($this->once()) - ->method('assign') - ->with($cartId, $billingAddressMock); $this->paymentMethodManagementMock->expects($this->once())->method('set')->with($cartId, $paymentMock); $this->assertTrue($this->model->savePaymentInformation($cartId, $email, $paymentMock, $billingAddressMock)); @@ -201,13 +198,13 @@ public function testSavePaymentInformationWithoutBillingAddress() $email = 'email@magento.com'; $paymentMock = $this->createMock(\Magento\Quote\Api\Data\PaymentInterface::class); $billingAddressMock = $this->createMock(\Magento\Quote\Api\Data\AddressInterface::class); - $quoteMock = $this->createMock(\Magento\Quote\Model\Quote::class); + $quoteMock = $this->createMock(Quote::class); $billingAddressMock->expects($this->once())->method('setEmail')->with($email)->willReturnSelf(); $this->billingAddressManagementMock->expects($this->never())->method('assign'); $this->paymentMethodManagementMock->expects($this->once())->method('set')->with($cartId, $paymentMock); - $quoteIdMaskMock = $this->createPartialMock(\Magento\Quote\Model\QuoteIdMask::class, ['getQuoteId', 'load']); + $quoteIdMaskMock = $this->createPartialMock(QuoteIdMask::class, ['getQuoteId', 'load']); $this->quoteIdMaskFactoryMock->expects($this->once())->method('create')->willReturn($quoteIdMaskMock); $quoteIdMaskMock->expects($this->once())->method('load')->with($cartId, 'masked_id')->willReturnSelf(); $quoteIdMaskMock->expects($this->once())->method('getQuoteId')->willReturn($cartId); @@ -228,6 +225,15 @@ public function testSavePaymentInformationAndPlaceOrderWithLocalizedException() $paymentMock = $this->createMock(\Magento\Quote\Api\Data\PaymentInterface::class); $billingAddressMock = $this->createMock(\Magento\Quote\Api\Data\AddressInterface::class); + $quoteMock = $this->createMock(Quote::class); + $quoteMock->method('getBillingAddress')->willReturn($billingAddressMock); + $this->cartRepositoryMock->method('getActive')->with($cartId)->willReturn($quoteMock); + + $quoteIdMask = $this->createPartialMock(QuoteIdMask::class, ['getQuoteId', 'load']); + $this->quoteIdMaskFactoryMock->method('create')->willReturn($quoteIdMask); + $quoteIdMask->method('load')->with($cartId, 'masked_id')->willReturnSelf(); + $quoteIdMask->method('getQuoteId')->willReturn($cartId); + $billingAddressMock->expects($this->once())->method('setEmail')->with($email)->willReturnSelf(); $adapterMockForSales = $this->getMockBuilder(AdapterInterface::class) @@ -250,10 +256,7 @@ public function testSavePaymentInformationAndPlaceOrderWithLocalizedException() ->willReturn($adapterMockForCheckout); $adapterMockForCheckout->expects($this->once())->method('beginTransaction'); $adapterMockForCheckout->expects($this->once())->method('rollback'); - - $this->billingAddressManagementMock->expects($this->once()) - ->method('assign') - ->with($cartId, $billingAddressMock); + $this->paymentMethodManagementMock->expects($this->once())->method('set')->with($cartId, $paymentMock); $phrase = new \Magento\Framework\Phrase(__('DB exception')); $exception = new \Magento\Framework\Exception\LocalizedException($phrase); @@ -262,4 +265,57 @@ public function testSavePaymentInformationAndPlaceOrderWithLocalizedException() $this->model->savePaymentInformationAndPlaceOrder($cartId, $email, $paymentMock, $billingAddressMock); } + + /** + * @param int $cartId + * @param \PHPUnit_Framework_MockObject_MockObject $billingAddressMock + * @return void + */ + private function getMockForAssignBillingAddress( + int $cartId, + \PHPUnit_Framework_MockObject_MockObject $billingAddressMock + ) : void { + $quoteIdMask = $this->createPartialMock(QuoteIdMask::class, ['getQuoteId', 'load']); + $this->quoteIdMaskFactoryMock->method('create') + ->willReturn($quoteIdMask); + $quoteIdMask->method('load') + ->with($cartId, 'masked_id') + ->willReturnSelf(); + $quoteIdMask->method('getQuoteId') + ->willReturn($cartId); + + $billingAddressId = 1; + $quote = $this->createMock(Quote::class); + $quoteBillingAddress = $this->createMock(Address::class); + $quoteShippingAddress = $this->createPartialMock( + Address::class, + ['setLimitCarrier', 'getShippingMethod'] + ); + $this->cartRepositoryMock->method('getActive') + ->with($cartId) + ->willReturn($quote); + $quote->expects($this->once()) + ->method('getBillingAddress') + ->willReturn($quoteBillingAddress); + $quote->expects($this->once()) + ->method('getShippingAddress') + ->willReturn($quoteShippingAddress); + $quoteBillingAddress->expects($this->once()) + ->method('getId') + ->willReturn($billingAddressId); + $quote->expects($this->once()) + ->method('removeAddress') + ->with($billingAddressId); + $quote->expects($this->once()) + ->method('setBillingAddress') + ->with($billingAddressMock); + $quote->expects($this->once()) + ->method('setDataChanges') + ->willReturnSelf(); + $quoteShippingAddress->method('getShippingMethod') + ->willReturn('flatrate_flatrate'); + $quoteShippingAddress->expects($this->once()) + ->method('setLimitCarrier') + ->with('flatrate'); + } } diff --git a/app/code/Magento/Quote/Model/PaymentMethodManagement.php b/app/code/Magento/Quote/Model/PaymentMethodManagement.php index 91d8fe4dbcffd..b6e4bcf5ccc8f 100644 --- a/app/code/Magento/Quote/Model/PaymentMethodManagement.php +++ b/app/code/Magento/Quote/Model/PaymentMethodManagement.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Quote\Model; @@ -52,38 +53,37 @@ public function set($cartId, \Magento\Quote\Api\Data\PaymentInterface $method) { /** @var \Magento\Quote\Model\Quote $quote */ $quote = $this->quoteRepository->get($cartId); - + $quote->setTotalsCollectedFlag(false); $method->setChecks([ \Magento\Payment\Model\Method\AbstractMethod::CHECK_USE_CHECKOUT, \Magento\Payment\Model\Method\AbstractMethod::CHECK_USE_FOR_COUNTRY, \Magento\Payment\Model\Method\AbstractMethod::CHECK_USE_FOR_CURRENCY, \Magento\Payment\Model\Method\AbstractMethod::CHECK_ORDER_TOTAL_MIN_MAX, ]); - $payment = $quote->getPayment(); - - $data = $method->getData(); - $payment->importData($data); if ($quote->isVirtual()) { - $quote->getBillingAddress()->setPaymentMethod($payment->getMethod()); + $address = $quote->getBillingAddress(); } else { + $address = $quote->getShippingAddress(); // check if shipping address is set - if ($quote->getShippingAddress()->getCountryId() === null) { + if ($address->getCountryId() === null) { throw new InvalidTransitionException( __('The shipping address is missing. Set the address and try again.') ); } - $quote->getShippingAddress()->setPaymentMethod($payment->getMethod()); - } - if (!$quote->isVirtual() && $quote->getShippingAddress()) { - $quote->getShippingAddress()->setCollectShippingRates(true); + $address->setCollectShippingRates(true); } + $paymentData = $method->getData(); + $payment = $quote->getPayment(); + $payment->importData($paymentData); + $address->setPaymentMethod($payment->getMethod()); + if (!$this->zeroTotalValidator->isApplicable($payment->getMethodInstance(), $quote)) { throw new InvalidTransitionException(__('The requested Payment Method is not available.')); } - $quote->setTotalsCollectedFlag(false)->collectTotals()->save(); + $quote->save(); return $quote->getPayment()->getId(); } diff --git a/app/code/Magento/Quote/Test/Unit/Model/PaymentMethodManagementTest.php b/app/code/Magento/Quote/Test/Unit/Model/PaymentMethodManagementTest.php index 68b077fcdb965..f18d1fa1b06e5 100644 --- a/app/code/Magento/Quote/Test/Unit/Model/PaymentMethodManagementTest.php +++ b/app/code/Magento/Quote/Test/Unit/Model/PaymentMethodManagementTest.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Quote\Test\Unit\Model; @@ -152,8 +153,8 @@ public function testSetVirtualProduct() ->with($paymentMethod) ->willReturnSelf(); - $quoteMock->expects($this->exactly(2))->method('getPayment')->willReturn($paymentMock); - $quoteMock->expects($this->exactly(2))->method('isVirtual')->willReturn(true); + $quoteMock->method('getPayment')->willReturn($paymentMock); + $quoteMock->expects($this->once())->method('isVirtual')->willReturn(true); $quoteMock->expects($this->once())->method('getBillingAddress')->willReturn($billingAddressMock); $methodInstance = $this->getMockForAbstractClass(\Magento\Payment\Model\MethodInterface::class); @@ -165,7 +166,6 @@ public function testSetVirtualProduct() ->willReturn(true); $quoteMock->expects($this->once())->method('setTotalsCollectedFlag')->with(false)->willReturnSelf(); - $quoteMock->expects($this->once())->method('collectTotals')->willReturnSelf(); $quoteMock->expects($this->once())->method('save')->willReturnSelf(); $paymentMock->expects($this->once())->method('getId')->willReturn($paymentId); @@ -218,9 +218,9 @@ public function testSetVirtualProductThrowsExceptionIfPaymentMethodNotAvailable( ->with($paymentMethod) ->willReturnSelf(); - $quoteMock->expects($this->once())->method('getPayment')->willReturn($paymentMock); - $quoteMock->expects($this->exactly(2))->method('isVirtual')->willReturn(true); - $quoteMock->expects($this->once())->method('getBillingAddress')->willReturn($billingAddressMock); + $quoteMock->method('getPayment')->willReturn($paymentMock); + $quoteMock->method('isVirtual')->willReturn(true); + $quoteMock->method('getBillingAddress')->willReturn($billingAddressMock); $methodInstance = $this->getMockForAbstractClass(\Magento\Payment\Model\MethodInterface::class); $paymentMock->expects($this->once())->method('getMethodInstance')->willReturn($methodInstance); @@ -268,17 +268,20 @@ public function testSetSimpleProduct() $shippingAddressMock = $this->createPartialMock( \Magento\Quote\Model\Quote\Address::class, - ['getCountryId', 'setPaymentMethod'] + ['getCountryId', 'setPaymentMethod', 'setCollectShippingRates'] ); $shippingAddressMock->expects($this->once())->method('getCountryId')->willReturn(100); $shippingAddressMock->expects($this->once()) ->method('setPaymentMethod') ->with($paymentMethod) ->willReturnSelf(); + $shippingAddressMock->expects($this->once()) + ->method('setCollectShippingRates') + ->with(true); - $quoteMock->expects($this->exactly(2))->method('getPayment')->willReturn($paymentMock); - $quoteMock->expects($this->exactly(2))->method('isVirtual')->willReturn(false); - $quoteMock->expects($this->exactly(4))->method('getShippingAddress')->willReturn($shippingAddressMock); + $quoteMock->method('getPayment')->willReturn($paymentMock); + $quoteMock->method('isVirtual')->willReturn(false); + $quoteMock->method('getShippingAddress')->willReturn($shippingAddressMock); $methodInstance = $this->getMockForAbstractClass(\Magento\Payment\Model\MethodInterface::class); $paymentMock->expects($this->once())->method('getMethodInstance')->willReturn($methodInstance); @@ -289,7 +292,6 @@ public function testSetSimpleProduct() ->willReturn(true); $quoteMock->expects($this->once())->method('setTotalsCollectedFlag')->with(false)->willReturnSelf(); - $quoteMock->expects($this->once())->method('collectTotals')->willReturnSelf(); $quoteMock->expects($this->once())->method('save')->willReturnSelf(); $paymentMock->expects($this->once())->method('getId')->willReturn($paymentId); @@ -303,7 +305,6 @@ public function testSetSimpleProduct() public function testSetSimpleProductTrowsExceptionIfShippingAddressNotSet() { $cartId = 100; - $methodData = ['method' => 'data']; $quoteMock = $this->createPartialMock( \Magento\Quote\Model\Quote::class, @@ -311,6 +312,7 @@ public function testSetSimpleProductTrowsExceptionIfShippingAddressNotSet() ); $this->quoteRepositoryMock->expects($this->once())->method('get')->with($cartId)->willReturn($quoteMock); + /** @var \Magento\Quote\Model\Quote\Payment|\PHPUnit_Framework_MockObject_MockObject $methodMock */ $methodMock = $this->createPartialMock(\Magento\Quote\Model\Quote\Payment::class, ['setChecks', 'getData']); $methodMock->expects($this->once()) ->method('setChecks') @@ -321,17 +323,13 @@ public function testSetSimpleProductTrowsExceptionIfShippingAddressNotSet() \Magento\Payment\Model\Method\AbstractMethod::CHECK_ORDER_TOTAL_MIN_MAX, ]) ->willReturnSelf(); - $methodMock->expects($this->once())->method('getData')->willReturn($methodData); - - $paymentMock = $this->createPartialMock(\Magento\Quote\Model\Quote\Payment::class, ['importData']); - $paymentMock->expects($this->once())->method('importData')->with($methodData)->willReturnSelf(); + $methodMock->expects($this->never())->method('getData'); $shippingAddressMock = $this->createPartialMock(\Magento\Quote\Model\Quote\Address::class, ['getCountryId']); $shippingAddressMock->expects($this->once())->method('getCountryId')->willReturn(null); - $quoteMock->expects($this->once())->method('getPayment')->willReturn($paymentMock); - $quoteMock->expects($this->once())->method('isVirtual')->willReturn(false); - $quoteMock->expects($this->once())->method('getShippingAddress')->willReturn($shippingAddressMock); + $quoteMock->method('isVirtual')->willReturn(false); + $quoteMock->method('getShippingAddress')->willReturn($shippingAddressMock); $this->model->set($cartId, $methodMock); } From 15c920ee03b8d3d0b6a97aede4e0a944c876abd9 Mon Sep 17 00:00:00 2001 From: Andrii Meysar Date: Wed, 30 May 2018 10:27:04 +0300 Subject: [PATCH 06/24] MAGETWO-90349: Template file 'header.html' is not found. --- .../Customer/Model/AccountManagement.php | 2 + .../Test/Unit/Model/AccountManagementTest.php | 101 ++++++++++++++ .../Magento/Email/Model/AbstractTemplate.php | 7 +- .../Magento/Email/Model/Template/Config.php | 25 +++- .../Test/Unit/Model/Template/ConfigTest.php | 14 ++ .../Theme/Model/Design/Config/Validator.php | 1 + .../Test/Unit/Model/Config/ValidatorTest.php | 4 +- .../Model/Design/Config/ValidatorTest.php | 125 ++++++++++++++++++ 8 files changed, 272 insertions(+), 7 deletions(-) create mode 100644 app/code/Magento/Theme/Test/Unit/Model/Design/Config/ValidatorTest.php diff --git a/app/code/Magento/Customer/Model/AccountManagement.php b/app/code/Magento/Customer/Model/AccountManagement.php index 7d0b271b9b137..4fc74fa695829 100644 --- a/app/code/Magento/Customer/Model/AccountManagement.php +++ b/app/code/Magento/Customer/Model/AccountManagement.php @@ -873,6 +873,8 @@ protected function sendEmailConfirmation(CustomerInterface $customer, $redirectU } catch (MailException $e) { // If we are not able to send a new account email, this should be ignored $this->logger->critical($e); + } catch (\UnexpectedValueException $e) { + $this->logger->error($e); } } diff --git a/app/code/Magento/Customer/Test/Unit/Model/AccountManagementTest.php b/app/code/Magento/Customer/Test/Unit/Model/AccountManagementTest.php index cfd1729e4e06e..9e3a16a307923 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/AccountManagementTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/AccountManagementTest.php @@ -1875,4 +1875,105 @@ private function prepareDateTimeFactory() return $dateTime; } + + /** + * @return void + */ + public function testCreateAccountUnexpectedValueException(): void + { + $websiteId = 1; + $storeId = null; + $defaultStoreId = 1; + $customerId = 1; + $customerEmail = 'email@email.com'; + $newLinkToken = '2jh43j5h2345jh23lh452h345hfuzasd96ofu'; + $exception = new \UnexpectedValueException('Template file was not found'); + + $datetime = $this->prepareDateTimeFactory(); + + $address = $this->createMock(\Magento\Customer\Api\Data\AddressInterface::class); + $address->expects($this->once()) + ->method('setCustomerId') + ->with($customerId); + $store = $this->createMock(\Magento\Store\Model\Store::class); + $store->expects($this->once()) + ->method('getId') + ->willReturn($defaultStoreId); + $website = $this->createMock(\Magento\Store\Model\Website::class); + $website->expects($this->atLeastOnce()) + ->method('getStoreIds') + ->willReturn([1, 2, 3]); + $website->expects($this->once()) + ->method('getDefaultStore') + ->willReturn($store); + $customer = $this->createMock(\Magento\Customer\Api\Data\CustomerInterface::class); + $customer->expects($this->atLeastOnce()) + ->method('getId') + ->willReturn($customerId); + $customer->expects($this->atLeastOnce()) + ->method('getEmail') + ->willReturn($customerEmail); + $customer->expects($this->atLeastOnce()) + ->method('getWebsiteId') + ->willReturn($websiteId); + $customer->expects($this->atLeastOnce()) + ->method('getStoreId') + ->willReturn($storeId); + $customer->expects($this->once()) + ->method('setStoreId') + ->with($defaultStoreId); + $customer->expects($this->once()) + ->method('getAddresses') + ->willReturn([$address]); + $customer->expects($this->once()) + ->method('setAddresses') + ->with(null); + $this->customerRepository->expects($this->once()) + ->method('get') + ->with($customerEmail) + ->willReturn($customer); + $this->share->expects($this->once()) + ->method('isWebsiteScope') + ->willReturn(true); + $this->storeManager->expects($this->atLeastOnce()) + ->method('getWebsite') + ->with($websiteId) + ->willReturn($website); + $this->customerRepository->expects($this->atLeastOnce()) + ->method('save') + ->willReturn($customer); + $this->addressRepository->expects($this->atLeastOnce()) + ->method('save') + ->with($address); + $this->customerRepository->expects($this->once()) + ->method('getById') + ->with($customerId) + ->willReturn($customer); + $this->random->expects($this->once()) + ->method('getUniqueHash') + ->willReturn($newLinkToken); + $customerSecure = $this->createPartialMock( + \Magento\Customer\Model\Data\CustomerSecure::class, + ['setRpToken', 'setRpTokenCreatedAt', 'getPasswordHash'] + ); + $customerSecure->expects($this->any()) + ->method('setRpToken') + ->with($newLinkToken); + $customerSecure->expects($this->any()) + ->method('setRpTokenCreatedAt') + ->with($datetime) + ->willReturnSelf(); + $customerSecure->expects($this->any()) + ->method('getPasswordHash') + ->willReturn(null); + $this->customerRegistry->expects($this->atLeastOnce()) + ->method('retrieveSecureData') + ->willReturn($customerSecure); + $this->emailNotificationMock->expects($this->once()) + ->method('newAccount') + ->willThrowException($exception); + $this->logger->expects($this->once())->method('error')->with($exception); + + $this->accountManagement->createAccount($customer); + } } diff --git a/app/code/Magento/Email/Model/AbstractTemplate.php b/app/code/Magento/Email/Model/AbstractTemplate.php index 4830ecfbb74b3..a6ecdaf24ebbb 100644 --- a/app/code/Magento/Email/Model/AbstractTemplate.php +++ b/app/code/Magento/Email/Model/AbstractTemplate.php @@ -531,14 +531,13 @@ protected function cancelDesignConfig() * * @param string $templateId * @return $this - * @throws \Magento\Framework\Exception\MailException */ public function setForcedArea($templateId) { - if ($this->area) { - throw new \LogicException(__('The area is already set.')); + if ($this->area === null) { + $this->area = $this->emailConfig->getTemplateArea($templateId); } - $this->area = $this->emailConfig->getTemplateArea($templateId); + return $this; } diff --git a/app/code/Magento/Email/Model/Template/Config.php b/app/code/Magento/Email/Model/Template/Config.php index bdd9054e7969b..d10c394dcb715 100644 --- a/app/code/Magento/Email/Model/Template/Config.php +++ b/app/code/Magento/Email/Model/Template/Config.php @@ -205,8 +205,9 @@ public function getTemplateFilename($templateId, $designParams = []) $designParams['module'] = $module; $file = $this->_getInfo($templateId, 'file'); + $filename = $this->getFilename($file, $designParams, $module); - return $this->viewFileSystem->getEmailTemplateFileName($file, $designParams, $module); + return $filename; } /** @@ -230,4 +231,26 @@ protected function _getInfo($templateId, $fieldName) } return $data[$templateId][$fieldName]; } + + /** + * Retrieve template file path. + * + * @param string $file + * @param array $designParams + * @param string $module + * + * @return string + * + * @throws \UnexpectedValueException + */ + private function getFilename(string $file, array $designParams, string $module): string + { + $filename = $this->viewFileSystem->getEmailTemplateFileName($file, $designParams, $module); + + if ($filename === false) { + throw new \UnexpectedValueException("Template file '{$file}' is not found."); + } + + return $filename; + } } diff --git a/app/code/Magento/Email/Test/Unit/Model/Template/ConfigTest.php b/app/code/Magento/Email/Test/Unit/Model/Template/ConfigTest.php index 47c3ac1e7e450..b396f2ede8977 100644 --- a/app/code/Magento/Email/Test/Unit/Model/Template/ConfigTest.php +++ b/app/code/Magento/Email/Test/Unit/Model/Template/ConfigTest.php @@ -272,6 +272,20 @@ public function testGetTemplateFilenameWithNoParams() $this->assertEquals('_files/Fixture/ModuleOne/view/frontend/email/one.html', $actualResult); } + /** + * @expectedException \UnexpectedValueException + * @expectedExceptionMessage Template file 'one.html' is not found + * @return void + */ + public function testGetTemplateFilenameWrongFileName(): void + { + $this->viewFileSystem->expects($this->once())->method('getEmailTemplateFileName') + ->with('one.html', $this->designParams, 'Fixture_ModuleOne') + ->willReturn(false); + + $this->model->getTemplateFilename('template_one', $this->designParams); + } + /** * @param string $getterMethod * @param $argument diff --git a/app/code/Magento/Theme/Model/Design/Config/Validator.php b/app/code/Magento/Theme/Model/Design/Config/Validator.php index 9fed5be6201b4..994eeba317a34 100644 --- a/app/code/Magento/Theme/Model/Design/Config/Validator.php +++ b/app/code/Magento/Theme/Model/Design/Config/Validator.php @@ -114,6 +114,7 @@ private function getTemplateText($templateId, DesignConfigInterface $designConfi if (is_numeric($templateId)) { $template->load($templateId); } else { + $template->setForcedArea($templateId); $template->loadDefault($templateId); } $text = $template->getTemplateText(); diff --git a/app/code/Magento/Theme/Test/Unit/Model/Config/ValidatorTest.php b/app/code/Magento/Theme/Test/Unit/Model/Config/ValidatorTest.php index 4a1a1982ea653..3b337312e3fb5 100644 --- a/app/code/Magento/Theme/Test/Unit/Model/Config/ValidatorTest.php +++ b/app/code/Magento/Theme/Test/Unit/Model/Config/ValidatorTest.php @@ -70,7 +70,7 @@ public function testValidateHasRecursiveReference() $designElementMock->expects($this->once())->method('getValue')->willReturn($fieldConfig['field']); $templateMock = $this->getMockBuilder(\Magento\Email\Model\TemplateInterface::class) - ->setMethods(['getTemplateText', 'emulateDesign', 'loadDefault', 'revertDesign']) + ->setMethods(['getTemplateText', 'emulateDesign', 'loadDefault', 'revertDesign', 'setForcedArea']) ->getMock(); $this->templateFactoryMock->expects($this->once())->method('create')->willReturn($templateMock); @@ -115,7 +115,7 @@ public function testValidateNoRecursiveReference() $designElementMock->expects($this->once())->method('getValue')->willReturn($fieldConfig['field']); $templateMock = $this->getMockBuilder(\Magento\Email\Model\TemplateInterface::class) - ->setMethods(['getTemplateText', 'emulateDesign', 'loadDefault', 'revertDesign']) + ->setMethods(['getTemplateText', 'emulateDesign', 'loadDefault', 'revertDesign', 'setForcedArea']) ->getMock(); $this->templateFactoryMock->expects($this->once())->method('create')->willReturn($templateMock); diff --git a/app/code/Magento/Theme/Test/Unit/Model/Design/Config/ValidatorTest.php b/app/code/Magento/Theme/Test/Unit/Model/Design/Config/ValidatorTest.php new file mode 100644 index 0000000000000..6b26235d0efcc --- /dev/null +++ b/app/code/Magento/Theme/Test/Unit/Model/Design/Config/ValidatorTest.php @@ -0,0 +1,125 @@ +objectManager = new ObjectManager($this); + $this->templateFactory = $this->getMockBuilder(\Magento\Framework\Mail\TemplateInterfaceFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMockForAbstractClass(); + $this->template = $this->getMockBuilder(\Magento\Framework\Mail\TemplateInterface::class) + ->disableOriginalConstructor() + ->setMethods( + [ + 'emulateDesign', + 'setForcedArea', + 'loadDefault', + 'getTemplateText', + 'revertDesign', + ] + ) + ->getMockForAbstractClass(); + $this->templateFactory->expects($this->any())->method('create')->willReturn($this->template); + $this->designConfig = $this->getMockBuilder(\Magento\Theme\Api\Data\DesignConfigInterface::class) + ->disableOriginalConstructor() + ->setMethods(['getExtensionAttributes']) + ->getMockForAbstractClass(); + } + + /** + * @return void + */ + public function testGetDefaultTemplateTextDefaultScope(): void + { + $templateId = 'email_template'; + $designData = [ + 'field_config' => ['field' => 'fieldValue'], + 'value' => $templateId, + ]; + + $this->templateFactory->expects($this->once())->method('create'); + $this->designConfig->expects($this->any())->method('getScope')->willReturn('default'); + $this->template->expects($this->once())->method('emulateDesign'); + $this->template->expects($this->once())->method('setForcedArea')->with($templateId); + $this->template->expects($this->once())->method('loadDefault')->with($templateId); + $this->template->expects($this->once())->method('getTemplateText'); + $this->template->expects($this->once())->method('revertDesign'); + + $extensionAttributes = $this->getMockBuilder(\Magento\Theme\Api\Data\DesignConfigExtensionInterface::class) + ->disableOriginalConstructor() + ->setMethods(['getDesignConfigData']) + ->getMockForAbstractClass(); + + $extensionAttributes->expects($this->any())->method('getDesignConfigData')->willReturn( + [ + $this->getDesignConfigData($designData), + ] + ); + + $this->designConfig->expects($this->any())->method('getExtensionAttributes')->willReturn($extensionAttributes); + + /** @var Validator $validator */ + $validator = $this->objectManager->getObject( + Validator::class, + [ + 'templateFactory' => $this->templateFactory, + 'fields' => ['field' => 'fieldValue'], + ] + ); + $validator->validate($this->designConfig); + } + + /** + * Returns design config data object. + * + * @param array $data + * @return \Magento\Theme\Model\Data\Design\Config\Data + */ + private function getDesignConfigData(array $data = []): \Magento\Theme\Model\Data\Design\Config\Data + { + return $this->objectManager->getObject( + \Magento\Theme\Model\Data\Design\Config\Data::class, + [ + 'data' => $data, + ] + ); + } +} From 107aa22bf146ed70318f97ae4c987e11754de5ac Mon Sep 17 00:00:00 2001 From: Myroslav Dobra Date: Wed, 30 May 2018 10:48:27 +0300 Subject: [PATCH 07/24] MAGETWO-66217: Product Url key value comes from product name on storeview level (use default = yes) --- .../Product/Initialization/Helper.php | 57 ++++------- .../Initialization/Helper/AttributeFilter.php | 58 +++++++++-- .../Helper/AttributeFilterTest.php | 98 +++++++++++++------ 3 files changed, 141 insertions(+), 72 deletions(-) diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper.php index beb6f2b13bcfe..95339870b4d61 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper.php @@ -3,14 +3,18 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Catalog\Controller\Adminhtml\Product\Initialization; use Magento\Catalog\Api\Data\ProductCustomOptionInterfaceFactory as CustomOptionFactory; use Magento\Catalog\Api\Data\ProductLinkInterfaceFactory as ProductLinkFactory; +use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\Catalog\Api\ProductRepositoryInterface\Proxy as ProductRepository; +use Magento\Catalog\Controller\Adminhtml\Product\Initialization\Helper\AttributeDefaultValueFilter; use Magento\Catalog\Model\Product; use Magento\Catalog\Model\Product\Initialization\Helper\ProductLinks; use Magento\Catalog\Model\Product\Link\Resolver as LinkResolver; +use Magento\Catalog\Model\Product\LinkTypeProvider; use Magento\Framework\App\ObjectManager; use Magento\Catalog\Controller\Adminhtml\Product\Initialization\Helper\AttributeFilter; @@ -81,7 +85,7 @@ class Helper private $dateTimeFilter; /** - * @var \Magento\Catalog\Model\Product\LinkTypeProvider + * @var LinkTypeProvider */ private $linkTypeProvider; @@ -99,10 +103,10 @@ class Helper * @param ProductLinks $productLinks * @param \Magento\Backend\Helper\Js $jsHelper * @param \Magento\Framework\Stdlib\DateTime\Filter\Date $dateFilter - * @param \Magento\Catalog\Api\Data\ProductCustomOptionInterfaceFactory|null $customOptionFactory - * @param \Magento\Catalog\Api\Data\ProductLinkInterfaceFactory|null $productLinkFactory - * @param \Magento\Catalog\Api\ProductRepositoryInterface|null $productRepository - * @param \Magento\Catalog\Model\Product\LinkTypeProvider|null $linkTypeProvider + * @param CustomOptionFactory|null $customOptionFactory + * @param ProductLinkFactory |null $productLinkFactory + * @param ProductRepositoryInterface|null $productRepository + * @param LinkTypeProvider|null $linkTypeProvider * @param AttributeFilter|null $attributeFilter * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ @@ -113,10 +117,10 @@ public function __construct( \Magento\Catalog\Model\Product\Initialization\Helper\ProductLinks $productLinks, \Magento\Backend\Helper\Js $jsHelper, \Magento\Framework\Stdlib\DateTime\Filter\Date $dateFilter, - \Magento\Catalog\Api\Data\ProductCustomOptionInterfaceFactory $customOptionFactory = null, - \Magento\Catalog\Api\Data\ProductLinkInterfaceFactory $productLinkFactory = null, - \Magento\Catalog\Api\ProductRepositoryInterface $productRepository = null, - \Magento\Catalog\Model\Product\LinkTypeProvider $linkTypeProvider = null, + CustomOptionFactory $customOptionFactory = null, + ProductLinkFactory $productLinkFactory = null, + ProductRepositoryInterface $productRepository = null, + LinkTypeProvider $linkTypeProvider = null, AttributeFilter $attributeFilter = null ) { $this->request = $request; @@ -125,16 +129,13 @@ public function __construct( $this->productLinks = $productLinks; $this->jsHelper = $jsHelper; $this->dateFilter = $dateFilter; - $this->customOptionFactory = $customOptionFactory ?: \Magento\Framework\App\ObjectManager::getInstance() - ->get(\Magento\Catalog\Api\Data\ProductCustomOptionInterfaceFactory::class); - $this->productLinkFactory = $productLinkFactory ?: \Magento\Framework\App\ObjectManager::getInstance() - ->get(\Magento\Catalog\Api\Data\ProductLinkInterfaceFactory::class); - $this->productRepository = $productRepository ?: \Magento\Framework\App\ObjectManager::getInstance() - ->get(\Magento\Catalog\Api\ProductRepositoryInterface::class); - $this->linkTypeProvider = $linkTypeProvider ?: \Magento\Framework\App\ObjectManager::getInstance() - ->get(\Magento\Catalog\Model\Product\LinkTypeProvider::class); - $this->attributeFilter = $attributeFilter ?: \Magento\Framework\App\ObjectManager::getInstance() - ->get(AttributeFilter::class); + + $objectManager = \Magento\Framework\App\ObjectManager::getInstance(); + $this->customOptionFactory = $customOptionFactory ?: $objectManager->get(CustomOptionFactory::class); + $this->productLinkFactory = $productLinkFactory ?: $objectManager->get(ProductLinkFactory::class); + $this->productRepository = $productRepository ?: $objectManager->get(ProductRepositoryInterface::class); + $this->linkTypeProvider = $linkTypeProvider ?: $objectManager->get(LinkTypeProvider::class); + $this->attributeFilter = $attributeFilter ?: $objectManager->get(AttributeFilter::class); } /** @@ -150,8 +151,7 @@ public function __construct( */ public function initializeFromData(\Magento\Catalog\Model\Product $product, array $productData) { - unset($productData['custom_attributes']); - unset($productData['extension_attributes']); + unset($productData['custom_attributes'], $productData['extension_attributes']); if ($productData) { $stockData = isset($productData['stock_data']) ? $productData['stock_data'] : []; @@ -199,28 +199,13 @@ public function initializeFromData(\Magento\Catalog\Model\Product $product, arra $productData['tier_price'] = isset($productData['tier_price']) ? $productData['tier_price'] : []; $useDefaults = (array)$this->request->getPost('use_default', []); - $productData = $this->attributeFilter->prepareProductAttributes($product, $productData, $useDefaults); - $product->addData($productData); if ($wasLockedMedia) { $product->lockAttribute('media'); } - /** - * Check "Use Default Value" checkboxes values - */ - foreach ($useDefaults as $attributeCode => $useDefaultState) { - if ($useDefaultState) { - $product->setData($attributeCode, null); - // UI component sends value even if field is disabled, so 'Use Config Settings' must be reset to false - if ($product->hasData('use_config_' . $attributeCode)) { - $product->setData('use_config_' . $attributeCode, false); - } - } - } - $product = $this->setProductLinks($product); $product = $this->fillProductOptions($product, $productOptions); diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilter.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilter.php index ee3a6d491e92f..09af738a13201 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilter.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilter.php @@ -28,11 +28,57 @@ class AttributeFilter * @param array $useDefaults * @return array */ - public function prepareProductAttributes(Product $product, array $productData, array $useDefaults) + public function prepareProductAttributes(Product $product, array $productData, array $useDefaults): array { - foreach ($productData as $attribute => $value) { - if ($this->isAttributeShouldNotBeUpdated($product, $useDefaults, $attribute, $value)) { - unset($productData[$attribute]); + $attributeList = $product->getAttributes(); + foreach ($productData as $attributeCode => $attributeValue) { + if ($this->isAttributeShouldNotBeUpdated($product, $useDefaults, $attributeCode, $attributeValue)) { + unset($productData[$attributeCode]); + } + + if (isset($useDefaults[$attributeCode]) && $useDefaults[$attributeCode] === '1') { + $productData = $this->prepareDefaultData($attributeList, $attributeCode, $productData); + $productData = $this->prepareConfigData($product, $attributeCode, $productData); + } + } + + return $productData; + } + + /** + * @param Product $product + * @param string $attributeCode + * @param array $productData + * @return array + */ + private function prepareConfigData(Product $product, $attributeCode, array $productData): array + { + // UI component sends value even if field is disabled, so 'Use Config Settings' must be reset to false + if ($product->hasData('use_config_' . $attributeCode)) { + $productData['use_config_' . $attributeCode] = false; + } + + return $productData; + } + + /** + * @param array $attributeList + * @param string $attributeCode + * @param array $productData + * @return array + */ + private function prepareDefaultData(array $attributeList, $attributeCode, array $productData): array + { + if (isset($attributeList[$attributeCode])) { + /** @var \Magento\Catalog\Model\ResourceModel\Eav\Attribute $attribute */ + $attribute = $attributeList[$attributeCode]; + $attributeType = $attribute->getBackendType(); + // For non-numberic types set the attributeValue to 'false' to trigger their removal from the db + if ($attributeType === 'varchar' || $attributeType === 'text' || $attributeType === 'datetime') { + $attribute->setIsRequired(false); + $productData[$attributeCode] = false; + } else { + $productData[$attributeCode] = null; } } @@ -46,9 +92,9 @@ public function prepareProductAttributes(Product $product, array $productData, a * @param $value * @return bool */ - private function isAttributeShouldNotBeUpdated(Product $product, $useDefaults, $attribute, $value) : bool + private function isAttributeShouldNotBeUpdated(Product $product, $useDefaults, $attribute, $value): bool { - $considerUseDefaultsAttribute = !isset($useDefaults[$attribute]) || $useDefaults[$attribute] === "1"; + $considerUseDefaultsAttribute = !isset($useDefaults[$attribute]) || $useDefaults[$attribute] === '1'; return ($value === '' && $considerUseDefaultsAttribute && !$product->getData($attribute)); } diff --git a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilterTest.php b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilterTest.php index 28617addc6d27..6e25d02df53ee 100644 --- a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilterTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilterTest.php @@ -3,10 +3,13 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Catalog\Test\Unit\Controller\Adminhtml\Product\Initialization\Helper; use Magento\Catalog\Controller\Adminhtml\Product\Initialization\Helper\AttributeFilter; use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\ResourceModel\Eav\Attribute; +use PHPUnit_Framework_MockObject_MockObject; class AttributeFilterTest extends \PHPUnit\Framework\TestCase { @@ -16,12 +19,12 @@ class AttributeFilterTest extends \PHPUnit\Framework\TestCase protected $model; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var PHPUnit_Framework_MockObject_MockObject */ protected $objectManagerMock; /** - * @var Product|\PHPUnit_Framework_MockObject_MockObject + * @var Product|PHPUnit_Framework_MockObject_MockObject */ protected $productMock; @@ -44,15 +47,25 @@ public function testPrepareProductAttributes( $expectedProductData, $initialProductData ) { + /** @var PHPUnit_Framework_MockObject_MockObject | Product $productMockMap */ $productMockMap = $this->getMockBuilder(Product::class) ->disableOriginalConstructor() - ->setMethods(['getData']) + ->setMethods(['getData', 'getAttributes']) ->getMock(); if (!empty($initialProductData)) { $productMockMap->expects($this->any())->method('getData')->willReturnMap($initialProductData); } + if ($useDefaults) { + $productMockMap + ->expects($this->once()) + ->method('getAttributes') + ->willReturn( + $this->getProductAttributesMock($useDefaults) + ); + } + $actualProductData = $this->model->prepareProductAttributes($productMockMap, $requestProductData, $useDefaults); $this->assertEquals($expectedProductData, $actualProductData); } @@ -69,15 +82,15 @@ public function setupInputDataProvider() 'name' => 'testName', 'sku' => 'testSku', 'price' => '100', - 'description' => '' + 'description' => '', ], 'useDefaults' => [], 'expectedProductData' => [ 'name' => 'testName', 'sku' => 'testSku', - 'price' => '100' + 'price' => '100', ], - 'initialProductData' => [] + 'initialProductData' => [], ], 'update_product_without_use_defaults' => [ 'productData' => [ @@ -85,21 +98,21 @@ public function setupInputDataProvider() 'sku' => 'testSku2', 'price' => '101', 'description' => '', - 'special_price' => null + 'special_price' => null, ], 'useDefaults' => [], 'expectedProductData' => [ 'name' => 'testName2', 'sku' => 'testSku2', 'price' => '101', - 'special_price' => null + 'special_price' => null, ], 'initialProductData' => [ ['name', 'testName2'], ['sku', 'testSku2'], ['price', '101'], - ['special_price', null] - ] + ['special_price', null], + ], ], 'update_product_without_use_defaults_2' => [ 'productData' => [ @@ -107,7 +120,7 @@ public function setupInputDataProvider() 'sku' => 'testSku2', 'price' => '101', 'description' => 'updated description', - 'special_price' => null + 'special_price' => null, ], 'useDefaults' => [], 'expectedProductData' => [ @@ -115,14 +128,14 @@ public function setupInputDataProvider() 'sku' => 'testSku2', 'price' => '101', 'description' => 'updated description', - 'special_price' => null + 'special_price' => null, ], 'initialProductData' => [ ['name', 'testName2'], ['sku', 'testSku2'], ['price', '101'], - ['special_price', null] - ] + ['special_price', null], + ], ], 'update_product_with_use_defaults' => [ 'productData' => [ @@ -130,25 +143,25 @@ public function setupInputDataProvider() 'sku' => 'testSku2', 'price' => '101', 'description' => '', - 'special_price' => null + 'special_price' => null, ], 'useDefaults' => [ - 'description' => '0' + 'description' => '0', ], 'expectedProductData' => [ 'name' => 'testName2', 'sku' => 'testSku2', 'price' => '101', 'special_price' => null, - 'description' => '' + 'description' => '', ], 'initialProductData' => [ ['name', 'testName2'], ['sku', 'testSku2'], ['price', '101'], ['special_price', null], - ['description', 'descr text'] - ] + ['description', 'descr text'], + ], ], 'update_product_with_use_defaults_2' => [ 'requestProductData' => [ @@ -156,48 +169,73 @@ public function setupInputDataProvider() 'sku' => 'testSku3', 'price' => '103', 'description' => 'descr modified', - 'special_price' => '100' + 'special_price' => '100', ], 'useDefaults' => [ - 'description' => '0' + 'description' => '0', ], 'expectedProductData' => [ 'name' => 'testName3', 'sku' => 'testSku3', 'price' => '103', 'special_price' => '100', - 'description' => 'descr modified' + 'description' => 'descr modified', ], 'initialProductData' => [ - ['name', null,'testName2'], + ['name', null, 'testName2'], ['sku', null, 'testSku2'], ['price', null, '101'], - ['description', null, 'descr text'] - ] + ['description', null, 'descr text'], + ], ], 'update_product_with_use_defaults_3' => [ 'requestProductData' => [ 'name' => 'testName3', 'sku' => 'testSku3', 'price' => '103', - 'special_price' => '100' + 'special_price' => '100', + 'description' => 'descr modified', ], 'useDefaults' => [ - 'description' => '1' + 'description' => '1', ], 'expectedProductData' => [ 'name' => 'testName3', 'sku' => 'testSku3', 'price' => '103', 'special_price' => '100', + 'description' => false, ], 'initialProductData' => [ - ['name', null,'testName2'], + ['name', null, 'testName2'], ['sku', null, 'testSku2'], ['price', null, '101'], - ['description', null, 'descr text'] - ] + ['description', null, 'descr text'], + ], ], ]; } + + /** + * @param array $useDefaults + * @return array + */ + private function getProductAttributesMock(array $useDefaults): array + { + $returnArray = []; + foreach ($useDefaults as $attributecode => $isDefault) { + if ($isDefault === '1') { + /** @var Attribute | PHPUnit_Framework_MockObject_MockObject $attribute */ + $attribute = $this->getMockBuilder(Attribute::class) + ->disableOriginalConstructor() + ->getMock(); + $attribute->expects($this->any()) + ->method('getBackendType') + ->willReturn('varchar'); + + $returnArray[$attributecode] = $attribute; + } + } + return $returnArray; + } } From e71aa356ee43e9510706901065174444f42f1b8d Mon Sep 17 00:00:00 2001 From: Andrii Meysar Date: Wed, 30 May 2018 10:58:49 +0300 Subject: [PATCH 08/24] MAGETWO-90349: Template file 'header.html' is not found. --- app/code/Magento/Email/Model/Template/Config.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Email/Model/Template/Config.php b/app/code/Magento/Email/Model/Template/Config.php index d10c394dcb715..9b45f509d97e5 100644 --- a/app/code/Magento/Email/Model/Template/Config.php +++ b/app/code/Magento/Email/Model/Template/Config.php @@ -234,7 +234,7 @@ protected function _getInfo($templateId, $fieldName) /** * Retrieve template file path. - * + * * @param string $file * @param array $designParams * @param string $module From ea9c0f195600347f2e65031761260e8a7093fe31 Mon Sep 17 00:00:00 2001 From: Viktor Sevch Date: Wed, 30 May 2018 11:17:20 +0300 Subject: [PATCH 09/24] MAGETWO-90762: [2.3.0] Placing an order takes more time then usual --- .../Test/Unit/Model/GuestPaymentInformationManagementTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Checkout/Test/Unit/Model/GuestPaymentInformationManagementTest.php b/app/code/Magento/Checkout/Test/Unit/Model/GuestPaymentInformationManagementTest.php index c4924ca211024..853ae0157e64a 100644 --- a/app/code/Magento/Checkout/Test/Unit/Model/GuestPaymentInformationManagementTest.php +++ b/app/code/Magento/Checkout/Test/Unit/Model/GuestPaymentInformationManagementTest.php @@ -168,7 +168,7 @@ public function testSavePaymentInformationAndPlaceOrderException() $adapterMockForCheckout->expects($this->once())->method('rollback'); $this->paymentMethodManagementMock->expects($this->once())->method('set')->with($cartId, $paymentMock); - $exception = new \Exception(__('DB exception')); + $exception = new \Magento\Framework\Exception\CouldNotSaveException(__('DB exception')); $this->cartManagementMock->expects($this->once())->method('placeOrder')->willThrowException($exception); $this->model->savePaymentInformationAndPlaceOrder($cartId, $email, $paymentMock, $billingAddressMock); From 026da34ff1b8eaa069a316e9c84dcbf1811f34b1 Mon Sep 17 00:00:00 2001 From: Myroslav Dobra Date: Wed, 30 May 2018 12:10:31 +0300 Subject: [PATCH 10/24] MAGETWO-89991: Change default AVS and CVV codes for Signifyd --- .../Magento/Braintree/Model/AvsEmsCodeMapper.php | 2 +- .../Test/Unit/Model/AvsEmsCodeMapperTest.php | 10 +++++----- .../Paypal/Model/Payflow/AvsEmsCodeMapper.php | 2 +- .../Unit/Model/Payflow/AvsEmsCodeMapperTest.php | 14 +++++++------- .../Signifyd/Model/PaymentVerificationFactory.php | 4 ++-- .../SignifydGateway/Debugger/DebuggerFactory.php | 2 +- .../SignifydGateway/Request/PurchaseBuilder.php | 6 +++++- app/code/Magento/Signifyd/etc/di.xml | 6 +----- .../Signifyd/Test/Repository/SignifydData.xml | 1 - .../Request/CreateCaseBuilderTest.php | 2 +- 10 files changed, 24 insertions(+), 25 deletions(-) diff --git a/app/code/Magento/Braintree/Model/AvsEmsCodeMapper.php b/app/code/Magento/Braintree/Model/AvsEmsCodeMapper.php index 1d5057d83d6cf..f9fae8a469b1d 100644 --- a/app/code/Magento/Braintree/Model/AvsEmsCodeMapper.php +++ b/app/code/Magento/Braintree/Model/AvsEmsCodeMapper.php @@ -24,7 +24,7 @@ class AvsEmsCodeMapper implements PaymentVerificationInterface * * @var string */ - private static $unavailableCode = 'U'; + private static $unavailableCode = ''; /** * List of mapping AVS codes diff --git a/app/code/Magento/Braintree/Test/Unit/Model/AvsEmsCodeMapperTest.php b/app/code/Magento/Braintree/Test/Unit/Model/AvsEmsCodeMapperTest.php index 9b80a2237a8fb..c82634d36db31 100644 --- a/app/code/Magento/Braintree/Test/Unit/Model/AvsEmsCodeMapperTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Model/AvsEmsCodeMapperTest.php @@ -84,11 +84,11 @@ public function testGetCodeWithException() public function getCodeDataProvider() { return [ - ['avsZip' => null, 'avsStreet' => null, 'expected' => 'U'], - ['avsZip' => null, 'avsStreet' => 'M', 'expected' => 'U'], - ['avsZip' => 'M', 'avsStreet' => null, 'expected' => 'U'], - ['avsZip' => 'M', 'avsStreet' => 'Unknown', 'expected' => 'U'], - ['avsZip' => 'I', 'avsStreet' => 'A', 'expected' => 'U'], + ['avsZip' => null, 'avsStreet' => null, 'expected' => ''], + ['avsZip' => null, 'avsStreet' => 'M', 'expected' => ''], + ['avsZip' => 'M', 'avsStreet' => null, 'expected' => ''], + ['avsZip' => 'M', 'avsStreet' => 'Unknown', 'expected' => ''], + ['avsZip' => 'I', 'avsStreet' => 'A', 'expected' => ''], ['avsZip' => 'M', 'avsStreet' => 'M', 'expected' => 'Y'], ['avsZip' => 'N', 'avsStreet' => 'M', 'expected' => 'A'], ['avsZip' => 'M', 'avsStreet' => 'N', 'expected' => 'Z'], diff --git a/app/code/Magento/Paypal/Model/Payflow/AvsEmsCodeMapper.php b/app/code/Magento/Paypal/Model/Payflow/AvsEmsCodeMapper.php index 661d1f3814a0b..1ec7f4832bcb2 100644 --- a/app/code/Magento/Paypal/Model/Payflow/AvsEmsCodeMapper.php +++ b/app/code/Magento/Paypal/Model/Payflow/AvsEmsCodeMapper.php @@ -24,7 +24,7 @@ class AvsEmsCodeMapper implements PaymentVerificationInterface * * @var string */ - private static $unavailableCode = 'U'; + private static $unavailableCode = ''; /** * List of mapping AVS codes diff --git a/app/code/Magento/Paypal/Test/Unit/Model/Payflow/AvsEmsCodeMapperTest.php b/app/code/Magento/Paypal/Test/Unit/Model/Payflow/AvsEmsCodeMapperTest.php index eb259043a2d4f..ea86a04206f7b 100644 --- a/app/code/Magento/Paypal/Test/Unit/Model/Payflow/AvsEmsCodeMapperTest.php +++ b/app/code/Magento/Paypal/Test/Unit/Model/Payflow/AvsEmsCodeMapperTest.php @@ -85,17 +85,17 @@ public function testGetCodeWithException() public function getCodeDataProvider() { return [ - ['avsZip' => null, 'avsStreet' => null, 'expected' => 'U'], - ['avsZip' => null, 'avsStreet' => 'Y', 'expected' => 'U'], - ['avsZip' => 'Y', 'avsStreet' => null, 'expected' => 'U'], + ['avsZip' => null, 'avsStreet' => null, 'expected' => ''], + ['avsZip' => null, 'avsStreet' => 'Y', 'expected' => ''], + ['avsZip' => 'Y', 'avsStreet' => null, 'expected' => ''], ['avsZip' => 'Y', 'avsStreet' => 'Y', 'expected' => 'Y'], ['avsZip' => 'N', 'avsStreet' => 'Y', 'expected' => 'A'], ['avsZip' => 'Y', 'avsStreet' => 'N', 'expected' => 'Z'], ['avsZip' => 'N', 'avsStreet' => 'N', 'expected' => 'N'], - ['avsZip' => 'X', 'avsStreet' => 'Y', 'expected' => 'U'], - ['avsZip' => 'N', 'avsStreet' => 'X', 'expected' => 'U'], - ['avsZip' => '', 'avsStreet' => 'Y', 'expected' => 'U'], - ['avsZip' => 'N', 'avsStreet' => '', 'expected' => 'U'] + ['avsZip' => 'X', 'avsStreet' => 'Y', 'expected' => ''], + ['avsZip' => 'N', 'avsStreet' => 'X', 'expected' => ''], + ['avsZip' => '', 'avsStreet' => 'Y', 'expected' => ''], + ['avsZip' => 'N', 'avsStreet' => '', 'expected' => ''] ]; } } diff --git a/app/code/Magento/Signifyd/Model/PaymentVerificationFactory.php b/app/code/Magento/Signifyd/Model/PaymentVerificationFactory.php index a26beda520944..5be5ccbc5e55a 100644 --- a/app/code/Magento/Signifyd/Model/PaymentVerificationFactory.php +++ b/app/code/Magento/Signifyd/Model/PaymentVerificationFactory.php @@ -60,7 +60,7 @@ public function __construct( * * @param string $paymentCode * @return PaymentVerificationInterface - * @throws \Exception + * @throws ConfigurationMismatchException */ public function createPaymentCvv($paymentCode) { @@ -73,7 +73,7 @@ public function createPaymentCvv($paymentCode) * * @param string $paymentCode * @return PaymentVerificationInterface - * @throws \Exception + * @throws ConfigurationMismatchException */ public function createPaymentAvs($paymentCode) { diff --git a/app/code/Magento/Signifyd/Model/SignifydGateway/Debugger/DebuggerFactory.php b/app/code/Magento/Signifyd/Model/SignifydGateway/Debugger/DebuggerFactory.php index 02031e6f5b9b5..19408e99ae02e 100644 --- a/app/code/Magento/Signifyd/Model/SignifydGateway/Debugger/DebuggerFactory.php +++ b/app/code/Magento/Signifyd/Model/SignifydGateway/Debugger/DebuggerFactory.php @@ -30,7 +30,7 @@ class DebuggerFactory /** * DebuggerFactory constructor. * - * @param bjectManagerInterface $objectManager + * @param ObjectManagerInterface $objectManager * @param Config $config */ public function __construct( diff --git a/app/code/Magento/Signifyd/Model/SignifydGateway/Request/PurchaseBuilder.php b/app/code/Magento/Signifyd/Model/SignifydGateway/Request/PurchaseBuilder.php index 858ce0f0f3287..5e544e4b4048e 100644 --- a/app/code/Magento/Signifyd/Model/SignifydGateway/Request/PurchaseBuilder.php +++ b/app/code/Magento/Signifyd/Model/SignifydGateway/Request/PurchaseBuilder.php @@ -7,12 +7,13 @@ use Magento\Framework\App\Area; use Magento\Framework\Config\ScopeInterface; +use Magento\Framework\Exception\ConfigurationMismatchException; use Magento\Framework\Intl\DateTimeFactory; use Magento\Sales\Api\Data\OrderPaymentInterface; use Magento\Sales\Model\Order; +use Magento\Signifyd\Model\PaymentMethodMapper\PaymentMethodMapper; use Magento\Signifyd\Model\PaymentVerificationFactory; use Magento\Signifyd\Model\SignifydOrderSessionId; -use Magento\Signifyd\Model\PaymentMethodMapper\PaymentMethodMapper; /** * Prepare data related to purchase event represented in case creation request. @@ -72,6 +73,7 @@ public function __construct( * * @param Order $order * @return array + * @throws ConfigurationMismatchException */ public function build(Order $order) { @@ -202,6 +204,7 @@ private function getOrderChannel() * * @param OrderPaymentInterface $orderPayment * @return string + * @throws ConfigurationMismatchException */ private function getAvsCode(OrderPaymentInterface $orderPayment) { @@ -214,6 +217,7 @@ private function getAvsCode(OrderPaymentInterface $orderPayment) * * @param OrderPaymentInterface $orderPayment * @return string + * @throws ConfigurationMismatchException */ private function getCvvCode(OrderPaymentInterface $orderPayment) { diff --git a/app/code/Magento/Signifyd/etc/di.xml b/app/code/Magento/Signifyd/etc/di.xml index 92ad8a0bfd87a..fd78fff27f619 100644 --- a/app/code/Magento/Signifyd/etc/di.xml +++ b/app/code/Magento/Signifyd/etc/di.xml @@ -15,11 +15,7 @@ - - - U - - + diff --git a/dev/tests/functional/tests/app/Magento/Signifyd/Test/Repository/SignifydData.xml b/dev/tests/functional/tests/app/Magento/Signifyd/Test/Repository/SignifydData.xml index 5b3be4b9d570a..02037534b0149 100644 --- a/dev/tests/functional/tests/app/Magento/Signifyd/Test/Repository/SignifydData.xml +++ b/dev/tests/functional/tests/app/Magento/Signifyd/Test/Repository/SignifydData.xml @@ -27,7 +27,6 @@ autotest Bad Declined - Unavailable (U) GBP 10.00 diff --git a/dev/tests/integration/testsuite/Magento/Signifyd/Model/SignifydGateway/Request/CreateCaseBuilderTest.php b/dev/tests/integration/testsuite/Magento/Signifyd/Model/SignifydGateway/Request/CreateCaseBuilderTest.php index e53e0c26e286a..594c065185ac7 100644 --- a/dev/tests/integration/testsuite/Magento/Signifyd/Model/SignifydGateway/Request/CreateCaseBuilderTest.php +++ b/dev/tests/integration/testsuite/Magento/Signifyd/Model/SignifydGateway/Request/CreateCaseBuilderTest.php @@ -86,7 +86,7 @@ public function testCreateCaseBuilderWithFullSetOfData() 'paymentGateway' => 'paypal_account', 'transactionId' => $payment->getLastTransId(), 'currency' => $order->getOrderCurrencyCode(), - 'avsResponseCode' => 'U', + 'avsResponseCode' => '', 'cvvResponseCode' => '', 'orderChannel' => 'WEB', 'totalPrice' => $order->getGrandTotal(), From aa41f77d390917562f227ae80dc9038630e82594 Mon Sep 17 00:00:00 2001 From: Andrii Meysar Date: Wed, 30 May 2018 13:01:14 +0300 Subject: [PATCH 11/24] MAGETWO-92134: [2.2.x] Payment Failed Email is not generated --- .../Magento/Authorizenet/Model/Directpost.php | 6 +- .../Checkout/Test/Unit/Helper/DataTest.php | 60 +++++++++---------- app/code/Magento/Paypal/Model/Payflowpro.php | 1 - .../Model/Service/PaymentFailuresService.php | 13 ++-- .../Magento/Paypal/Controller/PayflowTest.php | 4 +- .../Paypal/Controller/PayflowadvancedTest.php | 8 +-- .../Service/PaymentFailuresServiceTest.php | 2 +- 7 files changed, 46 insertions(+), 48 deletions(-) diff --git a/app/code/Magento/Authorizenet/Model/Directpost.php b/app/code/Magento/Authorizenet/Model/Directpost.php index de567a8895f7e..5476fd05a0fed 100644 --- a/app/code/Magento/Authorizenet/Model/Directpost.php +++ b/app/code/Magento/Authorizenet/Model/Directpost.php @@ -143,8 +143,8 @@ class Directpost extends \Magento\Authorizenet\Model\Authorizenet implements Tra * @param \Magento\Framework\Module\ModuleListInterface $moduleList * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate * @param \Magento\Authorizenet\Helper\Data $dataHelper - * @param Directpost\Request\Factory $requestFactory - * @param Directpost\Response\Factory $responseFactory + * @param \Magento\Authorizenet\Model\Directpost\Request\Factory $requestFactory + * @param \Magento\Authorizenet\Model\Directpost\Response\Factory $responseFactory * @param \Magento\Authorizenet\Model\TransactionService $transactionService * @param \Magento\Framework\HTTP\ZendClientFactory $httpClientFactory * @param \Magento\Sales\Model\OrderFactory $orderFactory @@ -171,7 +171,7 @@ public function __construct( \Magento\Authorizenet\Helper\Data $dataHelper, \Magento\Authorizenet\Model\Directpost\Request\Factory $requestFactory, \Magento\Authorizenet\Model\Directpost\Response\Factory $responseFactory, - TransactionService $transactionService, + \Magento\Authorizenet\Model\TransactionService $transactionService, \Magento\Framework\HTTP\ZendClientFactory $httpClientFactory, \Magento\Sales\Model\OrderFactory $orderFactory, \Magento\Store\Model\StoreManagerInterface $storeManager, diff --git a/app/code/Magento/Checkout/Test/Unit/Helper/DataTest.php b/app/code/Magento/Checkout/Test/Unit/Helper/DataTest.php index f6f9ff78c1cb2..53132ffaa748b 100644 --- a/app/code/Magento/Checkout/Test/Unit/Helper/DataTest.php +++ b/app/code/Magento/Checkout/Test/Unit/Helper/DataTest.php @@ -23,32 +23,32 @@ class DataTest extends \PHPUnit\Framework\TestCase /** * @var Data */ - private $_helper; + private $helper; /** * @var \PHPUnit_Framework_MockObject_MockObject */ - private $_transportBuilder; + private $transportBuilder; /** * @var \PHPUnit_Framework_MockObject_MockObject */ - private $_translator; + private $translator; /** * @var \PHPUnit_Framework_MockObject_MockObject */ - private $_checkoutSession; + private $checkoutSession; /** * @var \PHPUnit_Framework_MockObject_MockObject */ - private $_scopeConfig; + private $scopeConfig; /** * @var \PHPUnit_Framework_MockObject_MockObject */ - private $_eventManager; + private $eventManager; /** * @inheritdoc @@ -60,10 +60,10 @@ protected function setUp() $arguments = $objectManagerHelper->getConstructArguments($className); /** @var \Magento\Framework\App\Helper\Context $context */ $context = $arguments['context']; - $this->_translator = $arguments['inlineTranslation']; - $this->_eventManager = $context->getEventManager(); - $this->_scopeConfig = $context->getScopeConfig(); - $this->_scopeConfig->expects($this->any()) + $this->translator = $arguments['inlineTranslation']; + $this->eventManager = $context->getEventManager(); + $this->scopeConfig = $context->getScopeConfig(); + $this->scopeConfig->expects($this->any()) ->method('getValue') ->willReturnMap( [ @@ -118,16 +118,16 @@ protected function setUp() ] ); - $this->_checkoutSession = $arguments['checkoutSession']; + $this->checkoutSession = $arguments['checkoutSession']; $arguments['localeDate']->expects($this->any()) ->method('formatDateTime') ->willReturn('Oct 02, 2013'); - $this->_transportBuilder = $arguments['transportBuilder']; + $this->transportBuilder = $arguments['transportBuilder']; $this->priceCurrency = $arguments['priceCurrency']; - $this->_helper = $objectManagerHelper->getObject($className, $arguments); + $this->helper = $objectManagerHelper->getObject($className, $arguments); } /** @@ -141,7 +141,7 @@ public function testSendPaymentFailedEmail() ->getMock(); $quoteMock->expects($this->any())->method('getId')->willReturn(1); - $this->assertSame($this->_helper, $this->_helper->sendPaymentFailedEmail($quoteMock, 'test message')); + $this->assertSame($this->helper, $this->helper->sendPaymentFailedEmail($quoteMock, 'test message')); } /** @@ -149,14 +149,14 @@ public function testSendPaymentFailedEmail() */ public function testGetCheckout() { - $this->assertEquals($this->_checkoutSession, $this->_helper->getCheckout()); + $this->assertEquals($this->checkoutSession, $this->helper->getCheckout()); } public function testGetQuote() { $quoteMock = $this->createMock(\Magento\Quote\Model\Quote::class); - $this->_checkoutSession->expects($this->once())->method('getQuote')->will($this->returnValue($quoteMock)); - $this->assertEquals($quoteMock, $this->_helper->getQuote()); + $this->checkoutSession->expects($this->once())->method('getQuote')->will($this->returnValue($quoteMock)); + $this->assertEquals($quoteMock, $this->helper->getQuote()); } public function testFormatPrice() @@ -164,26 +164,26 @@ public function testFormatPrice() $price = 5.5; $quoteMock = $this->createMock(\Magento\Quote\Model\Quote::class); $storeMock = $this->createPartialMock(\Magento\Store\Model\Store::class, ['formatPrice', '__wakeup']); - $this->_checkoutSession->expects($this->once())->method('getQuote')->will($this->returnValue($quoteMock)); + $this->checkoutSession->expects($this->once())->method('getQuote')->will($this->returnValue($quoteMock)); $quoteMock->expects($this->once())->method('getStore')->will($this->returnValue($storeMock)); $this->priceCurrency->expects($this->once())->method('format')->will($this->returnValue('5.5')); - $this->assertEquals('5.5', $this->_helper->formatPrice($price)); + $this->assertEquals('5.5', $this->helper->formatPrice($price)); } public function testConvertPrice() { $price = 5.5; $this->priceCurrency->expects($this->once())->method('convertAndFormat')->willReturn($price); - $this->assertEquals(5.5, $this->_helper->convertPrice($price)); + $this->assertEquals(5.5, $this->helper->convertPrice($price)); } public function testCanOnepageCheckout() { - $this->_scopeConfig->expects($this->once())->method('getValue')->with( + $this->scopeConfig->expects($this->once())->method('getValue')->with( 'checkout/options/onepage_checkout_enabled', 'store' )->will($this->returnValue(true)); - $this->assertTrue($this->_helper->canOnepageCheckout()); + $this->assertTrue($this->helper->canOnepageCheckout()); } public function testIsContextCheckout() @@ -204,18 +204,18 @@ public function testIsContextCheckout() public function testIsCustomerMustBeLogged() { - $this->_scopeConfig->expects($this->once())->method('isSetFlag')->with( + $this->scopeConfig->expects($this->once())->method('isSetFlag')->with( 'checkout/options/customer_must_be_logged', \Magento\Store\Model\ScopeInterface::SCOPE_STORE )->will($this->returnValue(true)); - $this->assertTrue($this->_helper->isCustomerMustBeLogged()); + $this->assertTrue($this->helper->isCustomerMustBeLogged()); } public function testGetPriceInclTax() { $itemMock = $this->createPartialMock(\Magento\Framework\DataObject::class, ['getPriceInclTax']); $itemMock->expects($this->exactly(2))->method('getPriceInclTax')->will($this->returnValue(5.5)); - $this->assertEquals(5.5, $this->_helper->getPriceInclTax($itemMock)); + $this->assertEquals(5.5, $this->helper->getPriceInclTax($itemMock)); } public function testGetPriceInclTaxWithoutTax() @@ -256,7 +256,7 @@ public function testGetSubtotalInclTax() $expected = 5.5; $itemMock = $this->createPartialMock(\Magento\Framework\DataObject::class, ['getRowTotalInclTax']); $itemMock->expects($this->exactly(2))->method('getRowTotalInclTax')->will($this->returnValue($rowTotalInclTax)); - $this->assertEquals($expected, $this->_helper->getSubtotalInclTax($itemMock)); + $this->assertEquals($expected, $this->helper->getSubtotalInclTax($itemMock)); } public function testGetSubtotalInclTaxNegative() @@ -274,7 +274,7 @@ public function testGetSubtotalInclTaxNegative() $itemMock->expects($this->once()) ->method('getDiscountTaxCompensation')->will($this->returnValue($discountTaxCompensation)); $itemMock->expects($this->once())->method('getRowTotal')->will($this->returnValue($rowTotal)); - $this->assertEquals($expected, $this->_helper->getSubtotalInclTax($itemMock)); + $this->assertEquals($expected, $this->helper->getSubtotalInclTax($itemMock)); } public function testGetBasePriceInclTaxWithoutQty() @@ -321,7 +321,7 @@ public function testGetBaseSubtotalInclTax() $itemMock->expects($this->once())->method('getBaseTaxAmount'); $itemMock->expects($this->once())->method('getBaseDiscountTaxCompensation'); $itemMock->expects($this->once())->method('getBaseRowTotal'); - $this->_helper->getBaseSubtotalInclTax($itemMock); + $this->helper->getBaseSubtotalInclTax($itemMock); } public function testIsAllowedGuestCheckoutWithoutStore() @@ -329,9 +329,9 @@ public function testIsAllowedGuestCheckoutWithoutStore() $quoteMock = $this->createMock(\Magento\Quote\Model\Quote::class); $store = null; $quoteMock->expects($this->once())->method('getStoreId')->will($this->returnValue(1)); - $this->_scopeConfig->expects($this->once()) + $this->scopeConfig->expects($this->once()) ->method('isSetFlag') ->will($this->returnValue(true)); - $this->assertTrue($this->_helper->isAllowedGuestCheckout($quoteMock, $store)); + $this->assertTrue($this->helper->isAllowedGuestCheckout($quoteMock, $store)); } } diff --git a/app/code/Magento/Paypal/Model/Payflowpro.php b/app/code/Magento/Paypal/Model/Payflowpro.php index 16ba4efc90d6a..b5fdaf4ae9fd4 100644 --- a/app/code/Magento/Paypal/Model/Payflowpro.php +++ b/app/code/Magento/Paypal/Model/Payflowpro.php @@ -647,7 +647,6 @@ public function buildBasicRequest() * * @param DataObject $response * @return void - * @throws \Magento\Framework\Exception\LocalizedException * @throws \Magento\Payment\Gateway\Command\CommandException * @throws \Magento\Framework\Exception\State\InvalidTransitionException */ diff --git a/app/code/Magento/Sales/Model/Service/PaymentFailuresService.php b/app/code/Magento/Sales/Model/Service/PaymentFailuresService.php index 2e8da0dadd5b1..6da1dc1b7e7cc 100644 --- a/app/code/Magento/Sales/Model/Service/PaymentFailuresService.php +++ b/app/code/Magento/Sales/Model/Service/PaymentFailuresService.php @@ -189,10 +189,12 @@ private function getConfigValue(string $configPath, Quote $quote) * @param Quote $quote * @return string */ - private function getShippingMethod(Quote $quote) + private function getShippingMethod(Quote $quote): string { $shippingMethod = ''; - if ($shippingInfo = $quote->getShippingAddress()->getShippingMethod()) { + $shippingInfo = $quote->getShippingAddress()->getShippingMethod(); + + if ($shippingInfo) { $data = explode('_', $shippingInfo); $shippingMethod = $data[0]; } @@ -206,12 +208,9 @@ private function getShippingMethod(Quote $quote) * @param Quote $quote * @return string */ - private function getPaymentMethod(Quote $quote) + private function getPaymentMethod(Quote $quote): string { - $paymentMethod = ''; - if ($paymentInfo = $quote->getPayment()) { - $paymentMethod = $paymentInfo->getMethod(); - } + $paymentMethod = $quote->getPayment()->getMethod() ?? ''; return $paymentMethod; } diff --git a/dev/tests/integration/testsuite/Magento/Paypal/Controller/PayflowTest.php b/dev/tests/integration/testsuite/Magento/Paypal/Controller/PayflowTest.php index 367f68ce25872..670cc6850b233 100644 --- a/dev/tests/integration/testsuite/Magento/Paypal/Controller/PayflowTest.php +++ b/dev/tests/integration/testsuite/Magento/Paypal/Controller/PayflowTest.php @@ -63,7 +63,7 @@ protected function setUp() $this->order->getPayment()->setMethod(Config::METHOD_PAYFLOWLINK); /** @var $quote \Magento\Quote\Model\Quote */ - $quote = $this->_objectManager->create(Quote::class)->setStoreid($this->order->getStoreid()); + $quote = $this->_objectManager->create(Quote::class)->setStoreid($this->order->getStoreId()); $this->quoteRepository = $this->_objectManager->get(CartRepositoryInterface::class); $this->quoteRepository->save($quote); @@ -111,7 +111,7 @@ public function testFormActionIsContentGenerated() * @magentoConfigFixture current_store paypal/general/business_account merchant_2012050718_biz@example.com * @return void */ - public function testCancelAction() + public function testCancelAction(): void { $orderId = $this->order->getEntityId(); /** @var \Magento\Sales\Model\Order $order */ diff --git a/dev/tests/integration/testsuite/Magento/Paypal/Controller/PayflowadvancedTest.php b/dev/tests/integration/testsuite/Magento/Paypal/Controller/PayflowadvancedTest.php index 7996acc7ed3b0..bf404f74b769e 100644 --- a/dev/tests/integration/testsuite/Magento/Paypal/Controller/PayflowadvancedTest.php +++ b/dev/tests/integration/testsuite/Magento/Paypal/Controller/PayflowadvancedTest.php @@ -60,11 +60,11 @@ protected function setUp() /** @var OrderInterface $order */ $this->order = array_pop($orders); - $this->order->getPayment()->setMethod(Config::METHOD_PAYFLOWLINK); + $this->order->getPayment()->setMethod(Config::METHOD_PAYFLOWADVANCED); /** @var $quote \Magento\Quote\Model\Quote */ $quote = $this->_objectManager->create(Quote::class) - ->setStoreid($this->order->getStoreid()); + ->setStoreid($this->order->getStoreId()); $this->quoteRepository = $this->_objectManager->get(CartRepositoryInterface::class); $this->quoteRepository->save($quote); @@ -104,7 +104,7 @@ public function testFormActionIsContentGenerated() * @magentoConfigFixture current_store paypal/general/business_account merchant_2012050718_biz@example.com * @return void */ - public function testCancelAction() + public function testCancelAction(): void { $orderId = $this->order->getEntityId(); /** @var \Magento\Sales\Model\Order $order */ @@ -115,7 +115,7 @@ public function testCancelAction() $session = $this->_objectManager->get(Session::class); $session->setQuoteId($quote->getId()); $session->setPaypalStandardQuoteId($quote->getId())->setLastRealOrderId('100000001'); - $this->dispatch('paypal/payflow/cancelpayment'); + $this->dispatch('paypal/payflowadvanced/cancelpayment'); $order = $this->_objectManager->create(OrderRepositoryInterface::class)->get($orderId); $this->assertEquals('canceled', $order->getState()); diff --git a/dev/tests/integration/testsuite/Magento/Sales/Model/Service/PaymentFailuresServiceTest.php b/dev/tests/integration/testsuite/Magento/Sales/Model/Service/PaymentFailuresServiceTest.php index 1e334c9660d19..6367ff6d1a6d3 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/Model/Service/PaymentFailuresServiceTest.php +++ b/dev/tests/integration/testsuite/Magento/Sales/Model/Service/PaymentFailuresServiceTest.php @@ -59,7 +59,7 @@ protected function setUp() * @magentoAppIsolation enabled * @return void */ - public function testHandlerWithCustomer() + public function testHandlerWithCustomer(): void { $errorMessage = __('Transaction declined.'); $checkoutType = 'custom_checkout'; From 556bad21bfa142260f3dd9f7d0a2636c77067d06 Mon Sep 17 00:00:00 2001 From: OlgaVasyltsun Date: Wed, 30 May 2018 14:17:32 +0300 Subject: [PATCH 12/24] MAGETWO-89407: [2.3] PayPal Payments Pro settings gets from wrong store --- .../Service/Request/SecureTokenTest.php | 24 +++++++++++++------ .../_files/quote_payflowpro_rollback.php | 3 +++ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/dev/tests/integration/testsuite/Magento/Paypal/Model/Payflow/Service/Request/SecureTokenTest.php b/dev/tests/integration/testsuite/Magento/Paypal/Model/Payflow/Service/Request/SecureTokenTest.php index df6a1aae3fdb8..2318edbb80b25 100644 --- a/dev/tests/integration/testsuite/Magento/Paypal/Model/Payflow/Service/Request/SecureTokenTest.php +++ b/dev/tests/integration/testsuite/Magento/Paypal/Model/Payflow/Service/Request/SecureTokenTest.php @@ -70,7 +70,7 @@ protected function setUp() $this->service = $this->objectManager->create( SecureToken::class, [ - 'mathRandom' => $this->mathRandom + 'mathRandom' => $this->mathRandom, ] ); } @@ -89,8 +89,9 @@ protected function tearDown() * @magentoDataFixture Magento/Paypal/_files/quote_payflowpro.php * @magentoDataFixture Magento/Paypal/Fixtures/default_payment_configuration.php * @magentoAppArea adminhtml + * @return void */ - public function testRequestToken() + public function testRequestToken(): void { $quote = $this->getQuote('100000015'); $quote->setStoreId(null); @@ -103,8 +104,9 @@ public function testRequestToken() * @magentoDataFixture Magento/Paypal/_files/quote_payflowpro.php * @magentoDataFixture Magento/Paypal/Fixtures/store_payment_configuration.php * @magentoAppArea adminhtml + * @return void */ - public function testRequestTokenWithStoreConfiguration() + public function testRequestTokenWithStoreConfiguration(): void { $quote = $this->getQuote('100000015'); $store = $this->getStore('test'); @@ -118,8 +120,9 @@ public function testRequestTokenWithStoreConfiguration() * @magentoDataFixture Magento/Paypal/_files/quote_payflowpro.php * @magentoDataFixture Magento/Paypal/Fixtures/website_payment_configuration.php * @magentoAppArea adminhtml + * @return void */ - public function testRequestTokenWithWebsiteConfiguration() + public function testRequestTokenWithWebsiteConfiguration(): void { $quote = $this->getQuote('100000015'); $store = $this->getStore('fixture_second_store'); @@ -135,9 +138,15 @@ public function testRequestTokenWithWebsiteConfiguration() * @param string $expVendor * @param string $expUser * @param string $expPwd + * @return void */ - private function execute(Quote $quote, string $expPartner, string $expVendor, string $expUser, string $expPwd) - { + private function execute( + Quote $quote, + string $expPartner, + string $expVendor, + string $expUser, + string $expPwd + ): void { $secureTokenId = '31f2a7c8d257c70b1c9eb9051b90e0'; $token = '80IgSbabyj0CtBDWHZZeQN3'; @@ -174,8 +183,9 @@ private function execute(Quote $quote, string $expPartner, string $expVendor, st * @param string $expected * @param string $actual * @param string $property + * @return void */ - private function performAssertion(string $expected, string $actual, string $property) + private function performAssertion(string $expected, string $actual, string $property): void { self::assertEquals($expected, $actual, "$property should match."); } diff --git a/dev/tests/integration/testsuite/Magento/Paypal/_files/quote_payflowpro_rollback.php b/dev/tests/integration/testsuite/Magento/Paypal/_files/quote_payflowpro_rollback.php index 6eded10e5c555..32a76834dbf27 100644 --- a/dev/tests/integration/testsuite/Magento/Paypal/_files/quote_payflowpro_rollback.php +++ b/dev/tests/integration/testsuite/Magento/Paypal/_files/quote_payflowpro_rollback.php @@ -45,3 +45,6 @@ $quote = array_pop($items); $quoteRepository->delete($quote); } + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); From 6a0e8cbf0ba1f08a94140b7346be00ca95243890 Mon Sep 17 00:00:00 2001 From: Myroslav Dobra Date: Thu, 31 May 2018 09:59:03 +0300 Subject: [PATCH 13/24] MAGETWO-66217: Product Url key value comes from product name on storeview level (use default = yes) --- .../Initialization/Helper/AttributeFilter.php | 12 ++++++------ .../Initialization/Helper/AttributeFilterTest.php | 10 +++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilter.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilter.php index 09af738a13201..188b0b22f33bf 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilter.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilter.php @@ -51,7 +51,7 @@ public function prepareProductAttributes(Product $product, array $productData, a * @param array $productData * @return array */ - private function prepareConfigData(Product $product, $attributeCode, array $productData): array + private function prepareConfigData(Product $product, string $attributeCode, array $productData): array { // UI component sends value even if field is disabled, so 'Use Config Settings' must be reset to false if ($product->hasData('use_config_' . $attributeCode)) { @@ -67,7 +67,7 @@ private function prepareConfigData(Product $product, $attributeCode, array $prod * @param array $productData * @return array */ - private function prepareDefaultData(array $attributeList, $attributeCode, array $productData): array + private function prepareDefaultData(array $attributeList, string $attributeCode, array $productData): array { if (isset($attributeList[$attributeCode])) { /** @var \Magento\Catalog\Model\ResourceModel\Eav\Attribute $attribute */ @@ -87,12 +87,12 @@ private function prepareDefaultData(array $attributeList, $attributeCode, array /** * @param Product $product - * @param $useDefaults - * @param $attribute - * @param $value + * @param array $useDefaults + * @param string $attribute + * @param mixed $value * @return bool */ - private function isAttributeShouldNotBeUpdated(Product $product, $useDefaults, $attribute, $value): bool + private function isAttributeShouldNotBeUpdated(Product $product, array $useDefaults, $attribute, $value): bool { $considerUseDefaultsAttribute = !isset($useDefaults[$attribute]) || $useDefaults[$attribute] === '1'; diff --git a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilterTest.php b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilterTest.php index 6e25d02df53ee..424427b871456 100644 --- a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilterTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilterTest.php @@ -9,7 +9,7 @@ use Magento\Catalog\Controller\Adminhtml\Product\Initialization\Helper\AttributeFilter; use Magento\Catalog\Model\Product; use Magento\Catalog\Model\ResourceModel\Eav\Attribute; -use PHPUnit_Framework_MockObject_MockObject; +use PHPUnit_Framework_MockObject_MockObject as MockObject; class AttributeFilterTest extends \PHPUnit\Framework\TestCase { @@ -19,12 +19,12 @@ class AttributeFilterTest extends \PHPUnit\Framework\TestCase protected $model; /** - * @var PHPUnit_Framework_MockObject_MockObject + * @var MockObject */ protected $objectManagerMock; /** - * @var Product|PHPUnit_Framework_MockObject_MockObject + * @var Product|MockObject */ protected $productMock; @@ -47,7 +47,7 @@ public function testPrepareProductAttributes( $expectedProductData, $initialProductData ) { - /** @var PHPUnit_Framework_MockObject_MockObject | Product $productMockMap */ + /** @var MockObject | Product $productMockMap */ $productMockMap = $this->getMockBuilder(Product::class) ->disableOriginalConstructor() ->setMethods(['getData', 'getAttributes']) @@ -225,7 +225,7 @@ private function getProductAttributesMock(array $useDefaults): array $returnArray = []; foreach ($useDefaults as $attributecode => $isDefault) { if ($isDefault === '1') { - /** @var Attribute | PHPUnit_Framework_MockObject_MockObject $attribute */ + /** @var Attribute | MockObject $attribute */ $attribute = $this->getMockBuilder(Attribute::class) ->disableOriginalConstructor() ->getMock(); From 483c8583ae2bd97c8cbe0117b9b092427b4604e0 Mon Sep 17 00:00:00 2001 From: Viktor Sevch Date: Thu, 31 May 2018 10:17:23 +0300 Subject: [PATCH 14/24] MAGETWO-91104: [2.3.0] Using a multiselect product attribute with a custom source model in the adminhtml doesn't render selected value --- .../Product/Form/Modifier/EavTest.php | 321 +++++++----------- .../Product/Form/Modifier/Eav.php | 20 +- 2 files changed, 151 insertions(+), 190 deletions(-) diff --git a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/EavTest.php b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/EavTest.php index a29379647b9e1..320ec821d7ffb 100755 --- a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/EavTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/EavTest.php @@ -5,11 +5,10 @@ */ namespace Magento\Catalog\Test\Unit\Ui\DataProvider\Product\Form\Modifier; -use Magento\Catalog\Model\Product\Type; use Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\Eav; use Magento\Eav\Model\Config; +use Magento\Eav\Model\Entity\Attribute\Source\SourceInterface; use Magento\Framework\App\RequestInterface; -use Magento\Framework\EntityManager\EventManager; use Magento\Framework\Phrase; use Magento\Store\Model\StoreManagerInterface; use Magento\Store\Api\Data\StoreInterface; @@ -257,7 +256,15 @@ protected function setUp() $this->searchResultsMock = $this->getMockBuilder(SearchResultsInterface::class) ->getMockForAbstractClass(); $this->eavAttributeMock = $this->getMockBuilder(Attribute::class) - ->setMethods(['load', 'getAttributeGroupCode', 'getApplyTo', 'getFrontendInput', 'getAttributeCode']) + ->setMethods([ + 'load', + 'getAttributeGroupCode', + 'getApplyTo', + 'getFrontendInput', + 'getAttributeCode', + 'usesSource', + 'getSource', + ]) ->disableOriginalConstructor() ->getMock(); $this->productAttributeMock = $this->getMockBuilder(ProductAttributeInterface::class) @@ -451,64 +458,60 @@ public function testModifyData() } /** - * @param int $productId + * @param int|null $productId * @param bool $productRequired - * @param string $attrValue - * @param string $note + * @param string|null $attrValue * @param array $expected * @covers \Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\Eav::isProductExists * @covers \Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\Eav::setupAttributeMeta * @dataProvider setupAttributeMetaDataProvider */ - public function testSetupAttributeMetaDefaultAttribute($productId, $productRequired, $attrValue, $note, $expected) - { - $configPath = 'arguments/data/config'; + public function testSetupAttributeMetaDefaultAttribute( + $productId, + $productRequired, + $attrValue, + $expected + ) { + $configPath = 'arguments/data/config'; $groupCode = 'product-details'; $sortOrder = '0'; + $attributeOptions = [ + ['value' => 1, 'label' => 'Int label'], + ['value' => 1.5, 'label' => 'Float label'], + ['value' => true, 'label' => 'Boolean label'], + ['value' => 'string', 'label' => 'String label'], + ['value' => ['test1', 'test2'], 'label' => 'Array label'] + ]; + $attributeOptionsExpected = [ + ['value' => '1', 'label' => 'Int label'], + ['value' => '1.5', 'label' => 'Float label'], + ['value' => '1', 'label' => 'Boolean label'], + ['value' => 'string', 'label' => 'String label'], + ['value' => ['test1', 'test2'], 'label' => 'Array label'] + ]; - $this->productMock->expects($this->any()) - ->method('getId') - ->willReturn($productId); - - $this->productAttributeMock->expects($this->any()) - ->method('getIsRequired') - ->willReturn($productRequired); - - $this->productAttributeMock->expects($this->any()) - ->method('getDefaultValue') - ->willReturn('required_value'); - - $this->productAttributeMock->expects($this->any()) - ->method('getAttributeCode') - ->willReturn('code'); - - $this->productAttributeMock->expects($this->any()) - ->method('getValue') - ->willReturn('value'); - - $this->productAttributeMock->expects($this->any()) - ->method('getNote') - ->willReturn($note); - - $this->productAttributeMock->expects($this->any()) - ->method('getDefaultFrontendLabel') - ->willReturn(new Phrase('mylabel')); + $this->productMock->method('getId')->willReturn($productId); + $this->productAttributeMock->method('getIsRequired')->willReturn($productRequired); + $this->productAttributeMock->method('getDefaultValue')->willReturn('required_value'); + $this->productAttributeMock->method('getAttributeCode')->willReturn('code'); + $this->productAttributeMock->method('getValue')->willReturn('value'); $attributeMock = $this->getMockBuilder(AttributeInterface::class) ->setMethods(['getValue']) ->disableOriginalConstructor() ->getMockForAbstractClass(); - $attributeMock->expects($this->any()) - ->method('getValue') - ->willReturn($attrValue); + $attributeMock->method('getValue')->willReturn($attrValue); - $this->productMock->expects($this->any()) - ->method('getCustomAttribute') - ->willReturn($attributeMock); + $this->productMock->method('getCustomAttribute')->willReturn($attributeMock); + $this->eavAttributeMock->method('usesSource')->willReturn(true); + + $attributeSource = $this->getMockBuilder(SourceInterface::class)->getMockForAbstractClass(); + $attributeSource->method('getAllOptions')->willReturn($attributeOptions); - $this->arrayManagerMock->expects($this->any()) - ->method('set') + $this->eavAttributeMock->method('getSource')->willReturn($attributeSource); + + $this->arrayManagerMock->method('set') ->with( $configPath, [], @@ -516,16 +519,21 @@ public function testSetupAttributeMetaDefaultAttribute($productId, $productRequi ) ->willReturn($expected); - $this->arrayManagerMock->expects($this->any()) + $this->arrayManagerMock->expects($this->once()) ->method('merge') + ->with( + $this->anything(), + $this->anything(), + $this->callback( + function ($value) use ($attributeOptionsExpected) { + return $value['options'] === $attributeOptionsExpected; + } + ) + ) ->willReturn($expected); - $this->arrayManagerMock->expects($this->any()) - ->method('get') - ->willReturn([]); - - $this->arrayManagerMock->expects($this->any()) - ->method('exists'); + $this->arrayManagerMock->method('get')->willReturn([]); + $this->arrayManagerMock->method('exists')->willReturn(true); $this->assertEquals( $expected, @@ -539,147 +547,82 @@ public function testSetupAttributeMetaDefaultAttribute($productId, $productRequi public function setupAttributeMetaDataProvider() { return [ - 'default_null_prod_not_new_and_required' => $this->defaultNullProdNotNewAndRequired(), - 'default_null_prod_not_new_and_not_required' => $this->defaultNullProdNotNewAndNotRequired(), - 'default_null_prod_new_and_not_required' => $this->defaultNullProdNewAndNotRequired(), - 'default_null_prod_new_and_required' => $this->defaultNullProdNewAndRequired(), - 'default_null_prod_new_and_required_and_filled_notice' => - $this->defaultNullProdNewAndRequiredAndFilledNotice() - ]; - } - - /** - * @return array - */ - private function defaultNullProdNotNewAndRequired() - { - return [ - 'productId' => 1, - 'productRequired' => true, - 'attrValue' => 'val', - 'note' => null, - 'expected' => [ - 'dataType' => null, - 'formElement' => null, - 'visible' => null, - 'required' => true, - 'notice' => null, - 'default' => null, - 'label' => new Phrase('mylabel'), - 'code' => 'code', - 'source' => 'product-details', - 'scopeLabel' => '', - 'globalScope' => false, - 'sortOrder' => 0 - ], - ]; - } - - /** - * @return array - */ - private function defaultNullProdNotNewAndNotRequired() - { - return [ - 'productId' => 1, - 'productRequired' => false, - 'attrValue' => 'val', - 'note' => null, - 'expected' => [ - 'dataType' => null, - 'formElement' => null, - 'visible' => null, - 'required' => false, - 'notice' => null, - 'default' => null, - 'label' => new Phrase('mylabel'), - 'code' => 'code', - 'source' => 'product-details', - 'scopeLabel' => '', - 'globalScope' => false, - 'sortOrder' => 0 + 'default_null_prod_not_new_and_required' => [ + 'productId' => 1, + 'productRequired' => true, + 'attrValue' => 'val', + 'expected' => [ + 'dataType' => null, + 'formElement' => null, + 'visible' => null, + 'required' => true, + 'notice' => null, + 'default' => null, + 'label' => new Phrase(null), + 'code' => 'code', + 'source' => 'product-details', + 'scopeLabel' => '', + 'globalScope' => false, + 'sortOrder' => 0 + ], ], - ]; - } - - /** - * @return array - */ - private function defaultNullProdNewAndNotRequired() - { - return [ - 'productId' => null, - 'productRequired' => false, - 'attrValue' => null, - 'note' => null, - 'expected' => [ - 'dataType' => null, - 'formElement' => null, - 'visible' => null, - 'required' => false, - 'notice' => null, - 'default' => 'required_value', - 'label' => new Phrase('mylabel'), - 'code' => 'code', - 'source' => 'product-details', - 'scopeLabel' => '', - 'globalScope' => false, - 'sortOrder' => 0 + 'default_null_prod_not_new_and_not_required' => [ + 'productId' => 1, + 'productRequired' => false, + 'attrValue' => 'val', + 'expected' => [ + 'dataType' => null, + 'formElement' => null, + 'visible' => null, + 'required' => false, + 'notice' => null, + 'default' => null, + 'label' => new Phrase(null), + 'code' => 'code', + 'source' => 'product-details', + 'scopeLabel' => '', + 'globalScope' => false, + 'sortOrder' => 0 + ], ], - ]; - } - - /** - * @return array - */ - private function defaultNullProdNewAndRequired() - { - return [ - 'productId' => null, - 'productRequired' => false, - 'attrValue' => null, - 'note' => null, - 'expected' => [ - 'dataType' => null, - 'formElement' => null, - 'visible' => null, - 'required' => false, - 'notice' => null, - 'default' => 'required_value', - 'label' => new Phrase('mylabel'), - 'code' => 'code', - 'source' => 'product-details', - 'scopeLabel' => '', - 'globalScope' => false, - 'sortOrder' => 0 - ], - ]; - } - - /** - * @return array - */ - private function defaultNullProdNewAndRequiredAndFilledNotice() - { - return [ - 'productId' => null, - 'productRequired' => false, - 'attrValue' => null, - 'note' => 'example notice', - 'expected' => [ - 'dataType' => null, - 'formElement' => null, - 'visible' => null, - 'required' => false, - 'notice' => __('example notice'), - 'default' => 'required_value', - 'label' => new Phrase('mylabel'), - 'code' => 'code', - 'source' => 'product-details', - 'scopeLabel' => '', - 'globalScope' => false, - 'sortOrder' => 0 + 'default_null_prod_new_and_not_required' => [ + 'productId' => null, + 'productRequired' => false, + 'attrValue' => null, + 'expected' => [ + 'dataType' => null, + 'formElement' => null, + 'visible' => null, + 'required' => false, + 'notice' => null, + 'default' => 'required_value', + 'label' => new Phrase(null), + 'code' => 'code', + 'source' => 'product-details', + 'scopeLabel' => '', + 'globalScope' => false, + 'sortOrder' => 0 + ], ], + 'default_null_prod_new_and_required' => [ + 'productId' => null, + 'productRequired' => false, + 'attrValue' => null, + 'expected' => [ + 'dataType' => null, + 'formElement' => null, + 'visible' => null, + 'required' => false, + 'notice' => null, + 'default' => 'required_value', + 'label' => new Phrase(null), + 'code' => 'code', + 'source' => 'product-details', + 'scopeLabel' => '', + 'globalScope' => false, + 'sortOrder' => 0 + ], + ] ]; } } 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 b216ee8c9c547..0e6f17d761bc3 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 @@ -611,8 +611,9 @@ public function setupAttributeMeta(ProductAttributeInterface $attribute, $groupC // TODO: Refactor to $attribute->getOptions() when MAGETWO-48289 is done $attributeModel = $this->getAttributeModel($attribute); if ($attributeModel->usesSource()) { + $options = $attributeModel->getSource()->getAllOptions(); $meta = $this->arrayManager->merge($configPath, $meta, [ - 'options' => $attributeModel->getSource()->getAllOptions(), + 'options' => $this->convertOptionsValueToString($options), ]); } @@ -683,6 +684,23 @@ private function getAttributeDefaultValue(ProductAttributeInterface $attribute) return $attribute->getDefaultValue(); } + /** + * Convert options value to string. + * + * @param array $options + * @return array + */ + private function convertOptionsValueToString(array $options) : array + { + array_walk($options, function (&$value) { + if (isset($value['value']) && is_scalar($value['value'])) { + $value['value'] = (string)$value['value']; + } + }); + + return $options; + } + /** * @param ProductAttributeInterface $attribute * @param array $meta From f8355aefb9b3253a13842ed968e91cdf91b9df1b Mon Sep 17 00:00:00 2001 From: OlgaVasyltsun Date: Thu, 31 May 2018 15:28:13 +0300 Subject: [PATCH 15/24] MAGETWO-64173: Browser back button issue (Checkout pages) --- .../view/frontend/web/js/model/step-navigator.js | 11 ++++++++++- .../view/frontend/web/js/view/progress-bar.js | 5 +++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Checkout/view/frontend/web/js/model/step-navigator.js b/app/code/Magento/Checkout/view/frontend/web/js/model/step-navigator.js index c707792111c82..0f2b0f4e26869 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/model/step-navigator.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/model/step-navigator.js @@ -182,6 +182,15 @@ define([ }); }, + /** + * Sets window location hash. + * + * @param {String} hash + */ + setHash: function (hash) { + window.location.hash = hash; + }, + /** * Next step. */ @@ -199,7 +208,7 @@ define([ if (steps().length > activeIndex + 1) { code = steps()[activeIndex + 1].code; steps()[activeIndex + 1].isVisible(true); - window.location = window.checkoutConfig.checkoutUrl + '#' + code; + this.setHash(code); document.body.scrollTop = document.documentElement.scrollTop = 0; } } diff --git a/app/code/Magento/Checkout/view/frontend/web/js/view/progress-bar.js b/app/code/Magento/Checkout/view/frontend/web/js/view/progress-bar.js index 72cf4e3d479c3..683a18d0e4ead 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/view/progress-bar.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/view/progress-bar.js @@ -25,6 +25,11 @@ define([ initialize: function () { this._super(); window.addEventListener('hashchange', _.bind(stepNavigator.handleHash, stepNavigator)); + + if (!window.location.hash) { + stepNavigator.setHash(stepNavigator.steps().sort(stepNavigator.sortItems)[0].code); + } + stepNavigator.handleHash(); }, From 5542c81d42610c7ac5d15f1ff7aecf884f1f92fe Mon Sep 17 00:00:00 2001 From: Andrii Meysar Date: Thu, 31 May 2018 18:09:16 +0300 Subject: [PATCH 16/24] MAGETWO-92134: [2.2.x] Payment Failed Email is not generated --- app/code/Magento/Sales/Model/Service/PaymentFailuresService.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Sales/Model/Service/PaymentFailuresService.php b/app/code/Magento/Sales/Model/Service/PaymentFailuresService.php index 6da1dc1b7e7cc..3a49bbce256ef 100644 --- a/app/code/Magento/Sales/Model/Service/PaymentFailuresService.php +++ b/app/code/Magento/Sales/Model/Service/PaymentFailuresService.php @@ -283,7 +283,7 @@ private function getLocaleDate(): string */ private function getCustomerName(Quote $quote): string { - $customer = __('Guest'); + $customer = __('Guest')->render(); if (!$quote->getCustomerIsGuest()) { $customer = $quote->getCustomer()->getFirstname() . ' ' . $quote->getCustomer()->getLastname(); From 3ac0addc2d6e688d767686132ce1cf78639a2613 Mon Sep 17 00:00:00 2001 From: Viktor Sevch Date: Thu, 31 May 2018 18:17:27 +0300 Subject: [PATCH 17/24] MAGETWO-91104: [2.3.0] Using a multiselect product attribute with a custom source model in the adminhtml doesn't render selected value --- .../Product/Form/Modifier/EavTest.php | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/EavTest.php b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/EavTest.php index 320ec821d7ffb..0426e389d9aeb 100755 --- a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/EavTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/EavTest.php @@ -462,16 +462,17 @@ public function testModifyData() * @param bool $productRequired * @param string|null $attrValue * @param array $expected + * @return void * @covers \Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\Eav::isProductExists * @covers \Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\Eav::setupAttributeMeta * @dataProvider setupAttributeMetaDataProvider */ public function testSetupAttributeMetaDefaultAttribute( $productId, - $productRequired, + bool $productRequired, $attrValue, - $expected - ) { + array $expected + ) : void { $configPath = 'arguments/data/config'; $groupCode = 'product-details'; $sortOrder = '0'; @@ -480,14 +481,14 @@ public function testSetupAttributeMetaDefaultAttribute( ['value' => 1.5, 'label' => 'Float label'], ['value' => true, 'label' => 'Boolean label'], ['value' => 'string', 'label' => 'String label'], - ['value' => ['test1', 'test2'], 'label' => 'Array label'] + ['value' => ['test1', 'test2'], 'label' => 'Array label'], ]; $attributeOptionsExpected = [ ['value' => '1', 'label' => 'Int label'], ['value' => '1.5', 'label' => 'Float label'], ['value' => '1', 'label' => 'Boolean label'], ['value' => 'string', 'label' => 'String label'], - ['value' => ['test1', 'test2'], 'label' => 'Array label'] + ['value' => ['test1', 'test2'], 'label' => 'Array label'], ]; $this->productMock->method('getId')->willReturn($productId); @@ -563,7 +564,7 @@ public function setupAttributeMetaDataProvider() 'source' => 'product-details', 'scopeLabel' => '', 'globalScope' => false, - 'sortOrder' => 0 + 'sortOrder' => 0, ], ], 'default_null_prod_not_new_and_not_required' => [ @@ -582,7 +583,7 @@ public function setupAttributeMetaDataProvider() 'source' => 'product-details', 'scopeLabel' => '', 'globalScope' => false, - 'sortOrder' => 0 + 'sortOrder' => 0, ], ], 'default_null_prod_new_and_not_required' => [ @@ -601,7 +602,7 @@ public function setupAttributeMetaDataProvider() 'source' => 'product-details', 'scopeLabel' => '', 'globalScope' => false, - 'sortOrder' => 0 + 'sortOrder' => 0, ], ], 'default_null_prod_new_and_required' => [ @@ -620,7 +621,7 @@ public function setupAttributeMetaDataProvider() 'source' => 'product-details', 'scopeLabel' => '', 'globalScope' => false, - 'sortOrder' => 0 + 'sortOrder' => 0, ], ] ]; From 075a64c2c3aad5da6cdcc2fb18061cff6424a67c Mon Sep 17 00:00:00 2001 From: Andrii Meysar Date: Fri, 1 Jun 2018 13:10:25 +0300 Subject: [PATCH 18/24] MAGETWO-90349: Template file 'header.html' is not found. --- .../Test/Unit/Model/AbstractTemplateTest.php | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/Email/Test/Unit/Model/AbstractTemplateTest.php b/app/code/Magento/Email/Test/Unit/Model/AbstractTemplateTest.php index 46f3fecfb8848..35d562fa7e5ec 100644 --- a/app/code/Magento/Email/Test/Unit/Model/AbstractTemplateTest.php +++ b/app/code/Magento/Email/Test/Unit/Model/AbstractTemplateTest.php @@ -117,10 +117,11 @@ protected function setUp() /** * Return the model under test with additional methods mocked. * - * @param $mockedMethods array + * @param array $mockedMethods + * @param array $data * @return \Magento\Email\Model\Template|\PHPUnit_Framework_MockObject_MockObject */ - protected function getModelMock(array $mockedMethods = []) + protected function getModelMock(array $mockedMethods = [], array $data = []) { $helper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); return $this->getMockForAbstractClass( @@ -136,7 +137,8 @@ protected function getModelMock(array $mockedMethods = []) 'scopeConfig' => $this->scopeConfig, 'emailConfig' => $this->emailConfig, 'filterFactory' => $this->filterFactory, - 'templateFactory' => $this->templateFactory + 'templateFactory' => $this->templateFactory, + 'data' => $data, ] ), '', @@ -431,4 +433,17 @@ public function testGetDesignConfig() $expectedConfig = ['area' => 'test_area', 'store' => 2]; $this->assertEquals($expectedConfig, $model->getDesignConfig()->getData()); } + + /** + * @return void + */ + public function testSetForcedAreaWhenAreIsSet(): void + { + $templateId = 'test_template'; + $model = $this->getModelMock([], ['area' => 'frontend']); + + $this->emailConfig->expects($this->never())->method('getTemplateArea'); + + $model->setForcedArea($templateId); + } } From 9b1b80e8c69725d91d544003b82344fc6b0380c9 Mon Sep 17 00:00:00 2001 From: Andrii Meysar Date: Fri, 1 Jun 2018 14:32:43 +0300 Subject: [PATCH 19/24] MAGETWO-90349: Template file 'header.html' is not found. --- app/code/Magento/Email/Test/Unit/Model/AbstractTemplateTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Email/Test/Unit/Model/AbstractTemplateTest.php b/app/code/Magento/Email/Test/Unit/Model/AbstractTemplateTest.php index 35d562fa7e5ec..973d342e7f597 100644 --- a/app/code/Magento/Email/Test/Unit/Model/AbstractTemplateTest.php +++ b/app/code/Magento/Email/Test/Unit/Model/AbstractTemplateTest.php @@ -437,7 +437,7 @@ public function testGetDesignConfig() /** * @return void */ - public function testSetForcedAreaWhenAreIsSet(): void + public function testSetForcedAreaWhenAreaIsSet(): void { $templateId = 'test_template'; $model = $this->getModelMock([], ['area' => 'frontend']); From 45da02260c989f088b4645307e4af42d9b944b00 Mon Sep 17 00:00:00 2001 From: Viktor Sevch Date: Fri, 1 Jun 2018 15:53:13 +0300 Subject: [PATCH 20/24] MAGETWO-91807: Error when adding a product to the cart in the backend with the capitalization of the sku --- .../Catalog/Model/ProductRepository.php | 77 ++++++++++++++++--- .../Test/Unit/Model/ProductRepositoryTest.php | 27 ++++--- .../Catalog/Model/ProductRepositoryTest.php | 67 ++++++++++++++++ 3 files changed, 151 insertions(+), 20 deletions(-) create mode 100644 dev/tests/integration/testsuite/Magento/Catalog/Model/ProductRepositoryTest.php diff --git a/app/code/Magento/Catalog/Model/ProductRepository.php b/app/code/Magento/Catalog/Model/ProductRepository.php index 6a82658342824..a75e5349baa30 100644 --- a/app/code/Magento/Catalog/Model/ProductRepository.php +++ b/app/code/Magento/Catalog/Model/ProductRepository.php @@ -233,7 +233,8 @@ public function __construct( public function get($sku, $editMode = false, $storeId = null, $forceReload = false) { $cacheKey = $this->getCacheKey([$editMode, $storeId]); - if (!isset($this->instances[$sku][$cacheKey]) || $forceReload) { + $cachedProduct = $this->getProductFromLocalCache($sku, $cacheKey); + if ($cachedProduct === null || $forceReload) { $product = $this->productFactory->create(); $productId = $this->resourceModel->getIdBySku($sku); @@ -250,11 +251,10 @@ public function get($sku, $editMode = false, $storeId = null, $forceReload = fal } $product->load($productId); $this->cacheProduct($cacheKey, $product); + $cachedProduct = $product; } - if (!isset($this->instances[$sku])) { - $sku = trim($sku); - } - return $this->instances[$sku][$cacheKey]; + + return $cachedProduct; } /** @@ -312,7 +312,7 @@ protected function getCacheKey($data) private function cacheProduct($cacheKey, \Magento\Catalog\Api\Data\ProductInterface $product) { $this->instancesById[$product->getId()][$cacheKey] = $product; - $this->instances[$product->getSku()][$cacheKey] = $product; + $this->saveProductInLocalCache($product, $cacheKey); if ($this->cacheLimit && count($this->instances) > $this->cacheLimit) { $offset = round($this->cacheLimit / -2); @@ -338,7 +338,7 @@ protected function initializeProductData(array $productData, $createNew) $product->setWebsiteIds([$this->storeManager->getStore(true)->getWebsiteId()]); } } else { - unset($this->instances[$productData['sku']]); + $this->removeProductFromLocalCache($productData['sku']); $product = $this->get($productData['sku']); } @@ -613,7 +613,7 @@ public function save(\Magento\Catalog\Api\Data\ProductInterface $product, $saveO if ($tierPrices !== null) { $product->setData('tier_price', $tierPrices); } - unset($this->instances[$product->getSku()]); + $this->removeProductFromLocalCache($product->getSku()); unset($this->instancesById[$product->getId()]); $this->resourceModel->save($product); } catch (ConnectionException $exception) { @@ -650,8 +650,9 @@ public function save(\Magento\Catalog\Api\Data\ProductInterface $product, $saveO $e ); } - unset($this->instances[$product->getSku()]); + $this->removeProductFromLocalCache($product->getSku()); unset($this->instancesById[$product->getId()]); + return $this->get($product->getSku(), false, $product->getStoreId()); } @@ -663,7 +664,7 @@ public function delete(\Magento\Catalog\Api\Data\ProductInterface $product) $sku = $product->getSku(); $productId = $product->getId(); try { - unset($this->instances[$product->getSku()]); + $this->removeProductFromLocalCache($product->getSku()); unset($this->instancesById[$product->getId()]); $this->resourceModel->delete($product); } catch (ValidatorException $e) { @@ -673,8 +674,9 @@ public function delete(\Magento\Catalog\Api\Data\ProductInterface $product) __('The "%1" product couldn\'t be removed.', $sku) ); } - unset($this->instances[$sku]); + $this->removeProductFromLocalCache($sku); unset($this->instancesById[$productId]); + return true; } @@ -796,4 +798,57 @@ private function getCollectionProcessor() } return $this->collectionProcessor; } + + /** + * Gets product from the local cache by SKU. + * + * @param string $sku + * @param string $cacheKey + * @return Product|null + */ + private function getProductFromLocalCache(string $sku, string $cacheKey) + { + $preparedSku = $this->prepareSku($sku); + if (!isset($this->instances[$preparedSku])) { + return null; + } + + return $this->instances[$preparedSku][$cacheKey] ?? null; + } + + /** + * Removes product in the local cache. + * + * @param string $sku + * @return void + */ + private function removeProductFromLocalCache(string $sku) :void + { + $preparedSku = $this->prepareSku($sku); + unset($this->instances[$preparedSku]); + } + + /** + * Saves product in the local cache. + * + * @param Product $product + * @param string $cacheKey + * @return void + */ + private function saveProductInLocalCache(Product $product, string $cacheKey) : void + { + $preparedSku = $this->prepareSku($product->getSku()); + $this->instances[$preparedSku][$cacheKey] = $product; + } + + /** + * Converts SKU to lower case and trims. + * + * @param string $sku + * @return string + */ + private function prepareSku(string $sku): string + { + return mb_strtolower(trim($sku)); + } } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php index a370cbea13c2b..bf5c3d8276295 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php @@ -381,11 +381,11 @@ public function testGetByIdAbsentProduct() public function testGetByIdProductInEditMode() { $productId = 123; - $this->productFactoryMock->expects($this->once())->method('create') - ->will($this->returnValue($this->productMock)); - $this->productMock->expects($this->once())->method('setData')->with('_edit_mode', true); - $this->productMock->expects($this->once())->method('load')->with($productId); + $this->productFactoryMock->method('create')->willReturn($this->productMock); + $this->productMock->method('setData')->with('_edit_mode', true); + $this->productMock->method('load')->with($productId); $this->productMock->expects($this->atLeastOnce())->method('getId')->willReturn($productId); + $this->productMock->method('getSku')->willReturn('simple'); $this->assertEquals($this->productMock, $this->model->getById($productId, true)); } @@ -411,6 +411,7 @@ public function testGetByIdForCacheKeyGenerate($identifier, $editMode, $storeId) } $this->productMock->expects($this->once())->method('load')->with($identifier); $this->productMock->expects($this->atLeastOnce())->method('getId')->willReturn($identifier); + $this->productMock->method('getSku')->willReturn('simple'); $this->assertEquals($this->productMock, $this->model->getById($identifier, $editMode, $storeId)); //Second invocation should just return from cache $this->assertEquals($this->productMock, $this->model->getById($identifier, $editMode, $storeId)); @@ -433,6 +434,7 @@ public function testGetByIdForcedReload() $this->serializerMock->expects($this->exactly(3))->method('serialize'); $this->productMock->expects($this->exactly(4))->method('getId')->willReturn($identifier); + $this->productMock->method('getSku')->willReturn('simple'); $this->assertEquals($this->productMock, $this->model->getById($identifier, $editMode, $storeId)); //second invocation should just return from cache $this->assertEquals($this->productMock, $this->model->getById($identifier, $editMode, $storeId)); @@ -532,6 +534,7 @@ public function testGetByIdWithSetStoreId() $this->productMock->expects($this->once())->method('setData')->with('store_id', $storeId); $this->productMock->expects($this->once())->method('load')->with($productId); $this->productMock->expects($this->atLeastOnce())->method('getId')->willReturn($productId); + $this->productMock->method('getSku')->willReturn('simple'); $this->assertEquals($this->productMock, $this->model->getById($productId, false, $storeId)); } @@ -585,7 +588,8 @@ public function testSaveNew() ->expects($this->once()) ->method('toNestedArray') ->will($this->returnValue($this->productData)); - $this->productMock->expects($this->once())->method('getWebsiteIds')->willReturn([]); + $this->productMock->method('getWebsiteIds')->willReturn([]); + $this->productMock->method('getSku')->willReturn('simple'); $this->assertEquals($this->productMock, $this->model->save($this->productMock)); } @@ -597,7 +601,8 @@ public function testSaveNew() public function testSaveUnableToSaveException() { $this->storeManagerMock->expects($this->any())->method('getWebsites')->willReturn([1 => 'default']); - $this->resourceModelMock->expects($this->exactly(1))->method('getIdBySku')->will($this->returnValue(null)); + $this->resourceModelMock->expects($this->exactly(1)) + ->method('getIdBySku')->willReturn(null); $this->productFactoryMock->expects($this->exactly(2)) ->method('create') ->will($this->returnValue($this->productMock)); @@ -610,7 +615,8 @@ public function testSaveUnableToSaveException() ->expects($this->once()) ->method('toNestedArray') ->will($this->returnValue($this->productData)); - $this->productMock->expects($this->once())->method('getWebsiteIds')->willReturn([]); + $this->productMock->method('getWebsiteIds')->willReturn([]); + $this->productMock->method('getSku')->willReturn('simple'); $this->model->save($this->productMock); } @@ -637,6 +643,7 @@ public function testSaveException() ->method('toNestedArray') ->will($this->returnValue($this->productData)); $this->productMock->expects($this->once())->method('getWebsiteIds')->willReturn([]); + $this->productMock->method('getSku')->willReturn('simple'); $this->model->save($this->productMock); } @@ -661,6 +668,7 @@ public function testSaveInvalidProductException() ->method('toNestedArray') ->will($this->returnValue($this->productData)); $this->productMock->expects($this->once())->method('getWebsiteIds')->willReturn([]); + $this->productMock->method('getSku')->willReturn('simple'); $this->model->save($this->productMock); } @@ -692,6 +700,7 @@ public function testSaveThrowsTemporaryStateExceptionIfDatabaseConnectionErrorOc $this->productMock->expects($this->once()) ->method('getWebsiteIds') ->willReturn([]); + $this->productMock->method('getSku')->willReturn('simple'); $this->model->save($this->productMock); } @@ -734,9 +743,8 @@ public function testGetList() { $searchCriteriaMock = $this->createMock(\Magento\Framework\Api\SearchCriteriaInterface::class); $collectionMock = $this->createMock(\Magento\Catalog\Model\ResourceModel\Product\Collection::class); - $this->collectionFactoryMock->expects($this->once())->method('create')->willReturn($collectionMock); - + $this->productMock->method('getSku')->willReturn('simple'); $collectionMock->expects($this->once())->method('addAttributeToSelect')->with('*'); $collectionMock->expects($this->exactly(2))->method('joinAttribute')->withConsecutive( ['status', 'catalog_product/status', 'entity_id', null, 'inner'], @@ -1299,6 +1307,7 @@ public function testSaveWithDifferentWebsites() ]); $this->productMock->expects($this->once())->method('getWebsiteIds')->willReturn([1,2,3]); $this->productMock->expects($this->once())->method('setWebsiteIds')->willReturn([2,3]); + $this->productMock->method('getSku')->willReturn('simple'); $this->assertEquals($this->productMock, $this->model->save($this->productMock)); } diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductRepositoryTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductRepositoryTest.php new file mode 100644 index 0000000000000..d6ab99f2ca576 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductRepositoryTest.php @@ -0,0 +1,67 @@ +productRepository = Bootstrap::getObjectManager() + ->get(ProductRepositoryInterface::class); + } + + /** + * Check a case when product should be retrieved with different SKU variations. + * + * @param string $sku + * @return void + * @magentoDataFixture Magento/Catalog/_files/product_simple.php + * @dataProvider skuDataProvider + */ + public function testGetProduct(string $sku) : void + { + $expectedSku = 'simple'; + $product = $this->productRepository->get($sku); + + self::assertNotEmpty($product); + self::assertEquals($expectedSku, $product->getSku()); + } + + /** + * Get list of SKU variations for the same product. + * + * @return array + */ + public function skuDataProvider(): array + { + return [ + ['sku' => 'simple'], + ['sku' => 'Simple'], + ['sku' => 'simple '], + ]; + } +} From c5eab1c0fc9e0eac46e0d0fc7466973badc5e1fa Mon Sep 17 00:00:00 2001 From: Myroslav Dobra Date: Fri, 1 Jun 2018 16:05:03 +0300 Subject: [PATCH 21/24] MAGETWO-90349: Template file 'header.html' is not found. --- .../Test/Unit/Model/AbstractTemplateTest.php | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Email/Test/Unit/Model/AbstractTemplateTest.php b/app/code/Magento/Email/Test/Unit/Model/AbstractTemplateTest.php index 973d342e7f597..eeb7b5730dc93 100644 --- a/app/code/Magento/Email/Test/Unit/Model/AbstractTemplateTest.php +++ b/app/code/Magento/Email/Test/Unit/Model/AbstractTemplateTest.php @@ -438,11 +438,27 @@ public function testGetDesignConfig() * @return void */ public function testSetForcedAreaWhenAreaIsSet(): void + { + $templateId = 'test_template'; + $model = $this->getModelMock([], ['area' => null]); + + $this->emailConfig->expects($this->once()) + ->method('getTemplateArea') + ->with($templateId); + + $model->setForcedArea($templateId); + } + + /** + * @return void + */ + public function testNotSetForcedAreaWhenAreaIsSet(): void { $templateId = 'test_template'; $model = $this->getModelMock([], ['area' => 'frontend']); - $this->emailConfig->expects($this->never())->method('getTemplateArea'); + $this->emailConfig->expects($this->never()) + ->method('getTemplateArea'); $model->setForcedArea($templateId); } From 662a316cda4f5e951f3b59cd770472d40286ae5f Mon Sep 17 00:00:00 2001 From: Myroslav Dobra Date: Fri, 1 Jun 2018 16:07:14 +0300 Subject: [PATCH 22/24] MAGETWO-90349: Template file 'header.html' is not found. --- .../Magento/Email/Test/Unit/Model/AbstractTemplateTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Email/Test/Unit/Model/AbstractTemplateTest.php b/app/code/Magento/Email/Test/Unit/Model/AbstractTemplateTest.php index eeb7b5730dc93..4f545360616c6 100644 --- a/app/code/Magento/Email/Test/Unit/Model/AbstractTemplateTest.php +++ b/app/code/Magento/Email/Test/Unit/Model/AbstractTemplateTest.php @@ -437,7 +437,7 @@ public function testGetDesignConfig() /** * @return void */ - public function testSetForcedAreaWhenAreaIsSet(): void + public function testSetForcedAreaWhenAreaIsNotSet(): void { $templateId = 'test_template'; $model = $this->getModelMock([], ['area' => null]); @@ -452,7 +452,7 @@ public function testSetForcedAreaWhenAreaIsSet(): void /** * @return void */ - public function testNotSetForcedAreaWhenAreaIsSet(): void + public function testSetForcedAreaWhenAreaIsSet(): void { $templateId = 'test_template'; $model = $this->getModelMock([], ['area' => 'frontend']); From 31cbafe3f571c9f876d20d4421d64e078a22beec Mon Sep 17 00:00:00 2001 From: Viktor Sevch Date: Mon, 4 Jun 2018 10:17:41 +0300 Subject: [PATCH 23/24] MAGETWO-91807: Error when adding a product to the cart in the backend with the capitalization of the sku --- app/code/Magento/Catalog/Model/ProductRepository.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/code/Magento/Catalog/Model/ProductRepository.php b/app/code/Magento/Catalog/Model/ProductRepository.php index a75e5349baa30..03ddab3d44547 100644 --- a/app/code/Magento/Catalog/Model/ProductRepository.php +++ b/app/code/Magento/Catalog/Model/ProductRepository.php @@ -809,9 +809,6 @@ private function getCollectionProcessor() private function getProductFromLocalCache(string $sku, string $cacheKey) { $preparedSku = $this->prepareSku($sku); - if (!isset($this->instances[$preparedSku])) { - return null; - } return $this->instances[$preparedSku][$cacheKey] ?? null; } From 279655ad94a9de13d8d953d1f1d205cb5de9a0ac Mon Sep 17 00:00:00 2001 From: Myroslav Dobra Date: Tue, 5 Jun 2018 11:15:13 +0300 Subject: [PATCH 24/24] MAGETWO-90787: Swagger does not render correctly for many POST/PUT operations --- .../frontend/layout/swagger_index_index.xml | 35 +- .../frontend/templates/swagger-ui/index.phtml | 39 +- .../frontend/web/swagger-ui/css/print.css | 1172 - .../frontend/web/swagger-ui/css/reset.css | 125 - .../frontend/web/swagger-ui/css/screen.css | 1279 - .../frontend/web/swagger-ui/css/style.css | 270 +- .../web/swagger-ui/css/swagger-ui.css | 2 + .../web/swagger-ui/css/typography.css | 26 - .../fonts/droid-sans-v6-latin-700.eot | Bin 22922 -> 0 bytes .../fonts/droid-sans-v6-latin-700.svg | 411 - .../fonts/droid-sans-v6-latin-700.ttf | Bin 40513 -> 0 bytes .../fonts/droid-sans-v6-latin-700.woff | Bin 25992 -> 0 bytes .../fonts/droid-sans-v6-latin-700.woff2 | Bin 11480 -> 0 bytes .../fonts/droid-sans-v6-latin-regular.eot | Bin 22008 -> 0 bytes .../fonts/droid-sans-v6-latin-regular.svg | 403 - .../fonts/droid-sans-v6-latin-regular.ttf | Bin 39069 -> 0 bytes .../fonts/droid-sans-v6-latin-regular.woff | Bin 24868 -> 0 bytes .../fonts/droid-sans-v6-latin-regular.woff2 | Bin 11304 -> 0 bytes .../web/swagger-ui/images/explorer_icons.png | Bin 2474 -> 0 bytes .../web/swagger-ui/images/favicon-16x16.png | Bin 449 -> 0 bytes .../web/swagger-ui/images/favicon-32x32.png | Bin 1141 -> 0 bytes .../web/swagger-ui/images/favicon.ico | Bin 5430 -> 0 bytes .../web/swagger-ui/images/pet_store_api.png | Bin 637 -> 0 bytes .../web/swagger-ui/images/throbber.gif | Bin 6122 -> 0 bytes .../web/swagger-ui/images/wordnik_api.png | Bin 672 -> 0 bytes .../web/swagger-ui/js/lang/translator.js | 13 +- .../web/swagger-ui/js/lib/backbone-min.js | 15 - .../js/lib/handlebars.min-v4.0.10.js | 29 - .../swagger-ui/js/lib/highlight.9.1.0.pack.js | 1 - .../js/lib/highlight.9.1.0.pack_extended.js | 1 - .../web/swagger-ui/js/lib/jquery-1.8.0.min.js | 2 - .../swagger-ui/js/lib/jquery.ba-bbq.min.js | 18 - .../swagger-ui/js/lib/jquery.slideto.min.js | 1 - .../swagger-ui/js/lib/jquery.wiggle.min.js | 8 - .../web/swagger-ui/js/lib/jsoneditor.min.js | 5 - .../web/swagger-ui/js/lib/lodash.min.js | 2 - .../frontend/web/swagger-ui/js/lib/marked.js | 1274 - .../js/lib/object-assign-pollyfill.js | 1 - .../web/swagger-ui/js/lib/swagger-oauth.js | 290 - .../web/swagger-ui/js/lib/underscore-min.js | 6 - .../web/swagger-ui/js/lib/underscore-min.map | 1 - .../web/swagger-ui/js/magento-swagger.js | 95 +- .../web/swagger-ui/js/swagger-ui-bundle.js | 99 + .../js/swagger-ui-standalone-preset.js | 13 + .../frontend/web/swagger-ui/js/swagger-ui.js | 25344 ---------------- .../Constraint/AssertApiInfoTitleOnPage.php | 2 +- .../AssertEndpointContentDisplay.php | 4 +- .../AssertServiceContentDisplay.php | 4 +- .../Swagger/Test/Page/SwaggerUiPage.php | 38 +- .../Test/Js/_files/blacklist/magento.txt | 16 +- 50 files changed, 274 insertions(+), 30770 deletions(-) delete mode 100644 app/code/Magento/Swagger/view/frontend/web/swagger-ui/css/print.css delete mode 100644 app/code/Magento/Swagger/view/frontend/web/swagger-ui/css/reset.css delete mode 100644 app/code/Magento/Swagger/view/frontend/web/swagger-ui/css/screen.css create mode 100644 app/code/Magento/Swagger/view/frontend/web/swagger-ui/css/swagger-ui.css delete mode 100644 app/code/Magento/Swagger/view/frontend/web/swagger-ui/css/typography.css delete mode 100644 app/code/Magento/Swagger/view/frontend/web/swagger-ui/fonts/droid-sans-v6-latin-700.eot delete mode 100644 app/code/Magento/Swagger/view/frontend/web/swagger-ui/fonts/droid-sans-v6-latin-700.svg delete mode 100644 app/code/Magento/Swagger/view/frontend/web/swagger-ui/fonts/droid-sans-v6-latin-700.ttf delete mode 100644 app/code/Magento/Swagger/view/frontend/web/swagger-ui/fonts/droid-sans-v6-latin-700.woff delete mode 100644 app/code/Magento/Swagger/view/frontend/web/swagger-ui/fonts/droid-sans-v6-latin-700.woff2 delete mode 100644 app/code/Magento/Swagger/view/frontend/web/swagger-ui/fonts/droid-sans-v6-latin-regular.eot delete mode 100644 app/code/Magento/Swagger/view/frontend/web/swagger-ui/fonts/droid-sans-v6-latin-regular.svg delete mode 100644 app/code/Magento/Swagger/view/frontend/web/swagger-ui/fonts/droid-sans-v6-latin-regular.ttf delete mode 100644 app/code/Magento/Swagger/view/frontend/web/swagger-ui/fonts/droid-sans-v6-latin-regular.woff delete mode 100644 app/code/Magento/Swagger/view/frontend/web/swagger-ui/fonts/droid-sans-v6-latin-regular.woff2 delete mode 100644 app/code/Magento/Swagger/view/frontend/web/swagger-ui/images/explorer_icons.png delete mode 100755 app/code/Magento/Swagger/view/frontend/web/swagger-ui/images/favicon-16x16.png delete mode 100755 app/code/Magento/Swagger/view/frontend/web/swagger-ui/images/favicon-32x32.png delete mode 100755 app/code/Magento/Swagger/view/frontend/web/swagger-ui/images/favicon.ico delete mode 100644 app/code/Magento/Swagger/view/frontend/web/swagger-ui/images/pet_store_api.png delete mode 100644 app/code/Magento/Swagger/view/frontend/web/swagger-ui/images/throbber.gif delete mode 100644 app/code/Magento/Swagger/view/frontend/web/swagger-ui/images/wordnik_api.png delete mode 100644 app/code/Magento/Swagger/view/frontend/web/swagger-ui/js/lib/backbone-min.js delete mode 100644 app/code/Magento/Swagger/view/frontend/web/swagger-ui/js/lib/handlebars.min-v4.0.10.js delete mode 100644 app/code/Magento/Swagger/view/frontend/web/swagger-ui/js/lib/highlight.9.1.0.pack.js delete mode 100644 app/code/Magento/Swagger/view/frontend/web/swagger-ui/js/lib/highlight.9.1.0.pack_extended.js delete mode 100644 app/code/Magento/Swagger/view/frontend/web/swagger-ui/js/lib/jquery-1.8.0.min.js delete mode 100644 app/code/Magento/Swagger/view/frontend/web/swagger-ui/js/lib/jquery.ba-bbq.min.js delete mode 100644 app/code/Magento/Swagger/view/frontend/web/swagger-ui/js/lib/jquery.slideto.min.js delete mode 100644 app/code/Magento/Swagger/view/frontend/web/swagger-ui/js/lib/jquery.wiggle.min.js delete mode 100644 app/code/Magento/Swagger/view/frontend/web/swagger-ui/js/lib/jsoneditor.min.js delete mode 100644 app/code/Magento/Swagger/view/frontend/web/swagger-ui/js/lib/lodash.min.js delete mode 100644 app/code/Magento/Swagger/view/frontend/web/swagger-ui/js/lib/marked.js delete mode 100644 app/code/Magento/Swagger/view/frontend/web/swagger-ui/js/lib/object-assign-pollyfill.js delete mode 100644 app/code/Magento/Swagger/view/frontend/web/swagger-ui/js/lib/swagger-oauth.js delete mode 100644 app/code/Magento/Swagger/view/frontend/web/swagger-ui/js/lib/underscore-min.js delete mode 100644 app/code/Magento/Swagger/view/frontend/web/swagger-ui/js/lib/underscore-min.map create mode 100644 app/code/Magento/Swagger/view/frontend/web/swagger-ui/js/swagger-ui-bundle.js create mode 100644 app/code/Magento/Swagger/view/frontend/web/swagger-ui/js/swagger-ui-standalone-preset.js delete mode 100644 app/code/Magento/Swagger/view/frontend/web/swagger-ui/js/swagger-ui.js diff --git a/app/code/Magento/Swagger/view/frontend/layout/swagger_index_index.xml b/app/code/Magento/Swagger/view/frontend/layout/swagger_index_index.xml index 345f063a7aaa3..f14df1c70a790 100644 --- a/app/code/Magento/Swagger/view/frontend/layout/swagger_index_index.xml +++ b/app/code/Magento/Swagger/view/frontend/layout/swagger_index_index.xml @@ -10,32 +10,18 @@ Swagger UI - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + @@ -43,6 +29,7 @@ + diff --git a/app/code/Magento/Swagger/view/frontend/templates/swagger-ui/index.phtml b/app/code/Magento/Swagger/view/frontend/templates/swagger-ui/index.phtml index 27b3767f274bc..b20da68734579 100644 --- a/app/code/Magento/Swagger/view/frontend/templates/swagger-ui/index.phtml +++ b/app/code/Magento/Swagger/view/frontend/templates/swagger-ui/index.phtml @@ -12,11 +12,48 @@ * Modified by Magento, Modifications Copyright © Magento, Inc. All rights reserved. */ -/** @var \Magento\Swagger\Block\Index $block */ +/** @var \Magento\Swagger\Block\Index $block + * + * @codingStandardsIgnoreFile + */ $schemaUrl = $block->getSchemaUrl(); ?> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +