Browse Source

applied patch on user module
Implement language aware tokens for one time login link and cancel link
https://www.drupal.org/node/1754162

Bachir Soussi Chiadmi 7 years ago
parent
commit
9c43ba2fc6
4 changed files with 201 additions and 15 deletions
  1. 31 7
      modules/user/user.module
  2. 8 8
      modules/user/user.module.orig
  3. 53 0
      modules/user/user.test
  4. 109 0
      modules/user/user.test.orig

+ 31 - 7
modules/user/user.module

@@ -2356,14 +2356,26 @@ function user_external_login_register($name, $module) {
  *   following properties:
  *   - uid: The user ID number.
  *   - login: The UNIX timestamp of the user's last login.
+ * @param array $options
+ *   (optional) A keyed array of settings. Supported options are:
+ *   - langcode: A language code to be used when generating locale-sensitive
+ *    urls. If langcode is NULL the users preferred language is used.
  *
  * @return
  *   A unique URL that provides a one-time log in for the user, from which
  *   they can change their password.
  */
-function user_pass_reset_url($account) {
+function user_pass_reset_url($account, $options = array()) {
   $timestamp = REQUEST_TIME;
-  return url("user/reset/$account->uid/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login, $account->uid), array('absolute' => TRUE));
+  $url_options = array('absolute' => TRUE);
+  if (isset($options['langcode'])) {
+    $languages = language_list();
+    $url_options['language'] = $languages[$options['langcode']];
+  }
+  else {
+    $url_options['language'] = user_preferred_language($account);
+  }
+  return url("user/reset/$account->uid/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login, $account->uid), $url_options);
 }
 
 /**
@@ -2375,6 +2387,10 @@ function user_pass_reset_url($account) {
  *   - uid: The user ID number.
  *   - pass: The hashed user password string.
  *   - login: The UNIX timestamp of the user's last login.
+ * @param array $options
+ *   (optional) A keyed array of settings. Supported options are:
+ *   - langcode: A language code to be used when generating locale-sensitive
+ *    urls. If langcode is NULL the users preferred language is used.
  *
  * @return
  *   A unique URL that may be used to confirm the cancellation of the user
@@ -2383,9 +2399,17 @@ function user_pass_reset_url($account) {
  * @see user_mail_tokens()
  * @see user_cancel_confirm()
  */
-function user_cancel_url($account) {
+function user_cancel_url($account, $options = array()) {
   $timestamp = REQUEST_TIME;
-  return url("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login, $account->uid), array('absolute' => TRUE));
+  $url_options = array('absolute' => TRUE);
+  if (isset($options['langcode'])) {
+    $languages = language_list();
+    $url_options['language'] = $languages[$options['langcode']];
+  }
+  else {
+    $url_options['language'] = user_preferred_language($account);
+  }
+  return url("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login, $account->uid), $url_options);
 }
 
 /**
@@ -2875,7 +2899,7 @@ Your account on [site:name] has been canceled.
   if ($replace) {
     // We do not sanitize the token replacement, since the output of this
     // replacement is intended for an e-mail message, not a web browser.
-    return token_replace($text, $variables, array('language' => $language, 'callback' => 'user_mail_tokens', 'sanitize' => FALSE, 'clear' => TRUE));
+    return token_replace($text, $variables, array('language' => $language, 'langcode' => $langcode, 'callback' => 'user_mail_tokens', 'sanitize' => FALSE, 'clear' => TRUE));
   }
 
   return $text;
@@ -2902,8 +2926,8 @@ Your account on [site:name] has been canceled.
  */
 function user_mail_tokens(&$replacements, $data, $options) {
   if (isset($data['user'])) {
-    $replacements['[user:one-time-login-url]'] = user_pass_reset_url($data['user']);
-    $replacements['[user:cancel-url]'] = user_cancel_url($data['user']);
+    $replacements['[user:one-time-login-url]'] = user_pass_reset_url($data['user'], $options);
+    $replacements['[user:cancel-url]'] = user_cancel_url($data['user'], $options);
   }
 }
 

+ 8 - 8
modules/user/user.module.orig

@@ -418,13 +418,11 @@ function user_load_by_name($name) {
  *
  * @return
  *   A fully-loaded $user object upon successful save or FALSE if the save failed.
- *
- * @todo D8: Drop $edit and fix user_save() to be consistent with others.
  */
 function user_save($account, $edit = array(), $category = 'account') {
   $transaction = db_transaction();
   try {
-    if (!empty($edit['pass'])) {
+    if (isset($edit['pass']) && strlen(trim($edit['pass'])) > 0) {
       // Allow alternate password hashing schemes.
       require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'includes/password.inc');
       $edit['pass'] = user_hash_password(trim($edit['pass']));
@@ -791,7 +789,7 @@ function user_role_permissions($roles = array()) {
  *   (optional) The account to check, if not given use currently logged in user.
  *
  * @return
- *   Boolean TRUE if the current user has the requested permission.
+ *   Boolean TRUE if the user has the requested permission.
  *
  * All permission checks in Drupal should go through this function. This
  * way, we guarantee consistent behavior, and ensure that the superuser
@@ -1162,7 +1160,7 @@ function user_account_form(&$form, &$form_state) {
   $form['account']['roles'] = array(
     '#type' => 'checkboxes',
     '#title' => t('Roles'),
-    '#default_value' => (!$register && isset($account->roles) ? array_keys($account->roles) : array()),
+    '#default_value' => (!$register && !empty($account->roles) ? array_keys(array_filter($account->roles)) : array()),
     '#options' => $roles,
     '#access' => $roles && user_access('administer permissions'),
     DRUPAL_AUTHENTICATED_RID => $checkbox_authenticated,
@@ -1232,7 +1230,7 @@ function user_validate_current_pass(&$form, &$form_state) {
     // that prevent them from being empty if they are changed.
     if ((strlen(trim($form_state['values'][$key])) > 0) && ($form_state['values'][$key] != $account->$key)) {
       require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'includes/password.inc');
-      $current_pass_failed = empty($form_state['values']['current_pass']) || !user_check_password($form_state['values']['current_pass'], $account);
+      $current_pass_failed = strlen(trim($form_state['values']['current_pass'])) == 0 || !user_check_password($form_state['values']['current_pass'], $account);
       if ($current_pass_failed) {
         form_set_error('current_pass', t("Your current password is missing or incorrect; it's required to change the %name.", array('%name' => $name)));
         form_set_error($key);
@@ -1755,9 +1753,11 @@ function user_menu() {
 
   $items['admin/people/create'] = array(
     'title' => 'Add user',
+    'page callback' => 'user_admin',
     'page arguments' => array('create'),
     'access arguments' => array('administer users'),
     'type' => MENU_LOCAL_ACTION,
+    'file' => 'user.admin.inc',
   );
 
   // Administration pages.
@@ -2165,7 +2165,7 @@ function user_login_name_validate($form, &$form_state) {
  */
 function user_login_authenticate_validate($form, &$form_state) {
   $password = trim($form_state['values']['pass']);
-  if (!empty($form_state['values']['name']) && !empty($password)) {
+  if (!empty($form_state['values']['name']) && strlen(trim($password)) > 0) {
     // Do not allow any login from the current user's IP if the limit has been
     // reached. Default is 50 failed attempts allowed in one hour. This is
     // independent of the per-user limit to catch attempts from one IP to log
@@ -2256,7 +2256,7 @@ function user_login_final_validate($form, &$form_state) {
  */
 function user_authenticate($name, $password) {
   $uid = FALSE;
-  if (!empty($name) && !empty($password)) {
+  if (!empty($name) && strlen(trim($password)) > 0) {
     $account = user_load_by_name($name);
     if ($account) {
       // Allow alternate password hashing schemes.

+ 53 - 0
modules/user/user.test

@@ -2258,6 +2258,26 @@ class UserTokenReplaceTestCase extends DrupalWebTestCase {
     );
   }
 
+  public function setUp() {
+    parent::setUp('locale');
+
+    $account = $this->drupalCreateUser(array('access administration pages', 'administer languages'));
+    $this->drupalLogin($account);
+
+    // Add language.
+    $edit = array('langcode' => 'de');
+    $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
+
+    // Enable URL language detection and selection.
+    $edit = array('language[enabled][locale-url]' => 1);
+    $this->drupalPost('admin/config/regional/language/configure', $edit, t('Save settings'));
+
+    // Reset static caching.
+    drupal_static_reset('language_list');
+    drupal_static_reset('locale_url_outbound_alter');
+    drupal_static_reset('locale_language_url_rewrite_url');
+  }
+
   /**
    * Creates a user, then tests the tokens generated from it.
    */
@@ -2308,6 +2328,39 @@ class UserTokenReplaceTestCase extends DrupalWebTestCase {
       $output = token_replace($input, array('user' => $account), array('language' => $language, 'sanitize' => FALSE));
       $this->assertEqual($output, $expected, format_string('Unsanitized user token %token replaced.', array('%token' => $input)));
     }
+
+    $languages = language_list();
+
+    // Generate login and cancel link.
+    $tests = array();
+    $tests['[user:one-time-login-url]'] = user_pass_reset_url($account);
+    $tests['[user:cancel-url]'] = user_cancel_url($account);
+
+    // Generate tokens with interface language.
+    $link = url('user', array('absolute' => TRUE));
+    foreach ($tests as $input => $expected) {
+      $output = token_replace($input, array('user' => $account), array('langcode' => $language->language, 'callback' => 'user_mail_tokens', 'sanitize' => FALSE, 'clear' => TRUE));
+      $this->assertTrue(strpos($output, $link) === 0, 'Generated URL is in interface language.');
+    }
+
+    // Generate tokens with the user's preferred language.
+    $edit['language'] = 'de';
+    $account = user_save($account, $edit);
+    $link = url('user', array('language' => $languages[$account->language], 'absolute' => TRUE));
+    foreach ($tests as $input => $expected) {
+      $output = token_replace($input, array('user' => $account), array('callback' => 'user_mail_tokens', 'sanitize' => FALSE, 'clear' => TRUE));
+      $this->assertTrue(strpos($output, $link) === 0, "Generated URL is in the user's preferred language.");
+    }
+
+    // Generate tokens with one specific language.
+    $link = url('user', array('language' => $languages['de'], 'absolute' => TRUE));
+    foreach ($tests as $input => $expected) {
+      foreach (array($user1, $user2) as $account) {
+        $output = token_replace($input, array('user' => $account), array('langcode' => 'de', 'callback' => 'user_mail_tokens', 'sanitize' => FALSE, 'clear' => TRUE));
+        $this->assertTrue(strpos($output, $link) === 0, "Generated URL in in the requested language.");
+      }
+    }
+
   }
 }
 

+ 109 - 0
modules/user/user.test.orig

@@ -480,6 +480,34 @@ class UserPasswordResetTestCase extends DrupalWebTestCase {
     $this->assertText(t('Further instructions have been sent to your e-mail address.'), 'Password reset instructions mailed message displayed.');
   }
 
+  /**
+   * Test user password reset while logged in.
+   */
+  function testUserPasswordResetLoggedIn() {
+    $account = $this->drupalCreateUser();
+    $this->drupalLogin($account);
+    // Make sure the test account has a valid password.
+    user_save($account, array('pass' => user_password()));
+
+    // Generate one time login link.
+    $reset_url = user_pass_reset_url($account);
+    $this->drupalGet($reset_url);
+
+    $this->assertText('Reset password');
+    $this->drupalPost(NULL, NULL, t('Log in'));
+
+    $this->assertText('You have just used your one-time login link. It is no longer necessary to use this link to log in. Please change your password.');
+
+    $pass = user_password();
+    $edit = array(
+      'pass[pass1]' => $pass,
+      'pass[pass2]' => $pass,
+    );
+    $this->drupalPost(NULL, $edit, t('Save'));
+
+    $this->assertText('The changes have been saved.');
+  }
+
   /**
    * Attempts login using an expired password reset link.
    */
@@ -1849,6 +1877,19 @@ class UserCreateTestCase extends DrupalWebTestCase {
       $this->drupalGet('admin/people');
       $this->assertText($edit['name'], 'User found in list of users');
     }
+
+    // Test that the password '0' is considered a password.
+    $name = $this->randomName();
+    $edit = array(
+      'name' => $name,
+      'mail' => $name . '@example.com',
+      'pass[pass1]' => 0,
+      'pass[pass2]' => 0,
+      'notify' => FALSE,
+    );
+    $this->drupalPost('admin/people/create', $edit, t('Create new account'));
+    $this->assertText(t('Created a new user account for @name. No e-mail has been sent.', array('@name' => $edit['name'])), 'User created with password 0');
+    $this->assertNoText('Password field is required');
   }
 }
 
@@ -1926,6 +1967,74 @@ class UserEditTestCase extends DrupalWebTestCase {
     $this->drupalLogin($user1);
     $this->drupalLogout();
   }
+
+  /**
+   * Tests setting the password to "0".
+   */
+  public function testUserWith0Password() {
+    $admin = $this->drupalCreateUser(array('administer users'));
+    $this->drupalLogin($admin);
+    // Create a regular user.
+    $user1 = $this->drupalCreateUser(array());
+
+    $edit = array('pass[pass1]' => '0', 'pass[pass2]' => '0');
+    $this->drupalPost("user/" . $user1->uid . "/edit", $edit, t('Save'));
+    $this->assertRaw(t("The changes have been saved."));
+
+    $this->drupalLogout();
+    $user1->pass_raw = '0';
+    $this->drupalLogin($user1);
+    $this->drupalLogout();
+  }
+}
+
+/**
+ * Tests editing a user account with and without a form rebuild.
+ */
+class UserEditRebuildTestCase extends DrupalWebTestCase {
+
+  public static function getInfo() {
+    return array(
+      'name' => 'User edit with form rebuild',
+      'description' => 'Test user edit page when a form rebuild is triggered.',
+      'group' => 'User',
+    );
+  }
+
+  function setUp() {
+    parent::setUp('user_form_test');
+  }
+
+  /**
+   * Test user edit page when the form is set to rebuild.
+   */
+  function testUserEditFormRebuild() {
+    $user1 = $this->drupalCreateUser(array('change own username'));
+    $this->drupalLogin($user1);
+
+    $roles = array_keys($user1->roles);
+    // Save the user form twice.
+    $edit = array();
+    $edit['current_pass'] = $user1->pass_raw;
+    $this->drupalPost("user/$user1->uid/edit", $edit, t('Save'));
+    $this->assertRaw(t("The changes have been saved."));
+    $this->drupalPost(NULL, $edit, t('Save'));
+    $this->assertRaw(t("The changes have been saved."));
+    $saved_user1 = entity_load_unchanged('user', $user1->uid);
+    $this->assertEqual(count($roles), count($saved_user1->roles), 'Count of user roles in database matches original count.');
+    $diff = array_diff(array_keys($saved_user1->roles), $roles);
+    $this->assertTrue(empty($diff), format_string('User roles in database match original: @roles', array('@roles' => implode(', ', $saved_user1->roles))));
+    // Set variable that causes the form to be rebuilt in user_form_test.module.
+    variable_set('user_form_test_user_profile_form_rebuild', TRUE);
+    $this->drupalPost("user/$user1->uid/edit", $edit, t('Save'));
+    $this->assertRaw(t("The changes have been saved."));
+    $this->drupalPost(NULL, $edit, t('Save'));
+    $this->assertRaw(t("The changes have been saved."));
+    $saved_user1 = entity_load_unchanged('user', $user1->uid);
+    $this->assertEqual(count($roles), count($saved_user1->roles), 'Count of user roles in database matches original count.');
+    $diff = array_diff(array_keys($saved_user1->roles), $roles);
+    $this->assertTrue(empty($diff), format_string('User roles in database match original: @roles', array('@roles' => implode(', ', $saved_user1->roles))));
+  }
 }
 
 /**