diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc index b1d3b50..fc3460b 100644 --- a/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -1612,7 +1612,7 @@ function install_configure_form($form, &$form_state, &$install_state) { drupal_add_library('system', 'drupal.timezone'); // We add these strings as settings because JavaScript translation does not // work during installation. - drupal_add_js(array('copyFieldValue' => array('edit-site-mail' => array('edit-account-mail'))), 'setting'); + drupal_add_js(array('copyFieldValue' => array('edit-site-mail' => array('edit-user-mail'))), 'setting'); // Add JS to show / hide the 'Email administrator about site updates' elements drupal_add_js('jQuery(function () { Drupal.hideEmailAdministratorCheckbox() });', 'inline'); @@ -1879,7 +1879,8 @@ function _install_configure_form($form, &$form_state, &$install_state) { ); $form['admin_account']['account']['#tree'] = TRUE; - $form['admin_account']['account']['name'] = array('#type' => 'textfield', + $form['admin_account']['account']['username'] = array( + '#type' => 'textfield', '#title' => st('Username'), '#maxlength' => USERNAME_MAX_LENGTH, '#description' => st('Spaces are allowed; punctuation is not allowed except for periods, hyphens, and underscores.'), @@ -1893,6 +1894,7 @@ function _install_configure_form($form, &$form_state, &$install_state) { '#title' => st('E-mail address'), '#required' => TRUE, '#weight' => -5, + '#parents' => array('user', 'mail'), ); $form['admin_account']['account']['pass'] = array( '#type' => 'password_confirm', @@ -1960,8 +1962,8 @@ function _install_configure_form($form, &$form_state, &$install_state) { * @see install_configure_form_submit() */ function install_configure_form_validate($form, &$form_state) { - if ($error = user_validate_name($form_state['values']['account']['name'])) { - form_error($form['admin_account']['account']['name'], $error); + if ($error = account_validate_name($form_state['values']['account']['username'])) { + form_error($form['admin_account']['account']['username'], $error); } } @@ -1971,8 +1973,6 @@ function install_configure_form_validate($form, &$form_state) { * @see install_configure_form_validate() */ function install_configure_form_submit($form, &$form_state) { - global $user; - config('system.site') ->set('name', $form_state['values']['site_name']) ->set('mail', $form_state['values']['site_mail']) @@ -1988,20 +1988,30 @@ function install_configure_form_submit($form, &$form_state) { // Add the site maintenance account's email address to the list of // addresses to be notified when updates are available, if selected. if ($form_state['values']['update_status_module'][2]) { - config('update.settings')->set('notification.emails', array($form_state['values']['account']['mail']))->save(); + config('update.settings')->set('notification.emails', array($form_state['values']['user']['mail']))->save(); } } // We precreated user 1 with placeholder values. Let's save the real values. - $account = user_load(1); - $account->init = $account->mail = $form_state['values']['account']['mail']; - $account->roles = !empty($account->roles) ? $account->roles : array(); - $account->status = 1; - $account->timezone = $form_state['values']['date_default_timezone']; - $account->pass = $form_state['values']['account']['pass']; - $account->name = $form_state['values']['account']['name']; + $user = user_load(1); + $user->mail = $form_state['values']['user']['mail']; + $user->roles = !empty($account->roles) ? $account->roles : array(); + $user->status = 1; + $user->timezone = $form_state['values']['date_default_timezone']; + $user->save(); + + $account = entity_create('account', array( + 'uid' => $user->id(), + 'username' => $form_state['values']['account']['username'], + 'pass' => $form_state['values']['account']['pass'], + 'created' => REQUEST_TIME, + 'init' => $form_state['values']['user']['mail'], + )); $account->save(); + // Load global $user and perform final login tasks. + unset($user); + global $user; $user = user_load(1); user_login_finalize(); diff --git a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php index 400d885..3e6f842 100644 --- a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php +++ b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php @@ -388,27 +388,33 @@ protected function drupalCreateUser(array $permissions = array()) { } } - // Create a user assigned to that role. - $edit = array(); - $edit['name'] = $this->randomName(); - $edit['mail'] = $edit['name'] . '@example.com'; - $edit['pass'] = user_password(); - $edit['status'] = 1; + // Create a user and account assigned to that role. + $account_values = array(); + $account_values['username'] = $this->randomName(); + $account_values['pass'] = account_password(); + + $user_values = array(); + $user_values['mail'] = $account_values['username'] . '@example.com'; + $user_values['status'] = 1; if ($rid) { - $edit['roles'] = array($rid => $rid); + $user_values['roles'] = array($rid => $rid); } + $user = entity_create('user', $user_values); + $user->save(); - $account = entity_create('user', $edit); + $account_values['uid'] = $user->uid; + $account = entity_create('account', $account_values); $account->save(); - $this->assertTrue(!empty($account->uid), t('User created with name %name and pass %pass', array('%name' => $edit['name'], '%pass' => $edit['pass'])), t('User login')); + $this->assertTrue(!empty($account->uid), t('User created with name %username and pass %pass', array('%username' => $account_values['username'], '%pass' => $account_values['pass'])), t('User login')); if (empty($account->uid)) { return FALSE; } // Add the raw password so that we can log in as this user. - $account->pass_raw = $edit['pass']; - return $account; + $user->username = $account_values['username']; + $user->pass_raw = $account_values['pass']; + return $user; } /** @@ -531,14 +537,14 @@ protected function drupalLogin($user) { } $edit = array( - 'name' => $user->name, + 'username' => $user->username, 'pass' => $user->pass_raw ); $this->drupalPost('user', $edit, t('Log in')); // If a "log out" link appears on the page, it is almost certainly because // the login was successful. - $pass = $this->assertLink(t('Log out'), 0, t('User %name successfully logged in.', array('%name' => $user->name)), t('User login')); + $pass = $this->assertLink(t('Log out'), 0, t('User %username successfully logged in.', array('%username' => $user->username)), t('User login')); if ($pass) { $this->loggedInUser = $user; @@ -562,7 +568,7 @@ protected function drupalLogout() { // screen. $this->drupalGet('user/logout', array('query' => array('destination' => 'user'))); $this->assertResponse(200, t('User was logged out.')); - $pass = $this->assertField('name', t('Username field found.'), t('Logout')); + $pass = $this->assertField('username', t('Username field found.'), t('Logout')); $pass = $pass && $this->assertField('pass', t('Password field found.'), t('Logout')); if ($pass) { @@ -656,13 +662,15 @@ protected function setUp() { 'site_name' => 'Drupal', 'site_mail' => 'simpletest@example.com', 'account' => array( - 'name' => $this->root_user->name, - 'mail' => $this->root_user->mail, + 'username' => $this->root_user->name, 'pass' => array( 'pass1' => $this->root_user->pass_raw, 'pass2' => $this->root_user->pass_raw, ), ), + 'user' => array( + 'mail' => $this->root_user->mail, + ), // form_type_checkboxes_value() requires NULL instead of FALSE values // for programmatic form submissions to disable a checkbox. 'update_status_module' => array( diff --git a/core/modules/user/lib/Drupal/user/Account.php b/core/modules/user/lib/Drupal/user/Account.php new file mode 100644 index 0000000..910864c --- /dev/null +++ b/core/modules/user/lib/Drupal/user/Account.php @@ -0,0 +1,72 @@ +aid; + } +} \ No newline at end of file diff --git a/core/modules/user/lib/Drupal/user/AccountFormController.php b/core/modules/user/lib/Drupal/user/AccountFormController.php index b79e983..547eb80 100644 --- a/core/modules/user/lib/Drupal/user/AccountFormController.php +++ b/core/modules/user/lib/Drupal/user/AccountFormController.php @@ -32,31 +32,19 @@ public function form(array $form, array &$form_state, EntityInterface $account) '#weight' => -10, ); - // Only show name field on registration form or user can change own username. - $form['account']['name'] = array( + // Only show username field on registration form or user can change own username. + $form['account']['username'] = array( '#type' => 'textfield', '#title' => t('Username'), '#maxlength' => USERNAME_MAX_LENGTH, '#description' => t('Spaces are allowed; punctuation is not allowed except for periods, hyphens, apostrophes, and underscores.'), '#required' => TRUE, '#attributes' => array('class' => array('username'), 'autocomplete' => 'off'), - '#default_value' => (!$register ? $account->name : ''), + '#default_value' => (!$register ? $account->username : ''), '#access' => ($register || ($user->uid == $account->uid && user_access('change own username')) || $admin), '#weight' => -10, ); - // The mail field is NOT required if account originally had no mail set - // and the user performing the edit has 'administer users' permission. - // This allows users without e-mail address to be edited and deleted. - $form['account']['mail'] = array( - '#type' => 'email', - '#title' => t('E-mail address'), - '#description' => t('A valid e-mail address. All e-mails from the system will be sent to this address. The e-mail address is not made public and will only be used if you wish to receive a new password or wish to receive certain news or notifications by e-mail.'), - '#required' => !(empty($account->mail) && user_access('administer users')), - '#default_value' => (!$register ? $account->mail : ''), - '#attributes' => array('autocomplete' => 'off'), - ); - // Display password field only for existing users or when user is allowed to // assign a password during registration. if (!$register) { @@ -75,10 +63,9 @@ public function form(array $form, array &$form_state, EntityInterface $account) // The user may only change their own password without their current // password if they logged in via a one-time login link. if (!$pass_reset) { - $protected_values['mail'] = $form['account']['mail']['#title']; $protected_values['pass'] = t('Password'); $request_new = l(t('Request new password'), 'user/password', array('attributes' => array('title' => t('Request new password via e-mail.')))); - $current_pass_description = t('Required if you want to change the %mail or %pass below. !request_new.', array('%mail' => $protected_values['mail'], '%pass' => $protected_values['pass'], '!request_new' => $request_new)); + $current_pass_description = t('Required if you want to change the %pass below. !request_new.', array('%pass' => $protected_values['pass'], '!request_new' => $request_new)); } // The user must enter their current password to change to a new one. @@ -118,205 +105,43 @@ public function form(array $form, array &$form_state, EntityInterface $account) $status = $register ? $config->get('register') == USER_REGISTER_VISITORS : $account->status; } - $form['account']['status'] = array( - '#type' => 'radios', - '#title' => t('Status'), - '#default_value' => $status, - '#options' => array(t('Blocked'), t('Active')), - '#access' => $admin, - ); - - $roles = array_map('check_plain', user_roles(TRUE)); - // The disabled checkbox subelement for the 'authenticated user' role - // must be generated separately and added to the checkboxes element, - // because of a limitation in Form API not supporting a single disabled - // checkbox within a set of checkboxes. - // @todo This should be solved more elegantly. See issue #119038. - $checkbox_authenticated = array( - '#type' => 'checkbox', - '#title' => $roles[DRUPAL_AUTHENTICATED_RID], - '#default_value' => TRUE, - '#disabled' => TRUE, - ); - unset($roles[DRUPAL_AUTHENTICATED_RID]); - - $form['account']['roles'] = array( - '#type' => 'checkboxes', - '#title' => t('Roles'), - '#default_value' => (!$register && isset($account->roles) ? array_keys($account->roles) : array()), - '#options' => $roles, - '#access' => $roles && user_access('administer permissions'), - DRUPAL_AUTHENTICATED_RID => $checkbox_authenticated, - ); - $form['account']['notify'] = array( '#type' => 'checkbox', '#title' => t('Notify user of new account'), '#access' => $register && $admin, ); - // Signature. - $form['signature_settings'] = array( - '#type' => 'fieldset', - '#title' => t('Signature settings'), - '#weight' => 1, - '#access' => (!$register && $config->get('signatures')), - ); - - $form['signature_settings']['signature'] = array( - '#type' => 'text_format', - '#title' => t('Signature'), - '#default_value' => isset($account->signature) ? $account->signature : '', - '#description' => t('Your signature will be publicly displayed at the end of your comments.'), - '#format' => isset($account->signature_format) ? $account->signature_format : NULL, - ); - - // Picture/avatar. - $form['picture'] = array( - '#type' => 'fieldset', - '#title' => t('Picture'), - '#weight' => 1, - '#access' => (!$register && variable_get('user_pictures', 0)), - ); - - $form['picture']['picture'] = array( - '#type' => 'value', - '#value' => isset($account->picture) ? $account->picture : NULL, - ); - - $form['picture']['picture_current'] = array( - '#markup' => theme('user_picture', array('account' => $account)), - ); - - $form['picture']['picture_delete'] = array( - '#type' => 'checkbox', - '#title' => t('Delete picture'), - '#access' => !empty($account->picture->fid), - '#description' => t('Check this box to delete your current picture.'), - ); - - $form['picture']['picture_upload'] = array( - '#type' => 'file', - '#title' => t('Upload picture'), - '#size' => 48, - '#description' => t('Your virtual face or picture. Pictures larger than @dimensions pixels will be scaled down.', array('@dimensions' => variable_get('user_picture_dimensions', '85x85'))) . ' ' . filter_xss_admin(variable_get('user_picture_guidelines', '')), - ); - - $form['#validate'][] = 'user_validate_picture'; - - $user_preferred_langcode = $register ? $language_interface->langcode : user_preferred_langcode($account); - - $user_preferred_admin_langcode = $register ? $language_interface->langcode : user_preferred_langcode($account, 'admin'); - - // Is default the interface language? - include_once DRUPAL_ROOT . '/core/includes/language.inc'; - $interface_language_is_default = language_negotiation_method_get_first(LANGUAGE_TYPE_INTERFACE) != LANGUAGE_NEGOTIATION_SELECTED; - $form['language'] = array( - '#type' => language_multilingual() ? 'fieldset' : 'container', - '#title' => t('Language settings'), - // Display language selector when either creating a user on the admin - // interface or editing a user account. - '#access' => !$register || user_access('administer users'), - ); - - $form['language']['preferred_langcode'] = array( - '#type' => 'language_select', - '#title' => t('Site language'), - '#languages' => LANGUAGE_CONFIGURABLE, - '#default_value' => $user_preferred_langcode, - '#description' => $interface_language_is_default ? t("This account's preferred language for e-mails and site presentation.") : t("This account's preferred language for e-mails."), - ); - - $form['language']['preferred_admin_langcode'] = array( - '#type' => 'language_select', - '#title' => t('Administration pages language'), - '#languages' => LANGUAGE_CONFIGURABLE, - '#default_value' => $user_preferred_admin_langcode, - '#access' => user_access('access administration pages', $account), - ); - - // User entities contain both a langcode property (for identifying the - // language of the entity data) and a preferred_langcode property (see - // above). Rather than provide a UI forcing the user to choose both - // separately, assume that the user profile data is in the user's preferred - // language. This element provides that synchronization. For use-cases where - // this synchronization is not desired, a module can alter or remove this - // element. - $form['language']['langcode'] = array( - '#type' => 'value', - '#value_callback' => '_user_language_selector_langcode_value', - // For the synchronization to work, this element must have a larger weight - // than the preferred_langcode element. Set a large weight here in case - // a module alters the weight of the other element. - '#weight' => 100, - ); - return parent::form($form, $form_state, $account); } /** - * Overrides Drupal\Core\Entity\EntityFormController::submit(). + * Overrides Drupal\Core\Entity\EntityFormController::validate(). */ public function validate(array $form, array &$form_state) { parent::validate($form, $form_state); $account = $this->getEntity($form_state); // Validate new or changing username. - if (isset($form_state['values']['name'])) { - if ($error = user_validate_name($form_state['values']['name'])) { - form_set_error('name', $error); + if (isset($form_state['values']['username'])) { + if ($error = account_validate_name($form_state['values']['username'])) { + form_set_error('username', $error); } // Cast the user ID as an integer. It might have been set to NULL, which // could lead to unexpected results. else { - $name_taken = (bool) db_select('users') - ->fields('users', array('uid')) + $name_taken = (bool) db_select('account') + ->fields('account', array('uid')) ->condition('uid', (int) $account->uid, '<>') - ->condition('name', db_like($form_state['values']['name']), 'LIKE') + ->condition('username', db_like($form_state['values']['username']), 'LIKE') ->range(0, 1) ->execute() ->fetchField(); if ($name_taken) { - form_set_error('name', t('The name %name is already taken.', array('%name' => $form_state['values']['name']))); + form_set_error('username', t('The username %username is already taken.', array('%username' => $form_state['values']['username']))); } } } - - $mail = $form_state['values']['mail']; - - if (!empty($mail)) { - $mail_taken = (bool) db_select('users') - ->fields('users', array('uid')) - ->condition('uid', (int) $account->uid, '<>') - ->condition('mail', db_like($mail), 'LIKE') - ->range(0, 1) - ->execute() - ->fetchField(); - - if ($mail_taken) { - // Format error message dependent on whether the user is logged in or not. - if ($GLOBALS['user']->uid) { - form_set_error('mail', t('The e-mail address %email is already taken.', array('%email' => $mail))); - } - else { - form_set_error('mail', t('The e-mail address %email is already registered. Have you forgotten your password?', array('%email' => $mail, '@password' => url('user/password')))); - } - } - } - - // Make sure the signature isn't longer than the size of the database field. - // Signatures are disabled by default, so make sure it exists first. - if (isset($form_state['values']['signature'])) { - // Move text format for user signature into 'signature_format'. - $form_state['values']['signature_format'] = $form_state['values']['signature']['format']; - // Move text value for user signature into 'signature'. - $form_state['values']['signature'] = $form_state['values']['signature']['value']; - - $user_schema = drupal_get_schema('users'); - if (drupal_strlen($form_state['values']['signature']) > $user_schema['fields']['signature']['length']) { - form_set_error('signature', t('The signature is too long: it must be %max characters or less.', array('%max' => $user_schema['fields']['signature']['length']))); - } - } } + } diff --git a/core/modules/user/lib/Drupal/user/AccountStorageController.php b/core/modules/user/lib/Drupal/user/AccountStorageController.php new file mode 100644 index 0000000..86f797b --- /dev/null +++ b/core/modules/user/lib/Drupal/user/AccountStorageController.php @@ -0,0 +1,86 @@ +isNew() || (!empty($entity->pass) && $entity->pass != $entity->original->pass)) { + // Allow alternate password hashing schemes. + require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'core/includes/password.inc'); + $entity->pass = user_hash_password(trim($entity->pass)); + // Abort if the hashing failed and returned FALSE. + if (!$entity->pass) { + throw new EntityMalformedException('The entity does not have a password.'); + } + } + + if (!$entity->isNew()) { + // If the password is empty, that means it was not changed, so use the + // original password. + if (empty($entity->pass)) { + $entity->pass = $entity->original->pass; + } + } + + if (empty($entity->uid)) { + // Create a User for this Account. + $user = entity_create('user', array( + 'mail' => $entity->init, + 'created' => REQUEST_TIME, + 'status' => 1, + )); + $user->save(); + $entity->uid = $user->uid; + } + } + + /** + * Overrides Drupal\Core\Entity\DatabaseStorageController::postSave(). + */ + protected function postSave(EntityInterface $entity, $update) { + if ($update) { + // If the username was changed and the user entity nice name was set as + // the username, update it. + if ($entity->original->username != $entity->username && + $entity->original->username == $user->name) { + $user = entity_load('user', $entity->uid); + $user->name = $entity->username; + $user->save(); + } + + // If the password has been changed, delete all open sessions for the + // user and recreate the current one. + if ($entity->pass != $entity->original->pass) { + drupal_session_destroy_uid($entity->uid); + if ($entity->uid == $GLOBALS['user']->uid) { + drupal_session_regenerate(); + } + } + } + else { + $user = entity_load('user', $entity->uid); + $user->name = $entity->username; + $user->save(); + } + } +} diff --git a/core/modules/user/lib/Drupal/user/RegisterFormController.php b/core/modules/user/lib/Drupal/user/RegisterFormController.php index 6e4a260..4cd8b74 100644 --- a/core/modules/user/lib/Drupal/user/RegisterFormController.php +++ b/core/modules/user/lib/Drupal/user/RegisterFormController.php @@ -20,6 +20,8 @@ class RegisterFormController extends AccountFormController { public function form(array $form, array &$form_state, EntityInterface $account) { global $user; + $language_interface = language(LANGUAGE_TYPE_INTERFACE); + $register = empty($account->uid); $admin = user_access('administer users'); // Pass access information to the submit handler. Running an access check @@ -41,6 +43,15 @@ public function form(array $form, array &$form_state, EntityInterface $account) // Start with the default user account fields. $form = parent::form($form, $form_state, $account); + $form['account']['init'] = array( + '#type' => 'email', + '#title' => t('E-mail address'), + '#description' => t('A valid e-mail address. All e-mails from the system will be sent to this address. The e-mail address is not made public and will only be used if you wish to receive a new password or wish to receive certain news or notifications by e-mail.'), + '#required' => !(empty($account->init) && user_access('administer users')), + '#default_value' => (!$register ? $account->init : ''), + '#attributes' => array('autocomplete' => 'off'), + ); + // Attach field widgets, and hide the ones where the 'user_register_form' // setting is not on. field_attach_form('user', $account, $form, $form_state); @@ -78,14 +89,13 @@ public function submit(array $form, array &$form_state) { $pass = $form_state['values']['pass']; } else { - $pass = user_password(); + $pass = account_password(); } // Remove unneeded values. form_state_values_clean($form_state); $form_state['values']['pass'] = $pass; - $form_state['values']['init'] = $form_state['values']['mail']; parent::submit($form, $form_state); } @@ -101,6 +111,10 @@ public function save(array $form, array &$form_state) { $account->save(); + // Get our user entity and store our pass against it. + $user = user_load($account->uid); + $user->pass = $account->pass; + // Terminate if an error occurred while saving the account. if ($status =! SAVED_NEW) { drupal_set_message(t("Error saving user account."), 'error'); @@ -110,7 +124,7 @@ public function save(array $form, array &$form_state) { $form_state['user'] = $account; $form_state['values']['uid'] = $account->uid; - watchdog('user', 'New user: %name (%email).', array('%name' => $form_state['values']['name'], '%email' => $form_state['values']['mail']), WATCHDOG_NOTICE, l(t('edit'), 'user/' . $account->uid . '/edit')); + watchdog('user', 'New user: %username (%email).', array('%name' => $form_state['values']['username'], '%email' => $form_state['values']['init']), WATCHDOG_NOTICE, l(t('edit'), 'user/' . $account->uid . '/edit')); // Add plain text password into user account to generate mail tokens. $account->password = $pass; @@ -118,11 +132,11 @@ public function save(array $form, array &$form_state) { // New administrative account without notification. $uri = $account->uri(); if ($admin && !$notify) { - drupal_set_message(t('Created a new user account for %name. No e-mail has been sent.', array('@url' => url($uri['path'], $uri['options']), '%name' => $account->name))); + drupal_set_message(t('Created a new user account for %username. No e-mail has been sent.', array('@url' => url($uri['path'], $uri['options']), '%username' => $account->username))); } // No e-mail verification required; log in user immediately. elseif (!$admin && !config('user.settings')->get('verify_mail') && $account->status) { - _user_mail_notify('register_no_approval_required', $account); + _user_mail_notify('register_no_approval_required', $user); $form_state['uid'] = $account->uid; user_login_submit(array(), $form_state); drupal_set_message(t('Registration successful. You are now logged in.')); @@ -130,14 +144,14 @@ public function save(array $form, array &$form_state) { } // No administrator approval required. elseif ($account->status || $notify) { - if (empty($account->mail) && $notify) { - drupal_set_message(t('The new user %name was created without an email address, so no welcome message was sent.', array('@url' => url($uri['path'], $uri['options']), '%name' => $account->name))); + if (empty($account->init) && $notify) { + drupal_set_message(t('The new user %username was created without an email address, so no welcome message was sent.', array('@url' => url($uri['path'], $uri['options']), '%username' => $account->username))); } else { $op = $notify ? 'register_admin_created' : 'register_no_approval_required'; - _user_mail_notify($op, $account); + _user_mail_notify($op, $user); if ($notify) { - drupal_set_message(t('A welcome message with further instructions has been e-mailed to the new user %name.', array('@url' => url($uri['path'], $uri['options']), '%name' => $account->name))); + drupal_set_message(t('A welcome message with further instructions has been e-mailed to the new user %username.', array('@url' => url($uri['path'], $uri['options']), '%username' => $account->username))); } else { drupal_set_message(t('A welcome message with further instructions has been sent to your e-mail address.')); @@ -147,7 +161,7 @@ public function save(array $form, array &$form_state) { } // Administrator approval required. else { - _user_mail_notify('register_pending_approval', $account); + _user_mail_notify('register_pending_approval', $user); drupal_set_message(t('Thank you for applying for an account. Your account is currently pending approval by the site administrator.
In the meantime, a welcome message with further instructions has been sent to your e-mail address.')); $form_state['redirect'] = ''; } diff --git a/core/modules/user/lib/Drupal/user/SettingsFormController.php b/core/modules/user/lib/Drupal/user/SettingsFormController.php new file mode 100644 index 0000000..c65d5c4 --- /dev/null +++ b/core/modules/user/lib/Drupal/user/SettingsFormController.php @@ -0,0 +1,62 @@ +getEntity($form_state); + + // @todo Actually the cancel action can be assimilated to the delete one: we + // should alter it instead of providing a new one. + unset($element['delete']); + + $element['cancel'] = array( + '#type' => 'submit', + '#value' => t('Cancel account'), + '#submit' => array('user_edit_cancel_submit'), + '#access' => $account->uid > 1 && (($account->uid == $GLOBALS['user']->uid && user_access('cancel account')) || user_access('administer users')), + ); + + return $element; + } + + /** + * Overrides Drupal\Core\Entity\EntityFormController::submit(). + */ + public function submit(array $form, array &$form_state) { + // @todo Consider moving this into the parent method. + // Remove unneeded values. + form_state_values_clean($form_state); + parent::submit($form, $form_state); + } + + /** + * Overrides Drupal\Core\Entity\EntityFormController::save(). + */ + public function save(array $form, array &$form_state) { + $account = $this->getEntity($form_state); + $account->save(); + $form_state['values']['uid'] = $account->id(); + + // Clear the page cache because pages can contain usernames and/or profile + // information: + cache_invalidate(array('content' => TRUE)); + + drupal_set_message(t('The changes have been saved.')); + } +} diff --git a/core/modules/user/lib/Drupal/user/User.php b/core/modules/user/lib/Drupal/user/User.php index 2217588..63178ba 100644 --- a/core/modules/user/lib/Drupal/user/User.php +++ b/core/modules/user/lib/Drupal/user/User.php @@ -36,13 +36,6 @@ class User extends Entity { public $name = ''; /** - * The user's password (hashed). - * - * @var string - */ - public $pass; - - /** * The user's email address. * * @var string @@ -136,13 +129,6 @@ class User extends Entity { public $picture = 0; /** - * The email address used for initial account creation. - * - * @var string - */ - public $init = ''; - - /** * The user's roles. * * @var array @@ -151,8 +137,15 @@ class User extends Entity { /** * Implements Drupal\Core\Entity\EntityInterface::id(). + * + * To help other systems integrate with the anonymous user API we will save + * the user as soon as an ID is requested for it so we can keep track of it. */ public function id() { + if (!empty($this->enforceIsNew) || !$this->uid) { + $this->save(); + } + return $this->uid; } } diff --git a/core/modules/user/lib/Drupal/user/UserFormController.php b/core/modules/user/lib/Drupal/user/UserFormController.php new file mode 100644 index 0000000..6b250f3 --- /dev/null +++ b/core/modules/user/lib/Drupal/user/UserFormController.php @@ -0,0 +1,240 @@ +uid); + $admin = user_access('administer users'); + + // User information. + $form['user'] = array( + '#type' => 'container', + '#weight' => -10, + ); + + if ($admin) { + $status = isset($account->status) ? $account->status : 1; + } + else { + $status = $register ? $config->get('register') == USER_REGISTER_VISITORS : $account->status; + } + + // The mail field is NOT required if account originally had no mail set + // and the user performing the edit has 'administer users' permission. + // This allows users without e-mail address to be edited and deleted. + // We need to retrieve the user and store the mail against the account for + // the protected values validation to work. + $form['user']['mail'] = array( + '#type' => 'email', + '#title' => t('E-mail address'), + '#description' => t('A valid e-mail address. All e-mails from the system will be sent to this address. The e-mail address is not made public and will only be used if you wish to receive a new password or wish to receive certain news or notifications by e-mail.'), + '#required' => !(empty($account->mail) && user_access('administer users')), + '#default_value' => (!$register ? $account->mail : ''), + '#attributes' => array('autocomplete' => 'off'), + ); + + // Display password field only for existing users or when user is allowed to + // assign a password during registration. + if (!$register && $account_account = account_uid_load($account->uid)) { + $protected_values = array(); + $current_pass_description = ''; + + // The user may only change their own password without their current + // password if they logged in via a one-time login link. + $protected_values['mail'] = $form['user']['mail']['#title']; + $current_pass_description = t('Required if you want to change the %mail below..', array('%mail' => $protected_values['mail'])); + + // The user must enter their current password to change to a new one. + if ($user->uid == $account->uid) { + $form['user']['current_pass_required_values'] = array( + '#type' => 'value', + '#value' => $protected_values, + ); + + $form['user']['current_pass'] = array( + '#type' => 'password', + '#title' => t('Current password'), + '#size' => 25, + '#access' => !empty($protected_values), + '#description' => $current_pass_description, + '#weight' => -5, + '#attributes' => array('autocomplete' => 'off'), + ); + + $account->pass = $account_account->pass; + $form_state['user'] = $account; + $form['#validate'][] = 'user_validate_current_pass'; + } + } + + $form['user']['status'] = array( + '#type' => 'radios', + '#title' => t('Status'), + '#default_value' => $status, + '#options' => array(t('Blocked'), t('Active')), + '#access' => $admin, + ); + + $roles = array_map('check_plain', user_roles(TRUE)); + // The disabled checkbox subelement for the 'authenticated user' role + // must be generated separately and added to the checkboxes element, + // because of a limitation in Form API not supporting a single disabled + // checkbox within a set of checkboxes. + // @todo This should be solved more elegantly. See issue #119038. + $checkbox_authenticated = array( + '#type' => 'checkbox', + '#title' => $roles[DRUPAL_AUTHENTICATED_RID], + '#default_value' => TRUE, + '#disabled' => TRUE, + ); + unset($roles[DRUPAL_AUTHENTICATED_RID]); + + $form['user']['roles'] = array( + '#type' => 'checkboxes', + '#title' => t('Roles'), + '#default_value' => (!$register && isset($account->roles) ? array_keys($account->roles) : array()), + '#options' => $roles, + '#access' => $roles && user_access('administer permissions'), + DRUPAL_AUTHENTICATED_RID => $checkbox_authenticated, + ); + + // Signature. + $form['signature_settings'] = array( + '#type' => 'fieldset', + '#title' => t('Signature settings'), + '#weight' => 1, + '#access' => (!$register && $config->get('signatures')), + ); + + $form['signature_settings']['signature'] = array( + '#type' => 'text_format', + '#title' => t('Signature'), + '#default_value' => isset($account->signature) ? $account->signature : '', + '#description' => t('Your signature will be publicly displayed at the end of your comments.'), + '#format' => isset($account->signature_format) ? $account->signature_format : NULL, + ); + + // Picture/avatar. + $form['picture'] = array( + '#type' => 'fieldset', + '#title' => t('Picture'), + '#weight' => 1, + '#access' => (!$register && variable_get('user_pictures', 0)), + ); + + $form['picture']['picture'] = array( + '#type' => 'value', + '#value' => isset($account->picture) ? $account->picture : NULL, + ); + + $form['picture']['picture_current'] = array( + '#markup' => theme('user_picture', array('account' => $account)), + ); + + $form['picture']['picture_delete'] = array( + '#type' => 'checkbox', + '#title' => t('Delete picture'), + '#access' => !empty($account->picture->fid), + '#description' => t('Check this box to delete your current picture.'), + ); + + $form['picture']['picture_upload'] = array( + '#type' => 'file', + '#title' => t('Upload picture'), + '#size' => 48, + '#description' => t('Your virtual face or picture. Pictures larger than @dimensions pixels will be scaled down.', array('@dimensions' => variable_get('user_picture_dimensions', '85x85'))) . ' ' . filter_xss_admin(variable_get('user_picture_guidelines', '')), + ); + + $form['#validate'][] = 'user_validate_picture'; + + $user_preferred_langcode = $register ? $language_interface->langcode : user_preferred_langcode($account); + + $user_preferred_admin_langcode = $register ? $language_interface->langcode : user_preferred_langcode($account, 'admin'); + + // Is default the interface language? + include_once DRUPAL_ROOT . '/core/includes/language.inc'; + $interface_language_is_default = language_negotiation_method_get_first(LANGUAGE_TYPE_INTERFACE) != LANGUAGE_NEGOTIATION_SELECTED; + $form['language'] = array( + '#type' => language_multilingual() ? 'fieldset' : 'container', + '#title' => t('Language settings'), + // Display language selector when either creating a user on the admin + // interface or editing a user account. + '#access' => !$register || user_access('administer users'), + ); + + $form['language']['preferred_langcode'] = array( + '#type' => 'language_select', + '#title' => t('Site language'), + '#languages' => LANGUAGE_CONFIGURABLE, + '#default_value' => $user_preferred_langcode, + '#description' => $interface_language_is_default ? t("This account's preferred language for e-mails and site presentation.") : t("This account's preferred language for e-mails."), + ); + + $form['language']['preferred_admin_langcode'] = array( + '#type' => 'language_select', + '#title' => t('Administration pages language'), + '#languages' => LANGUAGE_CONFIGURABLE, + '#default_value' => $user_preferred_admin_langcode, + '#access' => user_access('access administration pages', $account), + ); + + // User entities contain both a langcode property (for identifying the + // language of the entity data) and a preferred_langcode property (see + // above). Rather than provide a UI forcing the user to choose both + // separately, assume that the user profile data is in the user's preferred + // language. This element provides that synchronization. For use-cases where + // this synchronization is not desired, a module can alter or remove this + // element. + $form['language']['langcode'] = array( + '#type' => 'value', + '#value_callback' => '_user_language_selector_langcode_value', + // For the synchronization to work, this element must have a larger weight + // than the preferred_langcode element. Set a large weight here in case + // a module alters the weight of the other element. + '#weight' => 100, + ); + + return parent::form($form, $form_state, $account); + } + + /** + * Overrides Drupal\Core\Entity\EntityFormController::submit(). + */ + public function validate(array $form, array &$form_state) { + parent::validate($form, $form_state); + + // Make sure the signature isn't longer than the size of the database field. + // Signatures are disabled by default, so make sure it exists first. + if (isset($form_state['values']['signature'])) { + // Move text format for user signature into 'signature_format'. + $form_state['values']['signature_format'] = $form_state['values']['signature']['format']; + // Move text value for user signature into 'signature'. + $form_state['values']['signature'] = $form_state['values']['signature']['value']; + + $user_schema = drupal_get_schema('users'); + if (drupal_strlen($form_state['values']['signature']) > $user_schema['fields']['signature']['length']) { + form_set_error('signature', t('The signature is too long: it must be %max characters or less.', array('%max' => $user_schema['fields']['signature']['length']))); + } + } + } +} diff --git a/core/modules/user/lib/Drupal/user/UserStorageController.php b/core/modules/user/lib/Drupal/user/UserStorageController.php index 2660300..b71c009 100644 --- a/core/modules/user/lib/Drupal/user/UserStorageController.php +++ b/core/modules/user/lib/Drupal/user/UserStorageController.php @@ -87,16 +87,6 @@ public function save(EntityInterface $entity) { * Overrides Drupal\Core\Entity\DatabaseStorageController::preSave(). */ protected function preSave(EntityInterface $entity) { - // Update the user password if it has changed. - if ($entity->isNew() || (!empty($entity->pass) && $entity->pass != $entity->original->pass)) { - // Allow alternate password hashing schemes. - require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'core/includes/password.inc'); - $entity->pass = user_hash_password(trim($entity->pass)); - // Abort if the hashing failed and returned FALSE. - if (!$entity->pass) { - throw new EntityMalformedException('The entity does not have a password.'); - } - } if (!empty($entity->picture_upload)) { $entity->picture = $entity->picture_upload; @@ -136,12 +126,6 @@ protected function preSave(EntityInterface $entity) { } } $entity->picture = empty($entity->picture->fid) ? 0 : $entity->picture->fid; - - // If the password is empty, that means it was not changed, so use the - // original password. - if (empty($entity->pass)) { - $entity->pass = $entity->original->pass; - } } // Prepare user roles. @@ -163,15 +147,6 @@ protected function preSave(EntityInterface $entity) { protected function postSave(EntityInterface $entity, $update) { if ($update) { - // If the password has been changed, delete all open sessions for the - // user and recreate the current one. - if ($entity->pass != $entity->original->pass) { - drupal_session_destroy_uid($entity->uid); - if ($entity->uid == $GLOBALS['user']->uid) { - drupal_session_regenerate(); - } - } - // Remove roles that are no longer enabled for the user. $entity->roles = array_filter($entity->roles); @@ -232,5 +207,8 @@ protected function postDelete($entities) { db_delete('authmap') ->condition('uid', array_keys($entities), 'IN') ->execute(); + db_delete('account') + ->condition('uid', array_keys($entities), 'IN') + ->execute(); } } diff --git a/core/modules/user/user.admin.inc b/core/modules/user/user.admin.inc index 396f4c0..8c217cc 100644 --- a/core/modules/user/user.admin.inc +++ b/core/modules/user/user.admin.inc @@ -11,7 +11,7 @@ function user_admin($callback_arg = '') { switch ($op) { case t('Create new account'): case 'create': - $account = entity_create('user', array()); + $account = entity_create('account', array()); $build['user_register'] = entity_get_form($account, 'register'); break; default: diff --git a/core/modules/user/user.install b/core/modules/user/user.install index a099f6b..26707be 100644 --- a/core/modules/user/user.install +++ b/core/modules/user/user.install @@ -18,8 +18,8 @@ function user_schema() { 'type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, - 'description' => 'Primary Key: Unique user ID.', 'default' => 0, + 'description' => 'Primary Key: Unique user ID.', ), 'uuid' => array( 'description' => 'Unique Key: Universally unique identifier for this entity.', @@ -32,7 +32,7 @@ function user_schema() { 'length' => 60, 'not null' => TRUE, 'default' => '', - 'description' => 'Unique user name.', + 'description' => 'Nice name for the user.', ), 'langcode' => array( 'type' => 'varchar', @@ -41,13 +41,6 @@ function user_schema() { 'default' => '', 'description' => "The {language}.langcode of the user's profile.", ), - 'pass' => array( - 'type' => 'varchar', - 'length' => 128, - 'not null' => TRUE, - 'default' => '', - 'description' => "User's password (hashed).", - ), 'mail' => array( 'type' => 'varchar', 'length' => 254, @@ -126,13 +119,6 @@ function user_schema() { 'default' => 0, 'description' => "Foreign key: {file_managed}.fid of user's picture.", ), - 'init' => array( - 'type' => 'varchar', - 'length' => 254, - 'not null' => FALSE, - 'default' => '', - 'description' => 'E-mail address used for initial account creation.', - ), 'data' => array( 'type' => 'blob', 'not null' => FALSE, @@ -149,7 +135,6 @@ function user_schema() { ), 'unique keys' => array( 'uuid' => array('uuid'), - 'name' => array('name'), ), 'primary key' => array('uid'), 'foreign keys' => array( @@ -160,6 +145,60 @@ function user_schema() { ), ); + $schema['account'] = array( + 'description' => 'Stores account data.', + 'fields' => array( + 'aid' => array( + 'type' => 'serial', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'description' => 'Primary Key: Unique account ID', + ), + 'uid' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + 'description' => "User's {users}.uid.", + ), + 'username' => array( + 'type' => 'varchar', + 'length' => 60, + 'not null' => TRUE, + 'default' => '', + 'description' => 'Unique user name.', + ), + 'pass' => array( + 'type' => 'varchar', + 'length' => 128, + 'not null' => TRUE, + 'default' => '', + 'description' => "User's password (hashed).", + ), + 'created' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'description' => 'Timestamp for when user was created.', + ), + 'init' => array( + 'type' => 'varchar', + 'length' => 254, + 'not null' => FALSE, + 'default' => '', + 'description' => 'E-mail address used for initial account creation.', + ), + ), + 'indexes' => array( + 'created' => array('created'), + 'uid' => array('uid'), + ), + 'unique keys' => array( + 'username' => array('username'), + ), + 'primary key' => array('aid'), + ); + $schema['authmap'] = array( 'description' => 'Stores distributed authentication mapping.', 'fields' => array( @@ -599,5 +638,98 @@ function user_update_8009(&$sandbox) { } /** + * Split the users table into accounts and users and migrate users into new + * structure. + */ +function user_update_8010(&$sandbox) { + if (!isset($sandbox['progress'])) { + $schema = array( + 'description' => 'Stores account data.', + 'fields' => array( + 'aid' => array( + 'type' => 'serial', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'description' => 'Primary Key: Unique account ID', + ), + 'uid' => array( + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + 'description' => "User's {users}.uid.", + ), + 'username' => array( + 'type' => 'varchar', + 'length' => 60, + 'not null' => TRUE, + 'default' => '', + 'description' => 'Unique user name.', + ), + 'pass' => array( + 'type' => 'varchar', + 'length' => 128, + 'not null' => TRUE, + 'default' => '', + 'description' => "User's password (hashed).", + ), + 'created' => array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'description' => 'Timestamp for when user was created.', + ), + 'init' => array( + 'type' => 'varchar', + 'length' => 254, + 'not null' => FALSE, + 'default' => '', + 'description' => 'E-mail address used for initial account creation.', + ), + ), + 'indexes' => array( + 'created' => array('created'), + 'uid' => array('uid'), + ), + 'unique keys' => array( + 'username' => array('username'), + ), + 'primary key' => array('aid'), + ); + db_create_table('account', $schema); + + // Set up the batch. + $sandbox['progress'] = 0; + // The anonymous user doesn't need an account to we start at 1. + // @todo: Do we need to make a stock anonymous user anymore? + $sandbox['last'] = 0; + $sandbox['max'] = db_query('SELECT COUNT(uid) FROM {users} WHERE uid > 0')->fetchField(); + } + + $users = db_query_range('SELECT * FROM {users} WHERE uid > :uid ORDER BY uid ASC', 0, 10, array(':uid' => $sandbox['last']))->fetchAllAssoc('uid'); + + foreach ($users as $uid => $user) { + db_insert('account') + ->fields( array( + 'uid' => $user->uid, + 'username' => $user->name, + 'pass' => $user->pass, + 'created' => $user->created, + 'init' => $user->init, + )) + ->execute(); + $sandbox['last'] = $user->uid; + } + + $sandbox['#finished'] = empty($sandbox['max']) ? 1 : ($sandbox['last'] / $sandbox['max']); + + // When the migrate has finished clean up the user table. + if ($sandbox['#finished'] == 1) { + db_drop_field('users', 'pass'); + db_drop_field('users', 'init'); + } +} + +/** * @} End of "addtogroup updates-7.x-to-8.x". */ diff --git a/core/modules/user/user.module b/core/modules/user/user.module index e42002b..16e8394 100644 --- a/core/modules/user/user.module +++ b/core/modules/user/user.module @@ -147,8 +147,7 @@ function user_entity_info() { 'label' => t('User'), 'controller class' => 'Drupal\user\UserStorageController', 'form controller class' => array( - 'profile' => 'Drupal\user\ProfileFormController', - 'register' => 'Drupal\user\RegisterFormController', + 'settings' => 'Drupal\user\SettingsFormController', ), 'base table' => 'users', 'uri callback' => 'user_uri', @@ -175,11 +174,54 @@ function user_entity_info() { ), ), ), + 'account' => array( + 'label' => t('Account'), + 'controller class' => 'Drupal\user\AccountStorageController', + 'form controller class' => array( + 'profile' => 'Drupal\user\ProfileFormController', + 'register' => 'Drupal\user\RegisterFormController', + ), + 'base table' => 'account', + 'uri callback' => 'account_uri', + 'label callback' => 'account_label', + 'fieldable' => FALSE, + 'entity class' => 'Drupal\user\Account', + 'entity keys' => array( + 'id' => 'aid', + ), + ), ); } /** - * Entity URI callback. + * Entity URI callback for accounts. + */ +function account_uri($account) { + return array( + 'path' => 'user/' . $account->uid, + ); +} + +/** + * Entity label callback for accounts. + * + * This label callback has langcode for security reasons. The username is the + * visual identifier for an account. + * + * @param $entity_type + * The entity type. + * @param $entity + * The entity object. + * + * @return + * The username on the account. + */ +function account_label($entity_type, $entity) { + return $entity->username; +} + +/** + * Entity URI callback for users. */ function user_uri($user) { return array( @@ -226,11 +268,6 @@ function user_field_info_alter(&$info) { function user_field_extra_fields() { $return['user']['user'] = array( 'form' => array( - 'account' => array( - 'label' => t('User name and password'), - 'description' => t('User module account form elements.'), - 'weight' => -10, - ), 'timezone' => array( 'label' => t('Timezone'), 'description' => t('User module timezone form element.'), @@ -340,25 +377,40 @@ function user_load_by_mail($mail) { } /** - * Fetches a user object by account name. + * Fetches a user object by username. * - * @param string $name - * String with the account's user name. + * @param string $username + * String with the account's username. * @return object|bool * A fully-loaded $user object upon successful user load or FALSE if user * cannot be loaded. * - * @see user_load_multiple() + * @see user_load() + * @see account_load_by_name() */ -function user_load_by_name($name) { - $users = entity_load_multiple_by_properties('user', array('name' => $name)); - return reset($users); +function user_load_by_name($username) { + $account = account_load_by_name($username); + return user_load($account->uid); +} + +/** + * Fetches an account object by username. + * + * @param string $username + * String with the account's username. + * @return object|bool + * A fully-loaded $account object upon successful user load or FALSE if user + * cannot be loaded. + */ +function account_load_by_name($username) { + $accounts = entity_load_multiple_by_properties('account', array('username' => $username)); + return reset($accounts); } /** * Verify the syntax of the given name. */ -function user_validate_name($name) { +function account_validate_name($name) { if (!$name) { return t('You must enter a username.'); } @@ -417,7 +469,7 @@ function user_validate_picture(&$form, &$form_state) { /** * Generate a random alphanumeric password. */ -function user_password($length = 10) { +function account_password($length = 10) { // This variable contains the list of allowable characters for the // password. Note that the number 0 and the letter 'O' have been // removed to avoid confusion between the two. The same is true @@ -538,18 +590,19 @@ function user_access($string, $account = NULL) { /** * Checks for usernames blocked by user administration. * - * @param $name - * A string containing a name of the user. + * @param $username + * A string containing a username of the account. * * @return - * Object with property 'name' (the user name), if the user is blocked; - * FALSE if the user is not blocked. - */ -function user_is_blocked($name) { - return db_select('users') - ->fields('users', array('name')) - ->condition('name', db_like($name), 'LIKE') - ->condition('status', 0) + * Object with property 'username' (the user name), if the account is + * blocked; FALSE if the user is not blocked. + */ +function user_is_blocked($username) { + $query = db_select('account'); + $query->join('users', 'users', 'users.uid = account.uid'); + return $query->fields('account', array('username')) + ->condition('account.username', db_like($username), 'LIKE') + ->condition('users.status', 0) ->execute()->fetchObject(); } @@ -687,12 +740,15 @@ function user_user_view($account) { '#markup' => theme('user_picture', array('account' => $account)), '#weight' => -10, ); - $account->content['member_for'] = array( - '#type' => 'item', - '#title' => t('Member for'), - '#markup' => format_interval(REQUEST_TIME - $account->created), - '#weight' => 5, - ); + $account_account = account_uid_load($account->uid); + if ($account_account) { + $account->content['member_for'] = array( + '#type' => 'item', + '#title' => t('Member for'), + '#markup' => format_interval(REQUEST_TIME - $account_account->created), + '#weight' => 5, + ); + } } /** @@ -735,7 +791,7 @@ function user_login_block($form) { $form['#id'] = 'user-login-form'; $form['#validate'] = user_login_default_validators(); $form['#submit'][] = 'user_login_submit'; - $form['name'] = array('#type' => 'textfield', + $form['username'] = array('#type' => 'textfield', '#title' => t('Username'), '#maxlength' => USERNAME_MAX_LENGTH, '#size' => 15, @@ -849,7 +905,7 @@ function user_block_view($delta = '') { case 'new': if (user_access('access content')) { // Retrieve a list of new users who have subsequently accessed the site successfully. - $items = db_query_range('SELECT uid, name FROM {users} WHERE status <> 0 AND access <> 0 ORDER BY created DESC', 0, variable_get('user_block_whois_new_count', 5))->fetchAll(); + $items = db_query_range('SELECT uid, name FROM {users} WHERE status <> 0 AND login <> 0 ORDER BY created DESC', 0, variable_get('user_block_whois_new_count', 5))->fetchAll(); $block['subject'] = t('Who\'s new'); $block['content'] = array( @@ -917,21 +973,21 @@ function user_preprocess_block(&$variables) { * By default, the passed-in object's 'name' property is used if it exists, or * else, the site-defined value for the 'anonymous' variable. However, a module * may override this by implementing - * hook_user_format_name_alter(&$name, $account). + * hook_user_format_name_alter(&$name, $user). * * @see hook_user_format_name_alter() * - * @param $account - * The account object for the user whose name is to be formatted. + * @param $user + * The user object for the user whose name is to be formatted. * * @return * An unsanitized string with the username to display. The code receiving * this result must ensure that check_plain() is called on it before it is * printed to the page. */ -function user_format_name($account) { - $name = !empty($account->name) ? $account->name : config('user.settings')->get('anonymous'); - drupal_alter('user_format_name', $name, $account); +function user_format_name($user) { + $name = !empty($user->name) ? $user->name : config('user.settings')->get('anonymous'); + drupal_alter('user_format_name', $name, $user); return $name; } @@ -1104,7 +1160,7 @@ function user_is_logged_in() { } function user_register() { - $account = entity_create('user', array()); + $account = entity_create('account', array()); return entity_get_form($account, 'register'); } @@ -1347,12 +1403,35 @@ function user_menu() { $items['user/%user/edit'] = array( 'title' => 'Edit', 'page callback' => 'entity_get_form', + 'page arguments' => array(1, 'settings'), + 'access callback' => 'user_edit_access', + 'access arguments' => array(1), + 'type' => MENU_LOCAL_TASK, + 'file' => 'user.pages.inc', + ); + + $items['user/%user/edit/user'] = array( + 'title' => 'User', + 'page callback' => 'entity_get_form', + 'page arguments' => array(1, 'settings'), + 'access callback' => 'user_edit_access', + 'access arguments' => array(1), + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'file' => 'user.pages.inc', + 'weight' => 98, + ); + + $items['user/%account_uid/edit/account'] = array( + 'title' => 'Account', + 'page callback' => 'entity_get_form', 'page arguments' => array(1, 'profile'), 'access callback' => 'user_edit_access', 'access arguments' => array(1), 'type' => MENU_LOCAL_TASK, 'file' => 'user.pages.inc', + 'weight' => 99, ); + return $items; } @@ -1494,6 +1573,27 @@ function user_uid_optional_to_arg($arg) { } /** + * Load either a specified or the current user account. + * + * @param $uid + * An optional user ID of the user to load. If not provided, the current + * user's ID will be used. + * @return + * A fully-loaded $user object upon successful user load, FALSE if user + * cannot be loaded. + * + * @see account_load() + * @todo rethink the naming of this in Drupal 8. + */ +function account_uid_load($uid = NULL) { + if (!isset($uid)) { + $uid = $GLOBALS['user']->uid; + } + $accounts = entity_load_multiple_by_properties('account', array('uid' => $uid)); + return reset($accounts); +} + +/** * Menu item title callback for the 'user' path. * * Anonymous users should see a title based on the requested page, but @@ -1585,7 +1685,7 @@ function user_login($form, &$form_state) { } // Display login form: - $form['name'] = array('#type' => 'textfield', + $form['username'] = array('#type' => 'textfield', '#title' => t('Username'), '#size' => 60, '#maxlength' => USERNAME_MAX_LENGTH, @@ -1595,7 +1695,7 @@ function user_login($form, &$form_state) { ), ); - $form['name']['#description'] = t('Enter your @s username.', array('@s' => config('system.site')->get('name'))); + $form['username']['#description'] = t('Enter your @s username.', array('@s' => config('system.site')->get('name'))); $form['pass'] = array('#type' => 'password', '#title' => t('Password'), '#description' => t('Enter the password that accompanies your username.'), @@ -1634,9 +1734,9 @@ function user_login_default_validators() { * A FAPI validate handler. Sets an error if supplied username has been blocked. */ function user_login_name_validate($form, &$form_state) { - if (isset($form_state['values']['name']) && user_is_blocked($form_state['values']['name'])) { + if (isset($form_state['values']['username']) && user_is_blocked($form_state['values']['username'])) { // Blocked in user administration. - form_set_error('name', t('The username %name has not been activated or is blocked.', array('%name' => $form_state['values']['name']))); + form_set_error('username', t('The username %username has not been activated or is blocked.', array('%username' => $form_state['values']['username']))); } } @@ -1648,7 +1748,7 @@ function user_login_name_validate($form, &$form_state) { function user_login_authenticate_validate($form, &$form_state) { $password = trim($form_state['values']['pass']); $flood_config = config('user.flood'); - if (!empty($form_state['values']['name']) && !empty($password)) { + if (!empty($form_state['values']['username']) && !empty($password)) { // 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 @@ -1658,7 +1758,7 @@ function user_login_authenticate_validate($form, &$form_state) { $form_state['flood_control_triggered'] = 'ip'; return; } - $account = db_query("SELECT * FROM {users} WHERE name = :name AND status = 1", array(':name' => $form_state['values']['name']))->fetchObject(); + $account = db_query("SELECT * FROM {account} a LEFT JOIN {users} u ON u.uid = a.uid WHERE a.username = :username AND u.status = 1", array(':username' => $form_state['values']['username']))->fetchObject(); if ($account) { if ($flood_config->get('uid_only')) { // Register flood events based on the uid only, so they apply for any @@ -1682,7 +1782,7 @@ function user_login_authenticate_validate($form, &$form_state) { } // We are not limited by flood control, so try to authenticate. // Set $form_state['uid'] as a flag for user_login_final_validate(). - $form_state['uid'] = user_authenticate($form_state['values']['name'], $password); + $form_state['uid'] = user_authenticate($form_state['values']['username'], $password); } } @@ -1705,16 +1805,16 @@ function user_login_final_validate($form, &$form_state) { if (isset($form_state['flood_control_triggered'])) { if ($form_state['flood_control_triggered'] == 'user') { - form_set_error('name', format_plural($flood_config->get('user_limit'), 'Sorry, there has been more than one failed login attempt for this account. It is temporarily blocked. Try again later or request a new password.', 'Sorry, there have been more than @count failed login attempts for this account. It is temporarily blocked. Try again later or request a new password.', array('@url' => url('user/password')))); + form_set_error('username', format_plural($flood_config->get('user_limit'), 'Sorry, there has been more than one failed login attempt for this account. It is temporarily blocked. Try again later or request a new password.', 'Sorry, there have been more than @count failed login attempts for this account. It is temporarily blocked. Try again later or request a new password.', array('@url' => url('user/password')))); } else { // We did not find a uid, so the limit is IP-based. - form_set_error('name', t('Sorry, too many failed login attempts from your IP address. This IP address is temporarily blocked. Try again later or request a new password.', array('@url' => url('user/password')))); + form_set_error('username', t('Sorry, too many failed login attempts from your IP address. This IP address is temporarily blocked. Try again later or request a new password.', array('@url' => url('user/password')))); } } else { - form_set_error('name', t('Sorry, unrecognized username or password. Have you forgotten your password?', array('@password' => url('user/password')))); - watchdog('user', 'Login attempt failed for %user.', array('%user' => $form_state['values']['name'])); + form_set_error('username', t('Sorry, unrecognized username or password. Have you forgotten your password?', array('@password' => url('user/password')))); + watchdog('user', 'Login attempt failed for %user.', array('%user' => $form_state['values']['username'])); } } elseif (isset($form_state['flood_control_user_identifier'])) { @@ -1734,10 +1834,10 @@ function user_login_final_validate($form, &$form_state) { * @return * The user's uid on success, or FALSE on failure to authenticate. */ -function user_authenticate($name, $password) { +function user_authenticate($username, $password) { $uid = FALSE; - if (!empty($name) && !empty($password)) { - $account = user_load_by_name($name); + if (!empty($username) && !empty($password)) { + $account = account_load_by_name($username); if ($account) { // Allow alternate password hashing schemes. require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'core/includes/password.inc'); @@ -1838,7 +1938,7 @@ function user_external_login_register($name, $module) { * 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, $options = array()) { +function account_pass_reset_url($account, $options = array()) { $timestamp = REQUEST_TIME; $langcode = isset($options['langcode']) ? $options['langcode'] : user_preferred_langcode($account); $url_options = array('absolute' => TRUE, 'language' => language_load($langcode)); @@ -2169,7 +2269,7 @@ function _user_mail_text($key, $langcode = NULL, $variables = array()) { */ function user_mail_tokens(&$replacements, $data, $options) { if (isset($data['user'])) { - $replacements['[user:one-time-login-url]'] = user_pass_reset_url($data['user'], $options); + $replacements['[user:one-time-login-url]'] = account_pass_reset_url($data['user'], $options); $replacements['[user:cancel-url]'] = user_cancel_url($data['user'], $options); } } diff --git a/core/modules/user/user.tokens.inc b/core/modules/user/user.tokens.inc index bc37434..a3d02bd 100644 --- a/core/modules/user/user.tokens.inc +++ b/core/modules/user/user.tokens.inc @@ -11,7 +11,7 @@ function user_token_info() { $types['user'] = array( 'name' => t('Users'), - 'description' => t('Tokens related to individual user accounts.'), + 'description' => t('Tokens related to individual users.'), 'needs-data' => 'user', ); $types['current-user'] = array( @@ -22,23 +22,23 @@ function user_token_info() { $user['uid'] = array( 'name' => t('User ID'), - 'description' => t("The unique ID of the user account."), + 'description' => t("The unique ID of the user."), ); $user['name'] = array( 'name' => t("Name"), - 'description' => t("The login name of the user account."), + 'description' => t("The nice name of the user."), ); $user['mail'] = array( 'name' => t("Email"), - 'description' => t("The email address of the user account."), + 'description' => t("The email address of the user."), ); $user['url'] = array( 'name' => t("URL"), - 'description' => t("The URL of the account profile page."), + 'description' => t("The URL of the user profile page."), ); $user['edit-url'] = array( 'name' => t("Edit URL"), - 'description' => t("The URL of the account edit page."), + 'description' => t("The URL of the user edit page."), ); $user['last-login'] = array( @@ -48,13 +48,39 @@ function user_token_info() { ); $user['created'] = array( 'name' => t("Created"), + 'description' => t("The date the user user was created."), + 'type' => 'date', + ); + + $types['account'] = array( + 'name' => t('Accounts'), + 'description' => t('Tokens related to individual user accounts.'), + 'needs-data' => 'account', + ); + $account['aid'] = array( + 'name' => t('Account ID'), + 'description' => t("The unique ID of the user account."), + ); + $account['uid'] = array( + 'name' => t('User ID'), + 'description' => t("The user ID of this account."), + ); + $account['username'] = array( + 'name' => t("Name"), + 'description' => t("The login name of the user account."), + ); + $account['created'] = array( + 'name' => t("Created"), 'description' => t("The date the user account was created."), 'type' => 'date', ); return array( 'types' => $types, - 'tokens' => array('user' => $user), + 'tokens' => array( + 'user' => $user, + 'account' => $account, + ), ); } @@ -127,5 +153,36 @@ function user_tokens($type, $tokens, array $data = array(), array $options = arr $replacements += token_generate('user', $tokens, array('user' => $account), $options); } + if ($type == 'account' && !empty($data['account'])) { + $account = $data['account']; + foreach ($tokens as $name => $original) { + switch ($name) { + // Basic account information. + case 'aid': + // In the case of hook user_presave aid is not set yet. + $replacements[$original] = !empty($account->aid) ? $account->aid : t('not yet assigned'); + break; + + case 'uid': + // In the case of hook user_presave uid is not set yet. + $replacements[$original] = !empty($account->uid) ? $account->uid : t('not yet assigned'); + break; + + case 'username': + $replacements[$original] = $sanitize ? check_plain($account->username) : $account->username; + break; + + case 'created': + // In the case of user_presave the created date may not yet be set. + $replacements[$original] = !empty($account->created) ? format_date($account->created, 'medium', '', NULL, $langcode) : t('not yet created'); + break; + } + } + + if ($registered_tokens = token_find_with_prefix($tokens, 'created')) { + $replacements += token_generate('date', $registered_tokens, array('date' => $account->created), $options); + } + } + return $replacements; }