Make WordPress Core

Opened 5 years ago

Last modified 4 months ago

#16841 new defect (bug)

Manually created user roles not showing in author dropdown regardless of assigned capabilities

Reported by: 10sexyapples Owned by:
Milestone: Future Release Priority: normal
Severity: normal Version: 3.1
Component: Role/Capability Keywords: needs-patch needs-unit-tests
Focuses: Cc:

Description (last modified by ocean90)

I posted the below on wp-testers hoping to verify or gain experiences from others in order to confirm this prior to reporting it as a bug, but, haven't received any further input, and it's just becoming more evident to me that perhaps it should be posted here, so, I hope I'm not creating a false report here. Please excuse me if I am.

I'm posting this as a link so that it will translate more clearly rather than just cluttering up this message with more text than necessary.

Upon further investigation I have also come across the following: #16451

I'm not an expert at debugging this sort of thing, but, I have attempted all within my knowledge thus far, and in looking at all of the returned objects and entries, the only difference I can spot between how the default user roles ( which show up properly in the dropdown ) and the custom created roles ( which do not ) are being returned is that the default user roles, editor for instance, correctly show the deprecated level_7 entry, and the custom user role ( with more assigned privileges than the editor ) does not show user levels in the capabilities array at all, and shows level_0 in the object.

This is leading me to think that perhaps there is something happening with those user levels in creating the author dropdown on the post editor that is blocking the custom user roles from showing up as expected.

When running the following code:

echo '<br /><br /><h3>Roles</h3>';
	foreach ( $wp_roles->role_names as $role => $name ) :
		echo '<br /> <br />';
		echo '<pre> Role displayed in Admin as ' . $name ;
		echo  '     Database entry: '  . $role . '</pre>';

		echo '<h5> Capabilities assigned to the role of ' . $name. '</h5>';
		// print_r( $caps);
		echo '<pre>';
		$rolename = get_role($role);
		$caps = $rolename->capabilities;
			foreach ($caps as $capability => $value):
				echo  $capability . ' '.  $value . "\n" ;
		echo '</pre>';

the default wp roles return the following capability value pairings

delete_published_posts 1

roles created with Justin Tadlock's members plugin return the following

delete_published_posts delete_published_posts

roles created manually in functions.php utilizing

add_role( 'test_role', 'Test Role' );

//saved and then removed before running the code below ...

$role = get_role('test_role');


return the following ...

delete_published_posts 1

but lack the user levels as mentioned above.

Let me know if there is any further info I can provide to help sort this out~

Change History (30)

#1 @10sexyapples
5 years ago

ahA! okay, I believe the use of user_level here is the culprit:

If user level is going to stick around in this way then shouldn't add_cap perhaps take this into consideration?

Last edited 5 years ago by scribu (previous) (diff)

#2 @scribu
5 years ago

  • Component changed from General to Role/Capability

The workaround would just be to add a 'level_1' cap to your role.

It's PITA, considering how user levels have been deprecated so long ago, but there you go.

#3 @scribu
5 years ago

PS: Instead of posting the full ticket url, just write #123, where 123 is the ticket number.

#4 @10sexyapples
5 years ago

Hmm. I actually tried adding the level caps, but, didn't see any change in the array after.
I was using add_cap.
Perhaps I saved, and then removed my code before it got picked up? I'm going to try it again.
And yes, definitely a PITA ... I saw your comments in having to put it there in the first place ...
As usual, thanks for the tips on proper posting etiquette ;-)
# 123 it is.

Last edited 5 years ago by 10sexyapples (previous) (diff)

#5 @scribu
5 years ago

Related: #16714

#6 @magicroundabout
4 years ago

Hello, this is my first post on Trac, so I hope it's useful.

I spotted that new users in roles added with add_role don't appear in the author dropdown list. This seems to have been introduced in #15871 when code was added to WP_User_Query->prepare_query to only select users with an old-school user-level of > 0 if 'who' => 'authors' was passed in the args.

I don't quite understand the discussion on the logic of this (in #15871). It seems to me that user levels are deprecated, so shouldn't this select users with the edit_posts capability?

Alternatively (additionally?), perhaps we could have a filter hook in wp_dropdown_users that lets us modify the arguments passed to get_users()?

#7 @scribu
4 years ago

We still use user levels there because we don't have a good way of getting the same list via capabilties. It's a mess.

#8 @lgladdy
4 years ago

And additional (imo) bug stops add_cap('level_1') from fixing this for existing users. I've posted a trac ticket for it #19747

Last edited 4 years ago by lgladdy (previous) (diff)

