Want to show featured posts first in FacetWP results? Here’s how to do it.
The requirement
A client, using FacetWP to show posts filtered by a few Advanced Custom Fields, wanted to introduce featured posts. These posts need to be show first, and the rest of the results should be shown random.
Since FacetWP does an AJAX request every time a page is requested, or a facet is changed, we need to do something to prevent every request to show random posts, since that could show posts on multiple result pages.
Thinking about a solution
When a client drops an idea, actually any idea, my brain starts to think on how to implement this. With my experience I know some filters of my favorite plugins, but not all. So I draft a solution first.
Posts need to have an extra field, where the client can indicate a post is featured. Since the posts already have some ACF fields, I will add the featured field to it.
Next up is filtering the query to feed FacetWP the $post_ids
in the right order, with featured posts first, followed by the other posts in a random order. I found out the facetwp_filtered_post_ids
filter can be used for that.
Coding the solution
Here’s my favorite part of implementing a clients request. The actual coding. I start with writing the facetwp_filtered_post_ids
filter function. This function has to return all post ids, in the right order, with the featured post on the front of the array.
The query arguments are self explanatory, except maybe for the meta_compare
argument. LIKE
is used here, because ACF stores the value of the uitgelicht
(Featured in Dutch) field in a serialized way. Be careful when using this, since this might give you unexpected results. In this case, the uitgelicht
field can only have a value of 'ja'
(yes), or ''
(empty), so we can safely use this.
The we launch the query and get the results on line 16. NOw we need to remove the featured posts from the $posts_ids
variable that is passed to this function through the filter. So we walk through the $post_ids
and put the featured post id in the $matches
array, and remove the post id from the $post_ids
array. This gives us two arrays, $matches
which holds the featured posts, and $post_ids
which holds all other posts, without the featured posts.
Now we need to randomize the posts in $post_ids
. As stated before, we don’t want to randomize on every request. With the client I discussed this, and came to a solution where results would be randomized every hour.
First I used the shuffle()
function to randomize the $post_ids
array. Sadly, this did not give me an option to keep the random order for a certain amount of time. So, here comes array_multisort()
.
We first set a duration in minutes, get the current time in minutes and subtract the duration from this time, to set the seed. Then we use the mt_srand()
function to create seed. Next is setting the order and use that to re-order the $post_ids
array with array_multisort()
.
The final thing we need to do in this filter function is merge the randomized post ids, together with the featured posts and return the array.
🚀 Performance tip
If you only need post ids, add 'fields' => 'ids' to the query $args array. This results in a query that only asks for ids, and not all other fields available in the posts table.
Apply the sorting to the FacetWP template
One final step is needed to get the results right. We need to set the order and orderby parameters on the FacetWP query for the template we want this to be used for. Here’s the facetwp_query_args
filter. We add the query parameters to the original query parameters, and return the $query_args
variable.
<?// put featured posts first in FacetWP resultsadd_filter( 'facetwp_filtered_post_ids', function ( $post_ids, $class ) {// Lookup projects with featured meta key set to ja$args = array('post_type' => 'post','post_status' => 'publish','meta_key' => 'uitgelicht','meta_value' => 'ja','meta_compare' => 'LIKE','fields' => 'ids',);$featured_posts_query = new WP_Query( $args );$featured_posts_array = $featured_posts_query->posts;// If a featured post, remove it from $post_ids and add it to $matches// We loop through $post_ids to preserve featured order$matches = array();foreach ( $post_ids as $key => $post_id ) {if ( in_array( $post_id, $featured_posts_array ) ) {$matches[] = $post_id;unset( $post_ids[ $key ] );}}// randomize post_ids$duration = 60; // in minutes, so 60 is one hour.$mins = date('i', strtotime('now'));$seed = $mins - ($mins % $duration);mt_srand($seed);$order = array_map(function() {return mt_rand();}, range(1, count($post_ids)));array_multisort($order, $post_ids);// Featured first, then default sort$post_ids = array_merge( $matches, $post_ids );return $post_ids;}, 15, 2 );add_filter( 'facetwp_query_args', 'nostromo_filter_posts', 10, 2 );function nostromo_filter_posts( $query_args, $class ) {// only use these extra query args if we are using this FacetWP templateif ( 'posts_overview' == $class->template[ 'name' ] ) {// add the sorting$query_args[ 'order' ] = 'ASC';$query_args[ 'orderby' ] = 'post__in meta_value';}return $query_args;}?>php