Skip to content

Commit 6383bde

Browse files
committed
ACP2E-961: Customer attribute 'Is required' attribute is not properly overridden per website scope in Admin
1 parent 65a1198 commit 6383bde

File tree

7 files changed

+302
-7
lines changed

7 files changed

+302
-7
lines changed

app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
use Magento\Framework\View\Result\PageFactory;
4747
use Magento\Newsletter\Model\SubscriberFactory;
4848
use Magento\Newsletter\Model\SubscriptionManagerInterface;
49+
use Magento\Store\Model\StoreManagerInterface;
4950

5051
/**
5152
* Save customer action.
@@ -69,6 +70,11 @@ class Save extends \Magento\Customer\Controller\Adminhtml\Index implements HttpP
6970
*/
7071
private $addressRegistry;
7172

73+
/**
74+
* @var StoreManagerInterface
75+
*/
76+
private $storeManager;
77+
7278
/**
7379
* Constructor
7480
*
@@ -99,6 +105,7 @@ class Save extends \Magento\Customer\Controller\Adminhtml\Index implements HttpP
99105
* @param JsonFactory $resultJsonFactory
100106
* @param SubscriptionManagerInterface $subscriptionManager
101107
* @param AddressRegistry|null $addressRegistry
108+
* @param StoreManagerInterface|null $storeManager
102109
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
103110
*/
104111
public function __construct(
@@ -128,7 +135,8 @@ public function __construct(
128135
ForwardFactory $resultForwardFactory,
129136
JsonFactory $resultJsonFactory,
130137
SubscriptionManagerInterface $subscriptionManager,
131-
AddressRegistry $addressRegistry = null
138+
AddressRegistry $addressRegistry = null,
139+
?StoreManagerInterface $storeManager = null
132140
) {
133141
parent::__construct(
134142
$context,
@@ -159,6 +167,7 @@ public function __construct(
159167
);
160168
$this->subscriptionManager = $subscriptionManager;
161169
$this->addressRegistry = $addressRegistry ?: ObjectManager::getInstance()->get(AddressRegistry::class);
170+
$this->storeManager = $storeManager ?? ObjectManager::getInstance()->get(StoreManagerInterface::class);
162171
}
163172

164173
/**
@@ -249,6 +258,7 @@ protected function _extractData(
249258
* @param array $extractedCustomerData
250259
* @return array
251260
* @deprecated 102.0.1 must be removed because addresses are save separately for now
261+
* @see \Magento\Customer\Controller\Adminhtml\Address\Save
252262
*/
253263
protected function saveDefaultFlags(array $addressIdList, array &$extractedCustomerData)
254264
{
@@ -291,6 +301,7 @@ protected function saveDefaultFlags(array $addressIdList, array &$extractedCusto
291301
* @param array $extractedCustomerData
292302
* @return array
293303
* @deprecated 102.0.1 addresses are saved separately for now
304+
* @see \Magento\Customer\Controller\Adminhtml\Address\Save
294305
*/
295306
protected function _extractCustomerAddressData(array &$extractedCustomerData)
296307
{
@@ -359,6 +370,13 @@ public function execute()
359370
}
360371
}
361372

373+
$storeId = $customer->getStoreId();
374+
if (empty($storeId)) {
375+
$website = $this->storeManager->getWebsite($customer->getWebsiteId());
376+
$storeId = current($website->getStoreIds());
377+
}
378+
$this->storeManager->setCurrentStore($storeId);
379+
362380
// Save customer
363381
if ($customerId) {
364382
$this->_customerRepository->save($customer);
@@ -465,6 +483,7 @@ private function updateSubscriptions(CustomerInterface $customer): void
465483
*
466484
* @return EmailNotificationInterface
467485
* @deprecated 100.1.0
486+
* @see no alternative
468487
*/
469488
private function getEmailNotification()
470489
{

app/code/Magento/Customer/Controller/Adminhtml/Index/Validate.php

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,120 @@
55
*/
66
namespace Magento\Customer\Controller\Adminhtml\Index;
77

8+
use Magento\Customer\Api\AccountManagementInterface;
9+
use Magento\Customer\Api\AddressRepositoryInterface;
10+
use Magento\Customer\Api\CustomerRepositoryInterface;
11+
use Magento\Customer\Api\Data\AddressInterfaceFactory;
12+
use Magento\Customer\Api\Data\CustomerInterfaceFactory;
13+
use Magento\Customer\Model\Address\Mapper;
14+
use Magento\Framework\Api\DataObjectHelper;
815
use Magento\Framework\App\Action\HttpGetActionInterface;
916
use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface;
1017
use Magento\Customer\Api\Data\CustomerInterface;
18+
use Magento\Framework\App\ObjectManager;
19+
use Magento\Framework\DataObjectFactory as ObjectFactory;
1120
use Magento\Framework\Message\Error;
1221
use Magento\Customer\Controller\Adminhtml\Index as CustomerAction;
22+
use Magento\Store\Model\StoreManagerInterface;
1323

1424
/**
1525
* Class for validation of customer
1626
*/
1727
class Validate extends CustomerAction implements HttpPostActionInterface, HttpGetActionInterface
1828
{
29+
/**
30+
* @var StoreManagerInterface
31+
*/
32+
private $storeManager;
33+
34+
/**
35+
* @param \Magento\Backend\App\Action\Context $context
36+
* @param \Magento\Framework\Registry $coreRegistry
37+
* @param \Magento\Framework\App\Response\Http\FileFactory $fileFactory
38+
* @param \Magento\Customer\Model\CustomerFactory $customerFactory
39+
* @param \Magento\Customer\Model\AddressFactory $addressFactory
40+
* @param \Magento\Customer\Model\Metadata\FormFactory $formFactory
41+
* @param \Magento\Newsletter\Model\SubscriberFactory $subscriberFactory
42+
* @param \Magento\Customer\Helper\View $viewHelper
43+
* @param \Magento\Framework\Math\Random $random
44+
* @param CustomerRepositoryInterface $customerRepository
45+
* @param \Magento\Framework\Api\ExtensibleDataObjectConverter $extensibleDataObjectConverter
46+
* @param Mapper $addressMapper
47+
* @param AccountManagementInterface $customerAccountManagement
48+
* @param AddressRepositoryInterface $addressRepository
49+
* @param CustomerInterfaceFactory $customerDataFactory
50+
* @param AddressInterfaceFactory $addressDataFactory
51+
* @param \Magento\Customer\Model\Customer\Mapper $customerMapper
52+
* @param \Magento\Framework\Reflection\DataObjectProcessor $dataObjectProcessor
53+
* @param DataObjectHelper $dataObjectHelper
54+
* @param ObjectFactory $objectFactory
55+
* @param \Magento\Framework\View\LayoutFactory $layoutFactory
56+
* @param \Magento\Framework\View\Result\LayoutFactory $resultLayoutFactory
57+
* @param \Magento\Framework\View\Result\PageFactory $resultPageFactory
58+
* @param \Magento\Backend\Model\View\Result\ForwardFactory $resultForwardFactory
59+
* @param \Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory
60+
* @param StoreManagerInterface|null $storeManager
61+
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
62+
*/
63+
public function __construct(
64+
\Magento\Backend\App\Action\Context $context,
65+
\Magento\Framework\Registry $coreRegistry,
66+
\Magento\Framework\App\Response\Http\FileFactory $fileFactory,
67+
\Magento\Customer\Model\CustomerFactory $customerFactory,
68+
\Magento\Customer\Model\AddressFactory $addressFactory,
69+
\Magento\Customer\Model\Metadata\FormFactory $formFactory,
70+
\Magento\Newsletter\Model\SubscriberFactory $subscriberFactory,
71+
\Magento\Customer\Helper\View $viewHelper,
72+
\Magento\Framework\Math\Random $random,
73+
CustomerRepositoryInterface $customerRepository,
74+
\Magento\Framework\Api\ExtensibleDataObjectConverter $extensibleDataObjectConverter,
75+
Mapper $addressMapper,
76+
AccountManagementInterface $customerAccountManagement,
77+
AddressRepositoryInterface $addressRepository,
78+
CustomerInterfaceFactory $customerDataFactory,
79+
AddressInterfaceFactory $addressDataFactory,
80+
\Magento\Customer\Model\Customer\Mapper $customerMapper,
81+
\Magento\Framework\Reflection\DataObjectProcessor $dataObjectProcessor,
82+
DataObjectHelper $dataObjectHelper,
83+
ObjectFactory $objectFactory,
84+
\Magento\Framework\View\LayoutFactory $layoutFactory,
85+
\Magento\Framework\View\Result\LayoutFactory $resultLayoutFactory,
86+
\Magento\Framework\View\Result\PageFactory $resultPageFactory,
87+
\Magento\Backend\Model\View\Result\ForwardFactory $resultForwardFactory,
88+
\Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory,
89+
?StoreManagerInterface $storeManager = null
90+
) {
91+
parent::__construct(
92+
$context,
93+
$coreRegistry,
94+
$fileFactory,
95+
$customerFactory,
96+
$addressFactory,
97+
$formFactory,
98+
$subscriberFactory,
99+
$viewHelper,
100+
$random,
101+
$customerRepository,
102+
$extensibleDataObjectConverter,
103+
$addressMapper,
104+
$customerAccountManagement,
105+
$addressRepository,
106+
$customerDataFactory,
107+
$addressDataFactory,
108+
$customerMapper,
109+
$dataObjectProcessor,
110+
$dataObjectHelper,
111+
$objectFactory,
112+
$layoutFactory,
113+
$resultLayoutFactory,
114+
$resultPageFactory,
115+
$resultForwardFactory,
116+
$resultJsonFactory
117+
);
118+
119+
$this->storeManager = $storeManager ?? ObjectManager::getInstance()->get(StoreManagerInterface::class);
120+
}
121+
19122
/**
20123
* Customer validation
21124
*
@@ -55,6 +158,11 @@ protected function _validateCustomer($response)
55158
$entity_id = $submittedData['entity_id'];
56159
$customer->setId($entity_id);
57160
}
161+
if (isset($data['website_id'])) {
162+
$website = $this->storeManager->getWebsite($data['website_id']);
163+
$storeId = current($website->getStoreIds());
164+
$this->storeManager->setCurrentStore($storeId);
165+
}
58166
$errors = $this->customerAccountManagement->validate($customer)->getMessages();
59167
} catch (\Magento\Framework\Validator\Exception $exception) {
60168
/* @var $error Error */

app/code/Magento/Customer/Model/AttributeMetadataResolver.php

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ class AttributeMetadataResolver
3737
'notice' => 'note',
3838
'default' => 'default_value',
3939
'size' => 'multiline_count',
40+
'attributeId' => 'attribute_id',
4041
];
4142

4243
/**
@@ -80,28 +81,37 @@ class AttributeMetadataResolver
8081
*/
8182
private $groupManagement;
8283

84+
/**
85+
* @var AttributeWebsiteRequired|null
86+
*/
87+
private ?AttributeWebsiteRequired $attributeWebsiteRequired;
88+
8389
/**
8490
* @param CountryWithWebsites $countryWithWebsiteSource
8591
* @param EavValidationRules $eavValidationRules
8692
* @param FileUploaderDataResolver $fileUploaderDataResolver
8793
* @param ContextInterface $context
8894
* @param ShareConfig $shareConfig
8995
* @param GroupManagement|null $groupManagement
96+
* @param AttributeWebsiteRequired|null $attributeWebsiteRequired
9097
*/
9198
public function __construct(
9299
CountryWithWebsites $countryWithWebsiteSource,
93100
EavValidationRules $eavValidationRules,
94101
FileUploaderDataResolver $fileUploaderDataResolver,
95102
ContextInterface $context,
96103
ShareConfig $shareConfig,
97-
?GroupManagement $groupManagement = null
104+
?GroupManagement $groupManagement = null,
105+
?AttributeWebsiteRequired $attributeWebsiteRequired = null
98106
) {
99107
$this->countryWithWebsiteSource = $countryWithWebsiteSource;
100108
$this->eavValidationRules = $eavValidationRules;
101109
$this->fileUploaderDataResolver = $fileUploaderDataResolver;
102110
$this->context = $context;
103111
$this->shareConfig = $shareConfig;
104112
$this->groupManagement = $groupManagement ?? ObjectManager::getInstance()->get(GroupManagement::class);
113+
$this->attributeWebsiteRequired = $attributeWebsiteRequired ??
114+
ObjectManager::getInstance()->get(AttributeWebsiteRequired::class);
105115
}
106116

107117
/**
@@ -238,5 +248,48 @@ public function processWebsiteMeta(&$meta): void
238248
'field' => 'website_ids'
239249
];
240250
}
251+
252+
if (isset($meta[CustomerInterface::WEBSITE_ID])) {
253+
$this->processWebsiteIsRequired($meta);
254+
}
255+
}
256+
257+
/**
258+
* Adds attribute 'required' validation according to the scope.
259+
*
260+
* @param array $meta
261+
* @return void
262+
*/
263+
private function processWebsiteIsRequired(&$meta): void
264+
{
265+
$attributeIds = array_values(
266+
array_map(
267+
function ($attribute) {
268+
return $attribute['arguments']['data']['config']['attributeId'];
269+
},
270+
$meta
271+
)
272+
);
273+
$websiteIds = array_values(
274+
array_map(
275+
function ($option) {
276+
return (int)$option['value'];
277+
},
278+
$meta[CustomerInterface::WEBSITE_ID]['arguments']['data']['config']['options']
279+
)
280+
);
281+
282+
$websiteRequired = $this->attributeWebsiteRequired->get($attributeIds, $websiteIds);
283+
array_walk(
284+
$meta,
285+
function (&$attribute) use ($websiteRequired) {
286+
$id = $attribute['arguments']['data']['config']['attributeId'];
287+
unset($attribute['arguments']['data']['config']['attributeId']);
288+
if (!empty($websiteRequired[$id])) {
289+
$attribute['arguments']['data']['config']
290+
['validation']['required-entry-website'] = $websiteRequired[$id];
291+
}
292+
}
293+
);
241294
}
242295
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Customer\Model;
9+
10+
use Magento\Framework\DB\Select;
11+
use Magento\Framework\DB\Sql\UnionExpression;
12+
13+
class AttributeWebsiteRequired
14+
{
15+
/**
16+
* @var ResourceModel\Attribute
17+
*/
18+
private ResourceModel\Attribute $attribute;
19+
20+
/**
21+
* @param ResourceModel\Attribute $attribute
22+
*/
23+
public function __construct(
24+
ResourceModel\Attribute $attribute
25+
) {
26+
$this->attribute = $attribute;
27+
}
28+
29+
/**
30+
* Returns the attributes value 'is_required' for all websites.
31+
*
32+
* @param array $attributeIds
33+
* @param array $websiteIds
34+
* @return array
35+
*/
36+
public function get(array $attributeIds, array $websiteIds): array
37+
{
38+
$defaultScope = 0;
39+
$connection = $this->attribute->getConnection();
40+
$selects[] = $connection->select()->from(
41+
[$this->attribute->getTable('customer_eav_attribute_website')],
42+
['attribute_id', 'website_id', 'is_required']
43+
)->where('attribute_id IN (?) AND is_required IS NOT NULL', $attributeIds);
44+
45+
$selects[] = $connection->select()->from(
46+
[$this->attribute->getTable('eav_attribute')],
47+
['attribute_id', 'website_id' => new \Zend_Db_Expr($defaultScope), 'is_required']
48+
)->where('attribute_id IN (?) AND is_required IS NOT NULL', $attributeIds);
49+
50+
$unionSelect = new UnionExpression($selects, Select::SQL_UNION_ALL);
51+
$data = $connection->fetchAll($unionSelect);
52+
$isRequired = [];
53+
foreach ($data as $row) {
54+
$isRequired[$row['website_id']][$row['attribute_id']] = (bool)$row['is_required'];
55+
}
56+
57+
$result = [];
58+
foreach ($attributeIds as $attributeId) {
59+
foreach ($websiteIds as $websiteId) {
60+
if (isset($isRequired[$websiteId][$attributeId])) {
61+
if ($isRequired[$websiteId][$attributeId]) {
62+
$result[$attributeId][] = $websiteId;
63+
}
64+
} elseif ($isRequired[$defaultScope][$attributeId]) {
65+
$result[$attributeId][] = $websiteId;
66+
}
67+
}
68+
}
69+
70+
return $result;
71+
}
72+
}

0 commit comments

Comments
 (0)