Three techniques to alter the query in WordPress

Howdy WP developer!

In this snippet, I will describe three different techniques to alter the WordPress default query with an example showing how to add a secondary page to the front page in the Customizr theme

Those are quite advanced WordPress programming techniques, so before diving into it make sure you are familiar with the following concepts :

 

Altering the WordPress default query in WordPress

There are many ways to get posts in WordPress, but we can distinghish two techniques :

  • Altering the WordPress default query in a particular context => what we are going to do here
  • Creating a new query from scratch with get_posts()  or a new WP_query  for example. This topic will not be treated here.

In the following snippets review the different techniques to alter the WordPress default query :

  1. the pre_get_posts way
  2. the query_posts way
  3. the SQL statement filters way
  4. New : the wp_the_query way

 

Case study : adding a second page to the front page in the Customizr theme

Let’s say you have defined a static page as front page and you want to display another page below it.


How to use this code? The following code snippets have to be copied and pasted in the functions.php file of a child theme.

 

The pre_get_posts way

This is the easiest and recommended way to alter the main query. This ‘pre_get_posts’  hook is called after the query variable object is created, but before the actual query is run.

add_action('pre_get_posts','alter_query');

function alter_query($query) {
	//gets the global query var object
	global $wp_query;

	//gets the front page id set in options
	$front_page_id = get_option('page_on_front');

	if ( 'page' != get_option('show_on_front') || $front_page_id != $wp_query->query_vars['page_id'] )
		return;

	if ( !$query->is_main_query() )
		return;

	$query-> set('post_type' ,'page');
	$query-> set('post__in' ,array( $front_page_id , [YOUR SECOND PAGE ID]  ));
	$query-> set('orderby' ,'post__in');
	$query-> set('p' , null);
	$query-> set( 'page_id' ,null);

	//we remove the actions hooked on the '__after_loop' (post navigation)
	remove_all_actions ( '__after_loop');
}

 

The query_posts() way

query_posts() is meant for altering the main loop. It does so by replacing the query used to generate the main loop content. Once you use query_posts(), your post-related global variables and template tags will be altered. Conditional tags that are called after you call query_posts() will also be altered – this may or may not be the intended result.

Even if this is a not the best way to alter the query, this technique shows how to make use of some interesting hooks before and after the loop in the Customizr theme.

//those hooks are located in the index.php template of the Customizr theme
add_action( '__before_loop' , 'alter_query' );
add_action( '__after_loop'  , 'alter_query' );

function alter_query() {
	//we call the global query variable from the current instance of WP_query class.
	global $wp_query;

	//gets the front page id set in options
	$front_page_id = get_option('page_on_front');

	//checks the context before altering the query
	if ( 'page' != get_option('show_on_front') || $front_page_id != $wp_query->query_vars['page_id'] )
		return;

	//this switch statement allows us to use the same call back function for two different hooks
	switch ( current_filter() ) {
		case '__before_loop':

			$args = array( 
				'post_type' 	=> 'page', 
				'post__in' 		=> array( $front_page_id , [YOUR SECOND PAGE ID]  ),
				'orderby'		=> 'post__in'//we want to keep the order defined in the 'post__in' array
			);

			//execute the 
			query_posts( $args );

			//set global $wp_query object back initial values for boolean and front page id
			$wp_query->is_page 					= true;
			$wp_query->is_singular 				= true;
			$wp_query->query_vars['p']  		= $front_page_id;
			$wp_query->query_vars['page_id']  	= $front_page_id;
			break;

		case '__after_loop':
			//reset the custom query
			wp_reset_query();
			break;
	}
}

This whole query_posts() issue is well explained by Andrew Nacin (WP core developer), here :
http://wordpress.tv/2013/03/15/andrew-nacin-wp_query-wordpress-in-depth/

 

The SQL statement filters way

Last but not least, this is a more advanced technique offering more flexibility if you need to do complex queries. For example this technique is the best way to build a query that compares dates from custom fields. (I will give an example of this approach soon)

In this example, we filter the where and orderby sql statements. The  join statement does not need to be modified here.

 

add_filter( 'posts_where' , 'posts_where_statement' );

function posts_where_statement( $where ) {
	//gets the global query var object
	global $wp_query;

	//gets the front page id set in options
	$front_page_id = get_option('page_on_front');

	//checks the context before altering the query
	if ( 'page' != get_option('show_on_front') || $front_page_id != $wp_query->query_vars['page_id'] )
		return $where;

	//changes the where statement
	$where = " AND wp_posts.ID IN ('{$front_page_id}', [YOUR SECOND PAGE ID] ) AND wp_posts.post_type = 'page' ";

	//removes the actions hooked on the '__after_loop' (post navigation)
	remove_all_actions ( '__after_loop');

	return $where;
}

add_filter( 'posts_orderby' , 'posts_orderby_statement' );

function posts_orderby_statement($orderby) {
	global $wp_query;
	$front_page_id = get_option('page_on_front');

	//checks the context before altering the query
	if ( 'page' != get_option('show_on_front') || $front_page_id != $wp_query->query_vars['page_id'] )
		return $orderby;

	//changes the orderby statement
	$orderby = " FIELD( wp_posts.ID, '{$front_page_id}' ,[YOUR SECOND PAGE ID] )";

    return $orderby;
}

 

The $wp_the_query way

The following generic code snippet uses a global var called $wp_the_query where WordPress carrefully store the initial main query.

@see core code in wp-settings.

The snippets uses the wordpress template_redirect hook : it’s defined in wp-includes/template-loader.php.
I kind of like it because it is fired once the main query and conditional tags have been fully setup and before the wp_head action.
It’s a really the perfect place to setup hooks that will handle conditions on the main query elements.

//setup hooks in the template_redirect action => once the main WordPress query is set
add_action( 'template_redirect', 'hooks_setup' , 20 );
function hooks_setup() {
    if (! is_home() ) //<= you can also use any conditional tag here
        return;
    add_action( '__before_loop'     , 'set_my_query' );
    add_action( '__after_loop'      , 'set_my_query', 100 );
}

function set_my_query() {
    global $wp_query, $wp_the_query;
    switch ( current_filter() ) {
    	case '__before_loop':
    		//replace the current query by a custom query
		    //Note : the initial query is stored in another global named $wp_the_query
		    $wp_query = new WP_Query( array(
		    	'post_type'         => 'post',
				'post_status'       => 'publish',
		       //others parameters...
		    ) );
    	break;

    	default:
    		//back to the initial WP query stored in $wp_the_query
    		$wp_query = $wp_the_query;
    	break;
    }
}

 

 

These techniques can easily be adapted to many cases : custom post types, taxonomies, custom fields, …

Related resources :

36 thoughts on “Three techniques to alter the query in WordPress”

Comments are closed.