While working on a project that I think will be useful to many freelancers, I came across a little problem. After about an hour of debugging, I found the reason, and I thought it would be good to document it here for posterity.
Before you continue reading, please be aware that this is a “stupid” mistake kind of thing, but that lead me to know more about wordpress, and the way it works.
The problem I run into was that the
current_user_can function of wordpress’ was returning false when I knew the user had the capability I was asking for, since I was logged in as a user that has such capability. Why was I getting false?
A Bit of Context
Let me give you some context to the problem so you can follow better. I am building, against my own good judgement, and another developers, a wordpress based project management system for freelancers and small teams. One developer told me I may be trying to stretch the boundaries of wordpress far too much, but I am a stubborn person, and yes, even I think it is not such a good idea to build it wordpress based, but my hope is that I can cling on to the popularity that wordpress has to make the system successful and popular.
The system works based on user types, that are nothing more than wordpress roles. As you may know, a wordpress role is just a way of specifying different types of users, and roles have predefined capabilities.
In order to add a new role, you just need to call the
add_role function, passing the role name, screen name, and capabilities. The capabilities is nothing but an array listing the capabilities of the role.
As you now know, my problem was that even though the user had a capability, it was being reported as not having it.
The Source of the Bug and the Solution
As it turns out, the source of the bug was a stupid mistake on my end. When you call
add_role and pass the capabilities list, you are supposed to pass it as an associative array:
$caps = array( 'somecap'=>true, 'someother'=>true );
But I just passed a regular array in. This caused the problem.
The solution is simply to re-generate the roles passing in an associative array.
Pretty simple, yet it took me about an hour to realize where my mistake was. If you want to know what I learned in the process, read on.
The Lesson Learned
The first thing I learned was that
current_user_can simply runs the
has_cap method of the current user’s, which in turn, among other things, returns true if all the capabilities specified are within the list of capabilities that the user has.
All the capabilities that the user has are set in a property of the user object called allcaps, and the way wordpress checks if a user has a certain capability is by passing that capability as an index of the allcaps property to see if it is empty or not. If it is empty, then the user does not have such capability, and the has_cap method returns false.
This is the relevant code, taken from wordpress 3.5.1
wp-include/capabilities.php line 939
if ( empty( $capabilities[ $cap ] ) ) return fasle
Notice that here allcaps has been passed through a filter and the returning value saved in $capabilities.
With that knowledge, I decided to look at the content of all caps, which revealed the problem:
//Not the actual output, but similar Aray( =>'cap', =>'anothercap', [user_role]=>1 [exist]=>1 );
As you can see, cap, and anothercap are both set as the values of index 0, and 1 respectively, not as the indexes like user_role, and exist are. This was a problem since wordpress was looking for
$capabilities['cap'], and didn’t find it. But why is it that they are not being set as indexes, but rather as values?
In order to find out, I had to do a bit more of code digging.
The Mysterious allcaps
All I knew by now was that the problem was the capabilities not being set as indexes of the capabilities list, but rather as values of numerical indexes, which made it impossible for wordpress to find them in the list of capabilities. However, I did not know why. Upon inspecting the admin user and the user with the custom role, I noticed that the capabilities of the admin user where being set correctly, that is, as indexes, not as values. I decided to check how the allcaps array was being set.
The allcaps attribute of the user object is set by the
get_role_caps method defined on line 734 of the capabilities file on wordpress 3.5.1, but there isn’t really much going on there. The function simply merges the capabilities of the role, and the ones of the user, but further investigation of the difference between the capabilities of the role of the admin user, and those of the user with the custom role, revealed that they were different. The ones of the admin were being set correctly, that is, as indexes, but the ones of the custom role were not. Why was that?
In order to know why that was happening, I decided to investigate how the capabilities of each role was set.
The user object has a property called roles, which lists the roles of a user, but only as a string identifying the role. If you want to get the actual role, you need to call the
get_role method of the wp_roles object. The wp_roles object is an instance of the WP_Roles class defined in the same file. The
get_role method returns an instance of the WP_Role class, which among other properties has a property called capabilities, which lists all the capabilities of the role, and which was being set wrong in my custom role.
Define the Role Object’s Capabilities Property
If you look at the source of the WP_Role class, you will see that the capabilities are passed to it on instantiation. This means that inspecting the WP_Role class won’t help us that much. Instead we need to find where the roles are instantiated to see how the capabilities that are passed to them are constructed.
Luckily by now we know that the roles are returned by the
get_role method of the wp_roles object. This means that we should start there if we want to know how they are instantiated, and eventually find out how the capabilities array is being constructed, and why it is not being constructed right in this case.
Line 244 of the capabilities file in wordpress 3.5.1 marks the beginning of the
get_role method. This method doesn’t do much. It simply returns the role object if it has it, or null if it doesn’t. The role objects are stored in the
role_objects property of the wp_roles object. This property is set when the object is created. If you look at the
_init method of the wp_roles object, you will see that the roles are obtained by getting and option from the database. This option is simply a serialized array containing all the role definitions and their capabilities. After unserializing the array, I noticed that all the default roles had their capabilities defined as indexes of the capabilities array, with their values set to true (actually to 1, but the serialized array marks that 1 as a boolean value) whereas my custom roles had their capabilities defined as values of numerical indexes in the capabilities array. That is when I realized I must have had done something wrong when I created my custom roles.
I decided to check the docs for the add_role function, and I noticed the proper way of defining roles, and their capabilities.
As you can see, I made a small, and stupid mistake when I created my custom roles, but it led me to a better comprehension of the platform that I am using. So, I encourage you to learn from your mistakes so that you can become a better programmer every day.