4.6.x user.module user_save($account, $array = array(), $category = 'account')
4.7.x user.module user_save($account, $array = array(), $category = 'account')
5.x user.module user_save($account, $array = array(), $category = 'account')
6.x user.module user_save($account, $array = array(), $category = 'account')
7.x user.module user_save($account, $edit = array(), $category = 'account')

Save changes to a user account or add a new user.

Parameters

$account: The user object for to modify or add. If you want to modify an existing user account, you will need to ensure that (a) $account is an object, and (b) you have set $account->uid to the numeric user ID of the user account you wish to modify. Pass in NULL or any non-object to add a new user.

$array: (optional) An array of fields and values to save. For example, array('name' => 'My name'); Keys that do not belong to columns in the user-related tables are added to the a serialized array in the 'data' column and will be loaded in the $user->data array by user_load(). Setting a field to NULL deletes it from the data column, if you are modifying an existing user account.

$category: (optional) The category for storing profile information in.

Return value

A fully-loaded $user object upon successful save or FALSE if the save failed.

11 calls to user_save()
comment_controls_submit in modules/comment/comment.module
Process comment_controls form submissions.
install_configure_form_submit in ./install.php
Form API submit for the site configuration form.
openid_authentication in modules/openid/openid.module
Authenticate a user or attempt registration.
system_admin_compact_page in modules/system/system.admin.inc
Menu callback; Sets whether the admin menu is in compact mode or not.
user_edit_submit in modules/user/user.pages.inc

... See full list

File

modules/user/user.module, line 225
Enables the user registration and login system.

Code

function user_save($account, $array = array(), $category = 'account') {
  // Dynamically compose a SQL query:
  $user_fields = user_fields();
  if (is_object($account) && $account->uid) {
    user_module_invoke('update', $array, $account, $category);
    $query = '';
    $data = unserialize(db_result(db_query('SELECT data FROM {users} WHERE uid = %d', $account->uid)));
    // Consider users edited by an administrator as logged in, if they haven't
    // already, so anonymous users can view the profile (if allowed).
    if (empty($array['access']) && empty($account->access) && user_access('administer users')) {
      $array['access'] = time();
    }
    foreach ($array as $key => $value) {
      if ($key == 'pass' && !empty($value)) {
        $query .= "$key = '%s', ";
        $v[] = md5($value);
      }
      else if ((substr($key, 0, 4) !== 'auth') && ($key != 'pass')) {
        if (in_array($key, $user_fields)) {
          // Save standard fields.
          $query .= "$key = '%s', ";
          $v[] = $value;
        }
        else if ($key != 'roles') {
          // Roles is a special case: it used below.
          if ($value === NULL) {
            unset($data[$key]);
          }
          elseif (!empty($key)) {
            $data[$key] = $value;
          }
        }
      }
    }
    $query .= "data = '%s' ";
    $v[] = serialize($data);

    $success = db_query("UPDATE {users} SET $query WHERE uid = %d", array_merge($v, array($account->uid)));
    if (!$success) {
      // The query failed - better to abort the save than risk further data loss.
      return FALSE;
    }

    // Reload user roles if provided.
    if (isset($array['roles']) && is_array($array['roles'])) {
      db_query('DELETE FROM {users_roles} WHERE uid = %d', $account->uid);

      foreach (array_keys($array['roles']) as $rid) {
        if (!in_array($rid, array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID))) {
          db_query('INSERT INTO {users_roles} (uid, rid) VALUES (%d, %d)', $account->uid, $rid);
        }
      }
    }

    // Delete a blocked user's sessions to kick them if they are online.
    if (isset($array['status']) && $array['status'] == 0) {
      sess_destroy_uid($account->uid);
    }

    // If the password changed, delete all open sessions and recreate
    // the current one.
    if (!empty($array['pass'])) {
      sess_destroy_uid($account->uid);
      if ($account->uid == $GLOBALS['user']->uid) {
        sess_regenerate();
      }
    }

    // Refresh user object.
    $user = user_load(array('uid' => $account->uid));

    // Send emails after we have the new user object.
    if (isset($array['status']) && $array['status'] != $account->status) {
      // The user's status is changing; conditionally send notification email.
      $op = $array['status'] == 1 ? 'status_activated' : 'status_blocked';
      _user_mail_notify($op, $user);
    }

    user_module_invoke('after_update', $array, $user, $category);
  }
  else {
    // Allow 'created' to be set by the caller.
    if (!isset($array['created'])) {
      $array['created'] = time();
    }
    // Consider users created by an administrator as already logged in, so
    // anonymous users can view the profile (if allowed).
    if (empty($array['access']) && user_access('administer users')) {
      $array['access'] = time();
    }

    // Note: we wait to save the data column to prevent module-handled
    // fields from being saved there. We cannot invoke hook_user('insert') here
    // because we don't have a fully initialized user object yet.
    foreach ($array as $key => $value) {
      switch ($key) {
        case 'pass':
          $fields[] = $key;
          $values[] = md5($value);
          $s[] = "'%s'";
          break;
        case 'mode':
        case 'sort':
        case 'timezone':
        case 'threshold':
        case 'created':
        case 'access':
        case 'login':
        case 'status':
          $fields[] = $key;
          $values[] = $value;
          $s[] = "%d";
          break;
        default:
          if (substr($key, 0, 4) !== 'auth' && in_array($key, $user_fields)) {
            $fields[] = $key;
            $values[] = $value;
            $s[] = "'%s'";
          }
          break;
      }
    }
    $success = db_query('INSERT INTO {users} (' . implode(', ', $fields) . ') VALUES (' . implode(', ', $s) . ')', $values);
    if (!$success) {
      // On a failed INSERT some other existing user's uid may be returned.
      // We must abort to avoid overwriting their account.
      return FALSE;
    }

    // Build the initial user object.
    $array['uid'] = db_last_insert_id('users', 'uid');
    $user = user_load(array('uid' => $array['uid']));

    user_module_invoke('insert', $array, $user, $category);

    // Build and save the serialized data field now.
    $data = array();
    foreach ($array as $key => $value) {
      if ((substr($key, 0, 4) !== 'auth') && ($key != 'roles') && (!in_array($key, $user_fields)) && ($value !== NULL)) {
        $data[$key] = $value;
      }
    }
    db_query("UPDATE {users} SET data = '%s' WHERE uid = %d", serialize($data), $user->uid);

    // Save user roles (delete just to be safe).
    if (isset($array['roles']) && is_array($array['roles'])) {
      db_query('DELETE FROM {users_roles} WHERE uid = %d', $array['uid']);
      foreach (array_keys($array['roles']) as $rid) {
        if (!in_array($rid, array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID))) {
          db_query('INSERT INTO {users_roles} (uid, rid) VALUES (%d, %d)', $array['uid'], $rid);
        }
      }
    }

    // Build the finished user object.
    $user = user_load(array('uid' => $array['uid']));
  }

  // Save distributed authentication mappings.
  $authmaps = array();
  foreach ($array as $key => $value) {
    if (substr($key, 0, 4) == 'auth') {
      $authmaps[$key] = $value;
    }
  }
  if (sizeof($authmaps) > 0) {
    user_set_authmaps($user, $authmaps);
  }

  return $user;
}

