Files
magento2/app/code/Magento/Customer/Model/ResourceModel/CustomerRepository.php
T
2022-12-20 10:59:16 -06:00

550 lines
21 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Magento\Customer\Model\ResourceModel;
use Magento\Customer\Api\CustomerMetadataInterface;
use Magento\Customer\Api\CustomerRepositoryInterface;
use Magento\Customer\Api\Data\CustomerInterface;
use Magento\Customer\Api\Data\CustomerSearchResultsInterfaceFactory;
use Magento\Customer\Api\GroupRepositoryInterface;
use Magento\Customer\Model\Customer as CustomerModel;
use Magento\Customer\Model\Customer\NotificationStorage;
use Magento\Customer\Model\CustomerFactory;
use Magento\Customer\Model\CustomerRegistry;
use Magento\Customer\Model\Data\CustomerSecureFactory;
use Magento\Customer\Model\Delegation\Data\NewOperation;
use Magento\Customer\Model\Delegation\Storage as DelegatedStorage;
use Magento\Customer\Model\ResourceModel\Customer\Collection;
use Magento\Framework\Api\DataObjectHelper;
use Magento\Framework\Api\ExtensibleDataObjectConverter;
use Magento\Framework\Api\ExtensionAttribute\JoinProcessorInterface;
use Magento\Framework\Api\ImageProcessorInterface;
use Magento\Framework\Api\Search\FilterGroup;
use Magento\Framework\Api\SearchCriteria\CollectionProcessorInterface;
use Magento\Framework\Api\SearchCriteriaInterface;
use Magento\Framework\App\ObjectManager;
use Magento\Framework\Event\ManagerInterface;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Store\Model\StoreManagerInterface;
/**
* Customer repository.
*
* CRUD operations for customer entity
*
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
* @SuppressWarnings(PHPMD.TooManyFields)
*/
class CustomerRepository implements CustomerRepositoryInterface
{
/**
* @var CustomerFactory
*/
protected $customerFactory;
/**
* @var CustomerSecureFactory
*/
protected $customerSecureFactory;
/**
* @var CustomerRegistry
*/
protected $customerRegistry;
/**
* @var AddressRepository
*/
protected $addressRepository;
/**
* @var Customer
*/
protected $customerResourceModel;
/**
* @var CustomerMetadataInterface
*/
protected $customerMetadata;
/**
* @var CustomerSearchResultsInterfaceFactory
*/
protected $searchResultsFactory;
/**
* @var ManagerInterface
*/
protected $eventManager;
/**
* @var StoreManagerInterface
*/
protected $storeManager;
/**
* @var ExtensibleDataObjectConverter
*/
protected $extensibleDataObjectConverter;
/**
* @var DataObjectHelper
*/
protected $dataObjectHelper;
/**
* @var ImageProcessorInterface
*/
protected $imageProcessor;
/**
* @var JoinProcessorInterface
*/
protected $extensionAttributesJoinProcessor;
/**
* @var CollectionProcessorInterface
*/
private $collectionProcessor;
/**
* @var NotificationStorage
*/
private $notificationStorage;
/**
* @var DelegatedStorage
*/
private $delegatedStorage;
/**
* @var GroupRepositoryInterface
*/
private $groupRepository;
/**
* @param CustomerFactory $customerFactory
* @param CustomerSecureFactory $customerSecureFactory
* @param CustomerRegistry $customerRegistry
* @param AddressRepository $addressRepository
* @param Customer $customerResourceModel
* @param CustomerMetadataInterface $customerMetadata
* @param CustomerSearchResultsInterfaceFactory $searchResultsFactory
* @param ManagerInterface $eventManager
* @param StoreManagerInterface $storeManager
* @param ExtensibleDataObjectConverter $extensibleDataObjectConverter
* @param DataObjectHelper $dataObjectHelper
* @param ImageProcessorInterface $imageProcessor
* @param JoinProcessorInterface $extensionAttributesJoinProcessor
* @param CollectionProcessorInterface $collectionProcessor
* @param NotificationStorage $notificationStorage
* @param DelegatedStorage|null $delegatedStorage
* @param GroupRepositoryInterface|null $groupRepository
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
CustomerFactory $customerFactory,
CustomerSecureFactory $customerSecureFactory,
CustomerRegistry $customerRegistry,
AddressRepository $addressRepository,
Customer $customerResourceModel,
CustomerMetadataInterface $customerMetadata,
CustomerSearchResultsInterfaceFactory $searchResultsFactory,
ManagerInterface $eventManager,
StoreManagerInterface $storeManager,
ExtensibleDataObjectConverter $extensibleDataObjectConverter,
DataObjectHelper $dataObjectHelper,
ImageProcessorInterface $imageProcessor,
JoinProcessorInterface $extensionAttributesJoinProcessor,
CollectionProcessorInterface $collectionProcessor,
NotificationStorage $notificationStorage,
DelegatedStorage $delegatedStorage = null,
?GroupRepositoryInterface $groupRepository = null
) {
$this->customerFactory = $customerFactory;
$this->customerSecureFactory = $customerSecureFactory;
$this->customerRegistry = $customerRegistry;
$this->addressRepository = $addressRepository;
$this->customerResourceModel = $customerResourceModel;
$this->customerMetadata = $customerMetadata;
$this->searchResultsFactory = $searchResultsFactory;
$this->eventManager = $eventManager;
$this->storeManager = $storeManager;
$this->extensibleDataObjectConverter = $extensibleDataObjectConverter;
$this->dataObjectHelper = $dataObjectHelper;
$this->imageProcessor = $imageProcessor;
$this->extensionAttributesJoinProcessor = $extensionAttributesJoinProcessor;
$this->collectionProcessor = $collectionProcessor;
$this->notificationStorage = $notificationStorage;
$this->delegatedStorage = $delegatedStorage ?? ObjectManager::getInstance()->get(DelegatedStorage::class);
$this->groupRepository = $groupRepository ?: ObjectManager::getInstance()->get(GroupRepositoryInterface::class);
}
/**
* Create or update a customer.
*
* @param \Magento\Customer\Api\Data\CustomerInterface $customer
* @param string $passwordHash
* @return \Magento\Customer\Api\Data\CustomerInterface
* @throws \Magento\Framework\Exception\InputException If bad input is provided
* @throws \Magento\Framework\Exception\State\InputMismatchException If the provided email is already used
* @throws \Magento\Framework\Exception\LocalizedException
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.NPathComplexity)
*/
public function save(CustomerInterface $customer, $passwordHash = null)
{
/** @var NewOperation|null $delegatedNewOperation */
$delegatedNewOperation = !$customer->getId() ? $this->delegatedStorage->consumeNewOperation() : null;
$prevCustomerData = $prevCustomerDataArr = null;
if ($customer->getId()) {
$prevCustomerData = $this->getById($customer->getId());
$prevCustomerDataArr = $this->prepareCustomerData($prevCustomerData->__toArray());
}
/** @var $customer \Magento\Customer\Model\Data\Customer */
$customerArr = $customer->__toArray();
$customer = $this->imageProcessor->save(
$customer,
CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER,
$prevCustomerData
);
$origAddresses = $customer->getAddresses();
$customer->setAddresses([]);
$customerData = $this->extensibleDataObjectConverter->toNestedArray($customer, [], CustomerInterface::class);
$customer->setAddresses($origAddresses);
/** @var CustomerModel $customerModel */
$customerModel = $this->customerFactory->create(['data' => $customerData]);
$this->populateWithOrigData($customerModel, $prevCustomerDataArr);
//Model's actual ID field maybe different than "id" so "id" field from $customerData may be ignored.
$customerModel->setId($customer->getId());
$storeId = $customerModel->getStoreId();
if ($storeId === null) {
$customerModel->setStoreId(
$prevCustomerData ? $prevCustomerData->getStoreId() : $this->storeManager->getStore()->getId()
);
}
$this->validateGroupId($customer->getGroupId());
$this->setCustomerGroupId($customerModel, $customerArr, $prevCustomerDataArr);
// Need to use attribute set or future updates can cause data loss
if (!$customerModel->getAttributeSetId()) {
$customerModel->setAttributeSetId(CustomerMetadataInterface::ATTRIBUTE_SET_ID_CUSTOMER);
}
$this->populateCustomerWithSecureData($customerModel, $passwordHash);
// If customer email was changed, reset RpToken info
if ($prevCustomerData && $prevCustomerData->getEmail() !== $customerModel->getEmail()) {
$customerModel->setRpToken(null);
$customerModel->setRpTokenCreatedAt(null);
}
if (!array_key_exists('addresses', $customerArr)
&& null !== $prevCustomerDataArr
&& array_key_exists('default_billing', $prevCustomerDataArr)
) {
$customerModel->setDefaultBilling($prevCustomerDataArr['default_billing']);
}
if (!array_key_exists('addresses', $customerArr)
&& null !== $prevCustomerDataArr
&& array_key_exists('default_shipping', $prevCustomerDataArr)
) {
$customerModel->setDefaultShipping($prevCustomerDataArr['default_shipping']);
}
$this->setValidationFlag($customerArr, $customerModel);
$customerModel->save();
$this->customerRegistry->push($customerModel);
$customerId = $customerModel->getId();
if (!$customer->getAddresses()
&& $delegatedNewOperation
&& $delegatedNewOperation->getCustomer()->getAddresses()
) {
$customer->setAddresses($delegatedNewOperation->getCustomer()->getAddresses());
}
if ($customer->getAddresses() !== null && !$customerModel->getData('ignore_validation_flag')) {
if ($customer->getId()) {
$existingAddresses = $this->getById($customer->getId())->getAddresses();
$getIdFunc = function ($address) {
return $address->getId();
};
$existingAddressIds = array_map($getIdFunc, $existingAddresses);
} else {
$existingAddressIds = [];
}
$savedAddressIds = [];
foreach ($customer->getAddresses() as $address) {
$address->setCustomerId($customerId)
->setRegion($address->getRegion());
$this->addressRepository->save($address);
if ($address->getId()) {
$savedAddressIds[] = $address->getId();
}
}
$this->deleteAddressesByIds(array_diff($existingAddressIds, $savedAddressIds));
}
$this->customerRegistry->remove($customerId);
$savedCustomer = $this->get($customer->getEmail(), $customer->getWebsiteId());
$this->eventManager->dispatch(
'customer_save_after_data_object',
[
'customer_data_object' => $savedCustomer,
'orig_customer_data_object' => $prevCustomerData,
'delegate_data' => $delegatedNewOperation ? $delegatedNewOperation->getAdditionalData() : [],
]
);
return $savedCustomer;
}
/**
* Populate customer model with previous data
*
* @param CustomerModel $customerModel
* @param ?array $prevCustomerDataArr
*/
private function populateWithOrigData(CustomerModel $customerModel, ?array $prevCustomerDataArr)
{
if (!empty($prevCustomerDataArr)) {
foreach ($prevCustomerDataArr as $field => $value) {
$customerModel->setOrigData($field, $value);
}
}
}
/**
* Delete addresses by ids
*
* @param array $addressIds
* @return void
*/
private function deleteAddressesByIds(array $addressIds): void
{
foreach ($addressIds as $id) {
$this->addressRepository->deleteById($id);
}
}
/**
* Validate customer group id if exist
*
* @param int|null $groupId
* @return bool
* @throws LocalizedException
*/
private function validateGroupId(?int $groupId): bool
{
if ($groupId) {
try {
$this->groupRepository->getById($groupId);
} catch (NoSuchEntityException $e) {
throw new LocalizedException(__('The specified customer group id does not exist.'));
}
}
return true;
}
/**
* Set secure data to customer model
*
* @param \Magento\Customer\Model\Customer $customerModel
* @param string|null $passwordHash
* @SuppressWarnings(PHPMD.NPathComplexity)
* @return void
*/
private function populateCustomerWithSecureData($customerModel, $passwordHash = null)
{
if ($customerModel->getId()) {
$customerSecure = $this->customerRegistry->retrieveSecureData($customerModel->getId());
$customerModel->setRpToken($passwordHash ? null : $customerSecure->getRpToken());
$customerModel->setRpTokenCreatedAt($passwordHash ? null : $customerSecure->getRpTokenCreatedAt());
$customerModel->setPasswordHash($passwordHash ?: $customerSecure->getPasswordHash());
$customerModel->setFailuresNum($customerSecure->getFailuresNum());
$customerModel->setFirstFailure($customerSecure->getFirstFailure());
$customerModel->setLockExpires($customerSecure->getLockExpires());
} elseif ($passwordHash) {
$customerModel->setPasswordHash($passwordHash);
}
if ($passwordHash && $customerModel->getId()) {
$this->customerRegistry->remove($customerModel->getId());
}
}
/**
* Retrieve customer.
*
* @param string $email
* @param int|null $websiteId
* @return \Magento\Customer\Api\Data\CustomerInterface
* @throws \Magento\Framework\Exception\NoSuchEntityException If customer with the specified email does not exist.
* @throws \Magento\Framework\Exception\LocalizedException
*/
public function get($email, $websiteId = null)
{
$customerModel = $this->customerRegistry->retrieveByEmail($email, $websiteId);
return $customerModel->getDataModel();
}
/**
* Get customer by Customer ID.
*
* @param int $customerId
* @return \Magento\Customer\Api\Data\CustomerInterface
* @throws \Magento\Framework\Exception\NoSuchEntityException If customer with the specified ID does not exist.
* @throws \Magento\Framework\Exception\LocalizedException
*/
public function getById($customerId)
{
$customerModel = $this->customerRegistry->retrieve($customerId);
return $customerModel->getDataModel();
}
/**
* Retrieve customers which match a specified criteria.
*
* This call returns an array of objects, but detailed information about each object’s attributes might not be
* included. See https://developer.adobe.com/commerce/webapi/rest/attributes#CustomerRepositoryInterface to determine
* which call to use to get detailed information about all attributes for an object.
*
* @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria
* @return \Magento\Customer\Api\Data\CustomerSearchResultsInterface
* @throws \Magento\Framework\Exception\LocalizedException
*/
public function getList(SearchCriteriaInterface $searchCriteria)
{
$searchResults = $this->searchResultsFactory->create();
$searchResults->setSearchCriteria($searchCriteria);
/** @var \Magento\Customer\Model\ResourceModel\Customer\Collection $collection */
$collection = $this->customerFactory->create()->getCollection();
$this->extensionAttributesJoinProcessor->process(
$collection,
CustomerInterface::class
);
// This is needed to make sure all the attributes are properly loaded
foreach ($this->customerMetadata->getAllAttributesMetadata() as $metadata) {
$collection->addAttributeToSelect($metadata->getAttributeCode());
}
// Needed to enable filtering on name as a whole
$collection->addNameToSelect();
// Needed to enable filtering based on billing address attributes
$collection->joinAttribute('billing_postcode', 'customer_address/postcode', 'default_billing', null, 'left')
->joinAttribute('billing_city', 'customer_address/city', 'default_billing', null, 'left')
->joinAttribute('billing_telephone', 'customer_address/telephone', 'default_billing', null, 'left')
->joinAttribute('billing_region', 'customer_address/region', 'default_billing', null, 'left')
->joinAttribute('billing_country_id', 'customer_address/country_id', 'default_billing', null, 'left')
->joinAttribute('billing_company', 'customer_address/company', 'default_billing', null, 'left');
$this->collectionProcessor->process($searchCriteria, $collection);
$searchResults->setTotalCount($collection->getSize());
$customers = [];
/** @var \Magento\Customer\Model\Customer $customerModel */
foreach ($collection as $customerModel) {
$customers[] = $customerModel->getDataModel();
}
$searchResults->setItems($customers);
return $searchResults;
}
/**
* Delete customer.
*
* @param \Magento\Customer\Api\Data\CustomerInterface $customer
* @return bool true on success
* @throws \Magento\Framework\Exception\LocalizedException
*/
public function delete(CustomerInterface $customer)
{
return $this->deleteById($customer->getId());
}
/**
* Delete customer by Customer ID.
*
* @param int $customerId
* @return bool true on success
* @throws \Magento\Framework\Exception\NoSuchEntityException
* @throws \Magento\Framework\Exception\LocalizedException
*/
public function deleteById($customerId)
{
$customerModel = $this->customerRegistry->retrieve($customerId);
$customerModel->delete();
$this->customerRegistry->remove($customerId);
$this->notificationStorage->remove(NotificationStorage::UPDATE_CUSTOMER_SESSION, $customerId);
return true;
}
/**
* Helper function that adds a FilterGroup to the collection.
*
* @deprecated 101.0.0
* @see no alternative
* @param FilterGroup $filterGroup
* @param Collection $collection
* @return void
*/
protected function addFilterGroupToCollection(FilterGroup $filterGroup, Collection $collection)
{
$fields = [];
foreach ($filterGroup->getFilters() as $filter) {
$condition = $filter->getConditionType() ? $filter->getConditionType() : 'eq';
$fields[] = ['attribute' => $filter->getField(), $condition => $filter->getValue()];
}
if ($fields) {
$collection->addFieldToFilter($fields);
}
}
/**
* Set ignore_validation_flag to skip model validation
*
* @param array $customerArray
* @param Customer $customerModel
* @return void
*/
private function setValidationFlag($customerArray, $customerModel)
{
if (isset($customerArray['ignore_validation_flag'])) {
$customerModel->setData('ignore_validation_flag', true);
}
}
/**
* Set customer group id
*
* @param Customer $customerModel
* @param array $customerArr
* @param array $prevCustomerDataArr
*/
private function setCustomerGroupId($customerModel, $customerArr, $prevCustomerDataArr)
{
if (!isset($customerArr['group_id']) && $prevCustomerDataArr && isset($prevCustomerDataArr['group_id'])) {
$customerModel->setGroupId($prevCustomerDataArr['group_id']);
}
}
/**
* Prepare customer data.
*
* @param array $customerData
* @return array
*/
private function prepareCustomerData(array $customerData): array
{
if (isset($customerData[CustomerInterface::CUSTOM_ATTRIBUTES])) {
foreach ($customerData[CustomerInterface::CUSTOM_ATTRIBUTES] as $attribute) {
$customerData[$attribute['attribute_code']] = $attribute['value'];
}
unset($customerData[CustomerInterface::CUSTOM_ATTRIBUTES]);
}
return $customerData;
}
}