CustomerRepository.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Customer\Model\ResourceModel;
  7. use Magento\Customer\Api\CustomerMetadataInterface;
  8. use Magento\Customer\Api\Data\CustomerInterface;
  9. use Magento\Customer\Api\Data\CustomerSearchResultsInterfaceFactory;
  10. use Magento\Framework\Api\ExtensibleDataObjectConverter;
  11. use Magento\Framework\Api\ExtensionAttribute\JoinProcessorInterface;
  12. use Magento\Customer\Model\CustomerFactory;
  13. use Magento\Customer\Model\CustomerRegistry;
  14. use Magento\Customer\Model\Data\CustomerSecureFactory;
  15. use Magento\Customer\Model\Customer\NotificationStorage;
  16. use Magento\Customer\Model\Delegation\Data\NewOperation;
  17. use Magento\Customer\Api\CustomerRepositoryInterface;
  18. use Magento\Framework\Api\DataObjectHelper;
  19. use Magento\Framework\Api\ImageProcessorInterface;
  20. use Magento\Framework\Api\SearchCriteria\CollectionProcessorInterface;
  21. use Magento\Framework\Api\SearchCriteriaInterface;
  22. use Magento\Framework\Api\Search\FilterGroup;
  23. use Magento\Framework\Event\ManagerInterface;
  24. use Magento\Customer\Model\Delegation\Storage as DelegatedStorage;
  25. use Magento\Framework\App\ObjectManager;
  26. use Magento\Store\Model\StoreManagerInterface;
  27. /**
  28. * Customer repository.
  29. *
  30. * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  31. * @SuppressWarnings(PHPMD.TooManyFields)
  32. */
  33. class CustomerRepository implements CustomerRepositoryInterface
  34. {
  35. /**
  36. * @var CustomerFactory
  37. */
  38. protected $customerFactory;
  39. /**
  40. * @var CustomerSecureFactory
  41. */
  42. protected $customerSecureFactory;
  43. /**
  44. * @var CustomerRegistry
  45. */
  46. protected $customerRegistry;
  47. /**
  48. * @var AddressRepository
  49. */
  50. protected $addressRepository;
  51. /**
  52. * @var Customer
  53. */
  54. protected $customerResourceModel;
  55. /**
  56. * @var CustomerMetadataInterface
  57. */
  58. protected $customerMetadata;
  59. /**
  60. * @var CustomerSearchResultsInterfaceFactory
  61. */
  62. protected $searchResultsFactory;
  63. /**
  64. * @var ManagerInterface
  65. */
  66. protected $eventManager;
  67. /**
  68. * @var StoreManagerInterface
  69. */
  70. protected $storeManager;
  71. /**
  72. * @var ExtensibleDataObjectConverter
  73. */
  74. protected $extensibleDataObjectConverter;
  75. /**
  76. * @var DataObjectHelper
  77. */
  78. protected $dataObjectHelper;
  79. /**
  80. * @var ImageProcessorInterface
  81. */
  82. protected $imageProcessor;
  83. /**
  84. * @var JoinProcessorInterface
  85. */
  86. protected $extensionAttributesJoinProcessor;
  87. /**
  88. * @var CollectionProcessorInterface
  89. */
  90. private $collectionProcessor;
  91. /**
  92. * @var NotificationStorage
  93. */
  94. private $notificationStorage;
  95. /**
  96. * @var DelegatedStorage
  97. */
  98. private $delegatedStorage;
  99. /**
  100. * @param CustomerFactory $customerFactory
  101. * @param CustomerSecureFactory $customerSecureFactory
  102. * @param CustomerRegistry $customerRegistry
  103. * @param AddressRepository $addressRepository
  104. * @param Customer $customerResourceModel
  105. * @param CustomerMetadataInterface $customerMetadata
  106. * @param CustomerSearchResultsInterfaceFactory $searchResultsFactory
  107. * @param ManagerInterface $eventManager
  108. * @param StoreManagerInterface $storeManager
  109. * @param ExtensibleDataObjectConverter $extensibleDataObjectConverter
  110. * @param DataObjectHelper $dataObjectHelper
  111. * @param ImageProcessorInterface $imageProcessor
  112. * @param JoinProcessorInterface $extensionAttributesJoinProcessor
  113. * @param CollectionProcessorInterface $collectionProcessor
  114. * @param NotificationStorage $notificationStorage
  115. * @param DelegatedStorage|null $delegatedStorage
  116. * @SuppressWarnings(PHPMD.ExcessiveParameterList)
  117. */
  118. public function __construct(
  119. CustomerFactory $customerFactory,
  120. CustomerSecureFactory $customerSecureFactory,
  121. CustomerRegistry $customerRegistry,
  122. AddressRepository $addressRepository,
  123. Customer $customerResourceModel,
  124. CustomerMetadataInterface $customerMetadata,
  125. CustomerSearchResultsInterfaceFactory $searchResultsFactory,
  126. ManagerInterface $eventManager,
  127. StoreManagerInterface $storeManager,
  128. ExtensibleDataObjectConverter $extensibleDataObjectConverter,
  129. DataObjectHelper $dataObjectHelper,
  130. ImageProcessorInterface $imageProcessor,
  131. JoinProcessorInterface $extensionAttributesJoinProcessor,
  132. CollectionProcessorInterface $collectionProcessor,
  133. NotificationStorage $notificationStorage,
  134. DelegatedStorage $delegatedStorage = null
  135. ) {
  136. $this->customerFactory = $customerFactory;
  137. $this->customerSecureFactory = $customerSecureFactory;
  138. $this->customerRegistry = $customerRegistry;
  139. $this->addressRepository = $addressRepository;
  140. $this->customerResourceModel = $customerResourceModel;
  141. $this->customerMetadata = $customerMetadata;
  142. $this->searchResultsFactory = $searchResultsFactory;
  143. $this->eventManager = $eventManager;
  144. $this->storeManager = $storeManager;
  145. $this->extensibleDataObjectConverter = $extensibleDataObjectConverter;
  146. $this->dataObjectHelper = $dataObjectHelper;
  147. $this->imageProcessor = $imageProcessor;
  148. $this->extensionAttributesJoinProcessor = $extensionAttributesJoinProcessor;
  149. $this->collectionProcessor = $collectionProcessor;
  150. $this->notificationStorage = $notificationStorage;
  151. $this->delegatedStorage = $delegatedStorage ?? ObjectManager::getInstance()->get(DelegatedStorage::class);
  152. }
  153. /**
  154. * @inheritdoc
  155. * @SuppressWarnings(PHPMD.CyclomaticComplexity)
  156. * @SuppressWarnings(PHPMD.NPathComplexity)
  157. */
  158. public function save(CustomerInterface $customer, $passwordHash = null)
  159. {
  160. /** @var NewOperation|null $delegatedNewOperation */
  161. $delegatedNewOperation = !$customer->getId() ? $this->delegatedStorage->consumeNewOperation() : null;
  162. $prevCustomerData = null;
  163. $prevCustomerDataArr = null;
  164. if ($customer->getId()) {
  165. $prevCustomerData = $this->getById($customer->getId());
  166. $prevCustomerDataArr = $prevCustomerData->__toArray();
  167. }
  168. /** @var $customer \Magento\Customer\Model\Data\Customer */
  169. $customerArr = $customer->__toArray();
  170. $customer = $this->imageProcessor->save(
  171. $customer,
  172. CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER,
  173. $prevCustomerData
  174. );
  175. $origAddresses = $customer->getAddresses();
  176. $customer->setAddresses([]);
  177. $customerData = $this->extensibleDataObjectConverter->toNestedArray($customer, [], CustomerInterface::class);
  178. $customer->setAddresses($origAddresses);
  179. /** @var Customer $customerModel */
  180. $customerModel = $this->customerFactory->create(['data' => $customerData]);
  181. //Model's actual ID field maybe different than "id" so "id" field from $customerData may be ignored.
  182. $customerModel->setId($customer->getId());
  183. $storeId = $customerModel->getStoreId();
  184. if ($storeId === null) {
  185. $customerModel->setStoreId($this->storeManager->getStore()->getId());
  186. }
  187. // Need to use attribute set or future updates can cause data loss
  188. if (!$customerModel->getAttributeSetId()) {
  189. $customerModel->setAttributeSetId(CustomerMetadataInterface::ATTRIBUTE_SET_ID_CUSTOMER);
  190. }
  191. $this->populateCustomerWithSecureData($customerModel, $passwordHash);
  192. // If customer email was changed, reset RpToken info
  193. if ($prevCustomerData && $prevCustomerData->getEmail() !== $customerModel->getEmail()) {
  194. $customerModel->setRpToken(null);
  195. $customerModel->setRpTokenCreatedAt(null);
  196. }
  197. if (!array_key_exists('addresses', $customerArr)
  198. && null !== $prevCustomerDataArr
  199. && array_key_exists('default_billing', $prevCustomerDataArr)
  200. ) {
  201. $customerModel->setDefaultBilling($prevCustomerDataArr['default_billing']);
  202. }
  203. if (!array_key_exists('addresses', $customerArr)
  204. && null !== $prevCustomerDataArr
  205. && array_key_exists('default_shipping', $prevCustomerDataArr)
  206. ) {
  207. $customerModel->setDefaultShipping($prevCustomerDataArr['default_shipping']);
  208. }
  209. $this->setValidationFlag($customerArr, $customerModel);
  210. $customerModel->save();
  211. $this->customerRegistry->push($customerModel);
  212. $customerId = $customerModel->getId();
  213. if (!$customer->getAddresses()
  214. && $delegatedNewOperation
  215. && $delegatedNewOperation->getCustomer()->getAddresses()
  216. ) {
  217. $customer->setAddresses($delegatedNewOperation->getCustomer()->getAddresses());
  218. }
  219. if ($customer->getAddresses() !== null && !$customerModel->getData('ignore_validation_flag')) {
  220. if ($customer->getId()) {
  221. $existingAddresses = $this->getById($customer->getId())->getAddresses();
  222. $getIdFunc = function ($address) {
  223. return $address->getId();
  224. };
  225. $existingAddressIds = array_map($getIdFunc, $existingAddresses);
  226. } else {
  227. $existingAddressIds = [];
  228. }
  229. $savedAddressIds = [];
  230. foreach ($customer->getAddresses() as $address) {
  231. $address->setCustomerId($customerId)
  232. ->setRegion($address->getRegion());
  233. $this->addressRepository->save($address);
  234. if ($address->getId()) {
  235. $savedAddressIds[] = $address->getId();
  236. }
  237. }
  238. $addressIdsToDelete = array_diff($existingAddressIds, $savedAddressIds);
  239. foreach ($addressIdsToDelete as $addressId) {
  240. $this->addressRepository->deleteById($addressId);
  241. }
  242. }
  243. $this->customerRegistry->remove($customerId);
  244. $savedCustomer = $this->get($customer->getEmail(), $customer->getWebsiteId());
  245. $this->eventManager->dispatch(
  246. 'customer_save_after_data_object',
  247. [
  248. 'customer_data_object' => $savedCustomer,
  249. 'orig_customer_data_object' => $prevCustomerData,
  250. 'delegate_data' => $delegatedNewOperation ? $delegatedNewOperation->getAdditionalData() : [],
  251. ]
  252. );
  253. return $savedCustomer;
  254. }
  255. /**
  256. * Set secure data to customer model
  257. *
  258. * @param \Magento\Customer\Model\Customer $customerModel
  259. * @param string|null $passwordHash
  260. * @SuppressWarnings(PHPMD.NPathComplexity)
  261. * @return void
  262. */
  263. private function populateCustomerWithSecureData($customerModel, $passwordHash = null)
  264. {
  265. if ($customerModel->getId()) {
  266. $customerSecure = $this->customerRegistry->retrieveSecureData($customerModel->getId());
  267. $customerModel->setRpToken($passwordHash ? null : $customerSecure->getRpToken());
  268. $customerModel->setRpTokenCreatedAt($passwordHash ? null : $customerSecure->getRpTokenCreatedAt());
  269. $customerModel->setPasswordHash($passwordHash ?: $customerSecure->getPasswordHash());
  270. $customerModel->setFailuresNum($customerSecure->getFailuresNum());
  271. $customerModel->setFirstFailure($customerSecure->getFirstFailure());
  272. $customerModel->setLockExpires($customerSecure->getLockExpires());
  273. } elseif ($passwordHash) {
  274. $customerModel->setPasswordHash($passwordHash);
  275. }
  276. if ($passwordHash && $customerModel->getId()) {
  277. $this->customerRegistry->remove($customerModel->getId());
  278. }
  279. }
  280. /**
  281. * @inheritdoc
  282. */
  283. public function get($email, $websiteId = null)
  284. {
  285. $customerModel = $this->customerRegistry->retrieveByEmail($email, $websiteId);
  286. return $customerModel->getDataModel();
  287. }
  288. /**
  289. * @inheritdoc
  290. */
  291. public function getById($customerId)
  292. {
  293. $customerModel = $this->customerRegistry->retrieve($customerId);
  294. return $customerModel->getDataModel();
  295. }
  296. /**
  297. * @inheritdoc
  298. */
  299. public function getList(SearchCriteriaInterface $searchCriteria)
  300. {
  301. $searchResults = $this->searchResultsFactory->create();
  302. $searchResults->setSearchCriteria($searchCriteria);
  303. /** @var \Magento\Customer\Model\ResourceModel\Customer\Collection $collection */
  304. $collection = $this->customerFactory->create()->getCollection();
  305. $this->extensionAttributesJoinProcessor->process(
  306. $collection,
  307. CustomerInterface::class
  308. );
  309. // This is needed to make sure all the attributes are properly loaded
  310. foreach ($this->customerMetadata->getAllAttributesMetadata() as $metadata) {
  311. $collection->addAttributeToSelect($metadata->getAttributeCode());
  312. }
  313. // Needed to enable filtering on name as a whole
  314. $collection->addNameToSelect();
  315. // Needed to enable filtering based on billing address attributes
  316. $collection->joinAttribute('billing_postcode', 'customer_address/postcode', 'default_billing', null, 'left')
  317. ->joinAttribute('billing_city', 'customer_address/city', 'default_billing', null, 'left')
  318. ->joinAttribute('billing_telephone', 'customer_address/telephone', 'default_billing', null, 'left')
  319. ->joinAttribute('billing_region', 'customer_address/region', 'default_billing', null, 'left')
  320. ->joinAttribute('billing_country_id', 'customer_address/country_id', 'default_billing', null, 'left')
  321. ->joinAttribute('billing_company', 'customer_address/company', 'default_billing', null, 'left');
  322. $this->collectionProcessor->process($searchCriteria, $collection);
  323. $searchResults->setTotalCount($collection->getSize());
  324. $customers = [];
  325. /** @var \Magento\Customer\Model\Customer $customerModel */
  326. foreach ($collection as $customerModel) {
  327. $customers[] = $customerModel->getDataModel();
  328. }
  329. $searchResults->setItems($customers);
  330. return $searchResults;
  331. }
  332. /**
  333. * @inheritdoc
  334. */
  335. public function delete(CustomerInterface $customer)
  336. {
  337. return $this->deleteById($customer->getId());
  338. }
  339. /**
  340. * @inheritdoc
  341. */
  342. public function deleteById($customerId)
  343. {
  344. $customerModel = $this->customerRegistry->retrieve($customerId);
  345. $customerModel->delete();
  346. $this->customerRegistry->remove($customerId);
  347. $this->notificationStorage->remove(NotificationStorage::UPDATE_CUSTOMER_SESSION, $customerId);
  348. return true;
  349. }
  350. /**
  351. * Helper function that adds a FilterGroup to the collection.
  352. *
  353. * @deprecated 101.0.0
  354. * @param FilterGroup $filterGroup
  355. * @param Collection $collection
  356. * @return void
  357. */
  358. protected function addFilterGroupToCollection(FilterGroup $filterGroup, Collection $collection)
  359. {
  360. $fields = [];
  361. foreach ($filterGroup->getFilters() as $filter) {
  362. $condition = $filter->getConditionType() ? $filter->getConditionType() : 'eq';
  363. $fields[] = ['attribute' => $filter->getField(), $condition => $filter->getValue()];
  364. }
  365. if ($fields) {
  366. $collection->addFieldToFilter($fields);
  367. }
  368. }
  369. /**
  370. * Set ignore_validation_flag to skip model validation
  371. *
  372. * @param array $customerArray
  373. * @param Customer $customerModel
  374. * @return void
  375. */
  376. private function setValidationFlag($customerArray, $customerModel)
  377. {
  378. if (isset($customerArray['ignore_validation_flag'])) {
  379. $customerModel->setData('ignore_validation_flag', true);
  380. }
  381. }
  382. }