#9 @markoheijnen
4 years ago

  • Cc marko@… added

#10 @pauldewouters
4 years ago

  • Cc pauldewouters added

#11 @scottconnerly
3 years ago

  • Cc scott@… added

#12 @BandonRandon
3 years ago

  • Cc BandonRandon added

#13 @viniciusrtf
2 years ago

While trying to apply level_1 to my custom role, I didn't remember the custom role name (it is different from display name and it doesn't appear anywhere in backend), so I figured it out with this dirty SQL query:

SELECT meta_value FROM wp_usermeta WHERE user_id = user-id-here;
Last edited 2 years ago by viniciusrtf (previous) (diff)

#14 @jchristopher
2 years ago

  • Cc jonathan@… added

#15 @sterlo
19 months ago

To clarify, this is all tied into this particular section:


$qv['meta_key'] = $wpdb->get_blog_prefix( $blog_id ) . 'user_level';
$qv['meta_value'] = 0;
$qv['meta_compare'] = '!=';

This will end up meaning "give me all users that have a wp_user_level that is not equal to 0".

If you go into MySQL and run something along the lines of this:

[mysql> select * from wp_usermeta WHERE user_id = 19;
| umeta_id | user_id | meta_key                     | meta_value                                            |
|      225 |      19 | first_name                   | John                                                 |
|      226 |      19 | last_name                    | Doe                                              |
|      227 |      19 | nickname                     | @jdoe                                            |
|      228 |      19 | description                  |                                                       |
|      229 |      19 | rich_editing                 | true                                                  |
|      230 |      19 | comment_shortcuts            | false                                                 |
|      231 |      19 | admin_color                  | fresh                                                 |
|      232 |      19 | use_ssl                      | 0                                                     |
|      233 |      19 | show_admin_bar_front         | true                                                  |
|      234 |      19 | wp_capabilities              | a:1:{s:15:"content-manager";b:1;}                     |
|      235 |      19 | wp_user_level                | 10                                                    |
|      236 |      19 | dismissed_wp_pointers        | wp350_media,wp360_revisions,wp360_locks,wp390_widgets |
|      429 |      19 | wpseo_title                  |                                                       |
|      430 |      19 | wpseo_metadesc               |                                                       |
|      431 |      19 | wpseo_metakey                |                                                       |
|      432 |      19 | _yoast_wpseo_profile_updated | 1400732591                                            |
|      433 |      19 | googleplus                   |                                                       |
|      434 |      19 | twitter                      |                                                       |
|      435 |      19 | facebook                     |                                                       |
19 rows in set (0.00 sec)]

The line you need to pay attention to is:

|      235 |      19 | wp_user_level                | 10                                                    |

When you create your custom role, you need to add something that is > 0 in terms of wp_user_level.

Adjusting the role to increase the user_level is the end goal. To accomplish that you can use the $role->add_cap('level_1'); method explained by others - as long as it is not level_0.

It is important to note that you need to edit the user and re-save them with the add_cap change in place. This will update the database so that wp_user_level is no longer 0.

#16 @DrewAPicture
18 months ago

  • Summary changed from manually created user roles not showing in author dropdown irregardless of assigned capabilities to Manually created user roles not showing in author dropdown regardless of assigned capabilities

#17 @coolmann
15 months ago

Any chance to see this fixed in 4.1?

#18 @ckpicker
14 months ago

Would love to see this fixed in a future version.

#19 @juiceboxint
10 months ago

+1. 99.8% of users won't ever come across this, but it's a big deal to those of us who use WP as a serious CMS.

#20 follow-up: @lgladdy
10 months ago

The add_cap('level_1'); workaround will also apply to any existing users with the role you're adding to from r31190 / WordPress 4.2 which at least makes the workaround a little easier.

[Edit: Update typo - 4.2 not 3.2]

Last edited 10 months ago by lgladdy (previous) (diff)

#21 in reply to: ↑ 20 ; follow-up: @juiceboxint
10 months ago

Replying to lgladdy:

The add_cap('level_1'); workaround will also apply to any existing users with the role you're adding to from r31190 / WordPress 3.2 which at least makes the workaround a little easier.

Yep, the only thing is that you still have to change each user's role to something else and then back in order to update the actual user_level field in the database, if they were assigned that role before you added the level_1 cap. (tested a few minute ago on 4.1) So the workaround itself is pretty obscure, and the caveat for the workaround is even more obscure.

#22 in reply to: ↑ 21 @lgladdy
10 months ago

Replying to juiceboxint:

