How to Allow Administrators to Edit Users in a WordPress Network

Jason Conroy of _FindingSimple emailed me today to ask if I knew how to allow site admins to edit the profiles of other users.

I hadn’t even realised administrators couldn’t edit users!

That’s because it relates only to WordPress networks. In a WordPress 3.x Network, the Super Admin role is the only role allowed to edit users.

Diagnosing the Problem

As a solution for Jason, my first thought was to simply assign the edit_users capability to the admin role.

I used Justin Tadlock’s Members plugin to check that the admin roles no longer had the edit_users & related user capabilities. However, despite not being able to edit users, admins do have the edit_users capability. In my opinion, that’s a bug in 3.1 or at very least a quirk. If a user has the edit_users capability, they should be able to edit users.

The only hope then was to enter the dark depths of the map_meta_cap function found in capabilities.php.

Around line 816 of that file, within map_meta_cap I found this bit of code:

	case 'edit_users':
		// If multisite these caps are allowed only for super admins.
		if ( is_multisite() && !is_super_admin( $user_id ) )
			$caps[] = 'do_not_allow';
		else
			$caps[] = 'edit_users'; // Explicit due to primitive fall through
		break;

And there it is. The master override that is preventing any role other than Super Admin from editing other users.

Solution

Fortunately, as is the wonder of WP, the $caps array is passed through a filter before being returned:

	return apply_filters('map_meta_cap', $caps, $cap, $user_id, $args);

So the trick then is to use the map_meta_cap filter to turn that do_not_allow capability in the $caps array into either edit_users, delete_users or create_users.

function mc_admin_users_caps( $caps, $cap, $user_id, $args ){

	foreach( $caps as $key => $capability ){

		if( $capability != 'do_not_allow' )
			continue;

		switch( $cap ) {
			case 'edit_user':
			case 'edit_users':
				$caps[$key] = 'edit_users';
				break;
			case 'delete_user':
			case 'delete_users':
				$caps[$key] = 'delete_users';
				break;
			case 'create_users':
				$caps[$key] = $cap;
				break;
		}
	}

	return $caps;
}
add_filter( 'map_meta_cap', 'mc_admin_users_caps', 10, 4 );

This restores the edit_users, delete_users and create_users capabilities to their former glory.

If a user has the admin role, or has been manually assigned any of these capabilities, they will then be able to edit users.

— Update 21 May 2012 —

A few important updates to get the code to work with WordPress 3.3 and to prevent admin users being able to edit super admin accounts and accounts of users not on their site.

Thanks to everyone in the comments, especially for those comments made by Andrew Cafourek, Peter Edwards & Morty Dot.

function mc_admin_users_caps( $caps, $cap, $user_id, $args ){

	foreach( $caps as $key => $capability ){

		if( $capability != 'do_not_allow' )
			continue;

		switch( $cap ) {
			case 'edit_user':
			case 'edit_users':
				$caps[$key] = 'edit_users';
				break;
			case 'delete_user':
			case 'delete_users':
				$caps[$key] = 'delete_users';
				break;
			case 'create_users':
				$caps[$key] = $cap;
				break;
		}
	}

	return $caps;
}
add_filter( 'map_meta_cap', 'mc_admin_users_caps', 1, 4 );
remove_all_filters( 'enable_edit_any_user_configuration' );
add_filter( 'enable_edit_any_user_configuration', '__return_true');

/**
 * Checks that both the editing user and the user being edited are
 * members of the blog and prevents the super admin being edited.
 */
function mc_edit_permission_check() {
	global $current_user, $profileuser;

	$screen = get_current_screen();

	get_currentuserinfo();

	if( ! is_super_admin( $current_user->ID ) && in_array( $screen->base, array( 'user-edit', 'user-edit-network' ) ) ) { // editing a user profile
		if ( is_super_admin( $profileuser->ID ) ) { // trying to edit a superadmin while less than a superadmin
			wp_die( __( 'You do not have permission to edit this user.' ) );
		} elseif ( ! ( is_user_member_of_blog( $profileuser->ID, get_current_blog_id() ) && is_user_member_of_blog( $current_user->ID, get_current_blog_id() ) )) { // editing user and edited user aren't members of the same blog
			wp_die( __( 'You do not have permission to edit this user.' ) );
		}
	}

}
add_filter( 'admin_head', 'mc_edit_permission_check', 1, 4 );

— Update 28 June 2012 —

Updated the mc_edit_permission_check() to prevent a super admin form being deleted. Thanks again to Peter Edwards in the comments.

— Update 29 September 2012 —

Updated the mc_edit_permission_check() to allow super admins to edit users again. Thanks to Scott Fennell in the comments.

