it only selects the users with the meta value and ignores those
without it
That’s because of this part: $query->set( 'meta_key', 'festival_friend' );
, which adds an extra clause to the meta query clauses, i.e. the meta_query
is basically appended with a array( 'key' => 'festival_friend' )
(yes, an array with just a key
item). Try var_dump( $query->meta_query->queries );
and you’d understand what I mean.
So, to fix that, just remove the part I mentioned above. However, you should also set the orderby
to meta_value_num
, if the meta value is numeric.
Also, you should not re-prepare the query, i.e. $query->prepare_query()
should not need to be called and instead, use the pre_get_users
action to filter the query args. So,
-
Remove this line:
$query->prepare_query();
. -
Replace the
add_action( 'pre_user_query', 'handle_column_sorting' )
withadd_action( 'pre_get_users', 'handle_column_sorting' )
.
Your code should now work, however, if you want the users which have the meta festival_friend
to always be on the top in the list, specifically when the sort order is ASC
(i.e. ascending), then try this after making the above changes:
-
Set a key for the clauses in your meta query array, e.g.
has_friend
andno_friend
.$meta_query = array( 'relation' => 'OR', // Clause 1: Find users having the meta. 'has_friend' => array( 'key' => 'festival_friend', 'compare' => 'EXISTS', ), // Clause 2: Find users without the meta. 'no_friend' => array( 'key' => 'festival_friend', 'compare' => 'NOT EXISTS' ), );
-
Add a custom arg named
orderby_friend
, whereby the value is the key of the first clause above.$query->set( 'orderby_friend', 'has_friend' );
-
Use this to modify the
ORDER BY
clause of the user query.add_action( 'pre_user_query', 'admin_custom_orderby_friend' ); function admin_custom_orderby_friend( $query ) { if ( ! is_admin() || ! $query->get( 'orderby_friend' ) ) { return; } $clause_key = $query->get( 'orderby_friend' ); $clauses_arr = $query->meta_query->get_clauses(); if ( empty( $clauses_arr[ $clause_key ] ) ) { return; } global $wpdb; $order = strtoupper( $query->get( 'order' ) ); $order = ( 'DESC' === $order ) ? 'DESC' : 'ASC'; $alias = $clauses_arr[ $clause_key ]['alias']; $query->query_orderby = <<<SQL ORDER BY # 1. Group the users. (CASE WHEN ( $alias.meta_key = 'festival_friend' ) THEN 1 ELSE 2 END) ASC, # 2. Sort them by the meta value. $alias.meta_value $order SQL; }
Notes
-
Plugins and themes can also filter the query, hence, instead of hard-coding it, I used
WP_Meta_Query::get_clauses()
to get the table alias of the target meta query clause. -
I used the
CASE
operator in MySQL / MariaDB to group the users into 2 groups — one for users having the meta, and the other group for users not having that meta. So the 1st group is at the top and will be sorted by the meta value. -
If the meta value is numeric, you can use
$alias.meta_value+0
instead of$alias.meta_value
.