Programmatically adding and removing roles to users in Drupal

[UPDATE: Here is a much simpler method for editing a user's roles.]

I thought there might be some sort of API function that allows me to add a user role to a user object by the role id (rid), but after looking at user_save() and some other information around the Drupal universe (like this thread), it looks like it's not as easy as I'd hoped. Definitely not like node_save(), where you just modify the node object, save it, and you're done!

I wrote this helper function that you could stick in your own custom module (tested with Drupal 7), which lets you add roles as simply as:

  custom_add_role_to_user($user->uid, 'role name here');

Here's the function:

/**
 * Add a role to a user.
 *
 * @param $user
 *   User object or user ID.
 * @param $role_name
 *   String value of role to be added.
 *
 * @see http_://drupal.org/node/28379#comment-4277052
 * @see http_://api.drupal.org/api/drupal/modules--user--user.module/function/user_save
 */
function custom_add_role_to_user($user, $role_name) {
  // For convenience, we'll allow user ids as well as full user objects.
  if (is_numeric($user)) {
    $user = user_load($user);
  }
  // If the user doesn't already have the role, add the role to that user.
  $key = array_search($role_name, $user->roles);
  if ($key == FALSE) {
    // Get the rid from the roles table.
    $roles = user_roles(TRUE);
    $rid = array_search($role_name, $roles);
    if ($rid != FALSE) {
      $new_role[$rid] = $role_name;
      $all_roles = $user->roles + $new_role; // Add new role to existing roles.
      user_save($user, array('roles' => $all_roles));
    }
  }
}

Hopefully, for Drupal 8, the user_save() function will be cleaned up (see the @todo), and this won't be such a chore.

[Edit: I've also written a handy function to remove user roles, which was made more robust in the way it removes roles using the $edit array of user_save() instead of modifying the user object's existing roles array, by g-hennux (see comments below). It's pasted below.]

/**
 * Remove a role from a user.
 *
 * @param $user
 *   User object or user ID.
 * @param $role_name
 *   String value of role to be removed.
 */
function custom_remove_role_from_user($user, $role_name) {
  // For convenience, we'll allow user ids as well as full user objects.
  if (is_numeric($user)) {
    $user = user_load($user);
  }
  // Only remove the role if the user already has it.
  $key = array_search($role_name, $user->roles);
  if ($key == TRUE) {
    // Get the rid from the roles table.
    $roles = user_roles(TRUE);
    $rid = array_search($role_name, $roles);
    if ($rid != FALSE) {
      // Make a copy of the roles array, without the deleted one.
      $new_roles = array();
      foreach($user->roles as $id => $name) {
        if ($id != $rid) {
          $new_roles[$id] = $name;
        }
      }
      user_save($user, array('roles' => $new_roles));
    }
  }
}

Comments

I'm using the extended_ldapgroups module to sync my groups with an LDAP directory. Your functions both work fine in Drupal 6 and adding a role to a user also triggers the add in LDAP, but removing a role frome a user does not. Any idea what could cause this?

Things are structured a bit differently in D6, so I'm not surprised the function above won't work correctly. I don't have a D6 site to test on right now, so you'll have to figure this out on your own :(

OK, so the problem is that the extended_ldapgroups module checks for a difference between the handed in $user->roles and the roles given in the $edit array. In your remove function, you unset() the role to be deleted in the $user->roles array, i.e. extended_ldapgroups won't notice that a role has actually been removed. If you modify the role deletion code to

<?php
if ($rid != FALSE) {
 
// make a copy of roles array, without the deleted one
 
$new_roles = array();
  foreach(
$user->roles as $id => $name) {
    if (
$id != $rid) {
     
$new_roles[$id] = $name;
    }
  }
 
user_save($user, array('roles' => $new_roles));
}
?>

this will work fine.

Thanks for the update! That actually should work better anyways, as it preserves the user object as it should be preserved... because of the crazy way user_save() works (which is to say, unlike node_save(), which is simpler).

I am getting this msg: Fatal error: Unsupported operand types in .../sites/all/themes/.../template.php on line 237

this line is: $all_roles = $user->roles + $new_role;

Isn't it '.' instead of '+'?

thanks

The + for arrays requires a certain version of PHP to run (I think something in the 5.2 range), but it's valid as long as you have the right version of PHP. It's simply easier to use than adding the item to the array manually...

Thank you, Jeff! This 2011 article had helped me in 2018. Very handy!