administerusersbyrole.module in Administer Users by Role 7.2
Same filename and directory in other branches
Provides fine-grained permissions for creating, editing, and deleting users.
This module allows site builders to set up fine-grained permissions for allowing users to edit and cancel other users: more specific than Drupal Core's all-or-nothing 'administer users' permission. It also provides and enforces a 'create users' permission.
File
administerusersbyrole.moduleView source
<?php
/**
* @file
* Provides fine-grained permissions for creating, editing, and deleting users.
*
* This module allows site builders to set up fine-grained permissions for
* allowing users to edit and cancel other users: more specific than
* Drupal Core's all-or-nothing 'administer users' permission. It also
* provides and enforces a 'create users' permission.
*/
/**
* Implements hook_permission().
*/
function administerusersbyrole_permission() {
$roles = user_roles(TRUE);
// Exclude the admin role. Once you can edit an admin, you can set their password, log in and do anything,
// which defeats the point of using this module.
$admin_rid = variable_get('user_admin_role', 0);
$perms = array();
$perms['create users'] = array(
'title' => 'Create new users',
);
$perms['access users overview'] = array(
'title' => 'Access the users overview page',
);
$ops = array(
'edit' => t('edit'),
'cancel' => t('cancel'),
);
foreach ($roles as $rid => $role) {
if ($rid == $admin_rid) {
continue;
}
foreach ($ops as $op => $operation) {
$perm_string = _administerusersbyrole_build_perm_string($rid, $op);
if ($rid === DRUPAL_AUTHENTICATED_RID) {
$perm_title = drupal_ucfirst(t("@operation users with no custom roles", array(
'@operation' => $operation,
)));
}
else {
$perm_title = drupal_ucfirst(t("@operation users with role %role", array(
'@operation' => $operation,
'%role' => $role,
)));
}
$perms[$perm_string] = array(
'title' => $perm_title,
);
}
}
return $perms;
}
/**
* Implements hook_menu_alter().
*/
function administerusersbyrole_menu_alter(&$items) {
// Dependency was added on chain_menu_access and, immediately after an update, the dependency may be missing.
// Make sure we don't render the whole website unusable in this case.
if (!module_exists('chain_menu_access')) {
return;
}
chain_menu_access_chain($items, 'user/%user', '_administerusersbyrole_check_access', array(
1,
'edit',
), TRUE);
chain_menu_access_chain($items, 'user/%user/edit', '_administerusersbyrole_check_access', array(
1,
'edit',
), TRUE);
chain_menu_access_chain($items, 'user/%user/cancel', '_administerusersbyrole_check_access', array(
1,
'cancel',
), TRUE);
$items['user/%user/cancel']['page callback'] = 'administerusersbyrole_cancel_confirm_wrapper';
$items['user/%user/cancel']['page arguments'] = array(
1,
);
chain_menu_access_chain($items, 'admin/people', 'user_access', array(
'access users overview',
), TRUE);
// The code in the user module to create a user relies on 'administer users' permission being set, so pass an argument to elevate permissions.
chain_menu_access_chain($items, 'admin/people/create', '_administerusersbyrole_can_create_users', array(
'elevate',
), TRUE);
// Compatibility with other contrib modules.
// If 'password_policy_password_tab' module (a sub-module of 'password_policy') is enabled, then check and update access.
if (module_exists('password_policy_password_tab')) {
chain_menu_access_chain($items, 'user/%user/password', '_administerusersbyrole_check_access', array(
1,
'edit',
), TRUE);
}
}
/**
* Implements hook_module_implements_alter().
*
* We need to be after the call from the entity module so our hook takes precedence.
*/
function administerusersbyrole_module_implements_alter(&$implementations, $hook) {
if ($hook == 'entity_info_alter') {
// Move our hook implementation to the bottom.
$group = $implementations['administerusersbyrole'];
unset($implementations['administerusersbyrole']);
$implementations['administerusersbyrole'] = $group;
}
}
/**
* Implements hook_views_default_views_alter().
*/
function administerusersbyrole_views_default_views_alter(&$views) {
if (isset($views['admin_views_user'])) {
// Add a tag to the admin_views module users view.
$handler =& $views['admin_views_user']->display['default']->handler;
$handler->display->display_options['query']['options']['query_tags'] = array(
'administerusersbyrole_edit_access',
);
}
}
/**
* Implements hook_query_alter().
*/
function administerusersbyrole_query_alter(QueryAlterableInterface $query) {
// The tag administerusersbyrole_edit_access is used to indicate that we should filter out users where there isn't edit access.
if ($query
->hasTag('administerusersbyrole_edit_access') && !user_access('administer users')) {
// Exclude the root user.
$query
->condition('users.uid', 1, '<>');
$roles = user_roles(TRUE);
foreach ($roles as $rid => $role) {
if (!user_access(_administerusersbyrole_build_perm_string($rid, 'edit'))) {
$exclude[$rid] = $rid;
}
}
if (isset($exclude[DRUPAL_AUTHENTICATED_RID])) {
// No permission unless there is a role.
$query
->join('users_roles', 'users_roles_2', 'users_roles_2.uid=users.uid');
unset($exclude[DRUPAL_AUTHENTICATED_RID]);
}
// Do an "anti-join" on the excluded roles - add a left join and then check the results set is null.
// NB We don't have to check that $exclude might be empty, because it always contains the admin role.
$urAlias = $query
->leftjoin('users_roles', 'ur', 'ur.uid=users.uid AND ur.rid IN (:exclude)', array(
':exclude' => $exclude,
));
$query
->isNull("{$urAlias}.uid");
}
}
/**
* Determine access to create user accounts.
*/
function _administerusersbyrole_can_create_users($extra = '') {
if (user_access('create users')) {
if ($extra === 'elevate') {
_administerusersbyrole_temp_administer_users();
}
return TRUE;
}
return FALSE;
}
/**
* Wrapper function for the cancel confirm form that first elevates to 'administer users' permission
* if required.
*/
function administerusersbyrole_cancel_confirm_wrapper($account) {
// If we are granting permissions, elevate to 'administer users'.
// Don't do this for a user cancelling their own account.
if (_administerusersbyrole_check_access($account, 'cancel')) {
_administerusersbyrole_temp_administer_users();
}
return drupal_get_form('user_cancel_confirm_form', $account);
}
/**
* Implements hook_form_FORM_ID_alter().
*/
function administerusersbyrole_form_user_admin_account_alter(&$form, &$form_state, $form_id) {
if (!user_access('administer users')) {
// Remove rows if user doesn't have permission to edit them.
// This deliberately removes users with cancel access but not edit access. Although it seems attractive to keep them,
// please DO NOT change this behaviour. If we did, then users would be able to run bulk edits on other users
// when having cancel permission but no edit permission.
foreach ($form['accounts']['#options'] as $uid => $fields) {
$account = user_load($uid);
// This form exposes operations such as block account that shouldn't be available on a user's own account,
// so just check our own permissions.
if (!_administerusersbyrole_check_access($account, 'edit')) {
unset($form['accounts']['#options'][$uid]);
}
}
}
}
/**
* Implements hook_form_FORM_ID_alter().
*
* Check for cancel permissions. The access control for the people admin page restricts the roles to those
* which we have _edit_ access rather than cancel access.
*/
function administerusersbyrole_form_user_multiple_cancel_confirm_alter(&$form, &$form_state) {
$anyallowed = FALSE;
foreach ($form_state['input']['accounts'] as $uid) {
$account = user_load($uid);
// This form bypasses checks and restrictions present when cancelling the user's own account,
// so just check our own permissions.
if (_administerusersbyrole_check_access($account, 'cancel')) {
$anyallowed = TRUE;
}
else {
drupal_set_message(t('You do not have permission to cancel %user.', array(
'%user' => $account->name,
)), 'error');
unset($form_state['input']['accounts'][$uid]);
unset($form['accounts'][$uid]);
}
}
if (!$anyallowed) {
drupal_goto(drupal_substr($form['#action'], 1));
}
}
/**
* Implements hook_form_FORM_ID_alter().
*
* Add extra visibility depending on our permissions.
*/
function administerusersbyrole_form_user_profile_form_alter(&$form, &$form_state) {
$account = $form['#user'];
// We just check against this module's own permissions.
// Don't check against the permissions in Drupal core, as those checks have already done and are subtle.
// (For example, users can't necesarily change their own username and can't block their own account.)
if (_administerusersbyrole_check_access($account, 'edit')) {
$form['account']['name']['#access'] = TRUE;
$form['account']['status']['#access'] = TRUE;
}
if (_administerusersbyrole_check_access($account, 'cancel')) {
$form['actions']['cancel']['#access'] = TRUE;
}
}
/**
* Implements hook_form_FORM_ID_alter().
*
* Add a wrapper to elevate user permissions during AJAX submissions
*/
function administerusersbyrole_form_user_register_form_alter(&$form, &$form_state) {
$form_state['wrapper_callback'] = '_administerusersbyrole_user_profile_form_wrapper';
}
/**
* Handler to elevate user permissions when form is reloaded, as by an AJAX submission
*/
function _administerusersbyrole_user_profile_form_wrapper($form, &$form_state) {
_administerusersbyrole_can_create_users('elevate');
return $form;
}
/**
* Implements hook_entity_info_alter().
*/
function administerusersbyrole_entity_info_alter(&$entity_info) {
$entity_info['user']['access callback'] = 'administerusersbyrole_metadata_user_access';
}
/**
* Implements hook_entity_property_info_alter().
*/
function administerusersbyrole_entity_property_info_alter(&$info) {
$properties =& $info['user']['properties'];
$properties['name']['access callback'] = 'administerusersbyrole_metadata_user_properties_access';
$properties['mail']['access callback'] = 'administerusersbyrole_metadata_user_properties_access';
$properties['status']['access callback'] = 'administerusersbyrole_metadata_user_properties_access';
$properties['theme']['access callback'] = 'administerusersbyrole_metadata_user_properties_access';
}
/**
* Access callback for the user entity.
*
* NB the names of variables here are deliberately different from the ones used in the entity module.
* The reason is to be consistent with the rest of this module. In the entity module:
* - Parameter 2 is named '$entity'. Entity is a user account, which is referred to as $account
* throughout in this module, so we stick with that.
* - Parameter 3 is named '$account', meaning the account to check as, which is referred to as $check_as
* in this module.
*/
function administerusersbyrole_metadata_user_access($op, $account = NULL, $check_as = NULL) {
// Call the base function.
if (entity_metadata_user_access($op, $account, $check_as)) {
return TRUE;
}
$convert = array(
'delete' => 'cancel',
'update' => 'edit',
);
if (isset($convert[$op])) {
// Call our own function.
$check_as = isset($check_as) ? $check_as : $GLOBALS['user'];
return _administerusersbyrole_check_access($account, $convert[$op], $check_as);
}
return FALSE;
}
/**
* Access callback for user entity properties.
*/
function administerusersbyrole_metadata_user_properties_access($op, $property, $account = NULL, $check_as = NULL) {
// Call the base function.
if (entity_metadata_user_properties_access($op, $property, $account, $check_as)) {
return TRUE;
}
$check_as = isset($check_as) ? $check_as : $GLOBALS['user'];
return _administerusersbyrole_check_access($account, 'edit', $check_as);
}
/**
* Check access to perform an operation on an account.
*
* This function checks the permissions of this module only. The calling code needs
* to check any Drupal core permissions that should also allow access.
*/
function _administerusersbyrole_check_access($account, $op, $check_as = NULL) {
// Never allow uid 0 (anonymous) or 1 (master admin).
if ($account->uid <= 1) {
return FALSE;
}
// We may have been passed a mock account object. If so, load the user to ensure
// that we have roles to check against.
if (!isset($account->roles)) {
$account = user_load($account->uid);
}
foreach ($account->roles as $rid => $role) {
// If there is only DRUPAL_AUTHENTICATED_RID, then we must test for it, otherwise skip it.
if ($rid === DRUPAL_AUTHENTICATED_RID && count($account->roles) > 1) {
continue;
}
if (!user_access(_administerusersbyrole_build_perm_string($rid, $op), $check_as)) {
return FALSE;
}
}
return TRUE;
}
/**
* Generates a permission string for a given a role.
*/
function _administerusersbyrole_build_perm_string($role_id, $op = 'edit') {
$perm = "{$op} users with role {$role_id}";
return $perm;
}
/**
* Temporarily override 'administer users' for the duration or processing this page.
*/
function _administerusersbyrole_temp_administer_users() {
global $user;
$static =& drupal_static('user_access');
$static[$user->uid]['administer users'] = TRUE;
}
/**
* Implements hook_views_api().
*/
function administerusersbyrole_views_api() {
return array(
'api' => 3,
'path' => drupal_get_path('module', 'administerusersbyrole') . '/views',
);
}