// ***************************************************************
// 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)