About Brent

Born to code.
This entry was posted in Blogdex, WordPress and tagged , , , , , . Bookmark the permalink.

59 Responses to How to Allow Administrators to Edit Users in a WordPress Network

  1. Jason says:

    Such a legend. THANKS!

  2. I’ve been using this method rather successfully for a few weeks now in a new multisite setup, though it seems with the latest WP update (3.1.3), it is no longer working. It is a bit odd though, I looked through the changelog for WP 3.1.3 and I dont see anything off the bat that would have touched the map_meta_cap function.

    I’m doing some digging this afternoon to see if I can uncover what is happening, but is anyone else seeing issues after the latest upgrade? Of course it could always be a bonehead bug I introduced myself!

  3. (forgot to subscribe to follow-up comments) 🙂

  4. Oooops! Turns out I was simply misunderstanding the bug I was seeing. With Brent’s code above, my editors (who I’d granted the edit_users capability) could see the Edit link for users, but were receiving an error: “You do not have permission to edit this user”.

    Adding this line to the wp-config file solved the problem:

    define( "EDIT_ANY_USER", true );

    This is the snippet used in old WPMU for allowing user editing and it seems it still carries some importance in 3.0+ Multisite instances. Hopefully as the merged branches mature a little bit, the user management will be a bit more streamlined!

  5. Thank you SO MUCH for this! I’d been looking for this kind of solution early this year and never found an answer. This works perfectly!

  6. Zen says:

    Thanks it works

  7. Jason says:

    Thank you! Thank you! Perfect solution for what i agree is a bit of a quirk.

  8. Jason says:

    ps.. thanks to Andrew for the “EDIT_ANY_USER” tip… works great for Editor roles.

  9. Gaston says:

    sos un groso!!!!!!!

  10. Thanks, it works perfectly for 3.2.1.

  11. Pingback: How to Allow Administrators to Create Users in a WordPress Network | Blog do Gabriel

  12. jplow says:

    Yes, this is awesome! Thanks for sharing!

  13. crdplimousin says:

    I don’t understand where do you paste your code ? in the wp-config.php file ?

  14. crdplimousin says:

    Thanks you
    An other question :
    When we use a lot a theme it’s not possible to paste this code in other file than function.php oh each theme to don’t do it x times… ?

    • If I understand correctly, you are saying your network has multiple themes and you want a solution that is external to your theme so you dont need to edit each theme individually.

      You could edit the WP core code, but that is rarely a good idea, because you will have to edit it each time you update.

      I would suggest you create a simple plugin to achieve this – that way, as long as your plugin is activated on a site, it wont matter what theme is active. I’m not a great resource for how to write plugins, but there are a number of tutorials out there with some simple Googling.

  15. ShinichiN says:

    Hi, thank you for this article..

    Does this code still work on wordpress 3.2?

    I made a simple plugin but it didn’t work.

    I could see a link below the name of users to edit, but when I click it, WP says I have no right to edit that user..

    Thanks.

  16. I recently moved this and a few other things out of my theme’s functions.php folder and into a custom plugin for a client and it works great. The only ting I had to change in this code was the priority of the filter.

    add_filter( 'map_meta_cap', 'mc_admin_users_caps', 1, 4 );

    Leaving it at priority of 10 didn’t work, but changing that to 1 makes it work just fine. So if you are putting this in functions.php, leave it as you see above, just copy+paste. But if you are putting this in a plugin, you will need to edit line #25 as I have shown here.

  17. @ShinichiN
    This didn’t work for me on 3.2 either, so I looked into it and found a filter in user_edit.php – enable_edit_any_user_configuration – which prevents user editing – this filter is set to return false in ms-default-filters.php. You need to add the following:

    remove_all_filters( 'enable_edit_any_user_configuration' );
    add_filter( 'enable_edit_any_user_configuration', '__return_true'); // '__return_true' is a WordPress API function in wp-includes/functions.php
    

    As far as I can tell from past revisions, this filter works in conjunction with an EDIT_ANY_USER constant (possibly a hang-up from WPMU).

  18. crdplimousin says:

    I create a plugin but it doesn’t work more with the last version (3.3.1)… Does anyone have a new solution ?
    My code :

    function mc_admin_users_caps( $caps, $cap, $user_id, $args ){

    foreach( $caps as $key => $capability ){

    if( $capability != 'do_not_allow' )
    continue;

    switch( $cap ) {
    case 'edit_user':
    case 'edit_users':
    $caps[$key] = 'edit_users';
    break;
    case 'delete_user':
    case 'delete_users':
    $caps[$key] = 'delete_users';
    break;
    case 'create_users':
    $caps[$key] = $cap;
    break;
    }
    }

    return $caps;
    }
    add_filter( 'map_meta_cap', 'mc_admin_users_caps', 1, 4 );

  19. Rafael says:

    Thank you.
    I’m from Brazil, lost several hours trying to resolve this, your solution worked … thank you.

  20. Hi, It works works for me on WP 3.3.2 with following code in a plugin:
    wp_content/plugins/plugin_name.php
    ———————————————————————————————————–
    $capability ){

    if( $capability != ‘do_not_allow’ )
    continue;

    switch( $cap ) {
    case ‘edit_user’:
    case ‘edit_users’:
    $caps[$key] = ‘edit_users’;
    break;
    case ‘delete_user’:
    case ‘delete_users’:
    $caps[$key] = ‘delete_users’;
    break;
    case ‘create_users’:
    $caps[$key] = $cap;
    break;
    }
    }

    return $caps;
    }
    add_filter( ‘map_meta_cap’, ‘mc_admin_users_caps’, 10, 4 );
    remove_all_filters( ‘enable_edit_any_user_configuration’ );
    add_filter( ‘enable_edit_any_user_configuration’, ‘__return_true’);
    ?>
    —————————————————————————————————————–

    But I also noticed a serious security bug.
    By changing the user id in the header when editing a user, a admin can also edit users (example the superadmin) that are no member of his blog. Note the user_id=1 for getting to the superadmin:

    user-edit.php?user_id=1&wp_http_referer=%2Fwp-admin%2Fusers.php

    Dont know WP that good, but is it a solution that someone adds a ‘member of subblog check’ in this?

  21. Good find Morty- that’s definitely a problem. I spent a little time this afternoon on a function to patch.

    function edit_perm_check() { //The mac_admin_users_cap function bleow has a flaw that allows edit access to all users if they change the user ID int he URL - this patches that bug
     	$screen = get_current_screen(); //get the relevant info for this page
    
    	global $current_user; //user doing the editing
    	get_currentuserinfo();
    
    	global $profileuser; //The person whose profile we are accessing
    	if($screen->base === 'user-edit' || $screen->base === 'user-edit-network') {		//make sure we care about this page
    		if (!is_super_admin($current_user->ID)) { //Skip all of this is the user is super admin.
    			//Next, check to see if the user being edited is a member of this blog AND check if the current user is a member of this blog
    			if (!(is_user_member_of_blog($profileuser->ID, get_current_blog_id()) && is_user_member_of_blog($current_user->ID, get_current_blog_id()))) {
    				wp_die( __( 'You do not have permission to edit this user.' ) );
    			} //end permission check
    		} //end Super admin check
    	}
    }
    add_filter( 'admin_head', 'edit_perm_check', 1, 4 );
    

    Basically, when accessing a user edit page, we check to see if both the user doing the editing and the user being edited are both members of the current blog (with an exception for Super Admins). I left the page check in for both ‘user-edit’ and user-edit-network’ because I have a plugin I share between single site and network WP setups, though on a single site, this function won’t do anything.

    This was a quick pass so feel free to suggest edits.

  22. Hi,
    I wrote the code on first post (updated version) in “functions.php” of theme but the admin can change the super admin. Can you help me?

  23. There is on small addition to be made to the mc_edit_permission_check function to make sure the user being edited is not a super_admin on line 42:

    if ( ! ( is_user_member_of_blog( $profileuser->ID, get_current_blog_id() ) && is_user_member_of_blog( $current_user->ID, get_current_blog_id() ) ) || is_super_admin( $profileuser->ID ))
    

    This still doesn’t prevent administrators of a blog deleting a super-administrator from their blog. I can’t see an obvious way of filtering this as the $screen->base for user deletion is set to ‘users’ rather than ‘user-edit’ or user-edit-network’ so it sidesteps the permission check. You may be able to check the $query_vars for user deletions to prevent this I guess. In a way, I don’t see that this is such a problem – super-admins can edit sites without being members of them

  24. Joel Davis says:

    I think line 42 is missing a ‘)’ at the end.

  25. lesteph says:

    I was still finding that the mc_edit_permission_check() function above was allowing an admin level user to edit details of a super admin who was a member of the same site.

    Here’s my amended function which seems to work so far:

    function mc_edit_permission_check() { 
    	global $current_user, $profileuser;
    
    	$screen = get_current_screen();
    
    	get_currentuserinfo();
    
    	if( $screen->base == 'user-edit' || $screen->base == 'user-edit-network' ) { // editing a user profile
    		if (!is_super_admin( $current_user->ID ) && is_super_admin( $profileuser->ID ) ) { // trying to edit a superadmin while less than a superadmin
    			wp_die( __( 'You do not have permission to edit this user.' ) );
    		} elseif ( ! ( is_user_member_of_blog( $profileuser->ID, get_current_blog_id() ) && is_user_member_of_blog( $current_user->ID, get_current_blog_id() ) )) { // editing user and edited user aren't members of the same blog
    			wp_die( __( 'You do not have permission to edit this user.' ) );
    		}
    	}
    }
    
  26. Ajent Oranje says:

    This is an awesome fix! Thank you so much, this solved many headaches dealing with my multisite and all the blog admins whining about not being able to edit user profiles.

    I owe you! =)

  27. Narendra says:

    Thanks for the fix.Works perfect….

  28. Hi,

    Great work here. However I found that super admins were left unable to edit user details. Therefore I amended the code as follows:

    function mc_edit_permission_check() {
    	global $current_user, $profileuser;
    
    	$screen = get_current_screen();
    
    	get_currentuserinfo();
    
    	if(!is_super_admin($current_user->ID)){
    		if( $screen->base == 'user-edit' || $screen->base == 'user-edit-network' ) { // editing a user profile
    			if ( ! is_super_admin( $current_user->ID ) && is_super_admin( $profileuser->ID ) ) { // trying to edit a superadmin while less than a superadmin
    				wp_die( __( 'You do not have permission to edit this user.' ) );
    			} elseif ( ! ( is_user_member_of_blog( $profileuser->ID, get_current_blog_id() ) && is_user_member_of_blog( $current_user->ID, get_current_blog_id() ) )) { // editing user and edited user aren't members of the same blog
    				wp_die( __( 'You do not have permission to edit this user.' ) );
    			}
    		}
    	}
    }
    
  29. Hozefa says:

    In your provided latest code there is a missing closing parenthesis on this line 40. the function closing parenthesis is missing.
    I think you should update it so users who have no idea about code errors will easily copy paste the code to their function.php file.

    Thanks
    Hozefa

  30. Bill Murray says:

    The code works great, but there’s a potential security flaw that I haven’t seen discussed.
    With this code installed, Admin #1 can invite another user on a network to join a site. Let’s say that other user, Admin #2, is already an admin of another site on the network. If Admin #2 accepts Admin #1’s invitation, Admin #1 can take control of Admin #2’s site by changing his password or other user information. If Admin #1 wants to be a subscriber on any other site on the same network, he can’t use the email address associated with his admin account.

    This isn’t a shortcoming of the code here, but I suspect that disabling edit_user in multisite is for the security issue I’m raising here. In other words, not giving site admins the edit_user capability in multisite is by design, not oversight.

  31. Our stop-gap for this:

    When a user attempts to add an existing network user to his blog, he can only choose from users that share his email domain. This works for us because our client base is very corporate. Keeps it “all in the family”, if you will.

    • Bill Murray says:

      Scott – Out of curiosity, how are you limiting an admin to only adding users that share his email domain? It’s an interesting idea, but it wouldn’t really work for us (we’re a soon-to-be public network). We have a number of admins who registered with email domains of gmail, so that would be a pretty big family. Personally, I think a safer solution would be to remove the ability for an admin to add an existing user on the network, but even that is not perfectly safe, because an admin of a site on our network may register on a domain-mapped site, not even realizing that site is also on our network.

      • scottfennell says:

        First, I made a function to check the current_user against 20-25 common, free email service domains (gmail, hotmail, etc…). No one with an email domain on that blacklist may add existing network users.

        The nature of our business model is such that almost all of our clients are going to have corporate email domains.

        Second, I made a function to populate a jQuery autosuggest with all the existing network users that share the email domain with the current_user. That is the only pool from which current_user can add existing network users. And yes, there is server-side validation behind that jQuery autosuggest as well.

  32. r109chaos says:

    Thank you for this, I really wish they’d implement this into core as a Settings option.

    I just threw this into mu-plugins directory as a plugin, shabam, awesome win.

  33. Pingback: 在 wordpress multisite 多網誌模式下讓子網站 administrator 也可以有部分 super admin 的權限 | 凱達米老師的教學檔案

  34. nickm says:

    Wow Brent. Thank you so much. Great stuff.

  35. Shyam Gupta says:

    Just to get follow up comments. Thanks.

  36. Hi to every one, the contents existing at this site are really remarkable for people experience, well, keep up the nice work fellows.

  37. jarelsc says:

    This was exactly what I had been looking for. Thanks so much for this!

  38. Alex says:

    This is awesome! Worked like a charm! Great job!

Comments are closed.