Skip to content

Commit 566132e

Browse files
committed
Merge remote-tracking branch 'origin/ACP2E-4156' into PR_2025_09_15_muntianu
2 parents d5ad8b7 + 4eea4da commit 566132e

File tree

15 files changed

+634
-82
lines changed

15 files changed

+634
-82
lines changed

app/code/Magento/Checkout/Model/GuestShippingInformationManagement.php

Lines changed: 123 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@
55
*/
66
namespace Magento\Checkout\Model;
77

8+
use Magento\Checkout\Api\Data\ShippingInformationInterface;
9+
use Magento\Customer\Model\AddressFactory;
10+
use Magento\Framework\Exception\InputException;
11+
use Magento\Framework\Exception\LocalizedException;
12+
use Magento\Framework\Validator\Factory as ValidatorFactory;
13+
use Magento\Quote\Api\Data\AddressInterface;
14+
815
class GuestShippingInformationManagement implements \Magento\Checkout\Api\GuestShippingInformationManagementInterface
916
{
1017
/**
@@ -17,31 +24,145 @@ class GuestShippingInformationManagement implements \Magento\Checkout\Api\GuestS
1724
*/
1825
protected $shippingInformationManagement;
1926

27+
/**
28+
* @var ValidatorFactory
29+
*/
30+
private $validatorFactory;
31+
32+
/**
33+
* @var AddressFactory
34+
*/
35+
private $addressFactory;
36+
2037
/**
2138
* @param \Magento\Quote\Model\QuoteIdMaskFactory $quoteIdMaskFactory
2239
* @param \Magento\Checkout\Api\ShippingInformationManagementInterface $shippingInformationManagement
40+
* @param ValidatorFactory $validatorFactory
41+
* @param AddressFactory $addressFactory
2342
* @codeCoverageIgnore
2443
*/
2544
public function __construct(
2645
\Magento\Quote\Model\QuoteIdMaskFactory $quoteIdMaskFactory,
27-
\Magento\Checkout\Api\ShippingInformationManagementInterface $shippingInformationManagement
46+
\Magento\Checkout\Api\ShippingInformationManagementInterface $shippingInformationManagement,
47+
ValidatorFactory $validatorFactory,
48+
AddressFactory $addressFactory
2849
) {
2950
$this->quoteIdMaskFactory = $quoteIdMaskFactory;
3051
$this->shippingInformationManagement = $shippingInformationManagement;
52+
$this->validatorFactory = $validatorFactory;
53+
$this->addressFactory = $addressFactory;
3154
}
3255

3356
/**
3457
* @inheritDoc
58+
*
59+
* @throws InputException
3560
*/
3661
public function saveAddressInformation(
3762
$cartId,
38-
\Magento\Checkout\Api\Data\ShippingInformationInterface $addressInformation
63+
ShippingInformationInterface $addressInformation
3964
) {
65+
$shippingAddress = $addressInformation->getShippingAddress();
66+
if ($shippingAddress) {
67+
$this->validateAddressAttributes($shippingAddress, 'shipping');
68+
}
69+
4070
/** @var $quoteIdMask \Magento\Quote\Model\QuoteIdMask */
4171
$quoteIdMask = $this->quoteIdMaskFactory->create()->load($cartId, 'masked_id');
4272
return $this->shippingInformationManagement->saveAddressInformation(
4373
(int) $quoteIdMask->getQuoteId(),
4474
$addressInformation
4575
);
4676
}
77+
78+
/**
79+
* Validate address attributes using customer_address validator with custom attributes
80+
*
81+
* @param AddressInterface $address
82+
* @param string $addressType
83+
* @return void
84+
* @throws InputException
85+
*/
86+
private function validateAddressAttributes(AddressInterface $address, string $addressType): void
87+
{
88+
try {
89+
$customerAddress = $this->createCustomerAddressFromQuoteAddress($address, $addressType);
90+
$extensionAttributes = $address->getExtensionAttributes();
91+
if ($extensionAttributes) {
92+
$extensionAttributesData = $extensionAttributes->__toArray();
93+
foreach ($extensionAttributesData as $attributeCode => $value) {
94+
if ($value !== null && $value !== '') {
95+
$customerAddress->setData($attributeCode, $value);
96+
}
97+
}
98+
}
99+
$customerAddress->setSkipRequiredValidation(true);
100+
$validator = $this->validatorFactory->createValidator('customer_address', 'save');
101+
if (!$validator->isValid($customerAddress)) {
102+
$this->throwValidationException($validator->getMessages(), $addressType);
103+
}
104+
} catch (LocalizedException $e) {
105+
throw new InputException(__($e->getMessage()));
106+
}
107+
}
108+
109+
/**
110+
* Create customer address object from quote address
111+
*
112+
* @param AddressInterface $address
113+
* @param string $addressType
114+
* @return \Magento\Customer\Model\Address
115+
*/
116+
private function createCustomerAddressFromQuoteAddress(
117+
AddressInterface $address,
118+
string $addressType
119+
): \Magento\Customer\Model\Address {
120+
$customerAddress = $this->addressFactory->create();
121+
$customerAddress->setData([
122+
'firstname' => $address->getFirstname(),
123+
'lastname' => $address->getLastname(),
124+
'street' => $address->getStreet(),
125+
'city' => $address->getCity(),
126+
'region' => $address->getRegion(),
127+
'region_id' => $address->getRegionId(),
128+
'region_code' => $address->getRegionCode(),
129+
'postcode' => $address->getPostcode(),
130+
'country_id' => $address->getCountryId(),
131+
'telephone' => $address->getTelephone(),
132+
'company' => $address->getCompany(),
133+
'email' => $address->getEmail(),
134+
'address_type' => $addressType
135+
]);
136+
137+
return $customerAddress;
138+
}
139+
140+
/**
141+
* Process validator messages and throw validation exception
142+
*
143+
* @param array $messages
144+
* @param string $addressType
145+
* @return void
146+
* @throws InputException
147+
*/
148+
private function throwValidationException(array $messages, string $addressType): void
149+
{
150+
$errorMessages = [];
151+
foreach ($messages as $message) {
152+
if (is_array($message)) {
153+
foreach ($message as $msg) {
154+
$errorMessages[] = $msg;
155+
}
156+
} else {
157+
$errorMessages[] = $message;
158+
}
159+
}
160+
throw new InputException(
161+
__(
162+
'The %1 address contains invalid data: %2',
163+
$addressType,
164+
implode(', ', $errorMessages)
165+
)
166+
);
167+
}
47168
}

