Image via istockphoto
John Reed

Written by John Reed

Share

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;
}
Note that if our custom query didn’t return any results, we set the $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!

Tags
PHPWordPressWP_Query