diff --git a/app/code/Magento/Checkout/view/frontend/web/js/model/new-customer-address.js b/app/code/Magento/Checkout/view/frontend/web/js/model/new-customer-address.js index 8b079d1fd5d15fecb0a8a63ec7e1c7d9e27d29a7..ab46c78c6f6e74f62a8c798c4c2b039b2eaf4876 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/model/new-customer-address.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/model/new-customer-address.js @@ -4,18 +4,19 @@ */ /*jshint browser:true jquery:true*/ /*global alert*/ -define([], function() { +define([], function () { /** - * @param addressData + * @param {Object} addressData * Returns new address object */ return function (addressData) { var identifier = Date.now(); + return { email: addressData.email, countryId: (addressData.country_id) ? addressData.country_id : window.checkoutConfig.defaultCountryId, - regionId: (addressData.region && addressData.region.region_id) - ? addressData.region.region_id + regionId: (addressData.region && addressData.region.region_id) ? + addressData.region.region_id : window.checkoutConfig.defaultRegionId, regionCode: (addressData.region) ? addressData.region.region_code : null, region: (addressData.region) ? addressData.region.region : null, @@ -33,25 +34,26 @@ define([], function() { suffix: addressData.suffix, vatId: addressData.vat_id, saveInAddressBook: addressData.save_in_address_book, - isDefaultShipping: function() { + customAttributes: addressData.custom_attributes, + isDefaultShipping: function () { return addressData.default_shipping; }, - isDefaultBilling: function() { + isDefaultBilling: function () { return addressData.default_billing; }, - getType: function() { + getType: function () { return 'new-customer-address'; }, - getKey: function() { + getKey: function () { return this.getType(); }, - getCacheKey: function() { + getCacheKey: function () { return this.getType() + identifier; }, - isEditable: function() { + isEditable: function () { return true; }, - canUseForBilling: function() { + canUseForBilling: function () { return true; } } diff --git a/app/code/Magento/Checkout/view/frontend/web/js/model/quote.js b/app/code/Magento/Checkout/view/frontend/web/js/model/quote.js index eb950d1e6f9a3d7d484c2379026a1cfd3a03d809..6f86ecedd1af3f5f85909c338ccd00e6cf390dc2 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/model/quote.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/model/quote.js @@ -44,7 +44,7 @@ define( return totals; }, setTotals: function(totalsData) { - if (_.isObject(totalsData.extension_attributes)) { + if (_.isObject(totalsData) && _.isObject(totalsData.extension_attributes)) { _.each(totalsData.extension_attributes, function(element, index) { totalsData[index] = element; }); diff --git a/app/code/Magento/Customer/Model/Address.php b/app/code/Magento/Customer/Model/Address.php index 54eeaf09f1ff76ef87522cccb5f2cc3d77107281..0d7638a37196a9062f05cc34e7ef9b2d983176c6 100644 --- a/app/code/Magento/Customer/Model/Address.php +++ b/app/code/Magento/Customer/Model/Address.php @@ -47,6 +47,11 @@ class Address extends \Magento\Customer\Model\Address\AbstractAddress */ protected $indexerRegistry; + /** + * @var \Magento\Customer\Model\Address\CustomAttributeListInterface + */ + private $attributeList; + /** * @param \Magento\Framework\Model\Context $context * @param \Magento\Framework\Registry $registry @@ -347,4 +352,27 @@ class Address extends \Magento\Customer\Model\Address\AbstractAddress $indexer = $this->indexerRegistry->get(Customer::CUSTOMER_GRID_INDEXER_ID); $indexer->reindexRow($this->getCustomerId()); } + + /** + * {@inheritdoc} + */ + protected function getCustomAttributesCodes() + { + return array_keys($this->getAttributeList()->getAttributes()); + } + + /** + * Get new AttributeList dependency for application code. + * @return \Magento\Customer\Model\Address\CustomAttributeListInterface + * @deprecated + */ + private function getAttributeList() + { + if (!$this->attributeList) { + $this->attributeList = \Magento\Framework\App\ObjectManager::getInstance()->get( + \Magento\Customer\Model\Address\CustomAttributeListInterface::class + ); + } + return $this->attributeList; + } } diff --git a/app/code/Magento/Customer/Model/Address/AbstractAddress.php b/app/code/Magento/Customer/Model/Address/AbstractAddress.php index 6855eb242e6e5f1e9b476a175dadc841d2b9fbf6..1851f858b992f2acfdd8285aeaca897682b24792 100644 --- a/app/code/Magento/Customer/Model/Address/AbstractAddress.php +++ b/app/code/Magento/Customer/Model/Address/AbstractAddress.php @@ -263,7 +263,7 @@ class AbstractAddress extends AbstractExtensibleModel implements AddressModelInt { if (is_array($key)) { $key = $this->_implodeArrayField($key); - } elseif (is_array($value) && $this->isAddressMultilineAttribute($key)) { + } elseif (is_array($value) && !empty($value) && $this->isAddressMultilineAttribute($key)) { $value = $this->_implodeArrayValues($value); } return parent::setData($key, $value); diff --git a/app/code/Magento/Customer/Model/Address/CustomAttributeList.php b/app/code/Magento/Customer/Model/Address/CustomAttributeList.php new file mode 100644 index 0000000000000000000000000000000000000000..f83b37c2a814debcc5d6b0c1ccd6a429cb3a755a --- /dev/null +++ b/app/code/Magento/Customer/Model/Address/CustomAttributeList.php @@ -0,0 +1,17 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Customer\Model\Address; + +class CustomAttributeList implements CustomAttributeListInterface +{ + /** + * {@inheritdoc} + */ + public function getAttributes() + { + return []; + } +} diff --git a/app/code/Magento/Customer/Model/Address/CustomAttributeListInterface.php b/app/code/Magento/Customer/Model/Address/CustomAttributeListInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..c04cfe16f49183e0e27ab8362068b75a83951d3c --- /dev/null +++ b/app/code/Magento/Customer/Model/Address/CustomAttributeListInterface.php @@ -0,0 +1,16 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Customer\Model\Address; + +interface CustomAttributeListInterface +{ + /** + * Retrieve list of customer addresses custom attributes + * + * @return array + */ + public function getAttributes(); +} diff --git a/app/code/Magento/Customer/etc/di.xml b/app/code/Magento/Customer/etc/di.xml index 647ec87efca25a1e583df18d3d469f586996b2c2..1e5f0ce0677ecf699b12b540bb9805d8129d053e 100644 --- a/app/code/Magento/Customer/etc/di.xml +++ b/app/code/Magento/Customer/etc/di.xml @@ -49,6 +49,8 @@ type="Magento\Customer\Model\EmailNotification" /> <preference for="Magento\Customer\Api\CustomerNameGenerationInterface" type="Magento\Customer\Helper\View" /> + <preference for="Magento\Customer\Model\Address\CustomAttributeListInterface" + type="Magento\Customer\Model\Address\CustomAttributeList" /> <type name="Magento\Customer\Model\Session"> <arguments> <argument name="configShare" xsi:type="object">Magento\Customer\Model\Config\Share\Proxy</argument> diff --git a/app/code/Magento/Customer/view/frontend/web/js/model/customer/address.js b/app/code/Magento/Customer/view/frontend/web/js/model/customer/address.js index e81016145c705e9d99d86265fb6f0dfd284f9d19..30fbef98fd39ab15db1c7971e3e86ad82e367253 100644 --- a/app/code/Magento/Customer/view/frontend/web/js/model/customer/address.js +++ b/app/code/Magento/Customer/view/frontend/web/js/model/customer/address.js @@ -32,6 +32,7 @@ define([], function() { vatId: addressData.vat_id, sameAsBilling: addressData.same_as_billing, saveInAddressBook: addressData.save_in_address_book, + customAttributes: addressData.custom_attributes, isDefaultShipping: function() { return addressData.default_shipping; }, diff --git a/app/code/Magento/Quote/Model/Quote/Item/Compare.php b/app/code/Magento/Quote/Model/Quote/Item/Compare.php index f61dc25660c9d1dd68b132aaceedc0fd7356315e..10bb712025462fc11f99c2f55206c6cf10f8b854 100644 --- a/app/code/Magento/Quote/Model/Quote/Item/Compare.php +++ b/app/code/Magento/Quote/Model/Quote/Item/Compare.php @@ -23,6 +23,9 @@ class Compare if (is_string($value) && is_array(@unserialize($value))) { $value = @unserialize($value); unset($value['qty'], $value['uenc']); + $value = array_filter($value, function ($optionValue) { + return !empty($optionValue); + }); } return $value; } diff --git a/app/code/Magento/Quote/Model/QuoteManagement.php b/app/code/Magento/Quote/Model/QuoteManagement.php index d8aedf4a8b986a45e237e460e127ba4d5060a8a0..f69e299b4542e7669d9bc23df733d9bd8b63b242 100644 --- a/app/code/Magento/Quote/Model/QuoteManagement.php +++ b/app/code/Magento/Quote/Model/QuoteManagement.php @@ -21,6 +21,8 @@ use Magento\Sales\Api\Data\OrderInterfaceFactory as OrderFactory; use Magento\Sales\Api\OrderManagementInterface as OrderManagement; use Magento\Store\Model\StoreManagerInterface; use Magento\Quote\Model\Quote\Address; +use Magento\Framework\App\ObjectManager; +use Magento\Quote\Model\QuoteIdMaskFactory; /** * Class QuoteManagement @@ -130,6 +132,11 @@ class QuoteManagement implements \Magento\Quote\Api\CartManagementInterface */ protected $quoteFactory; + /** + * @var QuoteIdMaskFactory + */ + private $quoteIdMaskFactory; + /** * @param EventManager $eventManager * @param QuoteValidator $quoteValidator @@ -262,6 +269,12 @@ class QuoteManagement implements \Magento\Quote\Api\CartManagementInterface $quote->setCustomer($customer); $quote->setCustomerIsGuest(0); + $quoteIdMaskFactory = $this->getQuoteIdMaskFactory(); + /** @var \Magento\Quote\Model\QuoteIdMask $quoteIdMask */ + $quoteIdMask = $quoteIdMaskFactory->create()->load($cartId, 'quote_id'); + if ($quoteIdMask->getId()) { + $quoteIdMask->delete(); + } $this->quoteRepository->save($quote); return true; @@ -547,4 +560,16 @@ class QuoteManagement implements \Magento\Quote\Api\CartManagementInterface $shipping->setIsDefaultBilling(true); } } + + /** + * @return QuoteIdMaskFactory + * @deprecated + */ + private function getQuoteIdMaskFactory() + { + if (!$this->quoteIdMaskFactory) { + $this->quoteIdMaskFactory = ObjectManager::getInstance()->get(QuoteIdMaskFactory::class); + } + return $this->quoteIdMaskFactory; + } } diff --git a/app/code/Magento/Quote/Test/Unit/Model/Quote/Item/CompareTest.php b/app/code/Magento/Quote/Test/Unit/Model/Quote/Item/CompareTest.php index a22d10aee9318aed5d07fa70768e8ecfa90ca940..07f9987a902522b5701c6fdab888274f664d2073 100644 --- a/app/code/Magento/Quote/Test/Unit/Model/Quote/Item/CompareTest.php +++ b/app/code/Magento/Quote/Test/Unit/Model/Quote/Item/CompareTest.php @@ -187,4 +187,31 @@ class CompareTest extends \PHPUnit_Framework_TestCase ->will($this->returnValue([])); $this->assertFalse($this->helper->compare($this->itemMock, $this->comparedMock)); } + + /** + * Verify that compare ignores empty options. + */ + public function testCompareWithEmptyValues() + { + $this->itemMock->expects($this->any()) + ->method('getProductId') + ->will($this->returnValue(1)); + $this->comparedMock->expects($this->any()) + ->method('getProductId') + ->will($this->returnValue(1)); + + $this->itemMock->expects($this->once())->method('getOptions')->willReturn([ + $this->getOptionMock('option-1', serialize([ + 'non-empty-option' => 'test', + 'empty_option' => '' + ])) + ]); + $this->comparedMock->expects($this->once())->method('getOptions')->willReturn([ + $this->getOptionMock('option-1', serialize([ + 'non-empty-option' => 'test' + ])) + ]); + + $this->assertTrue($this->helper->compare($this->itemMock, $this->comparedMock)); + } } diff --git a/app/code/Magento/Quote/Test/Unit/Model/QuoteManagementTest.php b/app/code/Magento/Quote/Test/Unit/Model/QuoteManagementTest.php index cd2a56b3b9d7b414b24530e17dbf409dfcf2fe14..2664e7ef21ad76da3bfd6b844caf42d8bbdb8900 100644 --- a/app/code/Magento/Quote/Test/Unit/Model/QuoteManagementTest.php +++ b/app/code/Magento/Quote/Test/Unit/Model/QuoteManagementTest.php @@ -124,7 +124,7 @@ class QuoteManagementTest extends \PHPUnit_Framework_TestCase /** * @var \PHPUnit_Framework_MockObject_MockObject */ - protected $quoteFactoryMock; + private $quoteIdMock; /** * @SuppressWarnings(PHPMD.ExcessiveMethodLength) @@ -238,7 +238,6 @@ class QuoteManagementTest extends \PHPUnit_Framework_TestCase ); $this->quoteFactoryMock = $this->getMock('\Magento\Quote\Model\QuoteFactory', ['create'], [], '', false); - $this->model = $objectManager->getObject( '\Magento\Quote\Model\QuoteManagement', [ @@ -264,6 +263,12 @@ class QuoteManagementTest extends \PHPUnit_Framework_TestCase 'quoteFactory' => $this->quoteFactoryMock ] ); + + // Set the new dependency + $this->quoteIdMock = $this->getMock('Magento\Quote\Model\QuoteIdMask', [], [], '', false); + $quoteIdFactoryMock = $this->getMock(\Magento\Quote\Model\QuoteIdMaskFactory::class, ['create'], [], '', false); + $this->setPropertyValue($this->model, 'quoteIdMaskFactory', $quoteIdFactoryMock); + } public function testCreateEmptyCartAnonymous() @@ -508,6 +513,13 @@ class QuoteManagementTest extends \PHPUnit_Framework_TestCase $customerId = 455; $storeId = 5; + $this->getPropertyValue($this->model, 'quoteIdMaskFactory') + ->expects($this->once()) + ->method('create') + ->willReturn($this->quoteIdMock); + $this->quoteIdMock->expects($this->once())->method('load')->with($cartId, 'quote_id')->willReturnSelf(); + $this->quoteIdMock->expects($this->once())->method('getId')->willReturn(10); + $this->quoteIdMock->expects($this->once())->method('delete'); $quoteMock = $this->getMock( '\Magento\Quote\Model\Quote', ['getCustomerId', 'setCustomer', 'setCustomerIsGuest'], @@ -739,7 +751,7 @@ class QuoteManagementTest extends \PHPUnit_Framework_TestCase $this->checkoutSessionMock->expects($this->once())->method('setLastOrderId')->with($orderId); $this->checkoutSessionMock->expects($this->once())->method('setLastRealOrderId')->with($orderIncrementId); $this->checkoutSessionMock->expects($this->once())->method('setLastOrderStatus')->with($orderStatus); - + $this->assertEquals($orderId, $service->placeOrder($cartId)); } @@ -935,7 +947,7 @@ class QuoteManagementTest extends \PHPUnit_Framework_TestCase $order = $this->getMock( 'Magento\Sales\Model\Order', ['setShippingAddress', 'getAddressesCollection', 'getAddresses', 'getBillingAddress', 'addAddresses', - 'setBillingAddress', 'setAddresses', 'setPayment', 'setItems', 'setQuoteId'], + 'setBillingAddress', 'setAddresses', 'setPayment', 'setItems', 'setQuoteId'], [], '', false @@ -979,4 +991,37 @@ class QuoteManagementTest extends \PHPUnit_Framework_TestCase ->willReturn($cartMock); $this->assertEquals($cartMock, $this->model->getCartForCustomer($customerId)); } + + /** + * Get any object property value. + * + * @param $object + * @param $property + * @return mixed + */ + protected function getPropertyValue($object, $property) + { + $reflection = new \ReflectionClass(get_class($object)); + $reflectionProperty = $reflection->getProperty($property); + $reflectionProperty->setAccessible(true); + + return $reflectionProperty->getValue($object); + } + + /** + * Set object property value. + * + * @param $object + * @param $property + * @param $value + */ + protected function setPropertyValue(&$object, $property, $value) + { + $reflection = new \ReflectionClass(get_class($object)); + $reflectionProperty = $reflection->getProperty($property); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($object, $value); + + return $object; + } } diff --git a/app/code/Magento/SalesRule/view/frontend/web/js/action/set-coupon-code.js b/app/code/Magento/SalesRule/view/frontend/web/js/action/set-coupon-code.js index b636a613d622ffe50570117408c6be86b414df0a..606fe4013ea762c5460c49202907463d7c7952f2 100644 --- a/app/code/Magento/SalesRule/view/frontend/web/js/action/set-coupon-code.js +++ b/app/code/Magento/SalesRule/view/frontend/web/js/action/set-coupon-code.js @@ -13,26 +13,24 @@ define( 'jquery', 'Magento_Checkout/js/model/quote', 'Magento_Checkout/js/model/resource-url-manager', - 'Magento_Checkout/js/model/payment-service', 'Magento_Checkout/js/model/error-processor', 'Magento_SalesRule/js/model/payment/discount-messages', 'mage/storage', - 'Magento_Checkout/js/action/get-totals', 'mage/translate', - 'Magento_Checkout/js/model/payment/method-list' + 'Magento_Checkout/js/action/get-payment-information', + 'Magento_Checkout/js/model/totals' ], function ( ko, $, quote, urlManager, - paymentService, errorProcessor, messageContainer, storage, - getTotalsAction, $t, - paymentMethodList + getPaymentInformationAction, + totals ) { 'use strict'; return function (couponCode, isApplied, isLoading) { @@ -49,11 +47,10 @@ define( var deferred = $.Deferred(); isLoading(false); isApplied(true); - getTotalsAction([], deferred); - $.when(deferred).done(function() { - paymentService.setPaymentMethods( - paymentMethodList() - ); + totals.isLoading(true); + getPaymentInformationAction(deferred); + $.when(deferred).done(function () { + totals.isLoading(false); }); messageContainer.addSuccessMessage({'message': message}); } @@ -61,6 +58,7 @@ define( ).fail( function (response) { isLoading(false); + totals.isLoading(false); errorProcessor.process(response, messageContainer); } ); diff --git a/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php b/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php index c67470fd69ae592503f7b18dba7eee920ba133a1..a201724eaaa6824e9c0500b2108699be44e33285 100644 --- a/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Quote/Api/GuestCartManagementTest.php @@ -70,9 +70,9 @@ class GuestCartManagementTest extends WebapiAbstract $quote = $this->objectManager->create('Magento\Quote\Model\Quote')->load('test01', 'reserved_order_id'); $cartId = $quote->getId(); /** @var \Magento\Quote\Model\QuoteIdMask $quoteIdMask */ - $quoteIdMask = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() - ->create('Magento\Quote\Model\QuoteIdMaskFactory') - ->create(); + $quoteIdMaskFactory = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->create('Magento\Quote\Model\QuoteIdMaskFactory'); + $quoteIdMask = $quoteIdMaskFactory->create(); $quoteIdMask->load($cartId, 'quote_id'); //Use masked cart Id $cartId = $quoteIdMask->getMaskedId(); @@ -110,6 +110,7 @@ class GuestCartManagementTest extends WebapiAbstract $this->assertEquals($customer->getId(), $quote->getCustomerId()); $this->assertEquals($customer->getFirstname(), $quote->getCustomerFirstname()); $this->assertEquals($customer->getLastname(), $quote->getCustomerLastname()); + $this->assertNull($quoteIdMaskFactory->create()->load($cartId, 'masked_id')->getId()); } /**