Creating Custom User Admin Actions In Drupal 7 Organic Groups

Organic Groups (OG) in Drupal 7 has a role based permission system that works on a group by group basis. This permissions system works separately to the main Drupal permission system, which can cause a couple of issues. For example, if you want to give a group role access to give other users roles then you'll need to give them the 'Administer groups' permission. The downside of this is that it overrides Drupal's core permissions to do with node deletion and allows the role to delete the group. Allowing any user to delete groups can lead to all sorts of problems so an alternative is needed.

The group people admin page (found at group/node/%nid%/admin/people) has a bulk operations form that allows users with access to the form to manage user group membership. To allow or deny a member a user just needs to select them from the list, select the action required and click Update. Here is the select statement from that page.

<select>
<option value="unblock" selected="selected">Approve membership of the selected users</option>
<option value="deny">Deny membership from the selected users</option>
<option value="block">Block membership from the selected users</option>
</select>

The Administer group permission also adds a few options to this action list to allow them to assign roles to particular users. It is possible to add options to this list by using the hook_og_user_operations() hook. The normal contents of the people admin page is controlled via the function og_ui_og_user_operations(), which is an implementation of this hook. The function creates an array of operations and returns this.

What I wanted to do was add a couple of options to this list so that users who had the group permission 'update group' but didn't have the group permission 'administer group' would be able to give other users the role of 'group leader'. This is just a special role that allows them to update the group and administer people within the group.

The easiest way to accomplish this is to create an associative array of operations. Each array contains a 'label' for the option and a 'callback' for the function that is used to process the action of the command. The following function adds two elements to the select list, one for adding the role 'group leader' and one for taking it away again.

/**
 * Implements hook_og_user_operations().
 */
function mymodule_og_user_operations($form = array(), $form_state = array()) {
  $operations = array();
  
  if (og_user_access($gid, 'update group') && !og_user_access($gid, 'administer group')) {
    'group_lead_grant' => array(
      'label' => t('Grant group lead role to user'),
      'callback' => 'group_leader_grant',
    ),
    'group_lead_revoke' => array(
      'label' => t('Revoke group lead role to user'),
      'callback' => 'group_leader_revoke',
    )
  }  
  
  return $operations;
}

This creates a select list like this.

<select>
<option value="unblock" selected="selected">Approve membership of the selected users</option>
<option value="deny">Deny membership from the selected users</option>
<option value="block">Block membership from the selected users</option>
<option value="group_lead_grant">Grant group lead role to user </option>
<option value="group_lead_revoke"> Revoke group lead role to user </option>
</select>

The callback functions for the above operations take the group ID (not the node ID) and an array of user ID's as a default. These functions use a function I wrote to load organic group roles by their name, rather than using the gid. There is also a small check to make sure that a user doesn't revoke their own role.

/**
 * Callback function from mymodule_og_user_operations().
 */
function group_leader_grant($gid, $uids) {
  if (og_user_access($gid, 'update group')) {
    $accounts = user_load_multiple($uids);
    $group = mymodule_og_roles_by_name('group leader');
    foreach ($accounts as $account) {
      og_role_grant($gid, $account->uid, $group['rid']);
    }
  }
}

/**
 * Callback function from mymodule_og_user_operations().
 */
function group_leader_revoke($gid, $uids) {
  global $user;
  
  if (og_user_access($gid, 'update group')) {
    $accounts = user_load_multiple($uids);
    $group = mymodule_og_roles_by_name('group leader');
    foreach ($accounts as $account) {
      if ($user->uid == $account->uid) {
        form_set_error('', "You can't revoke your own user account!");
        break;
      }
      og_role_revoke($gid, $account->uid, $group['rid']);
    }
  }
}

It is also possible to create a group of options by passing an associative array of items as the label. The index of the array is the group label in the select list. This approach is slightly more complex as it requires assigning the correct action from within the hook function itself. The index of the label array that was selected is passed to the function in the $form_state and an action is used to decide on what action to take. You can either add the code into this function, use the same callback functions as before or even use a single callback function that accepts arguments.

The following function implements a group of options that allow the user to select an action, and then defines a callback function to run in order to process the change.

/**
 * Implements hook_og_user_operations().
 */
function mymodule_og_user_operations($form = array(), $form_state = array()) {
  $operations = array();
  
  if (og_user_access($gid, 'update group') && !og_user_access($gid, 'administer group')) {
    $operations['Group leader administration'] = array(
      'label' => array(
        'group_leader_grant' => 'Grant group leader status',
        'group_leader_revoke' => 'Revoke group leader status'      
      )
    );    
  }  
  
  if (!empty($form_state['submitted'])) {
    switch ($form_state['values']['operation']) {
      case 'group_leader_grant':
        $operations[$form_state['values']['operation']] = array(
          'callback' => 'group_leader_role_edit',
          'callback arguments' => array('grant'),
        );
        break;
      case 'group_leader_revoke':
        $operations[$form_state['values']['operation']] = array(
          'callback' => 'group_leader_role_edit',
          'callback arguments' => array('revoke'),
        );        
        break;
    }
  }  

  return $operations;
}

This creates a select list like the following.

<select>
<option value="unblock" selected="selected">Approve membership of the selected users</option>
<option value="deny">Deny membership from the selected users</option>
<option value="block">Block membership from the selected users</option>
<optgroup label="Group leader administration">
<option value="group_leader_grant">Grant group leader status </option>
<option value="group_leader_revoke">Revoke group leader status </option>
</optgroup>
</select>

Here is the callback function used in the above hook. It is a single function that will either grant or revoke the group leader role to a user depending on what operation was selected from the mymodule_og_user_operations() function.

function group_leader_role_edit($gid, $uids, $op) {
  global $user;
  
  if (og_user_access($gid, 'update group')) {
    $accounts = user_load_multiple($uids);
    $group = mymodule_og_roles_by_name('group leader');
    foreach ($accounts as $account) {   
      switch ($op) {
        case 'grant':
          og_role_grant($gid, $account->uid, $group['rid']);
          break;
        case 'revoke':
          if ($user->uid == $account->uid) {
            form_set_error('', "You can't revoke your own user account!");
            break;
          }          
          og_role_revoke($gid, $account->uid, $group['rid']);
          break;
      }
    }
  }
}

With this in place I can now setup the group role permissions correctly so that users can't delete groups but can manage user roles within their group.

Add new comment

The content of this field is kept private and will not be shown publicly.
CAPTCHA
3 + 5 =
Solve this simple math problem and enter the result. E.g. for 1+3, enter 4.
This question is for testing whether or not you are a human visitor and to prevent automated spam submissions.