app/code/Magento/Checkout/Test/Unit/Model/GuestShippingInformationManagementTest.php

Lines changed: 81 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,46 +11,63 @@
1111
use Magento\Checkout\Api\Data\ShippingInformationInterface;
1212
use Magento\Checkout\Api\ShippingInformationManagementInterface;
1313
use Magento\Checkout\Model\GuestShippingInformationManagement;
14-
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
14+
use Magento\Customer\Model\Address;
15+
use Magento\Customer\Model\AddressFactory;
16+
use Magento\Framework\Exception\InputException;
17+
use Magento\Framework\Validator\Factory as ValidatorFactory;
18+
use Magento\Framework\Validator\ValidatorInterface;
19+
use Magento\Quote\Api\Data\AddressInterface;
1520
use Magento\Quote\Model\QuoteIdMask;
1621
use Magento\Quote\Model\QuoteIdMaskFactory;
1722
use PHPUnit\Framework\MockObject\MockObject;
1823
use PHPUnit\Framework\TestCase;
1924

25+
/**
26+
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
27+
*/
2028
class GuestShippingInformationManagementTest extends TestCase
2129
{
2230
/**
23-
* @var MockObject
31+
* @var ShippingInformationManagementInterface|MockObject
2432
*/
2533
protected $shippingInformationManagementMock;
2634

2735
/**
28-
* @var MockObject
36+
* @var QuoteIdMaskFactory|MockObject
2937
*/
3038
protected $quoteIdMaskFactoryMock;
3139

40+
/**
41+
* @var ValidatorFactory|MockObject
42+
*/
43+
protected $validatorFactoryMock;
44+
45+
/**
46+
* @var AddressFactory|MockObject
47+
*/
48+
protected $addressFactoryMock;
49+
3250
/**
3351
* @var GuestShippingInformationManagement
3452
*/
3553
protected $model;
3654

3755
protected function setUp(): void
3856
{
39-
$objectManager = new ObjectManager($this);
4057
$this->quoteIdMaskFactoryMock = $this->createPartialMock(
4158
QuoteIdMaskFactory::class,
4259
['create']
4360
);
4461
$this->shippingInformationManagementMock = $this->createMock(
4562
ShippingInformationManagementInterface::class
4663
);
47-
48-
$this->model = $objectManager->getObject(
49-
GuestShippingInformationManagement::class,
50-
[
51-
'quoteIdMaskFactory' => $this->quoteIdMaskFactoryMock,
52-
'shippingInformationManagement' => $this->shippingInformationManagementMock
53-
]
64+
$this->validatorFactoryMock = $this->createMock(ValidatorFactory::class);
65+
$this->addressFactoryMock = $this->createMock(AddressFactory::class);
66+
$this->model = new GuestShippingInformationManagement(
67+
$this->quoteIdMaskFactoryMock,
68+
$this->shippingInformationManagementMock,
69+
$this->validatorFactoryMock,
70+
$this->addressFactoryMock
5471
);
5572
}
5673

@@ -59,17 +76,34 @@ public function testSaveAddressInformation()
5976
$cartId = 'masked_id';
6077
$quoteId = '100';
6178
$addressInformationMock = $this->getMockForAbstractClass(ShippingInformationInterface::class);
62-
79+
$shippingAddressMock = $this->getMockForAbstractClass(AddressInterface::class);
80+
$addressInformationMock->expects($this->once())
81+
->method('getShippingAddress')
82+
->willReturn($shippingAddressMock);
83+
$shippingAddressMock->expects($this->once())
84+
->method('getExtensionAttributes')
85+
->willReturn(null);
86+
$customerAddressMock = $this->createMock(Address::class);
87+
$this->addressFactoryMock->expects($this->once())
88+
->method('create')
89+
->willReturn($customerAddressMock);
90+
$validatorMock = $this->createMock(ValidatorInterface::class);
91+
$this->validatorFactoryMock->expects($this->once())
92+
->method('createValidator')
93+
->with('customer_address', 'save')
94+
->willReturn($validatorMock);
95+
$validatorMock->expects($this->once())->method('isValid')->willReturn(true);
6396
$quoteIdMaskMock = $this->getMockBuilder(QuoteIdMask::class)
6497
->addMethods(['getQuoteId'])
6598
->onlyMethods(['load'])
6699
->disableOriginalConstructor()
67100
->getMock();
68101
$this->quoteIdMaskFactoryMock->expects($this->once())->method('create')->willReturn($quoteIdMaskMock);
69-
70-
$quoteIdMaskMock->expects($this->once())->method('load')->with($cartId, 'masked_id')->willReturnSelf();
102+
$quoteIdMaskMock->expects($this->once())
103+
->method('load')
104+
->with($cartId, 'masked_id')
105+
->willReturnSelf();
71106
$quoteIdMaskMock->expects($this->once())->method('getQuoteId')->willReturn($quoteId);
72-
73107
$paymentInformationMock = $this->getMockForAbstractClass(PaymentDetailsInterface::class);
74108
$this->shippingInformationManagementMock->expects($this->once())
75109
->method('saveAddressInformation')
@@ -78,7 +112,39 @@ public function testSaveAddressInformation()
78112
$addressInformationMock
79113
)
80114
->willReturn($paymentInformationMock);
115+
$this->model->saveAddressInformation($cartId, $addressInformationMock);
116+
}
81117

