Wednesday, 10 June 2009

Drupal 5: Automatically assign a role on user profile edit

Using Drupal 5, I recently had cause to create a system whereby when a user updates checkboxes in his or her profile, roles would automatically be assigned or unassigned. No problem, I thought, I would just use hook_user() to achieve this. According to the API, I would need the two $ops insert and update.

Writing the one for update was easy. The $account contained all the user's profile fields (the ones starting with profile_) and the roles could be assigned based on these (1 for add role, 0 for delete role).

I ran into a problem with insert. The $account contained the keys for the profile fields, but the values were all blank! I still don't really know why this is. The solution, as cumbersome as it might be, is to wait until the new user has a uid, then call user_load() on the user, at which point the profile fields will have their proper values. Then, exactly the same method can be used as in the update case.

As a footnote, we don't actively develop in Drupal 5 any more; all of our development occurs in Drupal 6, but we still support Drupal 5 sites. Here is the finished code in case anyone finds it useful:


/**
 * Implementation of hook_user().
 */
function mymodule_autorole_user($op, &$edit, &$account, $category = NULL) {
  if ($op == 'insert') {
    mymodule_autorole_apply_roles($account->uid);
  }
  else if ($op == 'update') {
    mymodule_autorole_apply_roles($account->uid);
  }
}

/**
 * Takes a user account object and uses it to update the user's roles.
 * 
 * @param $uid
 *    A fully populated user account object such as one returned by user_load().
 */
function mymodule_autorole_apply_roles($uid) {
  $account = user_load(array('uid' => $uid));
  
  // Filter out the profile fields from the account information.
  $profile_fields = array();
  
  foreach ($account as $fieldname => $field) {
    // Split up the field name by the underscore character. The field names we
    // are looking for are named like profile_something, but they could be
    // profile_something_something, so join back together after the split.
    $pieces = explode('_', $fieldname);
    
    if (array_shift($pieces) == 'profile') {
      $profile_fields[implode('_', $pieces) . ' club member'] = $field;
      
    }
  }
  
  $myaccount = user_load(array('uid' => $account->uid));
  $roles = user_roles();
  
  foreach ($profile_fields as $field => $value) {
    if ($value) {
      // The checkbox was checked, or the textfield had something in it. Add a
      // new role corresponding to this, if there is one.
      foreach ($roles as $key => $role) {
        if ($role == $field) {
          $myaccount->roles[$key] = $role;
        }
      }
    }
    else {
      // The checkbox was unchecked, or the textfield was empty. Unset the user
      // role corresponding to this, if there is one.
      foreach ($roles as $key => $role) {
        if ($role == $field) {
          unset($myaccount->roles[$key]);
        }
      }
    }
  }
  
  // Update the user with the new role assignments.
  db_query("DELETE FROM {users_roles} WHERE uid = %d", $myaccount->uid);
  
  foreach ($myaccount->roles as $rid => $role) {
    db_query("INSERT INTO {users_roles} (uid, rid) VALUES (%d, %d)", $myaccount->uid, $rid);
  }
}

1 comment:

Unknown said...

Hi Chris,

I'm facing almost the same problem, but with a little twist. At the moment I'm adding info in a certain profile field, and on a certain date I need to change a user role for all registered users depending on the profile info.

So, I'm not changing the user role when I'm actually changing the profile info. But I'm looking for a solution to do this in a "batch".

How could this be done?

thanks,
Geert