Magento 2 Documentation  2.3
Documentation for Magento 2 CMS v2.3 (December 2018)
AccountManagement.php
Go to the documentation of this file.
1 <?php
7 namespace Magento\Customer\Model;
8 
15 use Magento\Customer\Api\Data\ValidationResultsInterfaceFactory;
16 use Magento\Customer\Helper\View as CustomerViewHelper;
17 use Magento\Customer\Model\Config\Share as ConfigShare;
27 use Magento\Framework\DataObjectFactory as ObjectFactory;
49 use Magento\Framework\Stdlib\StringUtils as StringHelper;
52 use Psr\Log\LoggerInterface as PsrLogger;
55 use Magento\Customer\Model\ResourceModel\Visitor\CollectionFactory;
56 
65 {
71  const XML_PATH_REGISTER_EMAIL_TEMPLATE = 'customer/create_account/email_template';
72 
76  const XML_PATH_REGISTER_NO_PASSWORD_EMAIL_TEMPLATE = 'customer/create_account/email_no_password_template';
77 
81  const XML_PATH_REGISTER_EMAIL_IDENTITY = 'customer/create_account/email_identity';
82 
86  const XML_PATH_REMIND_EMAIL_TEMPLATE = 'customer/password/remind_email_template';
87 
91  const XML_PATH_FORGOT_EMAIL_TEMPLATE = 'customer/password/forgot_email_template';
92 
96  const XML_PATH_FORGOT_EMAIL_IDENTITY = 'customer/password/forgot_email_identity';
97 
102  const XML_PATH_IS_CONFIRM = 'customer/create_account/confirm';
103 
107  const XML_PATH_CONFIRM_EMAIL_TEMPLATE = 'customer/create_account/email_confirmation_template';
108 
112  const XML_PATH_CONFIRMED_EMAIL_TEMPLATE = 'customer/create_account/email_confirmed_template';
113 
119  const NEW_ACCOUNT_EMAIL_REGISTERED = 'registered';
120 
126  const NEW_ACCOUNT_EMAIL_REGISTERED_NO_PASSWORD = 'registered_no_password';
127 
133  const NEW_ACCOUNT_EMAIL_CONFIRMATION = 'confirmation';
134 
140  const NEW_ACCOUNT_EMAIL_CONFIRMED = 'confirmed';
141 
147  const EMAIL_REMINDER = 'email_reminder';
148 
149  const EMAIL_RESET = 'email_reset';
150 
154  const XML_PATH_MINIMUM_PASSWORD_LENGTH = 'customer/password/minimum_password_length';
155 
159  const XML_PATH_REQUIRED_CHARACTER_CLASSES_NUMBER = 'customer/password/required_character_classes_number';
160 
164  const XML_PATH_RESET_PASSWORD_TEMPLATE = 'customer/password/reset_password_template';
165 
170 
174  private $customerFactory;
175 
179  private $validationResultsDataFactory;
180 
184  private $eventManager;
185 
189  private $storeManager;
190 
194  private $mathRandom;
195 
199  private $validator;
200 
204  private $addressRepository;
205 
209  private $customerMetadataService;
210 
214  protected $logger;
215 
219  private $encryptor;
220 
224  private $customerRegistry;
225 
229  private $configShare;
230 
234  protected $stringHelper;
235 
239  private $customerRepository;
240 
244  private $scopeConfig;
245 
249  private $transportBuilder;
250 
254  private $sessionManager;
255 
259  private $saveHandler;
260 
264  private $visitorCollectionFactory;
265 
269  protected $dataProcessor;
270 
274  protected $registry;
275 
280 
284  protected $dateTime;
285 
289  protected $objectFactory;
290 
295 
299  protected $customerModel;
300 
304  protected $authentication;
305 
309  private $emailNotification;
310 
314  private $eavValidator;
315 
319  private $credentialsValidator;
320 
324  private $dateTimeFactory;
325 
329  private $accountConfirmation;
330 
334  private $searchCriteriaBuilder;
335 
369  public function __construct(
370  CustomerFactory $customerFactory,
371  ManagerInterface $eventManager,
372  StoreManagerInterface $storeManager,
373  Random $mathRandom,
374  Validator $validator,
375  ValidationResultsInterfaceFactory $validationResultsDataFactory,
376  AddressRepositoryInterface $addressRepository,
377  CustomerMetadataInterface $customerMetadataService,
378  CustomerRegistry $customerRegistry,
379  PsrLogger $logger,
380  Encryptor $encryptor,
381  ConfigShare $configShare,
382  StringHelper $stringHelper,
383  CustomerRepositoryInterface $customerRepository,
384  ScopeConfigInterface $scopeConfig,
385  TransportBuilder $transportBuilder,
388  CustomerViewHelper $customerViewHelper,
391  ObjectFactory $objectFactory,
393  CredentialsValidator $credentialsValidator = null,
394  DateTimeFactory $dateTimeFactory = null,
395  AccountConfirmation $accountConfirmation = null,
396  SessionManagerInterface $sessionManager = null,
397  SaveHandlerInterface $saveHandler = null,
398  CollectionFactory $visitorCollectionFactory = null,
399  SearchCriteriaBuilder $searchCriteriaBuilder = null
400  ) {
401  $this->customerFactory = $customerFactory;
402  $this->eventManager = $eventManager;
403  $this->storeManager = $storeManager;
404  $this->mathRandom = $mathRandom;
405  $this->validator = $validator;
406  $this->validationResultsDataFactory = $validationResultsDataFactory;
407  $this->addressRepository = $addressRepository;
408  $this->customerMetadataService = $customerMetadataService;
409  $this->customerRegistry = $customerRegistry;
410  $this->logger = $logger;
411  $this->encryptor = $encryptor;
412  $this->configShare = $configShare;
413  $this->stringHelper = $stringHelper;
414  $this->customerRepository = $customerRepository;
415  $this->scopeConfig = $scopeConfig;
416  $this->transportBuilder = $transportBuilder;
417  $this->dataProcessor = $dataProcessor;
418  $this->registry = $registry;
419  $this->customerViewHelper = $customerViewHelper;
420  $this->dateTime = $dateTime;
421  $this->customerModel = $customerModel;
422  $this->objectFactory = $objectFactory;
423  $this->extensibleDataObjectConverter = $extensibleDataObjectConverter;
424  $this->credentialsValidator =
425  $credentialsValidator ?: ObjectManager::getInstance()->get(CredentialsValidator::class);
426  $this->dateTimeFactory = $dateTimeFactory ?: ObjectManager::getInstance()->get(DateTimeFactory::class);
427  $this->accountConfirmation = $accountConfirmation ?: ObjectManager::getInstance()
428  ->get(AccountConfirmation::class);
429  $this->sessionManager = $sessionManager
430  ?: ObjectManager::getInstance()->get(SessionManagerInterface::class);
431  $this->saveHandler = $saveHandler
432  ?: ObjectManager::getInstance()->get(SaveHandlerInterface::class);
433  $this->visitorCollectionFactory = $visitorCollectionFactory
434  ?: ObjectManager::getInstance()->get(CollectionFactory::class);
435  $this->searchCriteriaBuilder = $searchCriteriaBuilder
436  ?: ObjectManager::getInstance()->get(SearchCriteriaBuilder::class);
437  }
438 
444  private function getAuthentication()
445  {
446  if (!($this->authentication instanceof AuthenticationInterface)) {
447  return \Magento\Framework\App\ObjectManager::getInstance()->get(
448  \Magento\Customer\Model\AuthenticationInterface::class
449  );
450  } else {
451  return $this->authentication;
452  }
453  }
454 
458  public function resendConfirmation($email, $websiteId = null, $redirectUrl = '')
459  {
460  $customer = $this->customerRepository->get($email, $websiteId);
461  if (!$customer->getConfirmation()) {
462  throw new InvalidTransitionException(__("Confirmation isn't needed."));
463  }
464 
465  try {
466  $this->getEmailNotification()->newAccount(
467  $customer,
468  self::NEW_ACCOUNT_EMAIL_CONFIRMATION,
469  $redirectUrl,
470  $this->storeManager->getStore()->getId()
471  );
472  } catch (MailException $e) {
473  // If we are not able to send a new account email, this should be ignored
474  $this->logger->critical($e);
475  }
476  }
477 
481  public function activate($email, $confirmationKey)
482  {
483  $customer = $this->customerRepository->get($email);
484  return $this->activateCustomer($customer, $confirmationKey);
485  }
486 
490  public function activateById($customerId, $confirmationKey)
491  {
492  $customer = $this->customerRepository->getById($customerId);
493  return $this->activateCustomer($customer, $confirmationKey);
494  }
495 
505  private function activateCustomer($customer, $confirmationKey)
506  {
507  // check if customer is inactive
508  if (!$customer->getConfirmation()) {
509  throw new InvalidTransitionException(__('The account is already active.'));
510  }
511 
512  if ($customer->getConfirmation() !== $confirmationKey) {
513  throw new InputMismatchException(__('The confirmation token is invalid. Verify the token and try again.'));
514  }
515 
516  $customer->setConfirmation(null);
517  $this->customerRepository->save($customer);
518  $this->getEmailNotification()->newAccount(
519  $customer,
520  'confirmed',
521  '',
522  $this->storeManager->getStore()->getId()
523  );
524  return $customer;
525  }
526 
530  public function authenticate($username, $password)
531  {
532  try {
533  $customer = $this->customerRepository->get($username);
534  } catch (NoSuchEntityException $e) {
535  throw new InvalidEmailOrPasswordException(__('Invalid login or password.'));
536  }
537 
538  $customerId = $customer->getId();
539  if ($this->getAuthentication()->isLocked($customerId)) {
540  throw new UserLockedException(__('The account is locked.'));
541  }
542  try {
543  $this->getAuthentication()->authenticate($customerId, $password);
544  } catch (InvalidEmailOrPasswordException $e) {
545  throw new InvalidEmailOrPasswordException(__('Invalid login or password.'));
546  }
547  if ($customer->getConfirmation() && $this->isConfirmationRequired($customer)) {
548  throw new EmailNotConfirmedException(__("This account isn't confirmed. Verify and try again."));
549  }
550 
551  $customerModel = $this->customerFactory->create()->updateData($customer);
552  $this->eventManager->dispatch(
553  'customer_customer_authenticated',
554  ['model' => $customerModel, 'password' => $password]
555  );
556 
557  $this->eventManager->dispatch('customer_data_object_login', ['customer' => $customer]);
558 
559  return $customer;
560  }
561 
565  public function validateResetPasswordLinkToken($customerId, $resetPasswordLinkToken)
566  {
567  $this->validateResetPasswordToken($customerId, $resetPasswordLinkToken);
568  return true;
569  }
570 
575  {
576  if ($websiteId === null) {
577  $websiteId = $this->storeManager->getStore()->getWebsiteId();
578  }
579  // load customer by email
580  $customer = $this->customerRepository->get($email, $websiteId);
581 
582  $newPasswordToken = $this->mathRandom->getUniqueHash();
583  $this->changeResetPasswordLinkToken($customer, $newPasswordToken);
584 
585  try {
586  switch ($template) {
588  $this->getEmailNotification()->passwordReminder($customer);
589  break;
591  $this->getEmailNotification()->passwordResetConfirmation($customer);
592  break;
593  default:
594  $this->handleUnknownTemplate($template);
595  break;
596  }
597  return true;
598  } catch (MailException $e) {
599  // If we are not able to send a reset password email, this should be ignored
600  $this->logger->critical($e);
601  }
602  return false;
603  }
604 
615  private function matchCustomerByRpToken(string $rpToken): CustomerInterface
616  {
617  $this->searchCriteriaBuilder->addFilter(
618  'rp_token',
619  $rpToken
620  );
621  $this->searchCriteriaBuilder->setPageSize(1);
622  $found = $this->customerRepository->getList(
623  $this->searchCriteriaBuilder->create()
624  );
625  if ($found->getTotalCount() > 1) {
626  //Failed to generated unique RP token
627  throw new ExpiredException(
628  new Phrase('Reset password token expired.')
629  );
630  }
631  if ($found->getTotalCount() === 0) {
632  //Customer with such token not found.
634  'rp_token',
635  $rpToken
636  );
637  }
638  //Unique customer found.
639  return $found->getItems()[0];
640  }
641 
648  private function handleUnknownTemplate($template)
649  {
650  throw new InputException(__(
651  'Invalid value of "%value" provided for the %fieldName field. Possible values: %template1 or %template2.',
652  [
653  'value' => $template,
654  'fieldName' => 'template',
655  'template1' => AccountManagement::EMAIL_REMINDER,
656  'template2' => AccountManagement::EMAIL_RESET
657  ]
658  ));
659  }
660 
664  public function resetPassword($email, $resetToken, $newPassword)
665  {
666  if (!$email) {
667  $customer = $this->matchCustomerByRpToken($resetToken);
668  $email = $customer->getEmail();
669  } else {
670  $customer = $this->customerRepository->get($email);
671  }
672  //Validate Token and new password strength
673  $this->validateResetPasswordToken($customer->getId(), $resetToken);
674  $this->credentialsValidator->checkPasswordDifferentFromEmail(
675  $email,
676  $newPassword
677  );
678  $this->checkPasswordStrength($newPassword);
679  //Update secure data
680  $customerSecure = $this->customerRegistry->retrieveSecureData($customer->getId());
681  $customerSecure->setRpToken(null);
682  $customerSecure->setRpTokenCreatedAt(null);
683  $customerSecure->setPasswordHash($this->createPasswordHash($newPassword));
684  $this->sessionManager->destroy();
685  $this->destroyCustomerSessions($customer->getId());
686  $this->customerRepository->save($customer);
687 
688  return true;
689  }
690 
698  protected function checkPasswordStrength($password)
699  {
700  $length = $this->stringHelper->strlen($password);
701  if ($length > self::MAX_PASSWORD_LENGTH) {
702  throw new InputException(
703  __(
704  'Please enter a password with at most %1 characters.',
705  self::MAX_PASSWORD_LENGTH
706  )
707  );
708  }
709  $configMinPasswordLength = $this->getMinPasswordLength();
710  if ($length < $configMinPasswordLength) {
711  throw new InputException(
712  __(
713  'The password needs at least %1 characters. Create a new password and try again.',
714  $configMinPasswordLength
715  )
716  );
717  }
718  if ($this->stringHelper->strlen(trim($password)) != $length) {
719  throw new InputException(
720  __("The password can't begin or end with a space. Verify the password and try again.")
721  );
722  }
723 
724  $requiredCharactersCheck = $this->makeRequiredCharactersCheck($password);
725  if ($requiredCharactersCheck !== 0) {
726  throw new InputException(
727  __(
728  'Minimum of different classes of characters in password is %1.' .
729  ' Classes of characters: Lower Case, Upper Case, Digits, Special Characters.',
730  $requiredCharactersCheck
731  )
732  );
733  }
734  }
735 
742  protected function makeRequiredCharactersCheck($password)
743  {
744  $counter = 0;
745  $requiredNumber = $this->scopeConfig->getValue(self::XML_PATH_REQUIRED_CHARACTER_CLASSES_NUMBER);
746  $return = 0;
747 
748  if (preg_match('/[0-9]+/', $password)) {
749  $counter++;
750  }
751  if (preg_match('/[A-Z]+/', $password)) {
752  $counter++;
753  }
754  if (preg_match('/[a-z]+/', $password)) {
755  $counter++;
756  }
757  if (preg_match('/[^a-zA-Z0-9]+/', $password)) {
758  $counter++;
759  }
760 
761  if ($counter < $requiredNumber) {
762  $return = $requiredNumber;
763  }
764 
765  return $return;
766  }
767 
773  protected function getMinPasswordLength()
774  {
775  return $this->scopeConfig->getValue(self::XML_PATH_MINIMUM_PASSWORD_LENGTH);
776  }
777 
782  {
783  // load customer by id
784  $customer = $this->customerRepository->getById($customerId);
785  if ($this->isConfirmationRequired($customer)) {
786  if (!$customer->getConfirmation()) {
788  }
790  }
792  }
793 
797  public function createAccount(CustomerInterface $customer, $password = null, $redirectUrl = '')
798  {
799  if ($password !== null) {
800  $this->checkPasswordStrength($password);
801  $customerEmail = $customer->getEmail();
802  try {
803  $this->credentialsValidator->checkPasswordDifferentFromEmail($customerEmail, $password);
804  } catch (InputException $e) {
805  throw new LocalizedException(
806  __("The password can't be the same as the email address. Create a new password and try again.")
807  );
808  }
809  $hash = $this->createPasswordHash($password);
810  } else {
811  $hash = null;
812  }
813  return $this->createAccountWithPasswordHash($customer, $hash, $redirectUrl);
814  }
815 
821  public function createAccountWithPasswordHash(CustomerInterface $customer, $hash, $redirectUrl = '')
822  {
823  // This logic allows an existing customer to be added to a different store. No new account is created.
824  // The plan is to move this logic into a new method called something like 'registerAccountWithStore'
825  if ($customer->getId()) {
826  $customer = $this->customerRepository->get($customer->getEmail());
827  $websiteId = $customer->getWebsiteId();
828 
829  if ($this->isCustomerInStore($websiteId, $customer->getStoreId())) {
830  throw new InputException(__('This customer already exists in this store.'));
831  }
832  // Existing password hash will be used from secured customer data registry when saving customer
833  }
834 
835  // Make sure we have a storeId to associate this customer with.
836  if (!$customer->getStoreId()) {
837  if ($customer->getWebsiteId()) {
838  $storeId = $this->storeManager->getWebsite($customer->getWebsiteId())->getDefaultStore()->getId();
839  } else {
840  $this->storeManager->setCurrentStore(null);
841  $storeId = $this->storeManager->getStore()->getId();
842  }
843  $customer->setStoreId($storeId);
844  }
845 
846  // Associate website_id with customer
847  if (!$customer->getWebsiteId()) {
848  $websiteId = $this->storeManager->getStore($customer->getStoreId())->getWebsiteId();
849  $customer->setWebsiteId($websiteId);
850  }
851 
852  // Update 'created_in' value with actual store name
853  if ($customer->getId() === null) {
854  $websiteId = $customer->getWebsiteId();
855  if ($websiteId && !$this->isCustomerInStore($websiteId, $customer->getStoreId())) {
856  throw new LocalizedException(__('The store view is not in the associated website.'));
857  }
858 
859  $storeName = $this->storeManager->getStore($customer->getStoreId())->getName();
860  $customer->setCreatedIn($storeName);
861  }
862 
863  $customerAddresses = $customer->getAddresses() ?: [];
864  $customer->setAddresses(null);
865  try {
866  // If customer exists existing hash will be used by Repository
867  $customer = $this->customerRepository->save($customer, $hash);
868  } catch (AlreadyExistsException $e) {
869  throw new InputMismatchException(
870  __('A customer with the same email address already exists in an associated website.')
871  );
872  } catch (LocalizedException $e) {
873  throw $e;
874  }
875  try {
876  foreach ($customerAddresses as $address) {
877  if ($address->getId()) {
878  $newAddress = clone $address;
879  $newAddress->setId(null);
880  $newAddress->setCustomerId($customer->getId());
881  $this->addressRepository->save($newAddress);
882  } else {
883  $address->setCustomerId($customer->getId());
884  $this->addressRepository->save($address);
885  }
886  }
887  $this->customerRegistry->remove($customer->getId());
888  } catch (InputException $e) {
889  $this->customerRepository->delete($customer);
890  throw $e;
891  }
892  $customer = $this->customerRepository->getById($customer->getId());
893  $newLinkToken = $this->mathRandom->getUniqueHash();
894  $this->changeResetPasswordLinkToken($customer, $newLinkToken);
895  $this->sendEmailConfirmation($customer, $redirectUrl);
896 
897  return $customer;
898  }
899 
904  {
905  $customer = $this->customerRepository->getById($customerId);
906  return $this->getAddressById($customer, $customer->getDefaultBilling());
907  }
908 
913  {
914  $customer = $this->customerRepository->getById($customerId);
915  return $this->getAddressById($customer, $customer->getDefaultShipping());
916  }
917 
925  protected function sendEmailConfirmation(CustomerInterface $customer, $redirectUrl)
926  {
927  try {
928  $hash = $this->customerRegistry->retrieveSecureData($customer->getId())->getPasswordHash();
930  if ($this->isConfirmationRequired($customer) && $hash != '') {
932  } elseif ($hash == '') {
934  }
935  $this->getEmailNotification()->newAccount($customer, $templateType, $redirectUrl, $customer->getStoreId());
936  } catch (MailException $e) {
937  // If we are not able to send a new account email, this should be ignored
938  $this->logger->critical($e);
939  } catch (\UnexpectedValueException $e) {
940  $this->logger->error($e);
941  }
942  }
943 
947  public function changePassword($email, $currentPassword, $newPassword)
948  {
949  try {
950  $customer = $this->customerRepository->get($email);
951  } catch (NoSuchEntityException $e) {
952  throw new InvalidEmailOrPasswordException(__('Invalid login or password.'));
953  }
954  return $this->changePasswordForCustomer($customer, $currentPassword, $newPassword);
955  }
956 
960  public function changePasswordById($customerId, $currentPassword, $newPassword)
961  {
962  try {
963  $customer = $this->customerRepository->getById($customerId);
964  } catch (NoSuchEntityException $e) {
965  throw new InvalidEmailOrPasswordException(__('Invalid login or password.'));
966  }
967  return $this->changePasswordForCustomer($customer, $currentPassword, $newPassword);
968  }
969 
981  private function changePasswordForCustomer($customer, $currentPassword, $newPassword)
982  {
983  try {
984  $this->getAuthentication()->authenticate($customer->getId(), $currentPassword);
985  } catch (InvalidEmailOrPasswordException $e) {
986  throw new InvalidEmailOrPasswordException(
987  __("The password doesn't match this account. Verify the password and try again.")
988  );
989  }
990  $customerEmail = $customer->getEmail();
991  $this->credentialsValidator->checkPasswordDifferentFromEmail($customerEmail, $newPassword);
992  $customerSecure = $this->customerRegistry->retrieveSecureData($customer->getId());
993  $customerSecure->setRpToken(null);
994  $customerSecure->setRpTokenCreatedAt(null);
995  $this->checkPasswordStrength($newPassword);
996  $customerSecure->setPasswordHash($this->createPasswordHash($newPassword));
997  $this->destroyCustomerSessions($customer->getId());
998  $this->customerRepository->save($customer);
999 
1000  return true;
1001  }
1002 
1009  protected function createPasswordHash($password)
1010  {
1011  return $this->encryptor->getHash($password, true);
1012  }
1013 
1019  private function getEavValidator()
1020  {
1021  if ($this->eavValidator === null) {
1022  $this->eavValidator = ObjectManager::getInstance()->get(Backend::class);
1023  }
1024  return $this->eavValidator;
1025  }
1026 
1031  {
1032  $validationResults = $this->validationResultsDataFactory->create();
1033 
1034  $oldAddresses = $customer->getAddresses();
1035  $customerModel = $this->customerFactory->create()->updateData(
1036  $customer->setAddresses([])
1037  );
1038  $customer->setAddresses($oldAddresses);
1039 
1040  $result = $this->getEavValidator()->isValid($customerModel);
1041  if ($result === false && is_array($this->getEavValidator()->getMessages())) {
1042  return $validationResults->setIsValid(false)->setMessages(
1043  call_user_func_array(
1044  'array_merge',
1045  $this->getEavValidator()->getMessages()
1046  )
1047  );
1048  }
1049  return $validationResults->setIsValid(true)->setMessages([]);
1050  }
1051 
1055  public function isEmailAvailable($customerEmail, $websiteId = null)
1056  {
1057  try {
1058  if ($websiteId === null) {
1059  $websiteId = $this->storeManager->getStore()->getWebsiteId();
1060  }
1061  $this->customerRepository->get($customerEmail, $websiteId);
1062  return false;
1063  } catch (NoSuchEntityException $e) {
1064  return true;
1065  }
1066  }
1067 
1071  public function isCustomerInStore($customerWebsiteId, $storeId)
1072  {
1073  $ids = [];
1074  if ((bool)$this->configShare->isWebsiteScope()) {
1075  $ids = $this->storeManager->getWebsite($customerWebsiteId)->getStoreIds();
1076  } else {
1077  foreach ($this->storeManager->getStores() as $store) {
1078  $ids[] = $store->getId();
1079  }
1080  }
1081 
1082  return in_array($storeId, $ids);
1083  }
1084 
1097  private function validateResetPasswordToken($customerId, $resetPasswordLinkToken)
1098  {
1099  if ($customerId !== null && $customerId <= 0) {
1100  throw new InputException(
1101  __(
1102  'Invalid value of "%value" provided for the %fieldName field.',
1103  ['value' => $customerId, 'fieldName' => 'customerId']
1104  )
1105  );
1106  }
1107 
1108  if ($customerId === null) {
1109  //Looking for the customer.
1110  $customerId = $this->matchCustomerByRpToken($resetPasswordLinkToken)
1111  ->getId();
1112  }
1113  if (!is_string($resetPasswordLinkToken) || empty($resetPasswordLinkToken)) {
1114  $params = ['fieldName' => 'resetPasswordLinkToken'];
1115  throw new InputException(__('"%fieldName" is required. Enter and try again.', $params));
1116  }
1117  $customerSecureData = $this->customerRegistry->retrieveSecureData($customerId);
1118  $rpToken = $customerSecureData->getRpToken();
1119  $rpTokenCreatedAt = $customerSecureData->getRpTokenCreatedAt();
1120  if (!Security::compareStrings($rpToken, $resetPasswordLinkToken)) {
1121  throw new InputMismatchException(__('The password token is mismatched. Reset and try again.'));
1122  } elseif ($this->isResetPasswordLinkTokenExpired($rpToken, $rpTokenCreatedAt)) {
1123  throw new ExpiredException(__('The password token is expired. Reset and try again.'));
1124  }
1125  return true;
1126  }
1127 
1136  public function isReadonly($customerId)
1137  {
1138  $customer = $this->customerRegistry->retrieveSecureData($customerId);
1139  return !$customer->getDeleteable();
1140  }
1141 
1154  protected function sendNewAccountEmail(
1155  $customer,
1156  $type = self::NEW_ACCOUNT_EMAIL_REGISTERED,
1157  $backUrl = '',
1158  $storeId = '0',
1159  $sendemailStoreId = null
1160  ) {
1161  $types = $this->getTemplateTypes();
1162 
1163  if (!isset($types[$type])) {
1164  throw new LocalizedException(
1165  __('The transactional account email type is incorrect. Verify and try again.')
1166  );
1167  }
1168 
1169  if (!$storeId) {
1170  $storeId = $this->getWebsiteStoreId($customer, $sendemailStoreId);
1171  }
1172 
1173  $store = $this->storeManager->getStore($customer->getStoreId());
1174 
1175  $customerEmailData = $this->getFullCustomerObject($customer);
1176 
1177  $this->sendEmailTemplate(
1178  $customer,
1179  $types[$type],
1180  self::XML_PATH_REGISTER_EMAIL_IDENTITY,
1181  ['customer' => $customerEmailData, 'back_url' => $backUrl, 'store' => $store],
1182  $storeId
1183  );
1184 
1185  return $this;
1186  }
1187 
1196  {
1198  }
1199 
1209  protected function getWebsiteStoreId($customer, $defaultStoreId = null)
1210  {
1211  if ($customer->getWebsiteId() != 0 && empty($defaultStoreId)) {
1212  $storeIds = $this->storeManager->getWebsite($customer->getWebsiteId())->getStoreIds();
1213  reset($storeIds);
1214  $defaultStoreId = current($storeIds);
1215  }
1216  return $defaultStoreId;
1217  }
1218 
1225  protected function getTemplateTypes()
1226  {
1236  $types = [
1237  self::NEW_ACCOUNT_EMAIL_REGISTERED => self::XML_PATH_REGISTER_EMAIL_TEMPLATE,
1238  self::NEW_ACCOUNT_EMAIL_REGISTERED_NO_PASSWORD => self::XML_PATH_REGISTER_NO_PASSWORD_EMAIL_TEMPLATE,
1239  self::NEW_ACCOUNT_EMAIL_CONFIRMED => self::XML_PATH_CONFIRMED_EMAIL_TEMPLATE,
1240  self::NEW_ACCOUNT_EMAIL_CONFIRMATION => self::XML_PATH_CONFIRM_EMAIL_TEMPLATE,
1241  ];
1242  return $types;
1243  }
1244 
1257  protected function sendEmailTemplate(
1258  $customer,
1259  $template,
1260  $sender,
1261  $templateParams = [],
1262  $storeId = null,
1263  $email = null
1264  ) {
1265  $templateId = $this->scopeConfig->getValue(
1266  $template,
1267  ScopeInterface::SCOPE_STORE,
1268  $storeId
1269  );
1270  if ($email === null) {
1271  $email = $customer->getEmail();
1272  }
1273 
1274  $transport = $this->transportBuilder->setTemplateIdentifier($templateId)
1275  ->setTemplateOptions(['area' => Area::AREA_FRONTEND, 'store' => $storeId])
1276  ->setTemplateVars($templateParams)
1277  ->setFrom($this->scopeConfig->getValue(
1278  $sender,
1279  ScopeInterface::SCOPE_STORE,
1280  $storeId
1281  ))
1282  ->addTo($email, $this->customerViewHelper->getCustomerName($customer))
1283  ->getTransport();
1284 
1285  $transport->sendMessage();
1286 
1287  return $this;
1288  }
1289 
1298  protected function isConfirmationRequired($customer)
1299  {
1300  return $this->accountConfirmation->isConfirmationRequired(
1301  $customer->getWebsiteId(),
1302  $customer->getId(),
1303  $customer->getEmail()
1304  );
1305  }
1306 
1315  protected function canSkipConfirmation($customer)
1316  {
1317  if (!$customer->getId()) {
1318  return false;
1319  }
1320 
1321  /* If an email was used to start the registration process and it is the same email as the one
1322  used to register, then this can skip confirmation.
1323  */
1324  $skipConfirmationIfEmail = $this->registry->registry("skip_confirmation_if_email");
1325  if (!$skipConfirmationIfEmail) {
1326  return false;
1327  }
1328 
1329  return strtolower($skipConfirmationIfEmail) === strtolower($customer->getEmail());
1330  }
1331 
1339  public function isResetPasswordLinkTokenExpired($rpToken, $rpTokenCreatedAt)
1340  {
1341  if (empty($rpToken) || empty($rpTokenCreatedAt)) {
1342  return true;
1343  }
1344 
1345  $expirationPeriod = $this->customerModel->getResetPasswordLinkExpirationPeriod();
1346 
1347  $currentTimestamp = $this->dateTimeFactory->create()->getTimestamp();
1348  $tokenTimestamp = $this->dateTimeFactory->create($rpTokenCreatedAt)->getTimestamp();
1349  if ($tokenTimestamp > $currentTimestamp) {
1350  return true;
1351  }
1352 
1353  $hourDifference = floor(($currentTimestamp - $tokenTimestamp) / (60 * 60));
1354  if ($hourDifference >= $expirationPeriod) {
1355  return true;
1356  }
1357 
1358  return false;
1359  }
1360 
1371  public function changeResetPasswordLinkToken($customer, $passwordLinkToken)
1372  {
1373  if (!is_string($passwordLinkToken) || empty($passwordLinkToken)) {
1374  throw new InputException(
1375  __(
1376  'Invalid value of "%value" provided for the %fieldName field.',
1377  ['value' => $passwordLinkToken, 'fieldName' => 'password reset token']
1378  )
1379  );
1380  }
1381  if (is_string($passwordLinkToken) && !empty($passwordLinkToken)) {
1382  $customerSecure = $this->customerRegistry->retrieveSecureData($customer->getId());
1383  $customerSecure->setRpToken($passwordLinkToken);
1384  $customerSecure->setRpTokenCreatedAt(
1385  $this->dateTimeFactory->create()->format(DateTime::DATETIME_PHP_FORMAT)
1386  );
1387  $this->customerRepository->save($customer);
1388  }
1389  return true;
1390  }
1391 
1400  {
1401  $storeId = $this->storeManager->getStore()->getId();
1402  if (!$storeId) {
1404  }
1405 
1406  $customerEmailData = $this->getFullCustomerObject($customer);
1407 
1408  $this->sendEmailTemplate(
1409  $customer,
1410  self::XML_PATH_REMIND_EMAIL_TEMPLATE,
1411  self::XML_PATH_FORGOT_EMAIL_IDENTITY,
1412  ['customer' => $customerEmailData, 'store' => $this->storeManager->getStore($storeId)],
1413  $storeId
1414  );
1415 
1416  return $this;
1417  }
1418 
1427  {
1428  $storeId = $this->storeManager->getStore()->getId();
1429  if (!$storeId) {
1431  }
1432 
1433  $customerEmailData = $this->getFullCustomerObject($customer);
1434 
1435  $this->sendEmailTemplate(
1436  $customer,
1437  self::XML_PATH_FORGOT_EMAIL_TEMPLATE,
1438  self::XML_PATH_FORGOT_EMAIL_IDENTITY,
1439  ['customer' => $customerEmailData, 'store' => $this->storeManager->getStore($storeId)],
1440  $storeId
1441  );
1442 
1443  return $this;
1444  }
1445 
1453  protected function getAddressById(CustomerInterface $customer, $addressId)
1454  {
1455  foreach ($customer->getAddresses() as $address) {
1456  if ($address->getId() == $addressId) {
1457  return $address;
1458  }
1459  }
1460  return null;
1461  }
1462 
1470  protected function getFullCustomerObject($customer)
1471  {
1472  // No need to flatten the custom attributes or nested objects since the only usage is for email templates and
1473  // object passed for events
1474  $mergedCustomerData = $this->customerRegistry->retrieveSecureData($customer->getId());
1475  $customerData = $this->dataProcessor->buildOutputDataArray(
1476  $customer,
1477  \Magento\Customer\Api\Data\CustomerInterface::class
1478  );
1479  $mergedCustomerData->addData($customerData);
1480  $mergedCustomerData->setData('name', $this->customerViewHelper->getCustomerName($customer));
1481  return $mergedCustomerData;
1482  }
1483 
1490  public function getPasswordHash($password)
1491  {
1492  return $this->encryptor->getHash($password);
1493  }
1494 
1501  private function getEmailNotification()
1502  {
1503  if (!($this->emailNotification instanceof EmailNotificationInterface)) {
1504  return \Magento\Framework\App\ObjectManager::getInstance()->get(
1505  EmailNotificationInterface::class
1506  );
1507  } else {
1508  return $this->emailNotification;
1509  }
1510  }
1511 
1520  private function destroyCustomerSessions($customerId)
1521  {
1522  $sessionLifetime = $this->scopeConfig->getValue(
1523  \Magento\Framework\Session\Config::XML_PATH_COOKIE_LIFETIME,
1524  \Magento\Store\Model\ScopeInterface::SCOPE_STORE
1525  );
1526  $dateTime = $this->dateTimeFactory->create();
1527  $activeSessionsTime = $dateTime->setTimestamp($dateTime->getTimestamp() - $sessionLifetime)
1530  $visitorCollection = $this->visitorCollectionFactory->create();
1531  $visitorCollection->addFieldToFilter('customer_id', $customerId);
1532  $visitorCollection->addFieldToFilter('last_visit_at', ['from' => $activeSessionsTime]);
1533  $visitorCollection->addFieldToFilter('session_id', ['neq' => $this->sessionManager->getSessionId()]);
1535  foreach ($visitorCollection->getItems() as $visitor) {
1536  $sessionId = $visitor->getSessionId();
1537  $this->saveHandler->destroy($sessionId);
1538  }
1539  }
1540 }
initiatePasswordReset($email, $template, $websiteId=null)
__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)
$addressRepository
sendEmailConfirmation(CustomerInterface $customer, $redirectUrl)
$customerData
elseif(isset( $params[ 'redirect_parent']))
Definition: iframe.phtml:17
$customer
Definition: customers.php:11
$email
Definition: details.phtml:13
isEmailAvailable($customerEmail, $websiteId=null)
createAccount(CustomerInterface $customer, $password=null, $redirectUrl='')
$storeManager
__()
Definition: __.php:13
$templateType
Definition: list.phtml:37
isCustomerInStore($customerWebsiteId, $storeId)
createAccountWithPasswordHash(CustomerInterface $customer, $hash, $redirectUrl='')
$customerRepository
$address
Definition: customer.php:38
$templateId
Definition: queue.php:15
$type
Definition: item.phtml:13
changePassword($email, $currentPassword, $newPassword)
changePasswordById($customerId, $currentPassword, $newPassword)
getAddressById(CustomerInterface $customer, $addressId)
changeResetPasswordLinkToken($customer, $passwordLinkToken)
isResetPasswordLinkTokenExpired($rpToken, $rpTokenCreatedAt)
sendNewAccountEmail( $customer, $type=self::NEW_ACCOUNT_EMAIL_REGISTERED, $backUrl='', $storeId='0', $sendemailStoreId=null)
sendEmailTemplate( $customer, $template, $sender, $templateParams=[], $storeId=null, $email=null)
validateResetPasswordLinkToken($customerId, $resetPasswordLinkToken)
resendConfirmation($email, $websiteId=null, $redirectUrl='')
activateById($customerId, $confirmationKey)
$customerRegistry
Definition: customers.php:12
static compareStrings($expected, $actual)
Definition: Security.php:26
$storeName
Definition: logo.phtml:13
getWebsiteStoreId($customer, $defaultStoreId=null)
$params[\Magento\Store\Model\StoreManager::PARAM_RUN_CODE]
Definition: website.php:18
resetPassword($email, $resetToken, $newPassword)
$template
Definition: export.php:12