Save.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Customer\Controller\Adminhtml\Index;
  7. use Magento\Customer\Api\AccountManagementInterface;
  8. use Magento\Customer\Api\AddressRepositoryInterface;
  9. use Magento\Customer\Api\CustomerRepositoryInterface;
  10. use Magento\Customer\Model\Address\Mapper;
  11. use Magento\Customer\Model\AddressRegistry;
  12. use Magento\Framework\Api\DataObjectHelper;
  13. use Magento\Customer\Api\Data\AddressInterfaceFactory;
  14. use Magento\Customer\Api\Data\CustomerInterfaceFactory;
  15. use Magento\Framework\DataObjectFactory as ObjectFactory;
  16. use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface;
  17. use Magento\Customer\Api\AddressMetadataInterface;
  18. use Magento\Customer\Api\CustomerMetadataInterface;
  19. use Magento\Customer\Api\Data\CustomerInterface;
  20. use Magento\Customer\Controller\RegistryConstants;
  21. use Magento\Customer\Model\EmailNotificationInterface;
  22. use Magento\Customer\Model\Metadata\Form;
  23. use Magento\Framework\Exception\LocalizedException;
  24. use Magento\Framework\Exception\NoSuchEntityException;
  25. use Magento\Framework\App\ObjectManager;
  26. /**
  27. * Save customer action.
  28. *
  29. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  30. */
  31. class Save extends \Magento\Customer\Controller\Adminhtml\Index implements HttpPostActionInterface
  32. {
  33. /**
  34. * @var EmailNotificationInterface
  35. */
  36. private $emailNotification;
  37. /**
  38. * @var AddressRegistry
  39. */
  40. private $addressRegistry;
  41. /**
  42. * Constructor
  43. *
  44. * @param \Magento\Backend\App\Action\Context $context
  45. * @param \Magento\Framework\Registry $coreRegistry
  46. * @param \Magento\Framework\App\Response\Http\FileFactory $fileFactory
  47. * @param \Magento\Customer\Model\CustomerFactory $customerFactory
  48. * @param \Magento\Customer\Model\AddressFactory $addressFactory
  49. * @param \Magento\Customer\Model\Metadata\FormFactory $formFactory
  50. * @param \Magento\Newsletter\Model\SubscriberFactory $subscriberFactory
  51. * @param \Magento\Customer\Helper\View $viewHelper
  52. * @param \Magento\Framework\Math\Random $random
  53. * @param CustomerRepositoryInterface $customerRepository
  54. * @param \Magento\Framework\Api\ExtensibleDataObjectConverter $extensibleDataObjectConverter
  55. * @param Mapper $addressMapper
  56. * @param AccountManagementInterface $customerAccountManagement
  57. * @param AddressRepositoryInterface $addressRepository
  58. * @param CustomerInterfaceFactory $customerDataFactory
  59. * @param AddressInterfaceFactory $addressDataFactory
  60. * @param \Magento\Customer\Model\Customer\Mapper $customerMapper
  61. * @param \Magento\Framework\Reflection\DataObjectProcessor $dataObjectProcessor
  62. * @param DataObjectHelper $dataObjectHelper
  63. * @param ObjectFactory $objectFactory
  64. * @param \Magento\Framework\View\LayoutFactory $layoutFactory
  65. * @param \Magento\Framework\View\Result\LayoutFactory $resultLayoutFactory
  66. * @param \Magento\Framework\View\Result\PageFactory $resultPageFactory
  67. * @param \Magento\Backend\Model\View\Result\ForwardFactory $resultForwardFactory
  68. * @param \Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory
  69. * @param AddressRegistry|null $addressRegistry
  70. * @SuppressWarnings(PHPMD.ExcessiveParameterList)
  71. */
  72. public function __construct(
  73. \Magento\Backend\App\Action\Context $context,
  74. \Magento\Framework\Registry $coreRegistry,
  75. \Magento\Framework\App\Response\Http\FileFactory $fileFactory,
  76. \Magento\Customer\Model\CustomerFactory $customerFactory,
  77. \Magento\Customer\Model\AddressFactory $addressFactory,
  78. \Magento\Customer\Model\Metadata\FormFactory $formFactory,
  79. \Magento\Newsletter\Model\SubscriberFactory $subscriberFactory,
  80. \Magento\Customer\Helper\View $viewHelper,
  81. \Magento\Framework\Math\Random $random,
  82. CustomerRepositoryInterface $customerRepository,
  83. \Magento\Framework\Api\ExtensibleDataObjectConverter $extensibleDataObjectConverter,
  84. Mapper $addressMapper,
  85. AccountManagementInterface $customerAccountManagement,
  86. AddressRepositoryInterface $addressRepository,
  87. CustomerInterfaceFactory $customerDataFactory,
  88. AddressInterfaceFactory $addressDataFactory,
  89. \Magento\Customer\Model\Customer\Mapper $customerMapper,
  90. \Magento\Framework\Reflection\DataObjectProcessor $dataObjectProcessor,
  91. DataObjectHelper $dataObjectHelper,
  92. ObjectFactory $objectFactory,
  93. \Magento\Framework\View\LayoutFactory $layoutFactory,
  94. \Magento\Framework\View\Result\LayoutFactory $resultLayoutFactory,
  95. \Magento\Framework\View\Result\PageFactory $resultPageFactory,
  96. \Magento\Backend\Model\View\Result\ForwardFactory $resultForwardFactory,
  97. \Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory,
  98. AddressRegistry $addressRegistry = null
  99. ) {
  100. parent::__construct(
  101. $context,
  102. $coreRegistry,
  103. $fileFactory,
  104. $customerFactory,
  105. $addressFactory,
  106. $formFactory,
  107. $subscriberFactory,
  108. $viewHelper,
  109. $random,
  110. $customerRepository,
  111. $extensibleDataObjectConverter,
  112. $addressMapper,
  113. $customerAccountManagement,
  114. $addressRepository,
  115. $customerDataFactory,
  116. $addressDataFactory,
  117. $customerMapper,
  118. $dataObjectProcessor,
  119. $dataObjectHelper,
  120. $objectFactory,
  121. $layoutFactory,
  122. $resultLayoutFactory,
  123. $resultPageFactory,
  124. $resultForwardFactory,
  125. $resultJsonFactory
  126. );
  127. $this->addressRegistry = $addressRegistry ?: ObjectManager::getInstance()->get(AddressRegistry::class);
  128. }
  129. /**
  130. * Reformat customer account data to be compatible with customer service interface
  131. *
  132. * @return array
  133. */
  134. protected function _extractCustomerData()
  135. {
  136. $customerData = [];
  137. if ($this->getRequest()->getPost('customer')) {
  138. $additionalAttributes = [
  139. CustomerInterface::DEFAULT_BILLING,
  140. CustomerInterface::DEFAULT_SHIPPING,
  141. 'confirmation',
  142. 'sendemail_store_id',
  143. 'extension_attributes',
  144. ];
  145. $customerData = $this->_extractData(
  146. 'adminhtml_customer',
  147. CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER,
  148. $additionalAttributes,
  149. 'customer'
  150. );
  151. }
  152. if (isset($customerData['disable_auto_group_change'])) {
  153. $customerData['disable_auto_group_change'] = (int) filter_var(
  154. $customerData['disable_auto_group_change'],
  155. FILTER_VALIDATE_BOOLEAN
  156. );
  157. }
  158. return $customerData;
  159. }
  160. /**
  161. * Perform customer data filtration based on form code and form object
  162. *
  163. * @param string $formCode The code of EAV form to take the list of attributes from
  164. * @param string $entityType entity type for the form
  165. * @param string[] $additionalAttributes The list of attribute codes to skip filtration for
  166. * @param string $scope scope of the request
  167. * @return array
  168. */
  169. protected function _extractData(
  170. $formCode,
  171. $entityType,
  172. $additionalAttributes = [],
  173. $scope = null
  174. ) {
  175. $metadataForm = $this->getMetadataForm($entityType, $formCode, $scope);
  176. $formData = $metadataForm->extractData($this->getRequest(), $scope);
  177. $formData = $metadataForm->compactData($formData);
  178. // Initialize additional attributes
  179. /** @var \Magento\Framework\DataObject $object */
  180. $object = $this->_objectFactory->create(['data' => $this->getRequest()->getPostValue()]);
  181. $requestData = $object->getData($scope);
  182. foreach ($additionalAttributes as $attributeCode) {
  183. $formData[$attributeCode] = isset($requestData[$attributeCode]) ? $requestData[$attributeCode] : false;
  184. }
  185. // Unset unused attributes
  186. $formAttributes = $metadataForm->getAttributes();
  187. foreach ($formAttributes as $attribute) {
  188. /** @var \Magento\Customer\Api\Data\AttributeMetadataInterface $attribute */
  189. $attributeCode = $attribute->getAttributeCode();
  190. if ($attribute->getFrontendInput() != 'boolean'
  191. && $formData[$attributeCode] === false
  192. ) {
  193. unset($formData[$attributeCode]);
  194. }
  195. }
  196. if (empty($formData['extension_attributes'])) {
  197. unset($formData['extension_attributes']);
  198. }
  199. return $formData;
  200. }
  201. /**
  202. * Saves default_billing and default_shipping flags for customer address
  203. *
  204. * @deprecated 102.0.1 must be removed because addresses are save separately for now
  205. * @param array $addressIdList
  206. * @param array $extractedCustomerData
  207. * @return array
  208. */
  209. protected function saveDefaultFlags(array $addressIdList, array & $extractedCustomerData)
  210. {
  211. $result = [];
  212. $extractedCustomerData[CustomerInterface::DEFAULT_BILLING] = null;
  213. $extractedCustomerData[CustomerInterface::DEFAULT_SHIPPING] = null;
  214. foreach ($addressIdList as $addressId) {
  215. $scope = sprintf('address/%s', $addressId);
  216. $addressData = $this->_extractData(
  217. 'adminhtml_customer_address',
  218. AddressMetadataInterface::ENTITY_TYPE_ADDRESS,
  219. ['default_billing', 'default_shipping'],
  220. $scope
  221. );
  222. if (is_numeric($addressId)) {
  223. $addressData['id'] = $addressId;
  224. }
  225. // Set default billing and shipping flags to customer
  226. if (!empty($addressData['default_billing']) && $addressData['default_billing'] === 'true') {
  227. $extractedCustomerData[CustomerInterface::DEFAULT_BILLING] = $addressId;
  228. $addressData['default_billing'] = true;
  229. } else {
  230. $addressData['default_billing'] = false;
  231. }
  232. if (!empty($addressData['default_shipping']) && $addressData['default_shipping'] === 'true') {
  233. $extractedCustomerData[CustomerInterface::DEFAULT_SHIPPING] = $addressId;
  234. $addressData['default_shipping'] = true;
  235. } else {
  236. $addressData['default_shipping'] = false;
  237. }
  238. $result[] = $addressData;
  239. }
  240. return $result;
  241. }
  242. /**
  243. * Reformat customer addresses data to be compatible with customer service interface
  244. *
  245. * @deprecated 102.0.1 addresses are saved separately for now
  246. * @param array $extractedCustomerData
  247. * @return array
  248. */
  249. protected function _extractCustomerAddressData(array & $extractedCustomerData)
  250. {
  251. $addresses = $this->getRequest()->getPost('address');
  252. $result = [];
  253. if (is_array($addresses)) {
  254. if (isset($addresses['_template_'])) {
  255. unset($addresses['_template_']);
  256. }
  257. $addressIdList = array_keys($addresses);
  258. $result = $this->saveDefaultFlags($addressIdList, $extractedCustomerData);
  259. }
  260. return $result;
  261. }
  262. /**
  263. * Save customer action
  264. *
  265. * @return \Magento\Backend\Model\View\Result\Redirect
  266. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  267. * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
  268. * @SuppressWarnings(PHPMD.NPathComplexity)
  269. */
  270. public function execute()
  271. {
  272. $returnToEdit = false;
  273. $originalRequestData = $this->getRequest()->getPostValue();
  274. $customerId = $this->getCurrentCustomerId();
  275. if ($originalRequestData) {
  276. try {
  277. // optional fields might be set in request for future processing by observers in other modules
  278. $customerData = $this->_extractCustomerData();
  279. if ($customerId) {
  280. $currentCustomer = $this->_customerRepository->getById($customerId);
  281. // No need to validate customer address while editing customer profile
  282. $this->disableAddressValidation($currentCustomer);
  283. $customerData = array_merge(
  284. $this->customerMapper->toFlatArray($currentCustomer),
  285. $customerData
  286. );
  287. $customerData['id'] = $customerId;
  288. }
  289. /** @var CustomerInterface $customer */
  290. $customer = $this->customerDataFactory->create();
  291. $this->dataObjectHelper->populateWithArray(
  292. $customer,
  293. $customerData,
  294. \Magento\Customer\Api\Data\CustomerInterface::class
  295. );
  296. $this->_eventManager->dispatch(
  297. 'adminhtml_customer_prepare_save',
  298. ['customer' => $customer, 'request' => $this->getRequest()]
  299. );
  300. if (isset($customerData['sendemail_store_id'])) {
  301. $customer->setStoreId($customerData['sendemail_store_id']);
  302. }
  303. // Save customer
  304. if ($customerId) {
  305. $this->_customerRepository->save($customer);
  306. $this->getEmailNotification()->credentialsChanged($customer, $currentCustomer->getEmail());
  307. } else {
  308. $customer = $this->customerAccountManagement->createAccount($customer);
  309. $customerId = $customer->getId();
  310. }
  311. $isSubscribed = null;
  312. if ($this->_authorization->isAllowed(null)) {
  313. $isSubscribed = $this->getRequest()->getPost('subscription');
  314. }
  315. if ($isSubscribed !== null) {
  316. if ($isSubscribed !== '0') {
  317. $this->_subscriberFactory->create()->subscribeCustomerById($customerId);
  318. } else {
  319. $this->_subscriberFactory->create()->unsubscribeCustomerById($customerId);
  320. }
  321. }
  322. // After save
  323. $this->_eventManager->dispatch(
  324. 'adminhtml_customer_save_after',
  325. ['customer' => $customer, 'request' => $this->getRequest()]
  326. );
  327. $this->_getSession()->unsCustomerFormData();
  328. // Done Saving customer, finish save action
  329. $this->_coreRegistry->register(RegistryConstants::CURRENT_CUSTOMER_ID, $customerId);
  330. $this->messageManager->addSuccess(__('You saved the customer.'));
  331. $returnToEdit = (bool)$this->getRequest()->getParam('back', false);
  332. } catch (\Magento\Framework\Validator\Exception $exception) {
  333. $messages = $exception->getMessages();
  334. if (empty($messages)) {
  335. $messages = $exception->getMessage();
  336. }
  337. $this->_addSessionErrorMessages($messages);
  338. $this->_getSession()->setCustomerFormData($originalRequestData);
  339. $returnToEdit = true;
  340. } catch (\Magento\Framework\Exception\AbstractAggregateException $exception) {
  341. $errors = $exception->getErrors();
  342. $messages = [];
  343. foreach ($errors as $error) {
  344. $messages[] = $error->getMessage();
  345. }
  346. $this->_addSessionErrorMessages($messages);
  347. $this->_getSession()->setCustomerFormData($originalRequestData);
  348. $returnToEdit = true;
  349. } catch (LocalizedException $exception) {
  350. $this->_addSessionErrorMessages($exception->getMessage());
  351. $this->_getSession()->setCustomerFormData($originalRequestData);
  352. $returnToEdit = true;
  353. } catch (\Exception $exception) {
  354. $this->messageManager->addException($exception, __('Something went wrong while saving the customer.'));
  355. $this->_getSession()->setCustomerFormData($originalRequestData);
  356. $returnToEdit = true;
  357. }
  358. }
  359. $resultRedirect = $this->resultRedirectFactory->create();
  360. if ($returnToEdit) {
  361. if ($customerId) {
  362. $resultRedirect->setPath(
  363. 'customer/*/edit',
  364. ['id' => $customerId, '_current' => true]
  365. );
  366. } else {
  367. $resultRedirect->setPath(
  368. 'customer/*/new',
  369. ['_current' => true]
  370. );
  371. }
  372. } else {
  373. $resultRedirect->setPath('customer/index');
  374. }
  375. return $resultRedirect;
  376. }
  377. /**
  378. * Get email notification
  379. *
  380. * @return EmailNotificationInterface
  381. * @deprecated 100.1.0
  382. */
  383. private function getEmailNotification()
  384. {
  385. if (!($this->emailNotification instanceof EmailNotificationInterface)) {
  386. return \Magento\Framework\App\ObjectManager::getInstance()->get(
  387. EmailNotificationInterface::class
  388. );
  389. } else {
  390. return $this->emailNotification;
  391. }
  392. }
  393. /**
  394. * Get metadata form
  395. *
  396. * @param string $entityType
  397. * @param string $formCode
  398. * @param string $scope
  399. * @return Form
  400. */
  401. private function getMetadataForm($entityType, $formCode, $scope)
  402. {
  403. $attributeValues = [];
  404. if ($entityType == CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER) {
  405. $customerId = $this->getCurrentCustomerId();
  406. if ($customerId) {
  407. $customer = $this->_customerRepository->getById($customerId);
  408. $attributeValues = $this->customerMapper->toFlatArray($customer);
  409. }
  410. }
  411. if ($entityType == AddressMetadataInterface::ENTITY_TYPE_ADDRESS) {
  412. $scopeData = explode('/', $scope);
  413. if (isset($scopeData[1]) && is_numeric($scopeData[1])) {
  414. $customerAddress = $this->addressRepository->getById($scopeData[1]);
  415. $attributeValues = $this->addressMapper->toFlatArray($customerAddress);
  416. }
  417. }
  418. $metadataForm = $this->_formFactory->create(
  419. $entityType,
  420. $formCode,
  421. $attributeValues,
  422. false,
  423. Form::DONT_IGNORE_INVISIBLE
  424. );
  425. return $metadataForm;
  426. }
  427. /**
  428. * Retrieve current customer ID
  429. *
  430. * @return int
  431. */
  432. private function getCurrentCustomerId()
  433. {
  434. $originalRequestData = $this->getRequest()->getPostValue(CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER);
  435. $customerId = isset($originalRequestData['entity_id'])
  436. ? $originalRequestData['entity_id']
  437. : null;
  438. return $customerId;
  439. }
  440. /**
  441. * Disable Customer Address Validation
  442. *
  443. * @param CustomerInterface $customer
  444. * @throws NoSuchEntityException
  445. */
  446. private function disableAddressValidation($customer)
  447. {
  448. foreach ($customer->getAddresses() as $address) {
  449. $addressModel = $this->addressRegistry->retrieve($address->getId());
  450. $addressModel->setShouldIgnoreValidation(true);
  451. }
  452. }
  453. }