Comments

asiby’s picture

When using this function to modify a user account that has some profile fields, 2 things may happen:

Case 1: You will lose the values stored in the other profile fields that are not included in the $array parameter (the fields that you are not trying to modify).

Solution:

You need to know that the profile module will delete all the existing profile values for the given user before saving them again. Very inefficient resource-wise, I know. But that's what happens. So, you need to load all the profile fields that you want to keep as well as the modified fields in the $array parameter before passing it to user_save(). IMHO, the normal behavior should be that the profile field simply update the provided fields.

Case 2: Some profile values are not making it to their corresponding fields in the database. Instead, they are being stored in a serialized array in the data field of the $user object. This will break things if you are using those profile fields in the views module as the views will not find the value of the field.

Solution:

This happens because some profile fields have not been made visible on the registration form. Because of that, even if a user is being created with user_save() by the admin (programmatically), the profile module will think that it is being done via the registration form and will NOT add the values of those fields. They will end up being stored in the $user->data field.

I hope this help.

Cheers

Asiby

lucacolombo’s picture

I red your #2 solution

"This happens because some profile fields have not been made visible on the registration form. Because of that, even if a user is being created with user_save() by the admin (programmatically), the profile module will think that it is being done via the registration form and will NOT add the values of those fields. They will end up being stored in the $user->data field."

I switched the "visible on the registration form" of any profile field to ON.

Then I programmatically inserted users and their profiles.

Anything works except that I cannot find profile data in Views.

Can you help help me please?

Thanks

luca

charlie-s’s picture

Asiby, you genius. Thanks for breaking this down. I was running into Case 2 -- fields not visible on the registration page weren't being saved. I'll have to use db_query() to perform this step manually, unfortunately.

aterchin’s picture

if you want to save certain profile data fields without printing out and saving them all, you need to write a custom function that does an update, not a delete + insert, which profile_save_profile() does.

// your custom profile saving function
function mymodule_save_profile(&$edit, &$user, $category, $register = FALSE){
  $result = _profile_get_fields($category, $register);
  while ($field = db_fetch_object($result)) {
    if(isset($edit[$field->name])){
      if (_profile_field_serialize($field->type)) {
        $edit[$field->name] = serialize($edit[$field->name]);
      }
      db_query("UPDATE {profile_values} SET value = '%s' WHERE fid = %d AND uid = %d", $edit[$field->name], $field->fid, $user->uid);
    }
    // Mark field as handled (prevents saving to user->data).
    $edit[$field->name] = NULL;
  }
}

