1<?php 2 3namespace Drupal\user\Form; 4 5use Drupal\Core\Config\ConfigFactory; 6use Drupal\Core\Flood\FloodInterface; 7use Drupal\Core\Form\FormBase; 8use Drupal\Core\Form\FormStateInterface; 9use Drupal\Core\Language\LanguageManagerInterface; 10use Drupal\Core\Render\Element\Email; 11use Drupal\user\UserInterface; 12use Drupal\user\UserStorageInterface; 13use Symfony\Component\DependencyInjection\ContainerInterface; 14 15/** 16 * Provides a user password reset form. 17 * 18 * Send the user an email to reset their password. 19 * 20 * @internal 21 */ 22class UserPasswordForm extends FormBase { 23 24 /** 25 * The user storage. 26 * 27 * @var \Drupal\user\UserStorageInterface 28 */ 29 protected $userStorage; 30 31 /** 32 * The language manager. 33 * 34 * @var \Drupal\Core\Language\LanguageManagerInterface 35 */ 36 protected $languageManager; 37 38 /** 39 * The flood service. 40 * 41 * @var \Drupal\Core\Flood\FloodInterface 42 */ 43 protected $flood; 44 45 /** 46 * Constructs a UserPasswordForm object. 47 * 48 * @param \Drupal\user\UserStorageInterface $user_storage 49 * The user storage. 50 * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager 51 * The language manager. 52 * @param \Drupal\Core\Config\ConfigFactory $config_factory 53 * The config factory. 54 * @param \Drupal\Core\Flood\FloodInterface $flood 55 * The flood service. 56 */ 57 public function __construct(UserStorageInterface $user_storage, LanguageManagerInterface $language_manager, ConfigFactory $config_factory = NULL, FloodInterface $flood = NULL) { 58 $this->userStorage = $user_storage; 59 $this->languageManager = $language_manager; 60 if (!$config_factory) { 61 @trigger_error('Calling ' . __METHOD__ . ' without the $config_factory is deprecated in drupal:8.8.0 and is required before drupal:9.0.0. See https://www.drupal.org/node/1681832', E_USER_DEPRECATED); 62 $config_factory = \Drupal::configFactory(); 63 } 64 $this->configFactory = $config_factory; 65 if (!$flood) { 66 @trigger_error('Calling ' . __METHOD__ . ' without the $flood parameter is deprecated in drupal:8.8.0 and is required before drupal:9.0.0. See https://www.drupal.org/node/1681832', E_USER_DEPRECATED); 67 $flood = \Drupal::service('flood'); 68 } 69 $this->flood = $flood; 70 } 71 72 /** 73 * {@inheritdoc} 74 */ 75 public static function create(ContainerInterface $container) { 76 return new static( 77 $container->get('entity_type.manager')->getStorage('user'), 78 $container->get('language_manager'), 79 $container->get('config.factory'), 80 $container->get('flood') 81 ); 82 } 83 84 /** 85 * {@inheritdoc} 86 */ 87 public function getFormId() { 88 return 'user_pass'; 89 } 90 91 /** 92 * {@inheritdoc} 93 */ 94 public function buildForm(array $form, FormStateInterface $form_state) { 95 $form['name'] = [ 96 '#type' => 'textfield', 97 '#title' => $this->t('Username or email address'), 98 '#size' => 60, 99 '#maxlength' => max(UserInterface::USERNAME_MAX_LENGTH, Email::EMAIL_MAX_LENGTH), 100 '#required' => TRUE, 101 '#attributes' => [ 102 'autocorrect' => 'off', 103 'autocapitalize' => 'off', 104 'spellcheck' => 'false', 105 'autofocus' => 'autofocus', 106 ], 107 ]; 108 // Allow logged in users to request this also. 109 $user = $this->currentUser(); 110 if ($user->isAuthenticated()) { 111 $form['name']['#type'] = 'value'; 112 $form['name']['#value'] = $user->getEmail(); 113 $form['mail'] = [ 114 '#prefix' => '<p>', 115 '#markup' => $this->t('Password reset instructions will be mailed to %email. You must log out to use the password reset link in the email.', ['%email' => $user->getEmail()]), 116 '#suffix' => '</p>', 117 ]; 118 } 119 else { 120 $form['mail'] = [ 121 '#prefix' => '<p>', 122 '#markup' => $this->t('Password reset instructions will be sent to your registered email address.'), 123 '#suffix' => '</p>', 124 ]; 125 $form['name']['#default_value'] = $this->getRequest()->query->get('name'); 126 } 127 $form['actions'] = ['#type' => 'actions']; 128 $form['actions']['submit'] = ['#type' => 'submit', '#value' => $this->t('Submit')]; 129 $form['#cache']['contexts'][] = 'url.query_args'; 130 131 return $form; 132 } 133 134 /** 135 * {@inheritdoc} 136 */ 137 public function validateForm(array &$form, FormStateInterface $form_state) { 138 $flood_config = $this->configFactory->get('user.flood'); 139 if (!$this->flood->isAllowed('user.password_request_ip', $flood_config->get('ip_limit'), $flood_config->get('ip_window'))) { 140 $form_state->setErrorByName('name', $this->t('Too many password recovery requests from your IP address. It is temporarily blocked. Try again later or contact the site administrator.')); 141 return; 142 } 143 $this->flood->register('user.password_request_ip', $flood_config->get('ip_window')); 144 $name = trim($form_state->getValue('name')); 145 // Try to load by email. 146 $users = $this->userStorage->loadByProperties(['mail' => $name]); 147 if (empty($users)) { 148 // No success, try to load by name. 149 $users = $this->userStorage->loadByProperties(['name' => $name]); 150 } 151 $account = reset($users); 152 if ($account && $account->id()) { 153 // Blocked accounts cannot request a new password. 154 if (!$account->isActive()) { 155 $form_state->setErrorByName('name', $this->t('%name is blocked or has not been activated yet.', ['%name' => $name])); 156 } 157 else { 158 // Register flood events based on the uid only, so they apply for any 159 // IP address. This allows them to be cleared on successful reset (from 160 // any IP). 161 $identifier = $account->id(); 162 if (!$this->flood->isAllowed('user.password_request_user', $flood_config->get('user_limit'), $flood_config->get('user_window'), $identifier)) { 163 $form_state->setErrorByName('name', $this->t('Too many password recovery requests for this account. It is temporarily blocked. Try again later or contact the site administrator.')); 164 return; 165 } 166 $this->flood->register('user.password_request_user', $flood_config->get('user_window'), $identifier); 167 $form_state->setValueForElement(['#parents' => ['account']], $account); 168 } 169 } 170 else { 171 $form_state->setErrorByName('name', $this->t('%name is not recognized as a username or an email address.', ['%name' => $name])); 172 } 173 } 174 175 /** 176 * {@inheritdoc} 177 */ 178 public function submitForm(array &$form, FormStateInterface $form_state) { 179 $langcode = $this->languageManager->getCurrentLanguage()->getId(); 180 181 $account = $form_state->getValue('account'); 182 // Mail one time login URL and instructions using current language. 183 $mail = _user_mail_notify('password_reset', $account, $langcode); 184 if (!empty($mail)) { 185 $this->logger('user')->notice('Password reset instructions mailed to %name at %email.', ['%name' => $account->getAccountName(), '%email' => $account->getEmail()]); 186 $this->messenger()->addStatus($this->t('Further instructions have been sent to your email address.')); 187 } 188 189 $form_state->setRedirect('user.page'); 190 } 191 192} 193