4

I'm using WP_User_Query to get a list of users from my database. I'm showing specific users only via use of the include parameter. For example:

$args = array(
    'include' => array( 1, 3, 5, 99, 61, 24, 12 ),
    'count_total' => true,
    'search' => sanitize_text_field( $_GET['phrase'] )
);

$query = new WP_User_Query( $args );
foreach( $query->results as $user ) {
    echo $user->ID . '<br />';
}

Results are currently ordered by user_login. My aim is to order results by the user IDs used in the include parameter. So for example, if 5 users are to be returned (let's say user IDs 1, 5, 99, 61 and 12), then I need the foreach loop to output results in this order:

1
5
99
61
12

Please note I've tried using usort() after the query has run (see here), but now I need the actual query results to be ordered. I don't wish to order the query results after the query has run.

Ref: http://codex.wordpress.org/Class_Reference/WP_User_Query

2 Answers 2

6
+100

Updated:

The WP_User_Query class, in WordPress 4.1+, supports it with :

'orderby' => 'include'

Since WordPress 4.7 there's also support for:

'orderby' => 'login__in'

and

'orderby' => 'nicename__in'

So we no longer need to implement it through a filter, like we did here below.

Previous Answer:

I wonder if this works for you:

add_action( 'pre_user_query', 'wpse_order_by_include_values' )
$query = new WP_User_Query( $args );
remove_action( 'pre_user_query', 'wpse_order_by_include_values' )

where

function wpse_order_by_include_values( $q )
{
    if( isset( $q->query_vars['include'] ) )
    {
        $values = join( ',', $q->query_vars['include'] );
        $q->query_orderby = 'ORDER BY FIELD( ID,' . $values . ' )';         
    }
}

using the FIELD() function in MySQL. There are some usage examples here in the comments of the MySQL doc page.

Update:

To make it more flexible we could use this mini plugin:

/**
 * Support the 'orderby' => '_include' parameter for the WP_User_Query 
 * to order by the values in the include array.
 * 
 * @see http://wordpress.stackexchange.com/a/167095/26350
 */

add_action( 'pre_user_query', function( $q ) {

    if( 
        isset( $q->query_vars['orderby'] )
        && '_include' === $q->query_vars['orderby']
        && isset( $q->query_vars['include'] ) 
        && count( $q->query_vars['include'] ) > 0 
    )
    {
        global $wpdb;
        $users_in = join( ',', array_map( 'absint', $q->query_vars['include'] ) );
        $q->query_orderby = 'ORDER BY FIELD( {$wpdb->users}.ID,' . $users_in . ' )';
    }

});

where we use the underscore prefix _include, just in case the core will use the include value in the future. Update: I've adjusted this so now it's more or less similar to the post__in ordering case of the WP_Query class.

Now you can simply use:

$args = array(
    'include'     => array( 1, 3, 5, 99, 61, 24, 12 ),
    'count_total' => true,
    'search'      => sanitize_text_field( $_GET['phrase'] )
    'orderby'     => '_include',                               //<-- our new value
);

$query = new WP_User_Query( $args );

to order by the include array.

13
  • Thanks @birgire, the approach looks great (I never thought to filter pre_user_query) but it doesn't produce the results I expect. $values = join( ',', $q->query_vars['include'] ) produces a comma-sep list of IDs which is good. So, I'm assuming the problem is with 'ORDER BY FIELD( ID,' . $values . ' )' Commented Oct 31, 2014 at 16:38
  • ok great, I'm going off-line so I will check this better when I get back.
    – birgire
    Commented Oct 31, 2014 at 16:43
  • OK no problem! any help is appreciated :) Commented Oct 31, 2014 at 16:45
  • 1
    @RyanLoremIpsum Good suggestion but it looks like the order then becomes unpredictable and it's recommended to always specify the order. Check for example these threads here, here and here
    – birgire
    Commented Nov 1, 2014 at 10:02
  • 2
    You're correct about the post__in ordering, I checked and it's also using the FIELD function to order by values. I've updated the answer so it's now handled in a similar way as in the WP_Query class.
    – birgire
    Commented Nov 1, 2014 at 11:52
1

One alternative offered to the above answer is to add a column (meta key) with meta values which can easily be sorted with 'orderby' => 'meta_value' in the args.

Here is a function I wrote to add the meta values to each user. I'm including it here because $my_order_ids = array( 3, 4, 6, 2, 5 ); determines the ascending order users should be sorted by in your query.

function my_order_column() {
// Defines the user IDs to be updated.
// Use the desired order when you define this.
$my_order_ids = array( 3, 4, 6, 2, 5 );

    // Runs only when $my_order_ids has changed by comparing  
    // it to _transient_my_order_column. 
    if (false === get_transient('my_order_column') ||
        get_transient('my_order_column') !== $my_order_ids) {

        // Updates _transient_my_order_column
        set_transient('my_order_column', $my_order_ids);

        // The first user
        $order_id = 1;
        // Add an integer value for each user 
        foreach ($my_order_ids as $user_id) {

        // Sets/updates the value of my_user_order
        update_user_meta( $user_id, 'my_user_order', $order_id );

        // Confirms that the new value of my_user_order matches the 
        // current value of $order_id
        if ( get_user_meta($user_id,  'my_user_order', true ) != $order_id )
            wp_die('An error occurred');

            $order_id++;
        }
    ?><div class="updated">
        <p><?php echo var_dump($my_order_ids) . '<br />Updated!'; ?></p>
    </div><?php
    }
}

add_action( 'admin_notices', 'my_order_column' );

This uses a foreach loop to add a new integer value to each user's my_user_order key, incrementing by 1 each time. All you need to do is define $my_order_ids in the order you want them to be sorted later.

The transient is just there so that the updates will only run when the values of $my_order_ids has changed (in other words, only update users once). You may want to comment out add_action( 'admin_notices', 'my_order_column' ); to prevent it from running unexpectedly if the transient is lost.

Now that our new custom meta key and meta values are set, we should have no problem getting the query to order them this way.

function my_user_query() {

$args = array(
    'orderby' => 'meta_value',     /***************************************/
    'order' => 'ASC',              /***Added orderby, order and meta_key***/
    'meta_key' => 'my_user_order', /***************************************/
    // Note: 'include' does not affect how output will be ordered. 
    // array( 6, 5, 3, 2, 4 ) would still be ordered by meta_value.
    'include' => array( 3, 4, 6, 2, 5 ),
    'count_total' => true,
    'search' => sanitize_text_field( $_GET['phrase'] )
);

$query = new WP_User_Query( $args );

    ?>
    <div class="updated">
        <p><?php foreach( $query->results as $user ) {
        echo $user->ID . '<br />'; } ?></p>
    </div>
    <?php  wp_reset_query();
}

add_action( 'admin_notices', 'my_user_query' );

In the above example, the following output is displayed as an admin notice:

3
4
6
2
5

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.