118+
/**
119+
* Validate save address information when it is invalid
120+
*
121+
* @return void
122+
* @throws \PHPUnit\Framework\MockObject\Exception
123+
*/
124+
public function testSaveAddressInformationWithInvalidAddress()
125+
{
126+
$cartId = 'masked_id';
127+
$addressInformationMock = $this->getMockForAbstractClass(ShippingInformationInterface::class);
128+
$shippingAddressMock = $this->getMockForAbstractClass(AddressInterface::class);
129+
$addressInformationMock->expects($this->once())
130+
->method('getShippingAddress')
131+
->willReturn($shippingAddressMock);
132+
$shippingAddressMock->method('getExtensionAttributes')->willReturn(null);
133+
$customerAddressMock = $this->createMock(Address::class);
134+
$this->addressFactoryMock->expects($this->once())->method('create')->willReturn($customerAddressMock);
135+
$validatorMock = $this->createMock(ValidatorInterface::class);
136+
$this->validatorFactoryMock->expects($this->once())
137+
->method('createValidator')
138+
->with('customer_address', 'save')
139+
->willReturn($validatorMock);
140+
$validatorMock->expects($this->once())->method('isValid')->willReturn(false);
141+
$validatorMock->expects($this->once())
142+
->method('getMessages')
143+
->willReturn(['First Name is not valid!', 'Last Name is not valid!']);
144+
$this->expectException(InputException::class);
145+
$this->expectExceptionMessage(
146+
'The shipping address contains invalid data: First Name is not valid!, Last Name is not valid!'
147+
);
82148
$this->model->saveAddressInformation($cartId, $addressInformationMock);
83149
}
84150
}

