'Account settings', 'access callback' => 'simple_pass_reset_pass_reset_access', 'access arguments' => array(2, 3, 4), 'page callback' => 'simple_pass_reset_pass_reset_page', 'page arguments' => array(2, 3, 4), 'type' => MENU_CALLBACK, ); } /** * Access callback for use with Drupal's menu API. */ function simple_pass_reset_pass_reset_access($uid, $timestamp, $hashed_pass) { if (user_is_logged_in()) { return FALSE; } // This timeout check imitates core logic in user_pass_reset(). The timeout // number is expressed in seconds, with 86400 equal to one day. There is no // UI to adjust this number, but sites can customize it in their settings.php. $timeout = variable_get('user_password_reset_timeout', 86400); // Ensure the user is not blocked. $users = user_load_multiple(array($uid), array('status' => '1')); if ($timestamp <= REQUEST_TIME && $account = reset($users)) { // Bypass the timeout check if the user has not logged in before. if ($account->login && REQUEST_TIME - $timestamp > $timeout) { drupal_set_message(t('You have tried to use a one-time login link that has expired. Please request a new one using the form below.'), 'status', FALSE); drupal_goto('user/password'); } elseif ($account->uid && $timestamp >= $account->login && $timestamp <= REQUEST_TIME && $hashed_pass == user_pass_rehash($account->pass, $timestamp, $account->login, $account->uid)) { return TRUE; } } return FALSE; } /** * Page callback for use with Drupal's menu API. * * This page replaces core one-time login form provided by user_pass_reset(). */ function simple_pass_reset_pass_reset_page($uid, $timestamp, $hashed_pass, $option = NULL) { // Never cache this page. drupal_page_is_cacheable(FALSE); module_load_include('inc', 'user', 'user.pages'); // When $option is original or login, preserve original behavior. if ($option == 'original') { return drupal_get_form('user_pass_reset', $uid, $timestamp, $hashed_pass); } elseif ($option == 'login') { return drupal_get_form('user_pass_reset', $uid, $timestamp, $hashed_pass, $option); } else { // Show the user edit form instead of silly one-time login form. $account = user_load($uid); $form = drupal_get_form('user_profile_form', $account); return $form; } } /** * Implements hook_form_FORM_ID_alter(). * * @see user_profile_form() */ function simple_pass_reset_form_user_profile_form_alter(&$form, &$form_state) { // Don't alter the normal profile edit form, but only the password reset form. if (arg(0) == 'user' && arg(1) == 'reset' && !user_is_logged_in()) { // Our submit handler will log the user in after form submit. $form['#submit'][] = 'simple_pass_reset_pass_reset_submit'; $form['actions']['submit']['#value'] = t('Save and log in as !username', array('!username' => format_username($form['#user']))); // Links provided by the Bakery module will not work because the user is not // logged in yet. if (!empty($form['bakery'])) { $form['bakery']['#access'] = FALSE; // Normally the Bakery module would make the following change to the // user_pass_reset form. if (!variable_get('bakery_is_master', FALSE)) { // Set a submit handler for the pseudo-reset form. $form['#submit'] = array('_bakery_reset_submit'); } } // Some third-party modules (like Bakery) might hide account elements. if (!isset($form['account']['#access']) || $form['account']['#access']) { // Require a new password. $form['account']['pass']['#required'] = TRUE; if (arg(5) == 'brief') { drupal_set_title(t('Choose a new password')); // Instead of "Reset password". // Hide "To change the current user password..." unset($form['account']['pass']['#description']); // The user is most interested in getting a working password, don't show their picture, timezone, etc. foreach (element_children($form) as $key) { if (isset($form[$key]['#type']) && in_array($form[$key]['#type'], array('hidden', 'actions', 'captcha'))) { // Do not alter these elements. } else { // Hide other elements. $form[$key]['#access'] = FALSE; } } // Except don't hide these. $form['account']['#access'] = TRUE; $form['actions']['#access'] = TRUE; // But seriously do hide these. $form['account']['mail']['#access'] = FALSE; } } // This is to avoid a PHP Notice in user_profile_form_submit(). https://www.drupal.org/node/2111293#comment-9262499 if (empty($_SESSION)) { $_SESSION = array('simple_pass_reset' => TRUE); } } } /** * Submit callback for Drupal form API. */ function simple_pass_reset_pass_reset_submit($form, &$form_state) { if (!user_is_logged_in()) { // Sanity check. // Remove roles that were disabled in the form. Normally the User module // will array_filter() these out for us. But remember_me and possibly other // modules have bugs that might prevent it from doing so. if (!empty($form_state['user']->roles)) { $form_state['user']->roles = array_filter($form_state['user']->roles); } // Load the user account afresh and finalize the login. // @see user_login_submit() global $user; $user = user_load($form_state['user']->uid); user_login_finalize(); watchdog('user', 'User %name used one-time login link.', array('%name' => $user->name)); if(empty($form_state['redirect'])) { $form_state['redirect'] = 'user'; } } } /** * Implements hook_module_implements_alter(). * * Asks Drupal to run our form_alter hooks after other modules. */ function simple_pass_reset_module_implements_alter(&$implementations, $hook) { // The hook we're interested in is hook_form_FORM_ID_alter(). Yet, in this function we have to manipulate 'form_alter'. Because Drupal is tricky like that. if ($hook == 'form_alter' && isset($implementations['simple_pass_reset'])) { // Make our form alters come last (so we act after other modules have already altered). $group = $implementations['simple_pass_reset']; unset($implementations['simple_pass_reset']); $implementations['simple_pass_reset'] = $group; } } /** * Implements hook_form_FORM_ID_alter(). * * This is not about simplifying the text on the password reset form, but it's * behavior. If a logged in user resets her password, she's sent a link via * email. But will get access denied clicking that link, because she's already * logged in! This hook will log her out when resetting password. This makes * the password reset form behave more like the user edit form when a user * changes their own password. (See where user_save() calls * drupal_session_destroy_uid().) */ function simple_pass_reset_form_user_pass_alter(&$form, &$form_state) { // If the user views the password reset form while logged in... if (user_is_logged_in()) { drupal_set_title(t('Reset my password')); // Update the help text and button text to indicate that submitting the form // will log them out. $form['mail']['#markup'] = t('We will e-mail a password reset link for your account to %email. You will be logged out when you submit this form and should use that link to log back in.', array('%email' => $form['name']['#value'])); $form['actions']['submit']['#value'] = t('E-mail reset link and log out'); // Add a form submit handler to log out upon submission. $form['#submit'][] = 'simple_pass_reset_form_user_pass_submit'; } } /** * Submit callback: log out when an authenticated user submits the password * reset form. */ function simple_pass_reset_form_user_pass_submit($form, &$form_state) { global $user; if (user_is_logged_in()) { drupal_session_destroy_uid($user->uid); // Some code copied from user_logout(), which we cannot call here because it // uses drupal_goto(). watchdog('user', 'Session closed for %name.', array('%name' => $user->name)); module_invoke_all('user_logout', $user); // Destroy the current session, and reset $user to the anonymous user. session_destroy(); // Note call drupal_set_message() AFTER session_destroy(). drupal_set_message(t('The password reset link has been sent to your e-mail address. You are now logged out.')); $form_state['redirect'] = ''; } }