It is possible, yes, but for this specific use-case I think you’ll need to do a little more legwork than usual.
Add artist_count
meta to building
posts
In principal we’d be trying to tell the WP_Query
responsible for retrieving building
s to “orderby number of artists,” but since many artist
s are associated with a single building
by way of an artist
‘s user meta only, there’s no direct way to perform that ordering through a WP_Query
(short of modifying the actual SQL).
I think the easiest way to mitigate this situation would be to add the artist count as post meta to each building and update that count as necessary. We can patch that in to your existing functionality regardless of how you’re currently setting roles and assigning buildings by hooking in to role and user meta changes:
/**
* Update buildings' artist_count meta and optionally a user's building meta.
*
* @param int $user_id The ID of the user who's being assigned a new building.
* @param int|null $new_building The ID of the user's new building, or `null` if the assignment is being removed.
* @param int|null $old_building The ID of the user's previous building, or `null` if there wasn't one.
* @param boolean $update_user Whether or not to update the user's `building` meta.
**/
function wpse391043_assign_artist_building( $user_id, $new_building = null, $old_building = null, $update_user = false ) {
if( empty( $new_building ) && empty( $old_building ) )
return;
// Subtract one from the previous building's artist count, if applicable.
if( ! empty( $old_building ) ) {
$building_count = get_post_meta( $old_building, 'artist_count', true );
update_post_meta(
$old_building,
'artist_count',
empty( $building_count ) ? 0 : $building_count - 1
);
}
// Add one to the new building's artist count, and set the user's new building meta if applicable.
if( ! empty( $new_building ) ) {
$building_count = get_post_meta( $new_building, 'artist_count', true );
update_post_meta(
$new_building,
'artist_count',
empty( $building_count ) ? 1 : $building_count + 1
);
if( $update_user )
update_user_meta( $user_id, 'building', $new_building );
}
else if( $update_user ) {
// If there's no new building ID, remove the user meta.
delete_user_meta( $user_id, 'building' );
}
}
/**
* When a user has building meta set with no previous value, increment the building's
* artist_count meta.
**/
function wpse391043_add_user_building_meta( $user_id, $meta_key, $building_id ) {
if( $meta_key !== 'building' )
return;
wpse391043_assign_artist_building( $user_id, $building_id );
}
add_action( 'add_user_meta', 'wpse391043_add_user_building_meta', 10, 3 );
/**
* When a user's building meta changes, update both building's artist_count meta.
**/
function wpse391043_update_user_building_meta( $meta_id, $user_id, $meta_key, $building_id ) {
if( $meta_key !== 'building' )
return;
$old_building = get_user_meta( $meta_id, 'building', true );
wpse391043_assign_artist_building( $user_id, $building_id, $old_building );
}
add_action( 'update_user_meta', 'wpse391043_update_user_building_meta', 10, 4 );
/**
* Remove a user's building meta and update the building's artist_count meta if a user
* looses the artists_role role.
*/
function wpse391043_update_user_role( $user_id, $role, $old_roles ) {
if( $role === 'artists_role' || current( $old_roles ) !== 'artists_role' )
return;
$old_building = get_user_meta( $user_id, 'building', true );
wpse391043_assign_artist_building( $user_id, null, $old_building, true );
}
add_action( 'set_user_role', 'wpse391043_update_user_role', 10, 3 );
With that in place, buildings’ artist_count
meta should now stay up-to-date with whatever changes to users’ building
meta are taking place; and rest_get_users_per_building()
gets a nice performance boost in that now it only need look up one piece of post meta:
function rest_get_users_per_building( $building ) {
$artist_count = get_post_meta( $building['id'], 'artist_count', true );
return empty( $artist_count ) ? 0 : $artist_count;
}
This will also benefit the actual orderby
operation on the whole, as it does not need to calculate artist counts for every building each time such a request is made.
Register the artists
orderby
REST parameter
Now we can use a rest_{post type}_collection_params
filter in order to expose a new artists
orderby
param:
/**
* Add `artists` as an orderby parameter for the building REST controller.
**/
function wpse391043_rest_building_params( $params ) {
$params['orderby']['enum'][] = 'artists';
return $params;
}
add_filter( 'rest_building_collection_params', 'wpse391043_rest_building_params' );
Modify the REST WP_Query
arguments to account for orderby=artists
And a rest_{post type}_query
filter in order to factor that parameter in to the building post type query:
/**
* If `orderby` is `artists`, modify the query to order by `artist_count` meta.
**/
function wpse391043_rest_building_query( $args, $request ) {
$orderby = $request->get_param( 'orderby' );
if( empty( $orderby ) || $orderby !== 'artists' )
return $args;
$args['orderby'] = 'meta_value_num';
$args['meta_key'] = 'artist_count';
return $args;
}
add_filter( 'rest_building_collection_query', 'wpse391043_rest_building_query', 10, 2 );
After which, you should be able to order buildings by their artist_count
meta using the new parameter:
/wp-json/wp/v2/buildings?orderby=artists
Synchronize current artist counts to the new meta
You might need to get the new artist_count
meta set up on each building to reflect the current assignments, which could be done with a little loop which can you run once:
function wpse391043_sync_building_artist_counts() {
$building_counts = []; // A mapping of building IDs to artist counts.
$assigned_artist_ids = get_users(
[
'role' => 'artists_role',
'meta_key' => 'building',
'meta_compare' => 'EXISTS',
'number' => -1,
'fields ' => 'ID',
]
);
foreach( $assigned_artist_ids as $user_id ) {
$building_id = get_user_meta( $user_id, 'building' );
if( empty( $building_id ) )
continue;
if( ! isset( $building_counts[ $building_id ] ) )
$building_counts[ $building_id ] = 0;
$building_counts[ $building_id ]++;
}
foreach( $building_counts as $building_id => $artist_count )
update_post_meta( $building_id, 'artist_count', $artist_count );
}