app/code/Magento/Checkout/i18n/en_US.csv

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,3 +193,4 @@ Changes you made to the cart will not be saved.,Changes you made to the cart wil
193193
Leave,Leave
194194
Cancel,Cancel
195195
VAT,VAT
196+
"The %1 address contains invalid data: %2", "The %1 address contains invalid data: %2"

app/code/Magento/Eav/Model/Attribute/Data/Date.php

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
<?php
22
/**
3-
* Copyright © Magento, Inc. All rights reserved.
4-
* See COPYING.txt for license details.
3+
* Copyright 2011 Adobe
4+
* All Rights Reserved.
55
*/
66
namespace Magento\Eav\Model\Attribute\Data;
77

88
use Magento\Framework\App\RequestInterface;
9+
use Magento\Framework\Exception\LocalizedException;
910

1011
/**
1112
* EAV Entity Attribute Date Data Model
12-
*
13-
* @author Magento Core Team <[email protected]>
1413
*/
1514
class Date extends \Magento\Eav\Model\Attribute\Data\AbstractData
1615
{
@@ -28,12 +27,14 @@ public function extractValue(RequestInterface $request)
2827

2928
/**
3029
* Validate data
30+
*
3131
* Return true or array of errors
3232
*
3333
* @param array|string $value
3434
* @return bool|array
3535
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
3636
* @SuppressWarnings(PHPMD.NPathComplexity)
37+
* @throws LocalizedException
3738
*/
3839
public function validateValue($value)
3940
{
@@ -45,6 +46,10 @@ public function validateValue($value)
4546
$value = $this->getEntity()->getDataUsingMethod($attribute->getAttributeCode());
4647
}
4748

49+
if ((!$attribute->getIsRequired() || ($this->getEntity()?->getSkipRequiredValidation())) && empty($value)) {
50+
return true;
51+
}
52+
4853
if ($attribute->getIsRequired() && empty($value)) {
4954
$label = __($attribute->getStoreLabel());
5055
$errors[] = __('"%1" is a required value.', $label);

0 commit comments

Comments
 (0)