simple_pass_reset.module 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. <?php
  2. /**
  3. * Implements hook_menu_alter().
  4. */
  5. function simple_pass_reset_menu_alter(&$items) {
  6. // Drupal's default behavior is to show the user a log-in form before
  7. // their user profile. We replace this item to skip the uneccesary step.
  8. $items['user/reset/%/%/%'] = array(
  9. 'title' => 'Account settings',
  10. 'access callback' => 'simple_pass_reset_pass_reset_access',
  11. 'access arguments' => array(2, 3, 4),
  12. 'page callback' => 'simple_pass_reset_pass_reset_page',
  13. 'page arguments' => array(2, 3, 4),
  14. 'type' => MENU_CALLBACK,
  15. );
  16. }
  17. /**
  18. * Access callback for use with Drupal's menu API.
  19. */
  20. function simple_pass_reset_pass_reset_access($uid, $timestamp, $hashed_pass) {
  21. if (user_is_logged_in()) {
  22. return FALSE;
  23. }
  24. // This timeout check imitates core logic in user_pass_reset(). The timeout
  25. // number is expressed in seconds, with 86400 equal to one day. There is no
  26. // UI to adjust this number, but sites can customize it in their settings.php.
  27. $timeout = variable_get('user_password_reset_timeout', 86400);
  28. // Ensure the user is not blocked.
  29. $users = user_load_multiple(array($uid), array('status' => '1'));
  30. if ($timestamp <= REQUEST_TIME && $account = reset($users)) {
  31. // Bypass the timeout check if the user has not logged in before.
  32. if ($account->login && REQUEST_TIME - $timestamp > $timeout) {
  33. 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);
  34. drupal_goto('user/password');
  35. }
  36. elseif ($account->uid && $timestamp >= $account->login && $timestamp <= REQUEST_TIME && $hashed_pass == user_pass_rehash($account->pass, $timestamp, $account->login, $account->uid)) {
  37. return TRUE;
  38. }
  39. }
  40. return FALSE;
  41. }
  42. /**
  43. * Page callback for use with Drupal's menu API.
  44. *
  45. * This page replaces core one-time login form provided by user_pass_reset().
  46. */
  47. function simple_pass_reset_pass_reset_page($uid, $timestamp, $hashed_pass, $option = NULL) {
  48. // Never cache this page.
  49. drupal_page_is_cacheable(FALSE);
  50. module_load_include('inc', 'user', 'user.pages');
  51. // When $option is original or login, preserve original behavior.
  52. if ($option == 'original') {
  53. return drupal_get_form('user_pass_reset', $uid, $timestamp, $hashed_pass);
  54. }
  55. elseif ($option == 'login') {
  56. return drupal_get_form('user_pass_reset', $uid, $timestamp, $hashed_pass, $option);
  57. }
  58. else {
  59. // Show the user edit form instead of silly one-time login form.
  60. $account = user_load($uid);
  61. $form = drupal_get_form('user_profile_form', $account);
  62. return $form;
  63. }
  64. }
  65. /**
  66. * Implements hook_form_FORM_ID_alter().
  67. *
  68. * @see user_profile_form()
  69. */
  70. function simple_pass_reset_form_user_profile_form_alter(&$form, &$form_state) {
  71. // Don't alter the normal profile edit form, but only the password reset form.
  72. if (arg(0) == 'user' && arg(1) == 'reset' && !user_is_logged_in()) {
  73. // Our submit handler will log the user in after form submit.
  74. $form['#submit'][] = 'simple_pass_reset_pass_reset_submit';
  75. $form['actions']['submit']['#value'] = t('Save and log in as !username', array('!username' => format_username($form['#user'])));
  76. // Links provided by the Bakery module will not work because the user is not
  77. // logged in yet.
  78. if (!empty($form['bakery'])) {
  79. $form['bakery']['#access'] = FALSE;
  80. // Normally the Bakery module would make the following change to the
  81. // user_pass_reset form.
  82. if (!variable_get('bakery_is_master', FALSE)) {
  83. // Set a submit handler for the pseudo-reset form.
  84. $form['#submit'] = array('_bakery_reset_submit');
  85. }
  86. }
  87. // Some third-party modules (like Bakery) might hide account elements.
  88. if (!isset($form['account']['#access']) || $form['account']['#access']) {
  89. // Require a new password.
  90. $form['account']['pass']['#required'] = TRUE;
  91. if (arg(5) == 'brief') {
  92. drupal_set_title(t('Choose a new password')); // Instead of "Reset password".
  93. // Hide "To change the current user password..."
  94. unset($form['account']['pass']['#description']);
  95. // The user is most interested in getting a working password, don't show their picture, timezone, etc.
  96. foreach (element_children($form) as $key) {
  97. if (isset($form[$key]['#type']) && in_array($form[$key]['#type'], array('hidden', 'actions', 'captcha'))) {
  98. // Do not alter these elements.
  99. }
  100. else {
  101. // Hide other elements.
  102. $form[$key]['#access'] = FALSE;
  103. }
  104. }
  105. // Except don't hide these.
  106. $form['account']['#access'] = TRUE;
  107. $form['actions']['#access'] = TRUE;
  108. // But seriously do hide these.
  109. $form['account']['mail']['#access'] = FALSE;
  110. }
  111. }
  112. // This is to avoid a PHP Notice in user_profile_form_submit(). https://www.drupal.org/node/2111293#comment-9262499
  113. if (empty($_SESSION)) {
  114. $_SESSION = array('simple_pass_reset' => TRUE);
  115. }
  116. }
  117. }
  118. /**
  119. * Submit callback for Drupal form API.
  120. */
  121. function simple_pass_reset_pass_reset_submit($form, &$form_state) {
  122. if (!user_is_logged_in()) { // Sanity check.
  123. // Remove roles that were disabled in the form. Normally the User module
  124. // will array_filter() these out for us. But remember_me and possibly other
  125. // modules have bugs that might prevent it from doing so.
  126. if (!empty($form_state['user']->roles)) {
  127. $form_state['user']->roles = array_filter($form_state['user']->roles);
  128. }
  129. // Load the user account afresh and finalize the login.
  130. // @see user_login_submit()
  131. global $user;
  132. $user = user_load($form_state['user']->uid);
  133. user_login_finalize();
  134. watchdog('user', 'User %name used one-time login link.', array('%name' => $user->name));
  135. if(empty($form_state['redirect'])) {
  136. $form_state['redirect'] = 'user';
  137. }
  138. }
  139. }
  140. /**
  141. * Implements hook_module_implements_alter().
  142. *
  143. * Asks Drupal to run our form_alter hooks after other modules.
  144. */
  145. function simple_pass_reset_module_implements_alter(&$implementations, $hook) {
  146. // 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.
  147. if ($hook == 'form_alter' && isset($implementations['simple_pass_reset'])) {
  148. // Make our form alters come last (so we act after other modules have already altered).
  149. $group = $implementations['simple_pass_reset'];
  150. unset($implementations['simple_pass_reset']);
  151. $implementations['simple_pass_reset'] = $group;
  152. }
  153. }
  154. /**
  155. * Implements hook_form_FORM_ID_alter().
  156. *
  157. * This is not about simplifying the text on the password reset form, but it's
  158. * behavior. If a logged in user resets her password, she's sent a link via
  159. * email. But will get access denied clicking that link, because she's already
  160. * logged in! This hook will log her out when resetting password. This makes
  161. * the password reset form behave more like the user edit form when a user
  162. * changes their own password. (See where user_save() calls
  163. * drupal_session_destroy_uid().)
  164. */
  165. function simple_pass_reset_form_user_pass_alter(&$form, &$form_state) {
  166. // If the user views the password reset form while logged in...
  167. if (user_is_logged_in()) {
  168. drupal_set_title(t('Reset my password'));
  169. // Update the help text and button text to indicate that submitting the form
  170. // will log them out.
  171. $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']));
  172. $form['actions']['submit']['#value'] = t('E-mail reset link and log out');
  173. // Add a form submit handler to log out upon submission.
  174. $form['#submit'][] = 'simple_pass_reset_form_user_pass_submit';
  175. }
  176. }
  177. /**
  178. * Submit callback: log out when an authenticated user submits the password
  179. * reset form.
  180. */
  181. function simple_pass_reset_form_user_pass_submit($form, &$form_state) {
  182. global $user;
  183. if (user_is_logged_in()) {
  184. drupal_session_destroy_uid($user->uid);
  185. // Some code copied from user_logout(), which we cannot call here because it
  186. // uses drupal_goto().
  187. watchdog('user', 'Session closed for %name.', array('%name' => $user->name));
  188. module_invoke_all('user_logout', $user);
  189. // Destroy the current session, and reset $user to the anonymous user.
  190. session_destroy();
  191. // Note call drupal_set_message() AFTER session_destroy().
  192. drupal_set_message(t('The password reset link has been sent to your e-mail address. You are now logged out.'));
  193. $form_state['redirect'] = '<front>';
  194. }
  195. }