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