// ***************************************************************
// Remove any role that has a capability that the current user doesn't have.
// uses the 'editable_roles' filter which might not exist yet. Probably
// won't be around until 2.6 is released, maybe 2.5.1...

function filter_editable_roles($roles) {
foreach ($roles as $role => $details) :
foreach ($details['capabilities'] as $capability => $value) :
if (!current_user_can($capability)) :
unset ($roles[$role]);
endforeach; //capabilities
endforeach; //foreach $wp_roles;
return $roles;
add_filter('editable_roles', 'filter_editable_roles');

// ***************************************************************
// Check if the logged in user should be allowed to edit another
// user.
// For hooking into 'user_has_cap' filter. Use when doing a
// check for current_user_can('edit_user', $user_id);
// Works by comparing the logged-in user to the $user_object,
// if $user_object has any capability that the logged-in user DOESN'T
// then the edit is denied.
// NOTE: this won't help stopping users from being PROMOTED to an
// innapropriate role. For that the promotion logic needs to get
// the filtered roles with get_editable_roles
// $allcaps - a copy of $wp_roles->role_names , it should return with
// innapropriate roles removed.

function check_user_editable($allcaps, $caps, $args) {
// only run if we're checking the 'edit_users' cap
// also, only if there is a second argument in $args (the second, edited, user)
if ($caps[0] == 'edit_users' && $args[2]) :

// Get full information about the user that they want to edit.
global $user_object, $wp_roles;

// The $user_object variable is often not available when checks like this are made.

// line 109 of /wp-admin/user-edit.php

// Not doing that yet though, for now we'll just
// return the list as before if the $user_object doesn't have the
// same ID as the argument that was passed into the filter

if (!$user_object) $user_object = new wp_user($args[2]);

if ($user_object->ID != $args[2]) return $allcaps;

$edited_user_caps = $user_object->allcaps;
$edited_user_roles = $user_object->roles;
$checked_roles = array();

// go through edited user's roles, and check for missing caps.
foreach ($edited_user_roles as $role => $name) :
$rolecaps = $wp_roles->roles[$name]['capabilities'];
foreach($rolecaps as $capability => $value) :
if (!current_user_can($capability)) :
unset ($allcaps['edit_users']);
return $allcaps;
endforeach; // rolecaps
// add the role to a list of checked roles if there are no problems
$checked_roles[] = $name;
endforeach; // foreach edited_user_roles

// This only runs if there were no conflicts while checking roles
// go through the edited users caps and check if current user has them all
foreach ($edited_user_caps as $capability => $value) :
if (in_array($capability, $checked_roles)) :
elseif (!current_user_can($capability)) :
unset ($allcaps['edit_users']);
return $allcaps;
endforeach; //capabilities
endif;// if edit_users

return $allcaps;

add_filter('user_has_cap', 'check_user_editable', 10, 3)