Written by John Reed
WP_Query is a class that performs the bulk of the heavy lifting in WordPress. While most modifications to theme templates don’t require dealing with this class directly, The Loop, Conditional Tags, and many other features rely on it, so it’s important to understand the scope of this powerful set of functions.
As we begin to dig in to the mother of all WordPress classes, let’s focus on the simple yet powerful post__in
parameter, which accepts an array of IDs of posts to retrieve. For example:
$query = new WP_Query( array( 'post__in' => array( 2, 5, 12, 14, 20 ) ) );
Pass in some post IDs, get posts back. Easy! But when and how can we use this parameter dynamically?
Custom Queries and The Loop
In advanced theme development, you may eventually find the need to write your own custom queries. While it’s possible to write a modified loop to display the results of such a query using a foreach construct and the setup_postdata function, there may be situations where it’s more efficient to leave your template code alone.
Enter pre_get_posts
We recently developed advanced search functionality for one of our clients and found ourselves in this very situation. After wiring up our front-end search filters to our custom query, we didn’t want to have to rewrite our theme’s search results template. Using the pre_get_posts
filter, we can execute a custom query and in turn alter the global $query
object that is used in The Loop.
First, we add our filter and make sure we’re not altering any searches in the WordPress admin:
add_filter( 'pre_get_posts' , 'custom_advanced_search' ); function custom_advanced_search( $query ) { // don't alter search results in the admin! if( $query->is_admin ) return $query; }
Then, we go ahead and execute our custom query (WHERE
clause simplified for brevity):
if( $query->is_search ) { global $wpdb; // custom query $query_string = " SELECT $wpdb->posts.* FROM $wpdb->posts WHERE $wpdb->posts.post_type = 'custom_post_type' GROUP BY $wpdb->posts.ID "; // execute custom query $results = $wpdb->get_results($query_string, OBJECT); }
Finally, we map our results to an array of IDs, and update the post__in
parameter via the set method:
add_filter( 'pre_get_posts' , 'custom_advanced_search' ); function custom_advanced_search( $query ) { // don't alter search results in the admin! if( $query->is_admin ) return $query; if( $query->is_search ) { global $wpdb; // custom query $query_string = " SELECT $wpdb->posts.* FROM $wpdb->posts WHERE $wpdb->posts.post_type = 'custom_post_type' GROUP BY $wpdb->posts.ID "; // execute custom query $results = $wpdb->get_results($query_string, OBJECT); // map results to a simple array of post IDs $posts = array_map( function($post) { return $post->ID; }, $results ); // prevent empty array $posts = count($posts) ? $posts : array(-1); // only get posts from our custom query! $query->set( 'post__in', $posts ); } return $query; }
$posts
variable to an array containing -1; this is a simple workaround as passing an empty array to post__in
currently returns the most recent posts (see Ticket #28099).Since we have directly altered the global $query
object, our search template will now automatically render the new results via The Loop, without any additional programming changes. (As a bonus, you can even set the orderby
parameter to post__in
to preserve the order passed via the post__in
array!)
Tip of the Iceberg
The post__in
parameter is just one of the many cool features of the WP_Query class. Have questions or need custom WordPress development? Contact us or let us know in the comments!