Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WordPress - pre_get_posts in place of query_posts on pages

Tags:

php

wordpress

My situation is somewhat complex, I'll try to explain it as succinctly as possible.

I'm currently using query_posts to modify the main query on custom pages on my site, which as far as I can tell works quite well, though I've read that using query_posts is bad practice for a number of different reasons.

So, why am I using query_posts and not creating a WP_Query object you may ask?

It's because I'm using the infinite-scroll plugin, infinite-scroll doesn't play nice with WP_query, but it works absolutely fine when you simply modify the main query with query_posts. For example, pagination doesn't work using infinite scroll + WP_query (main concern).

On one page, I'm modifying the query to get most viewed posts.

<?php $paged = get_query_var( 'paged' ) ? get_query_var( 'paged' ) : 1; ?>     
<?php query_posts( array( 'meta_key' => 'wpb_post_views_count', 'orderby' => 'meta_value_num', 'order' => 'DESC' ,  'paged' => $paged, ) ); ?>     


<?php if (have_posts()) : ?>

<?php while ( have_posts() ) : the_post() ?>

    <?php if ( has_post_format( 'video' )) {
            get_template_part( 'video-post' );
        }elseif ( has_post_format( 'image' )) {
            get_template_part( 'image-post' );
        } else {
           get_template_part( 'standard-post' );
        }

    ?>

<?php endwhile;?>

<?php endif; ?>

So after a lot of reading I gather that my other option to modify the main query is using pre_get_posts, though I'm somewhat unsure as to how to go about this.

Take this for example:-

function textdomain_exclude_category( $query ) {
    if ( $query->is_home() && $query->is_main_query() ) {
        $query->set( 'cat', '-1,-2' );
    }
}
add_action( 'pre_get_posts', 'textdomain_exclude_category' );

Alright, so simple enough - if it's the home page, modify the main query and exclude two categories.

What I'm confused about and can't figure out is:-

  1. the use case scenario for custom page templates. With my query_posts modification I can just drop in the array before if (have_posts()), select my page template, publish it and away I go. With pre_get_posts I can't figure out how to say for example $query->most-viewed etc

  2. array( 'meta_key' => 'wpb_post_views_count', 'orderby' => 'meta_value_num', 'order' => 'DESC' , 'paged' => $paged, ) );

How the heck do I do that with pre_get_posts and make sure it's paginated, ie. works with infinite scroll? In all the examples I've seen with pre_get_posts there's no arrays.

like image 758
andy Avatar asked Feb 28 '14 05:02

andy


People also ask

How do I change the search query in WordPress?

You can simply add following code in your functions. php file in your WordPress theme directory. function searchfilter($query) { if ($query->is_search && ! is_admin() ) { $query->set('post_type',array('trip')); } return $query; } add_filter('pre_get_posts','searchfilter');

What is Pre_get_posts?

In WordPress, pre_get_posts is an action that makes it possible to modify an existing WP_Query , before that query is actually run. pre_get_posts offers some solutions that are more performant than writing a custom WP_Query , and enables solutions to other problems that would be quite difficult otherwise.

What is main query in WordPress?

The “main query” is whatever WordPress uses to build the content on the current page. For instance, on my Genesis category archive it's the 10 most recent posts in that category. The first four examples above all require altering the main query.

How do I find post queries in WordPress?

Place a call to query_posts() in one of your Template files before The Loop begins. The WP_Query object will generate a new SQL query using your parameters. When you do this, WordPress ignores the other parameters it receives via the URL (such as page number or category).


1 Answers

How to use the pre_get_posts hook to display list of posts on a page, through a custom page template?

I've been playing with the pre_get_posts hook and here's one idea

Step #1:

Ceate a page called for example Show with the slug:

example.com/show

Step #2:

Create a custom page template:

tpl_show.php

located in the current theme directory.

Step #3:

We construct the following pre_get_posts action callback:

function b2e_pre_get_posts( $query )
{
    $target_page = 'show';                             // EDIT to your needs

    if (    ! is_admin()                               // front-end only
         && $query->is_main_query()                    // main query only
         && $target_page === $query->get( 'pagename' ) // matching pagename only
    ) {
        // modify query_vars:
        $query->set( 'post_type',      'post'                 );  // override 'post_type'
        $query->set( 'pagename',       null                   );  // override 'pagename'
        $query->set( 'posts_per_page', 10                     );
        $query->set( 'meta_key',       'wpb_post_views_count' );
        $query->set( 'orderby',        'meta_value_num'       );
        $query->set( 'order',          'DESC'                 );

        // Support for paging
        $query->is_singular = 0;

        // custom page template
        add_filter( 'template_include', 'b2e_template_include', 99 );
    }
}

add_action( 'pre_get_posts', 'b2e_pre_get_posts' );

where

function b2e_template_include( $template )
{
    $target_tpl = 'tpl_show.php'; // EDIT to your needs

    remove_filter( 'template_include', 'b2e_template_include', 99 );

    $new_template = locate_template( array( $target_tpl ) );

    if ( ! empty( $new_template ) )
        $template = $new_template; ;

    return $template;
}

This should also give us pagination:

example.com/show/page/2
example.com/show/page/3

etc.

Notes

I updated the answer and removed the query-object part modification, based on the suggestion from @PieterGoosen, since it could e.g. break the breadcrumbs on his setup.

Also removed the is_page() check within the pre_get_posts hook, since it might still give some irregularities in some cases. The reason is that the query-object is not always available. This is being worked on, see e.g. #27015. There are workarounds possible if we want to use the is_page() or is_front_page().

I constructed the following table, just to get a better overview of some of the properties and query varaiables of the main WP_Query object, for a given slug:

table

It's interesting to note that the pagination in WP_Query depends on the nopaging not being set and the current page not being singular (from the 4.4 source):

// Paging
if ( empty($q['nopaging']) && !$this->is_singular ) {
    $page = absint($q['paged']);
    if ( !$page )
        $page = 1;

    // If 'offset' is provided, it takes precedence over 'paged'.
    if ( isset( $q['offset'] ) && is_numeric( $q['offset'] ) ) {
        $q['offset'] = absint( $q['offset'] );
        $pgstrt = $q['offset'] . ', ';
    } else {
        $pgstrt = absint( ( $page - 1 ) * $q['posts_per_page'] ) . ', ';
    }
    $limits = 'LIMIT ' . $pgstrt . $q['posts_per_page'];
}

where we can see that the LIMIT part of the generated SQL query is within the conditional check. This explains why we modify the is_singular property above.

We could have used other filter/hooks, but here we used pre_get_posts as mentioned by the OP.

Hope this help.

like image 114
birgire Avatar answered Oct 12 '22 23:10

birgire