Yep, the only thing is that you still have to change each user's role to something else and then back in order to update the actual user_level field in the database, if they were assigned that role before you added the level_1 cap. (tested a few minute ago on 4.1) So the workaround itself is pretty obscure, and the caveat for the workaround is even more obscure.

That's what has been fixed and will be released in 4.2 - You won't need to do that anymore - add_cap will update any existing users :)

[Edit: Update typo - 4.2 not 3.2]

Last edited 10 months ago by lgladdy (previous) (diff)

This ticket was mentioned in Slack in #core by lgladdy. View the logs.

10 months ago

#24 @lgladdy
10 months ago

I'm going to write a patch for this one, with the hope of it being included in 4.2

As far as I see it, we should test if they have a level_* greater than 0, or if they have the edit_posts capability.

Is there any other roles that should result in a user being included in the dropdown?

#25 @juiceboxint
10 months ago

Awesome, thanks for tackling it! That would be a good quick fix - any built-in role that has an inherent user level > 1 will also have edit_posts, and authorship has never worked for custom roles prior to your patch (without the arcane workaround anyway) so this seems like a safe bet to me.

A more complete solution (maybe step 2?) would be to consider the post type context as well, though: if the user has the equivalent edit_ cap for a custom post type, they appear in the authors dropdown for those posts. This would allow a user to potentially appear in the authors list for a Post but not a Page, or one CPT but not another, etc.

Again, shouldn't have any BC issues with this because it's never worked before your patch makes it into core. The only thing I wonder about is whether the quick fix will actually create its own BC issues for the more complete fix if that one is done later on. For instance, after your patch, a user with edit_posts but not edit_cpt could be set as the author of a custom post type cpt, and later on when the contextual-author fix is in core, that user is no longer a valid or selectable author of cpt. This will be an extremely unlikely scenario, but it's worth thinking about whether it's worth skipping straight to the complete fix.

#26 @lgladdy
10 months ago

  • Keywords 2nd-opinion added

This is going to be harder than I originally thought.

As earlier in the post, the code at fault here is at https://core.trac.wordpress.org/browser/trunk/src/wp-includes/user.php#L714

If you provide a 'who'=>'authors' key to a get_users command it will return all users with a level greater than 0 using a meta query.

There is no nice way (that I can see) within WP_User_Query::prepare_query for us to do anything smart with capabilities, so i'm thinking there maybe 4 ways to try and fix this.

1) Not use the 'who'=>'authors' key at all, and do a get_users() query to get all the users and then loop through to check their capability. This probably has the potential to be seriously slow on sites with many users?

2) Loop over all the roles, figure out which have edit_posts cap on them and then do a get_users/WP_User_Query query, either using the role option (it currently only supports one role per query, so maybe that could be expanded to support an array of roles? or multiple queries and merge).

3) Add a new capability option into WP_User_Query. This would basically be number 2 anyway - we'd just need to get the wp_user_roles option to figure out the list of roles, and then run that as a query similar to a multiple role search, but someone more familiar with core coding standards, the WP_User_Query class or the Role/Capability component maintainer(s) should be able to say if this is a good idea.

4) Do something inside WP_Roles::add_cap or, more likely, WP_User::update_user_level_from_caps - We could also check if the user has edit_posts and if so, set their default level_* to 1, rather than 0. This seems a bit evil because it's a workaround to legacy/deprecated level_* syntax, and we probably don't want to introduce new code that touches that if the end goal is it not existing at all?

I think 3 actually is a the best idea, depending on how acceptable it would be to use get_option inside WP_User_Query. It would solve the problem, and give developers more functionality.


#27 @boonebgorges
10 months ago

  • Keywords needs-patch needs-unit-tests added; 2nd-opinion removed
  • Milestone changed from Awaiting Review to Future Release

As scribu said earlier, this is a mess. @lgladdy, thanks for digging into it.

Something like 2 or 3 is probably worth exploring, at least for the short term. As you suggest, the only capability-related data we can easily query on a per-user basis is the deprecated level (which is the original problem here in the ticket) and wpX_capabilities, where wpX_ is the current blog prefix. The latter piece of usermeta stores a serialized array of a user's roles for that blog, and the 'role' param of WP_User_Query translates to a LIKE query against this data. Doing this multiple times is going to scale pretty poorly, but it's not a great deal worse performance-wise than what's already there, and it will allow us to fix the various role-related bugs raised in this ticket.

This ticket was mentioned in Slack in #core by boone. View the logs.

9 months ago

#29 @ocean90
4 months ago

  • Description modified (diff)

#30 @ocean90
4 months ago

#24985 was marked as a duplicate.

Note: See TracTickets for help on using tickets.