| Current Path : /var/www/clients/client3/web2/web/vendor/magento/module-customer/Model/ |
| Current File : /var/www/clients/client3/web2/web/vendor/magento/module-customer/Model/AccountManagement.php |
<?php
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
namespace Magento\Customer\Model;
use Magento\Customer\Api\AccountManagementInterface;
use Magento\Customer\Api\AddressRepositoryInterface;
use Magento\Customer\Api\CustomerMetadataInterface;
use Magento\Customer\Api\CustomerRepositoryInterface;
use Magento\Customer\Api\Data\AddressInterface;
use Magento\Customer\Api\Data\CustomerInterface;
use Magento\Customer\Api\Data\ValidationResultsInterfaceFactory;
use Magento\Customer\Api\SessionCleanerInterface;
use Magento\Customer\Helper\View as CustomerViewHelper;
use Magento\Customer\Model\Config\Share as ConfigShare;
use Magento\Customer\Model\Customer as CustomerModel;
use Magento\Customer\Model\Customer\CredentialsValidator;
use Magento\Customer\Model\ForgotPasswordToken\GetCustomerByToken;
use Magento\Customer\Model\Metadata\Validator;
use Magento\Customer\Model\ResourceModel\Visitor\CollectionFactory;
use Magento\Directory\Model\AllowedCountries;
use Magento\Eav\Model\Validator\Attribute\Backend;
use Magento\Framework\Api\ExtensibleDataObjectConverter;
use Magento\Framework\Api\SearchCriteriaBuilder;
use Magento\Framework\App\Area;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\App\ObjectManager;
use Magento\Framework\DataObjectFactory as ObjectFactory;
use Magento\Framework\Encryption\EncryptorInterface as Encryptor;
use Magento\Framework\Encryption\Helper\Security;
use Magento\Framework\Event\ManagerInterface;
use Magento\Framework\Exception\AlreadyExistsException;
use Magento\Framework\Exception\EmailNotConfirmedException;
use Magento\Framework\Exception\InputException;
use Magento\Framework\Exception\InvalidEmailOrPasswordException;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Exception\MailException;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\Exception\State\ExpiredException;
use Magento\Framework\Exception\State\InputMismatchException;
use Magento\Framework\Exception\State\InvalidTransitionException;
use Magento\Framework\Exception\State\UserLockedException;
use Magento\Framework\Intl\DateTimeFactory;
use Magento\Framework\Mail\Template\TransportBuilder;
use Magento\Framework\Math\Random;
use Magento\Framework\Reflection\DataObjectProcessor;
use Magento\Framework\Registry;
use Magento\Framework\Session\SaveHandlerInterface;
use Magento\Framework\Session\SessionManagerInterface;
use Magento\Framework\Stdlib\DateTime;
use Magento\Framework\Stdlib\StringUtils as StringHelper;
use Magento\Store\Model\ScopeInterface;
use Magento\Store\Model\StoreManagerInterface;
use Psr\Log\LoggerInterface as PsrLogger;
use Magento\Framework\AuthorizationInterface;
/**
* Handle various customer account actions
*
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
* @SuppressWarnings(PHPMD.TooManyFields)
* @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
* @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
*/
class AccountManagement implements AccountManagementInterface
{
/**
* Configuration paths for create account email template
*
* @deprecated Get rid of Helpers in Password Security Management
* @see EmailNotification::XML_PATH_REGISTER_EMAIL_TEMPLATE
*/
const XML_PATH_REGISTER_EMAIL_TEMPLATE = 'customer/create_account/email_template';
/**
* Configuration paths for register no password email template
*
* @deprecated Get rid of Helpers in Password Security Management
* @see EmailNotification::XML_PATH_REGISTER_EMAIL_TEMPLATE
*/
const XML_PATH_REGISTER_NO_PASSWORD_EMAIL_TEMPLATE = 'customer/create_account/email_no_password_template';
/**
* Configuration paths for remind email identity
*
* @deprecated Get rid of Helpers in Password Security Management
* @see EmailNotification::XML_PATH_REGISTER_EMAIL_TEMPLATE
*/
const XML_PATH_REGISTER_EMAIL_IDENTITY = 'customer/create_account/email_identity';
/**
* Configuration paths for remind email template
*
* @deprecated Get rid of Helpers in Password Security Management
* @see EmailNotification::XML_PATH_REGISTER_EMAIL_TEMPLATE
*/
const XML_PATH_REMIND_EMAIL_TEMPLATE = 'customer/password/remind_email_template';
/**
* Configuration paths for forgot email email template
*
* @deprecated Get rid of Helpers in Password Security Management
* @see EmailNotification::XML_PATH_REGISTER_EMAIL_TEMPLATE
*/
const XML_PATH_FORGOT_EMAIL_TEMPLATE = 'customer/password/forgot_email_template';
/**
* Configuration paths for forgot email identity
*
* @deprecated Get rid of Helpers in Password Security Management
* @see EmailNotification::XML_PATH_REGISTER_EMAIL_TEMPLATE
*/
const XML_PATH_FORGOT_EMAIL_IDENTITY = 'customer/password/forgot_email_identity';
/**
* Configuration paths for account confirmation required
*
* @deprecated Get rid of Helpers in Password Security Management
* @see AccountConfirmation::XML_PATH_IS_CONFIRM
*/
const XML_PATH_IS_CONFIRM = 'customer/create_account/confirm';
/**
* Configuration paths for account confirmation email template
*
* @deprecated Get rid of Helpers in Password Security Management
* @see EmailNotification::XML_PATH_REGISTER_EMAIL_TEMPLATE
*/
const XML_PATH_CONFIRM_EMAIL_TEMPLATE = 'customer/create_account/email_confirmation_template';
/**
* Configuration paths for confirmation confirmed email template
*
* @deprecated Get rid of Helpers in Password Security Management
* @see EmailNotification::XML_PATH_REGISTER_EMAIL_TEMPLATE
*/
const XML_PATH_CONFIRMED_EMAIL_TEMPLATE = 'customer/create_account/email_confirmed_template';
/**
* Constants for the type of new account email to be sent
*
* @deprecated Get rid of Helpers in Password Security Management
* @see EmailNotificationInterface::NEW_ACCOUNT_EMAIL_REGISTERED
*/
const NEW_ACCOUNT_EMAIL_REGISTERED = 'registered';
/**
* Welcome email, when password setting is required
*
* @deprecated Get rid of Helpers in Password Security Management
* @see EmailNotificationInterface::NEW_ACCOUNT_EMAIL_REGISTERED
*/
const NEW_ACCOUNT_EMAIL_REGISTERED_NO_PASSWORD = 'registered_no_password';
/**
* Welcome email, when confirmation is enabled
*
* @deprecated Get rid of Helpers in Password Security Management
* @see EmailNotificationInterface::NEW_ACCOUNT_EMAIL_REGISTERED
*/
const NEW_ACCOUNT_EMAIL_CONFIRMATION = 'confirmation';
/**
* Confirmation email, when account is confirmed
*
* @deprecated Get rid of Helpers in Password Security Management
* @see EmailNotificationInterface::NEW_ACCOUNT_EMAIL_REGISTERED
*/
const NEW_ACCOUNT_EMAIL_CONFIRMED = 'confirmed';
/**
* Constants for types of emails to send out.
* pdl:
* forgot, remind, reset email templates
*/
const EMAIL_REMINDER = 'email_reminder';
const EMAIL_RESET = 'email_reset';
/**
* Configuration path to customer password minimum length
*/
const XML_PATH_MINIMUM_PASSWORD_LENGTH = 'customer/password/minimum_password_length';
/**
* Configuration path to customer password required character classes number
*/
const XML_PATH_REQUIRED_CHARACTER_CLASSES_NUMBER = 'customer/password/required_character_classes_number';
/**
* Configuration path to customer reset password email template
*
* @deprecated Get rid of Helpers in Password Security Management
* @see Magento/Customer/Model/EmailNotification::XML_PATH_REGISTER_EMAIL_TEMPLATE
*/
const XML_PATH_RESET_PASSWORD_TEMPLATE = 'customer/password/reset_password_template';
/**
* Minimum password length
*
* @deprecated Get rid of Helpers in Password Security Management
* @see \Magento\Customer\Model\AccountManagement::XML_PATH_MINIMUM_PASSWORD_LENGTH
*/
const MIN_PASSWORD_LENGTH = 6;
/**
* Authorization level of a basic admin session
*
* @see _isAllowed()
*/
const ADMIN_RESOURCE = 'Magento_Customer::manage';
/**
* @var CustomerFactory
*/
private $customerFactory;
/**
* @var \Magento\Customer\Api\Data\ValidationResultsInterfaceFactory
*/
private $validationResultsDataFactory;
/**
* @var ManagerInterface
*/
private $eventManager;
/**
* @var \Magento\Store\Model\StoreManagerInterface
*/
private $storeManager;
/**
* @var Random
*/
private $mathRandom;
/**
* @var Validator
*/
private $validator;
/**
* @var AddressRepositoryInterface
*/
private $addressRepository;
/**
* @var CustomerMetadataInterface
*/
private $customerMetadataService;
/**
* @var PsrLogger
*/
protected $logger;
/**
* @var Encryptor
*/
private $encryptor;
/**
* @var CustomerRegistry
*/
private $customerRegistry;
/**
* @var ConfigShare
*/
private $configShare;
/**
* @var StringHelper
*/
protected $stringHelper;
/**
* @var CustomerRepositoryInterface
*/
private $customerRepository;
/**
* @var ScopeConfigInterface
*/
private $scopeConfig;
/**
* @var TransportBuilder
*/
private $transportBuilder;
/**
* @var DataObjectProcessor
*/
protected $dataProcessor;
/**
* @var \Magento\Framework\Registry
*/
protected $registry;
/**
* @var CustomerViewHelper
*/
protected $customerViewHelper;
/**
* @var DateTime
*/
protected $dateTime;
/**
* @var ObjectFactory
*/
protected $objectFactory;
/**
* @var \Magento\Framework\Api\ExtensibleDataObjectConverter
*/
protected $extensibleDataObjectConverter;
/**
* @var CustomerModel
*/
protected $customerModel;
/**
* @var AuthenticationInterface
*/
protected $authentication;
/**
* @var EmailNotificationInterface
*/
private $emailNotification;
/**
* @var \Magento\Eav\Model\Validator\Attribute\Backend
*/
private $eavValidator;
/**
* @var CredentialsValidator
*/
private $credentialsValidator;
/**
* @var DateTimeFactory
*/
private $dateTimeFactory;
/**
* @var AccountConfirmation
*/
private $accountConfirmation;
/**
* @var SearchCriteriaBuilder
*/
private $searchCriteriaBuilder;
/**
* @var AddressRegistry
*/
private $addressRegistry;
/**
* @var AllowedCountries
*/
private $allowedCountriesReader;
/**
* @var GetCustomerByToken
*/
private $getByToken;
/**
* @var SessionCleanerInterface
*/
private $sessionCleaner;
/**
* @var AuthorizationInterface
*/
private $authorization;
/**
* @param CustomerFactory $customerFactory
* @param ManagerInterface $eventManager
* @param StoreManagerInterface $storeManager
* @param Random $mathRandom
* @param Validator $validator
* @param ValidationResultsInterfaceFactory $validationResultsDataFactory
* @param AddressRepositoryInterface $addressRepository
* @param CustomerMetadataInterface $customerMetadataService
* @param CustomerRegistry $customerRegistry
* @param PsrLogger $logger
* @param Encryptor $encryptor
* @param ConfigShare $configShare
* @param StringHelper $stringHelper
* @param CustomerRepositoryInterface $customerRepository
* @param ScopeConfigInterface $scopeConfig
* @param TransportBuilder $transportBuilder
* @param DataObjectProcessor $dataProcessor
* @param Registry $registry
* @param CustomerViewHelper $customerViewHelper
* @param DateTime $dateTime
* @param CustomerModel $customerModel
* @param ObjectFactory $objectFactory
* @param ExtensibleDataObjectConverter $extensibleDataObjectConverter
* @param CredentialsValidator|null $credentialsValidator
* @param DateTimeFactory|null $dateTimeFactory
* @param AccountConfirmation|null $accountConfirmation
* @param SessionManagerInterface|null $sessionManager
* @param SaveHandlerInterface|null $saveHandler
* @param CollectionFactory|null $visitorCollectionFactory
* @param SearchCriteriaBuilder|null $searchCriteriaBuilder
* @param AddressRegistry|null $addressRegistry
* @param GetCustomerByToken|null $getByToken
* @param AllowedCountries|null $allowedCountriesReader
* @param SessionCleanerInterface|null $sessionCleaner
* @param AuthorizationInterface|null $authorization
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
* @SuppressWarnings(PHPMD.NPathComplexity)
* @SuppressWarnings(PHPMD.LongVariable)
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function __construct(
CustomerFactory $customerFactory,
ManagerInterface $eventManager,
StoreManagerInterface $storeManager,
Random $mathRandom,
Validator $validator,
ValidationResultsInterfaceFactory $validationResultsDataFactory,
AddressRepositoryInterface $addressRepository,
CustomerMetadataInterface $customerMetadataService,
CustomerRegistry $customerRegistry,
PsrLogger $logger,
Encryptor $encryptor,
ConfigShare $configShare,
StringHelper $stringHelper,
CustomerRepositoryInterface $customerRepository,
ScopeConfigInterface $scopeConfig,
TransportBuilder $transportBuilder,
DataObjectProcessor $dataProcessor,
Registry $registry,
CustomerViewHelper $customerViewHelper,
DateTime $dateTime,
CustomerModel $customerModel,
ObjectFactory $objectFactory,
ExtensibleDataObjectConverter $extensibleDataObjectConverter,
CredentialsValidator $credentialsValidator = null,
DateTimeFactory $dateTimeFactory = null,
AccountConfirmation $accountConfirmation = null,
SessionManagerInterface $sessionManager = null,
SaveHandlerInterface $saveHandler = null,
CollectionFactory $visitorCollectionFactory = null,
SearchCriteriaBuilder $searchCriteriaBuilder = null,
AddressRegistry $addressRegistry = null,
GetCustomerByToken $getByToken = null,
AllowedCountries $allowedCountriesReader = null,
SessionCleanerInterface $sessionCleaner = null,
AuthorizationInterface $authorization = null
) {
$this->customerFactory = $customerFactory;
$this->eventManager = $eventManager;
$this->storeManager = $storeManager;
$this->mathRandom = $mathRandom;
$this->validator = $validator;
$this->validationResultsDataFactory = $validationResultsDataFactory;
$this->addressRepository = $addressRepository;
$this->customerMetadataService = $customerMetadataService;
$this->customerRegistry = $customerRegistry;
$this->logger = $logger;
$this->encryptor = $encryptor;
$this->configShare = $configShare;
$this->stringHelper = $stringHelper;
$this->customerRepository = $customerRepository;
$this->scopeConfig = $scopeConfig;
$this->transportBuilder = $transportBuilder;
$this->dataProcessor = $dataProcessor;
$this->registry = $registry;
$this->customerViewHelper = $customerViewHelper;
$this->dateTime = $dateTime;
$this->customerModel = $customerModel;
$this->objectFactory = $objectFactory;
$this->extensibleDataObjectConverter = $extensibleDataObjectConverter;
$objectManager = ObjectManager::getInstance();
$this->credentialsValidator =
$credentialsValidator ?: $objectManager->get(CredentialsValidator::class);
$this->dateTimeFactory = $dateTimeFactory ?: $objectManager->get(DateTimeFactory::class);
$this->accountConfirmation = $accountConfirmation ?: $objectManager
->get(AccountConfirmation::class);
$this->searchCriteriaBuilder = $searchCriteriaBuilder
?: $objectManager->get(SearchCriteriaBuilder::class);
$this->addressRegistry = $addressRegistry
?: $objectManager->get(AddressRegistry::class);
$this->getByToken = $getByToken
?: $objectManager->get(GetCustomerByToken::class);
$this->allowedCountriesReader = $allowedCountriesReader
?: $objectManager->get(AllowedCountries::class);
$this->sessionCleaner = $sessionCleaner ?? $objectManager->get(SessionCleanerInterface::class);
$this->authorization = $authorization ?? $objectManager->get(AuthorizationInterface::class);
}
/**
* Get authentication
*
* @return AuthenticationInterface
*/
private function getAuthentication()
{
if (!($this->authentication instanceof AuthenticationInterface)) {
return \Magento\Framework\App\ObjectManager::getInstance()->get(
\Magento\Customer\Model\AuthenticationInterface::class
);
} else {
return $this->authentication;
}
}
/**
* @inheritdoc
*/
public function resendConfirmation($email, $websiteId = null, $redirectUrl = '')
{
$customer = $this->customerRepository->get($email, $websiteId);
if (!$customer->getConfirmation()) {
throw new InvalidTransitionException(__("Confirmation isn't needed."));
}
try {
$this->getEmailNotification()->newAccount(
$customer,
self::NEW_ACCOUNT_EMAIL_CONFIRMATION,
$redirectUrl,
$this->storeManager->getStore()->getId()
);
} catch (MailException $e) {
// If we are not able to send a new account email, this should be ignored
$this->logger->critical($e);
return false;
}
return true;
}
/**
* @inheritdoc
*/
public function activate($email, $confirmationKey)
{
$customer = $this->customerRepository->get($email);
return $this->activateCustomer($customer, $confirmationKey);
}
/**
* @inheritdoc
*/
public function activateById($customerId, $confirmationKey)
{
$customer = $this->customerRepository->getById($customerId);
return $this->activateCustomer($customer, $confirmationKey);
}
/**
* Activate a customer account using a key that was sent in a confirmation email.
*
* @param \Magento\Customer\Api\Data\CustomerInterface $customer
* @param string $confirmationKey
* @return \Magento\Customer\Api\Data\CustomerInterface
* @throws InputException
* @throws InputMismatchException
* @throws InvalidTransitionException
* @throws LocalizedException
* @throws NoSuchEntityException
*/
private function activateCustomer($customer, $confirmationKey)
{
// check if customer is inactive
if (!$customer->getConfirmation()) {
throw new InvalidTransitionException(__('The account is already active.'));
}
if ($customer->getConfirmation() !== $confirmationKey) {
throw new InputMismatchException(__('The confirmation token is invalid. Verify the token and try again.'));
}
$customer->setConfirmation(null);
// No need to validate customer and customer address while activating customer
$this->setIgnoreValidationFlag($customer);
$this->customerRepository->save($customer);
$this->getEmailNotification()->newAccount(
$customer,
'confirmed',
'',
$this->storeManager->getStore()->getId()
);
return $customer;
}
/**
* @inheritdoc
*/
public function authenticate($username, $password)
{
try {
$customer = $this->customerRepository->get($username);
} catch (NoSuchEntityException $e) {
throw new InvalidEmailOrPasswordException(__('Invalid login or password.'));
}
$customerId = $customer->getId();
if ($this->getAuthentication()->isLocked($customerId)) {
throw new UserLockedException(__('The account is locked.'));
}
try {
$this->getAuthentication()->authenticate($customerId, $password);
} catch (InvalidEmailOrPasswordException $e) {
throw new InvalidEmailOrPasswordException(__('Invalid login or password.'));
}
if ($customer->getConfirmation() && $this->isConfirmationRequired($customer)) {
throw new EmailNotConfirmedException(__("This account isn't confirmed. Verify and try again."));
}
$customerModel = $this->customerFactory->create()->updateData($customer);
$this->eventManager->dispatch(
'customer_customer_authenticated',
['model' => $customerModel, 'password' => $password]
);
$this->eventManager->dispatch('customer_data_object_login', ['customer' => $customer]);
return $customer;
}
/**
* @inheritdoc
*/
public function validateResetPasswordLinkToken($customerId, $resetPasswordLinkToken)
{
$this->validateResetPasswordToken($customerId, $resetPasswordLinkToken);
return true;
}
/**
* @inheritdoc
*/
public function initiatePasswordReset($email, $template, $websiteId = null)
{
if ($websiteId === null) {
$websiteId = $this->storeManager->getStore()->getWebsiteId();
}
// load customer by email
$customer = $this->customerRepository->get($email, $websiteId);
// No need to validate customer address while saving customer reset password token
$this->disableAddressValidation($customer);
$newPasswordToken = $this->mathRandom->getUniqueHash();
$this->changeResetPasswordLinkToken($customer, $newPasswordToken);
try {
switch ($template) {
case AccountManagement::EMAIL_REMINDER:
$this->getEmailNotification()->passwordReminder($customer);
break;
case AccountManagement::EMAIL_RESET:
$this->getEmailNotification()->passwordResetConfirmation($customer);
break;
default:
$this->handleUnknownTemplate($template);
break;
}
return true;
} catch (MailException $e) {
// If we are not able to send a reset password email, this should be ignored
$this->logger->critical($e);
}
return false;
}
/**
* Handle not supported template
*
* @param string $template
* @throws InputException
*/
private function handleUnknownTemplate($template)
{
throw new InputException(
__(
'Invalid value of "%value" provided for the %fieldName field. '
. 'Possible values: %template1 or %template2.',
[
'value' => $template,
'fieldName' => 'template',
'template1' => AccountManagement::EMAIL_REMINDER,
'template2' => AccountManagement::EMAIL_RESET
]
)
);
}
/**
* @inheritdoc
*/
public function resetPassword($email, $resetToken, $newPassword)
{
if (!$email) {
$params = ['fieldName' => 'email'];
throw new InputException(__('"%fieldName" is required. Enter and try again.', $params));
} else {
$customer = $this->customerRepository->get($email);
}
// No need to validate customer and customer address while saving customer reset password token
$this->disableAddressValidation($customer);
$this->setIgnoreValidationFlag($customer);
//Validate Token and new password strength
$this->validateResetPasswordToken($customer->getId(), $resetToken);
$this->credentialsValidator->checkPasswordDifferentFromEmail(
$email,
$newPassword
);
$this->checkPasswordStrength($newPassword);
//Update secure data
$customerSecure = $this->customerRegistry->retrieveSecureData($customer->getId());
$customerSecure->setRpToken(null);
$customerSecure->setRpTokenCreatedAt(null);
$customerSecure->setPasswordHash($this->createPasswordHash($newPassword));
$this->sessionCleaner->clearFor((int)$customer->getId());
$this->customerRepository->save($customer);
return true;
}
/**
* Make sure that password complies with minimum security requirements.
*
* @param string $password
* @return void
* @throws InputException
*/
protected function checkPasswordStrength($password)
{
$length = $this->stringHelper->strlen($password);
if ($length > self::MAX_PASSWORD_LENGTH) {
throw new InputException(
__(
'Please enter a password with at most %1 characters.',
self::MAX_PASSWORD_LENGTH
)
);
}
$configMinPasswordLength = $this->getMinPasswordLength();
if ($length < $configMinPasswordLength) {
throw new InputException(
__(
'The password needs at least %1 characters. Create a new password and try again.',
$configMinPasswordLength
)
);
}
if ($this->stringHelper->strlen(trim($password)) != $length) {
throw new InputException(
__("The password can't begin or end with a space. Verify the password and try again.")
);
}
$requiredCharactersCheck = $this->makeRequiredCharactersCheck($password);
if ($requiredCharactersCheck !== 0) {
throw new InputException(
__(
'Minimum of different classes of characters in password is %1.' .
' Classes of characters: Lower Case, Upper Case, Digits, Special Characters.',
$requiredCharactersCheck
)
);
}
}
/**
* Check password for presence of required character sets
*
* @param string $password
* @return int
*/
protected function makeRequiredCharactersCheck($password)
{
$counter = 0;
$requiredNumber = $this->scopeConfig->getValue(self::XML_PATH_REQUIRED_CHARACTER_CLASSES_NUMBER);
$return = 0;
if (preg_match('/[0-9]+/', $password)) {
$counter++;
}
if (preg_match('/[A-Z]+/', $password)) {
$counter++;
}
if (preg_match('/[a-z]+/', $password)) {
$counter++;
}
if (preg_match('/[^a-zA-Z0-9]+/', $password)) {
$counter++;
}
if ($counter < $requiredNumber) {
$return = $requiredNumber;
}
return $return;
}
/**
* Retrieve minimum password length
*
* @return int
*/
protected function getMinPasswordLength()
{
return $this->scopeConfig->getValue(self::XML_PATH_MINIMUM_PASSWORD_LENGTH);
}
/**
* @inheritdoc
*/
public function getConfirmationStatus($customerId)
{
// load customer by id
$customer = $this->customerRepository->getById($customerId);
if ($this->isConfirmationRequired($customer)) {
if (!$customer->getConfirmation()) {
return self::ACCOUNT_CONFIRMED;
}
return self::ACCOUNT_CONFIRMATION_REQUIRED;
}
return self::ACCOUNT_CONFIRMATION_NOT_REQUIRED;
}
/**
* @inheritdoc
*
* @throws LocalizedException
*/
public function createAccount(CustomerInterface $customer, $password = null, $redirectUrl = '')
{
$groupId = $customer->getGroupId();
if (isset($groupId) && !$this->authorization->isAllowed(self::ADMIN_RESOURCE)) {
$customer->setGroupId(null);
}
if ($password !== null) {
$this->checkPasswordStrength($password);
$customerEmail = $customer->getEmail();
try {
$this->credentialsValidator->checkPasswordDifferentFromEmail($customerEmail, $password);
} catch (InputException $e) {
throw new LocalizedException(
__("The password can't be the same as the email address. Create a new password and try again.")
);
}
$hash = $this->createPasswordHash($password);
} else {
$hash = null;
}
return $this->createAccountWithPasswordHash($customer, $hash, $redirectUrl);
}
/**
* @inheritdoc
*
* @throws InputMismatchException
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.NPathComplexity)
*/
public function createAccountWithPasswordHash(CustomerInterface $customer, $hash, $redirectUrl = '')
{
// This logic allows an existing customer to be added to a different store. No new account is created.
// The plan is to move this logic into a new method called something like 'registerAccountWithStore'
if ($customer->getId()) {
$customer = $this->customerRepository->get($customer->getEmail());
$websiteId = $customer->getWebsiteId();
if ($this->isCustomerInStore($websiteId, $customer->getStoreId())) {
throw new InputException(__('This customer already exists in this store.'));
}
// Existing password hash will be used from secured customer data registry when saving customer
}
// Make sure we have a storeId to associate this customer with.
if (!$customer->getStoreId()) {
if ($customer->getWebsiteId()) {
$storeId = $this->storeManager->getWebsite($customer->getWebsiteId())->getDefaultStore()->getId();
} else {
$this->storeManager->setCurrentStore(null);
$storeId = $this->storeManager->getStore()->getId();
}
$customer->setStoreId($storeId);
}
// Associate website_id with customer
if (!$customer->getWebsiteId()) {
$websiteId = $this->storeManager->getStore($customer->getStoreId())->getWebsiteId();
$customer->setWebsiteId($websiteId);
}
$this->validateCustomerStoreIdByWebsiteId($customer);
// Update 'created_in' value with actual store name
if ($customer->getId() === null) {
$storeName = $this->storeManager->getStore($customer->getStoreId())->getName();
$customer->setCreatedIn($storeName);
}
$customerAddresses = $customer->getAddresses() ?: [];
$customer->setAddresses(null);
try {
// If customer exists existing hash will be used by Repository
$customer = $this->customerRepository->save($customer, $hash);
} catch (AlreadyExistsException $e) {
throw new InputMismatchException(
__('A customer with the same email address already exists in an associated website.')
);
} catch (LocalizedException $e) {
throw $e;
}
try {
foreach ($customerAddresses as $address) {
if (!$this->isAddressAllowedForWebsite($address, $customer->getStoreId())) {
continue;
}
if ($address->getId()) {
$newAddress = clone $address;
$newAddress->setId(null);
$newAddress->setCustomerId($customer->getId());
$this->addressRepository->save($newAddress);
} else {
$address->setCustomerId($customer->getId());
$this->addressRepository->save($address);
}
}
$this->customerRegistry->remove($customer->getId());
} catch (InputException $e) {
$this->customerRepository->delete($customer);
throw $e;
}
$customer = $this->customerRepository->getById($customer->getId());
$newLinkToken = $this->mathRandom->getUniqueHash();
$this->changeResetPasswordLinkToken($customer, $newLinkToken);
$this->sendEmailConfirmation($customer, $redirectUrl);
return $customer;
}
/**
* @inheritdoc
*/
public function getDefaultBillingAddress($customerId)
{
$customer = $this->customerRepository->getById($customerId);
return $this->getAddressById($customer, $customer->getDefaultBilling());
}
/**
* @inheritdoc
*/
public function getDefaultShippingAddress($customerId)
{
$customer = $this->customerRepository->getById($customerId);
return $this->getAddressById($customer, $customer->getDefaultShipping());
}
/**
* Send either confirmation or welcome email after an account creation
*
* @param CustomerInterface $customer
* @param string $redirectUrl
* @return void
* @throws LocalizedException
* @throws NoSuchEntityException
*/
protected function sendEmailConfirmation(CustomerInterface $customer, $redirectUrl)
{
try {
$hash = $this->customerRegistry->retrieveSecureData($customer->getId())->getPasswordHash();
$templateType = self::NEW_ACCOUNT_EMAIL_REGISTERED;
if ($this->isConfirmationRequired($customer) && $hash != '') {
$templateType = self::NEW_ACCOUNT_EMAIL_CONFIRMATION;
} elseif ($hash == '') {
$templateType = self::NEW_ACCOUNT_EMAIL_REGISTERED_NO_PASSWORD;
}
$this->getEmailNotification()->newAccount($customer, $templateType, $redirectUrl, $customer->getStoreId());
} catch (MailException $e) {
// If we are not able to send a new account email, this should be ignored
$this->logger->critical($e);
} catch (\UnexpectedValueException $e) {
$this->logger->error($e);
}
}
/**
* @inheritdoc
*
* @throws InvalidEmailOrPasswordException
*/
public function changePassword($email, $currentPassword, $newPassword)
{
try {
$customer = $this->customerRepository->get($email);
} catch (NoSuchEntityException $e) {
throw new InvalidEmailOrPasswordException(__('Invalid login or password.'));
}
return $this->changePasswordForCustomer($customer, $currentPassword, $newPassword);
}
/**
* @inheritdoc
*
* @throws InvalidEmailOrPasswordException
*/
public function changePasswordById($customerId, $currentPassword, $newPassword)
{
try {
$customer = $this->customerRepository->getById($customerId);
} catch (NoSuchEntityException $e) {
throw new InvalidEmailOrPasswordException(__('Invalid login or password.'));
}
return $this->changePasswordForCustomer($customer, $currentPassword, $newPassword);
}
/**
* Change customer password
*
* @param CustomerInterface $customer
* @param string $currentPassword
* @param string $newPassword
* @return bool true on success
* @throws InputException
* @throws InputMismatchException
* @throws InvalidEmailOrPasswordException
* @throws LocalizedException
* @throws NoSuchEntityException
* @throws UserLockedException
*/
private function changePasswordForCustomer($customer, $currentPassword, $newPassword)
{
try {
$this->getAuthentication()->authenticate($customer->getId(), $currentPassword);
} catch (InvalidEmailOrPasswordException $e) {
throw new InvalidEmailOrPasswordException(
__("The password doesn't match this account. Verify the password and try again.")
);
}
$customerEmail = $customer->getEmail();
$this->credentialsValidator->checkPasswordDifferentFromEmail($customerEmail, $newPassword);
$this->checkPasswordStrength($newPassword);
$customerSecure = $this->customerRegistry->retrieveSecureData($customer->getId());
$customerSecure->setRpToken(null);
$customerSecure->setRpTokenCreatedAt(null);
$customerSecure->setPasswordHash($this->createPasswordHash($newPassword));
$this->sessionCleaner->clearFor((int)$customer->getId());
$this->disableAddressValidation($customer);
$this->customerRepository->save($customer);
return true;
}
/**
* Create a hash for the given password
*
* @param string $password
* @return string
*/
protected function createPasswordHash($password)
{
return $this->encryptor->getHash($password, true);
}
/**
* Get EAV validator
*
* @return Backend
*/
private function getEavValidator()
{
if ($this->eavValidator === null) {
$this->eavValidator = ObjectManager::getInstance()->get(Backend::class);
}
return $this->eavValidator;
}
/**
* @inheritdoc
*/
public function validate(CustomerInterface $customer)
{
$validationResults = $this->validationResultsDataFactory->create();
$oldAddresses = $customer->getAddresses();
$customerModel = $this->customerFactory->create()->updateData(
$customer->setAddresses([])
);
$customer->setAddresses($oldAddresses);
$result = $this->getEavValidator()->isValid($customerModel);
if ($result === false && is_array($this->getEavValidator()->getMessages())) {
return $validationResults->setIsValid(false)->setMessages(
// phpcs:ignore Magento2.Functions.DiscouragedFunction
call_user_func_array(
'array_merge',
$this->getEavValidator()->getMessages()
)
);
}
return $validationResults->setIsValid(true)->setMessages([]);
}
/**
* @inheritdoc
*/
public function isEmailAvailable($customerEmail, $websiteId = null)
{
try {
if ($websiteId === null) {
$websiteId = $this->storeManager->getStore()->getWebsiteId();
}
$this->customerRepository->get($customerEmail, $websiteId);
return false;
} catch (NoSuchEntityException $e) {
return true;
}
}
/**
* @inheritDoc
*/
public function isCustomerInStore($customerWebsiteId, $storeId)
{
$ids = [];
if ((bool)$this->configShare->isWebsiteScope()) {
$ids = $this->storeManager->getWebsite($customerWebsiteId)->getStoreIds();
} else {
foreach ($this->storeManager->getStores() as $store) {
$ids[] = $store->getId();
}
}
return in_array($storeId, $ids);
}
/**
* Validate customer store id by customer website id.
*
* @param CustomerInterface $customer
* @return bool
* @throws LocalizedException
*/
public function validateCustomerStoreIdByWebsiteId(CustomerInterface $customer)
{
if (!$this->isCustomerInStore($customer->getWebsiteId(), $customer->getStoreId())) {
throw new LocalizedException(__('The store view is not in the associated website.'));
}
return true;
}
/**
* Validate the Reset Password Token for a customer.
*
* @param int $customerId
* @param string $resetPasswordLinkToken
*
* @return bool
* @throws ExpiredException If token is expired
* @throws InputException If token or customer id is invalid
* @throws InputMismatchException If token is mismatched
* @throws LocalizedException
* @throws NoSuchEntityException If customer doesn't exist
* @SuppressWarnings(PHPMD.LongVariable)
*/
private function validateResetPasswordToken(int $customerId, string $resetPasswordLinkToken): bool
{
if (!$customerId) {
throw new InputException(
__(
'Invalid value of "%value" provided for the %fieldName field.',
['value' => $customerId, 'fieldName' => 'customerId']
)
);
}
if (!$resetPasswordLinkToken) {
$params = ['fieldName' => 'resetPasswordLinkToken'];
throw new InputException(__('"%fieldName" is required. Enter and try again.', $params));
}
$customerSecureData = $this->customerRegistry->retrieveSecureData($customerId);
$rpToken = $customerSecureData->getRpToken();
$rpTokenCreatedAt = $customerSecureData->getRpTokenCreatedAt();
if (!Security::compareStrings($rpToken, $resetPasswordLinkToken)) {
throw new InputMismatchException(__('The password token is mismatched. Reset and try again.'));
} elseif ($this->isResetPasswordLinkTokenExpired($rpToken, $rpTokenCreatedAt)) {
throw new ExpiredException(__('The password token is expired. Reset and try again.'));
}
return true;
}
/**
* Check if customer can be deleted.
*
* @param int $customerId
* @return bool
* @throws \Magento\Framework\Exception\NoSuchEntityException If group is not found
* @throws LocalizedException
*/
public function isReadonly($customerId)
{
$customer = $this->customerRegistry->retrieveSecureData($customerId);
return !$customer->getDeleteable();
}
/**
* Send email with new account related information
*
* @param CustomerInterface $customer
* @param string $type
* @param string $backUrl
* @param string $storeId
* @param string $sendemailStoreId
* @return $this
* @throws LocalizedException
* @deprecated 100.1.0
*/
protected function sendNewAccountEmail(
$customer,
$type = self::NEW_ACCOUNT_EMAIL_REGISTERED,
$backUrl = '',
$storeId = '0',
$sendemailStoreId = null
) {
$types = $this->getTemplateTypes();
if (!isset($types[$type])) {
throw new LocalizedException(
__('The transactional account email type is incorrect. Verify and try again.')
);
}
if (!$storeId) {
$storeId = $this->getWebsiteStoreId($customer, $sendemailStoreId);
}
$store = $this->storeManager->getStore($customer->getStoreId());
$customerEmailData = $this->getFullCustomerObject($customer);
$this->sendEmailTemplate(
$customer,
$types[$type],
self::XML_PATH_REGISTER_EMAIL_IDENTITY,
['customer' => $customerEmailData, 'back_url' => $backUrl, 'store' => $store],
$storeId
);
return $this;
}
/**
* Send email to customer when his password is reset
*
* @param CustomerInterface $customer
* @return $this
* @throws LocalizedException
* @throws NoSuchEntityException
* @deprecated 100.1.0
*/
protected function sendPasswordResetNotificationEmail($customer)
{
return $this->sendPasswordResetConfirmationEmail($customer);
}
/**
* Get either first store ID from a set website or the provided as default
*
* @param CustomerInterface $customer
* @param int|string|null $defaultStoreId
* @return int
* @deprecated 100.1.0
* @throws LocalizedException
*/
protected function getWebsiteStoreId($customer, $defaultStoreId = null)
{
if ($customer->getWebsiteId() != 0 && empty($defaultStoreId)) {
$storeIds = $this->storeManager->getWebsite($customer->getWebsiteId())->getStoreIds();
reset($storeIds);
$defaultStoreId = current($storeIds);
}
return $defaultStoreId;
}
/**
* Get template types
*
* @return array
* @deprecated 100.1.0
*/
protected function getTemplateTypes()
{
/**
* self::NEW_ACCOUNT_EMAIL_REGISTERED welcome email, when confirmation is disabled
* and password is set
* self::NEW_ACCOUNT_EMAIL_REGISTERED_NO_PASSWORD welcome email, when confirmation is disabled
* and password is not set
* self::NEW_ACCOUNT_EMAIL_CONFIRMED welcome email, when confirmation is enabled
* and password is set
* self::NEW_ACCOUNT_EMAIL_CONFIRMATION email with confirmation link
*/
$types = [
self::NEW_ACCOUNT_EMAIL_REGISTERED => self::XML_PATH_REGISTER_EMAIL_TEMPLATE,
self::NEW_ACCOUNT_EMAIL_REGISTERED_NO_PASSWORD => self::XML_PATH_REGISTER_NO_PASSWORD_EMAIL_TEMPLATE,
self::NEW_ACCOUNT_EMAIL_CONFIRMED => self::XML_PATH_CONFIRMED_EMAIL_TEMPLATE,
self::NEW_ACCOUNT_EMAIL_CONFIRMATION => self::XML_PATH_CONFIRM_EMAIL_TEMPLATE,
];
return $types;
}
/**
* Send corresponding email template
*
* @param CustomerInterface $customer
* @param string $template configuration path of email template
* @param string $sender configuration path of email identity
* @param array $templateParams
* @param int|null $storeId
* @param string $email
* @return $this
* @throws MailException
* @deprecated 100.1.0
*/
protected function sendEmailTemplate(
$customer,
$template,
$sender,
$templateParams = [],
$storeId = null,
$email = null
) {
$templateId = $this->scopeConfig->getValue(
$template,
ScopeInterface::SCOPE_STORE,
$storeId
);
if ($email === null) {
$email = $customer->getEmail();
}
$transport = $this->transportBuilder->setTemplateIdentifier($templateId)
->setTemplateOptions(
[
'area' => Area::AREA_FRONTEND,
'store' => $storeId
]
)
->setTemplateVars($templateParams)
->setFrom(
$this->scopeConfig->getValue(
$sender,
ScopeInterface::SCOPE_STORE,
$storeId
)
)
->addTo($email, $this->customerViewHelper->getCustomerName($customer))
->getTransport();
$transport->sendMessage();
return $this;
}
/**
* Check if accounts confirmation is required in config
*
* @param CustomerInterface $customer
* @return bool
* @deprecated 101.0.4
* @see AccountConfirmation::isConfirmationRequired
*/
protected function isConfirmationRequired($customer)
{
return $this->accountConfirmation->isConfirmationRequired(
$customer->getWebsiteId(),
$customer->getId(),
$customer->getEmail()
);
}
/**
* Check whether confirmation may be skipped when registering using certain email address
*
* @param CustomerInterface $customer
* @return bool
* @deprecated 101.0.4
* @see AccountConfirmation::isConfirmationRequired
*/
protected function canSkipConfirmation($customer)
{
if (!$customer->getId()) {
return false;
}
/* If an email was used to start the registration process and it is the same email as the one
used to register, then this can skip confirmation.
*/
$skipConfirmationIfEmail = $this->registry->registry("skip_confirmation_if_email");
if (!$skipConfirmationIfEmail) {
return false;
}
return strtolower($skipConfirmationIfEmail) === strtolower($customer->getEmail());
}
/**
* Check if rpToken is expired
*
* @param string $rpToken
* @param string $rpTokenCreatedAt
* @return bool
*/
public function isResetPasswordLinkTokenExpired($rpToken, $rpTokenCreatedAt)
{
if (empty($rpToken) || empty($rpTokenCreatedAt)) {
return true;
}
$expirationPeriod = $this->customerModel->getResetPasswordLinkExpirationPeriod();
$currentTimestamp = $this->dateTimeFactory->create()->getTimestamp();
$tokenTimestamp = $this->dateTimeFactory->create($rpTokenCreatedAt)->getTimestamp();
if ($tokenTimestamp > $currentTimestamp) {
return true;
}
$hourDifference = floor(($currentTimestamp - $tokenTimestamp) / (60 * 60));
if ($hourDifference >= $expirationPeriod) {
return true;
}
return false;
}
/**
* Change reset password link token
*
* Stores new reset password link token
*
* @param CustomerInterface $customer
* @param string $passwordLinkToken
* @return bool
* @throws InputException
* @throws InputMismatchException
* @throws LocalizedException
* @throws NoSuchEntityException
*/
public function changeResetPasswordLinkToken($customer, $passwordLinkToken)
{
if (!is_string($passwordLinkToken) || empty($passwordLinkToken)) {
throw new InputException(
__(
'Invalid value of "%value" provided for the %fieldName field.',
['value' => $passwordLinkToken, 'fieldName' => 'password reset token']
)
);
}
if (is_string($passwordLinkToken) && !empty($passwordLinkToken)) {
$customerSecure = $this->customerRegistry->retrieveSecureData($customer->getId());
$customerSecure->setRpToken($passwordLinkToken);
$customerSecure->setRpTokenCreatedAt(
$this->dateTimeFactory->create()->format(DateTime::DATETIME_PHP_FORMAT)
);
$this->setIgnoreValidationFlag($customer);
$this->customerRepository->save($customer);
}
return true;
}
/**
* Send email with new customer password
*
* @param CustomerInterface $customer
* @return $this
* @throws LocalizedException
* @throws NoSuchEntityException
* @deprecated 100.1.0
*/
public function sendPasswordReminderEmail($customer)
{
$storeId = $this->storeManager->getStore()->getId();
if (!$storeId) {
$storeId = $this->getWebsiteStoreId($customer);
}
$customerEmailData = $this->getFullCustomerObject($customer);
$this->sendEmailTemplate(
$customer,
self::XML_PATH_REMIND_EMAIL_TEMPLATE,
self::XML_PATH_FORGOT_EMAIL_IDENTITY,
['customer' => $customerEmailData, 'store' => $this->storeManager->getStore($storeId)],
$storeId
);
return $this;
}
/**
* Send email with reset password confirmation link
*
* @param CustomerInterface $customer
* @return $this
* @throws LocalizedException
* @throws NoSuchEntityException
* @deprecated 100.1.0
*/
public function sendPasswordResetConfirmationEmail($customer)
{
$storeId = $this->storeManager->getStore()->getId();
if (!$storeId) {
$storeId = $this->getWebsiteStoreId($customer);
}
$customerEmailData = $this->getFullCustomerObject($customer);
$this->sendEmailTemplate(
$customer,
self::XML_PATH_FORGOT_EMAIL_TEMPLATE,
self::XML_PATH_FORGOT_EMAIL_IDENTITY,
['customer' => $customerEmailData, 'store' => $this->storeManager->getStore($storeId)],
$storeId
);
return $this;
}
/**
* Get address by id
*
* @param CustomerInterface $customer
* @param int $addressId
* @return AddressInterface|null
*/
protected function getAddressById(CustomerInterface $customer, $addressId)
{
foreach ($customer->getAddresses() as $address) {
if ($address->getId() == $addressId) {
return $address;
}
}
return null;
}
/**
* Create an object with data merged from Customer and CustomerSecure
*
* @param CustomerInterface $customer
* @return Data\CustomerSecure
* @throws NoSuchEntityException
* @deprecated 100.1.0
*/
protected function getFullCustomerObject($customer)
{
// No need to flatten the custom attributes or nested objects since the only usage is for email templates and
// object passed for events
$mergedCustomerData = $this->customerRegistry->retrieveSecureData($customer->getId());
$customerData = $this->dataProcessor->buildOutputDataArray(
$customer,
\Magento\Customer\Api\Data\CustomerInterface::class
);
$mergedCustomerData->addData($customerData);
$mergedCustomerData->setData('name', $this->customerViewHelper->getCustomerName($customer));
return $mergedCustomerData;
}
/**
* Return hashed password, which can be directly saved to database.
*
* @param string $password
* @return string
*/
public function getPasswordHash($password)
{
return $this->encryptor->getHash($password, true);
}
/**
* Disable Customer Address Validation
*
* @param CustomerInterface $customer
* @throws NoSuchEntityException
*/
private function disableAddressValidation($customer)
{
foreach ($customer->getAddresses() as $address) {
$addressModel = $this->addressRegistry->retrieve($address->getId());
$addressModel->setShouldIgnoreValidation(true);
}
}
/**
* Get email notification
*
* @return EmailNotificationInterface
* @deprecated 100.1.0
*/
private function getEmailNotification()
{
if (!($this->emailNotification instanceof EmailNotificationInterface)) {
return \Magento\Framework\App\ObjectManager::getInstance()->get(
EmailNotificationInterface::class
);
} else {
return $this->emailNotification;
}
}
/**
* Set ignore_validation_flag for reset password flow to skip unnecessary address and customer validation
*
* @param Customer $customer
* @return void
*/
private function setIgnoreValidationFlag($customer)
{
$customer->setData('ignore_validation_flag', true);
}
/**
* Check is address allowed for store
*
* @param AddressInterface $address
* @param int|null $storeId
* @return bool
*/
private function isAddressAllowedForWebsite(AddressInterface $address, $storeId): bool
{
$allowedCountries = $this->allowedCountriesReader->getAllowedCountries(ScopeInterface::SCOPE_STORE, $storeId);
return in_array($address->getCountryId(), $allowedCountries);
}
}