0

So I am building a system with 3 different custom roles - Admins, Providers, and Users.

Providers have access to users.php, or the list users page. However, they should not be able to see/edit/access Admins, or other Providers. They should be able to see/edit themselves obviously, and see/edit all Users.

My question is how can I prevent Providers from seeing the "ALL" or "Admins" tab/link on this page? How can I ensure they only see themselves and Users?

Image included for clarity of what I am trying to accomplish: enter image description here

I have tried a few methods in the pre_user_query hook. Here is a simplified version of my code.

Read the comments!


// alter what shows on the list users page by role
function abc_pre_user_query($user_search) {

    // first off, I can't even get users because 'get_users' causes this error:
    // Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 262144 bytes)
    /*
    $args = array(
            'role'   => 'abc_admin',
            'fields' => 'all',
        );
    $users = get_users( $args );
    */

    // So for testing, I just manually create a list of user IDs to exclude
    // this would be IDs of all abc_admins and providers (besides the current provider)
    $users = array('18', '19', '38');


    /************************
    METHOD 1 - SQL
    ************************/

    $user_search->query_where = str_replace('WHERE 1=1', 
        "WHERE 1=1 AND 
        (wp_usermeta.meta_key = 'wp_capabilities' AND wp_usermeta.meta_value NOT LIKE '%abc_admin%') AND
        ((wp_usermeta.meta_key = 'wp_capabilities' AND wp_usermeta.meta_value NOT LIKE '%provider%') OR wp_users.ID = " . $current_uid . ")", 
        $user_search->query_where);

    // This works! But when the user is on "ALL", it shows zero users and shows this error:
    // [Unknown column 'wp_usermeta.meta_key' in 'where clause']
    // So I was thinking of forcing user.php to redirect to users.php?role=provider, but this seems hacky

    // maybe I am on the right path here and am missing something?

    /************************
    METHOD 2 - Query vars
    ************************/

    // Here I try various ways to alter the $user_search WP User Query Object
    // Either change 'exclude' to my $users array, or change 'role' to only grab the user role
    // ... to no avail

    //$user_search::set('exclude', $users);         // Deprecated: Non-static method WP_User_Query::set() should not be called statically
    //$user_search::set('role', 'user');            // Deprecated: Non-static method WP_User_Query::set() should not be called statically
    //$user_search->query_vars['exclude'] = $users; // does nothing
    //$user_search->query_vars['role'] = 'user';    // does nothing

    // I can print $user_search here, and can confirm the lines that say "does nothing"
    // actually change in query_vars, but on the users page NOTHING CHANGES

    // Maybe using ::set is the correct way and I am calling it incorrectly?

}
add_action('pre_user_query', 'abc_pre_user_query');

So as you can see, I tried 2 main methods to get this to work. I can't imagine this is the first time somebody has had to do something like this.

I am using User Role Editor by the way. However there is no ultra specific capability like list_abc_admins or list_other_providers or anything. There is only list_users, which the provider has, so that role can view ALL accounts on the site, which is exactly my problem.

Maybe the preferred approach has something to do with creating more highly specific capabilities and implementing them somehow? I feel like altering the user query is way more efficient, but I guess I could be wrong.

I can post the $user_search variable or whatever else if needed.

Thanks in advance!

1 Answer 1

2

Method 1, SQL

Notes about your SQL

[Unknown column 'wp_usermeta.meta_key' in 'where clause']

This is solved by adding this to the JOIN part of the query:

JOIN wp_usermeta ON ( wp_usermeta.user_id = wp_users.ID )

You could check the value of the JOINS and, if false === strpos( 'wp_usermeta', $joins ), adding it yourself.

When getting all users, my guess is that WordPress doesn't join to wp_usermeta since it doesn't need to deal with capabilities.


Method 2, altering the query object

Maybe using ::set is the correct way and I am calling it incorrectly?

Bingo! You're calling ::set statically, when it should be called as a method of the object:

$user_search->set('exclude', $users);
$user_search->set('role', 'user');

In PHP, :: denotes a static method call, or functions that are part of the class definition, while -> is used to operate on methods that are part of the class instance. You often see this notation used to talk about method names SomeClass::Method(), which can be confusing - it's not necessarily saying that you use ::Method to call the method, but rather it's visually trying to represent it as a method of that class. Typically, on instantiation, you'd do this:

$obj = new SomeClass();
$obj->Method();

Unless the method is declared static, in which case

$obj::Method();

Would be correct.


Not good answer

You can do this with the load-(page) hook and CSS. Unfortunately, there doesn't seem to be a good way to do this with filters that I've found yet.

To do this with CSS, you can do something like this:

add_action( 'load-users.php', function(){

    if ( can_see_editors() ) {
        return;
    }

    echo <<<HTML
<style>
#wpbody ul.subsubsub li.editor {
    display: none;
}
</style>
HTML;
});

Replace the first method in the if with your logic for users who can see the TDF Admin. If they can't, it continues down to render CSS to the page hiding the element - in your case, highlight the "TDF Admin" filter and inspect it to find it's CSS class name (as opposed to li.editor).

This fix is pretty weak - anyone can still inspect the page and reveal the link, and nothing is preventing them from altering the URL to point to the URL filtering TDF administrators.

3
  • Thanks for the help with method 2! However, even after calling set correctly, nothing changes on the users list page. The provider is still able to see all users even with exclude and/or role set. Commented Jul 6, 2018 at 16:11
  • I am currently trying to get your advice on method 1 to work with SQL. I am having trouble figuring out exactly what query var to add that JOIN statement too, but I will keep you updated. Commented Jul 6, 2018 at 16:12
  • Got it! if ($user_search->query_from == 'FROM wp_users') { $user_search->query_from = 'FROM wp_users INNER JOIN wp_usermeta ON ( wp_users.ID = wp_usermeta.user_id )'; } Commented Jul 6, 2018 at 16:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.