// you just got some user data from a form...
global $user;
// load the profile data
profile_load_profile(&$user);
// your changes to resave into the profile without touching the other ones
$edit['profile_city'] = "Chicago";
$edit['profile_state'] = "IL";
// save just city and state
mymodule_save_profile(&$edit, &$user, "My Information");
veryrose’s picture

thanks,
if a user is being created with user_save() by the admin (programmatically), then the profile field don't exist, so it couldn't be updated. then i edit some code from yours:

function my_module_save_profile(&$edit, &$user, $category, $register = FALSE) {
  $result = _profile_get_fields($category, $register);
  while ($field = db_fetch_object($result)) {
    if(isset($edit[$field->name])){ // this prevent from deleting other stored fields
      if (_profile_field_serialize($field->type)) {
        $edit[$field->name] = serialize($edit[$field->name]);
      }
      db_query("DELETE FROM {profile_values} WHERE fid = %d AND uid = %d", $field->fid, $user->uid);
      db_query("INSERT INTO {profile_values} (fid, uid, value) VALUES (%d, %d, '%s')", $field->fid, $user->uid, $edit[$field->name]);
    }
    // Mark field as handled (prevents saving to user->data).
    $edit[$field->name] = NULL;
  }
}
aterchin’s picture

you're doing the same thing as an update because your delete/insert is within the 'isset' check, but you are correct in that i'm assuming you have all the profile fields created before you start adding users into the system.

if you need to add an additional profile field at some later date, you'd just initialize it by inserting a empty row with corresponding fid into the profile values table for all users.

monaw’s picture

The $category argument for this function is NOT optional if you have categories defined for the profile. If you don't give the profile category, the profile field will NOT be saved...argh! This means if you have more than one category to save, you have to make separate calls to this function...one call for each category. Also, very inefficient...sigh.

NancyDru’s picture

variable’s picture

>>if you have more than one category to save, you have to make separate calls to this function
if you have just one additional category of profile fields (except of 'account'), you can pass it in $category argument, and both, main fields, and additional, from profile module will updated/saved

VikrantR’s picture

I think this will solve your problem. Using this you can add/update user information coming from external source like XML or any other.

1. first create $user array() with your all required fields and append profile fields that you have newly created by using profile module.

$user = array();
$user['status'] = 1; //for active user
$user['mail'] = 'test@test.com';
//profile field
$user['profile_field_fname'] = 'Vikrant';

$user_account = array();
if($user_uid = module_name_get_uid_by_email($user['mail'])) {
//It will use for update.
$user_account = user_load(array(uid => $user_uid));
}
//save or update user information.
user_save($user_account, $user);

//Check existing record by email.
function module_name_get_uid_by_email($email){
$query = db_query("SELECT uid FROM {users} WHERE mail = '%s'",$email);
$result = db_fetch_object($query);
return $result->uid;
}

davidAlonsoG’s picture

I think it's possible to use:

user_load(array('mail'=>$user['mail']));

insted of your module_name_get_uid_by_email function.

VikrantR’s picture

user_save returns user object after save. So you can return any value like name,email or uid etc.

$my_user = user_save($user_account, $user);
return $my_user->uid;

joshmiller’s picture

It may be obvious to some, but not to me...

If you want to change $user->status or add a $user->role -- you don't directly change the user object, you add these to an array at the end of the function, like so:


$account = user_load(2); // load the user account you want to change
$update['status'] = 1;
$roles = user_roles(true); // load the site's roles
$update['roles'][2] = $roles[2]; // authenticated user
$update['roles'][4] = $roles[4]; // any other role you wish (could come from a variable)
$updated_account = user_save($account, $update);

2pha’s picture

Thanks Josh, it was not obvious to me either.

sjeandroz’s picture

Hi,

I have a big problem,

In a function called in ajax, I do that:

   $myuser = user_load('9');

    $id = $_POST['id'];
    $id = str_replace("editable_", "", $id);
   
    //$changes = get_object_vars($user);
    $changes[$id] = $_POST['value'];

    $myuser = user_save($myuser, $changes);

This code works great for some fields (for example profile_firstname) but for some other (like mail), the modification doesn't persist after a page reloading.

The modification of mail's field is correctly applied in the database, but when I reload a page, the modification is lost and the mail's field return to is original value....

PS: the problem is the same if this code is called without ajax...

sjeandroz’s picture

Sorry, after search it's because of a conflict with the module ldap_data...

Thanks

bailey86’s picture

There is some useful lifesaver code at:

http://ericlondon.com/programmatically-addupdate-users-using-user-save

Also, it looks like you can add a new user AND set which ID number you want to use.

If you use

user_save(null, (array) $user_object);

where $user_object->uid is set to a value then the user is created with the user id of uid.

NB - This might seem a bit un-useful - but I'm using it to import users from a data file which has been exported from another Drupal instance - see my module at http://drupal.org/sandbox/bailey86/1278830

Miro Goepel’s picture

In case we don't want to make a profile field visible but still want to use user_save(): Is there another way to prevent the field value from getting stored in the user's data field?