WP_Query and Pagination on a Static front page

For the past two days I’ve been trying to debug an issue with WP_Query() and Pagination.

A website I’m a dev for uses a WordPress theme (Salient by ThemeNectar) that includes the visual composer plugin.
My job is essentially to make sure the site is just functional, so a while ago I wrote an extension for the visual composer.
This extension uses WP_Query() to create a new query, looks for posts in specific categories (with a default established),
and then returns the output of that query

Here’s the code that renders posts and pagination inside that visual composer component

public function renderPosts( $atts, $content = null ) {
    global $post;
    setup_postdata($post);

     extract( shortcode_atts( array(
            'foo' => 5, //default of 5
            'categoryslug' => 'news-views' // Currently news and views.
    ), $atts) );

    // For getting the Query variable on a statcci front page, you have
    // to use 'page' and not 'paged'. Weird.          
    $paged = ( get_query_var('page') ) ? get_query_var('page') : 1;


    // Two things needed: Not using the game library,
    // Definitely using the supplied slug.
    // These are two objects.

    $dontUse = get_category_by_slug('game-library');
    $catUsed = get_category_by_slug($atts->categoryslug);

    // Args for the custom query
     $query_args = array(
       'posts_per_page'   => intval($foo),
       'category__not_in' => $dontUse->term_id,
       'cat' => $catUsed->term_id,
       'page' => $paged
     );

    $custom_query = new WP_Query($query_args);                      

            $output="<div id="blogroll">";  
            while ( $custom_query->have_posts() ) : $custom_query->the_post();

                $output .= "<div class="home_post col span_12 clear-both">";
                $output .=      "<div class="col span_3"><a href="" . get_the_permalink() . "">" . get_the_post_thumbnail(get_the_ID(), 'home_post_thumb') . "</a></div>";
                $output .=      "<div class="col span_9 col_last right-edge">";
                $output .=        "<h2 class="home_post_header">";
                $output .=           '<a href="' . get_the_permalink() . '">' . get_the_title() . "</a>";
                $output .=        "</h2>";
                $output .=        get_the_excerpt();
                $output .=      '<a class="home-more-link" href="' . get_the_permalink() . '"><span class="continue-reading">Read More</span></a>';
                $output .=      "</div>";
                $output .= "</div>";

            endwhile;

            wp_reset_postdata();

            // Pagination not working, but it outputs just fine?
            $output .= '<div id="pagination" class="blogroll-pagination">' . home_pagination($custom_query) . '</div></div>';
            wp_reset_query();              

            return $output;  
 }

Now, I’m aware that the code to generate the content is just a giant concatenated string, but it’s only because the extension
function has to return something. At the very end, I output pagination with use of another function defined above.

Here’s the function I’ve created called home_pagination(query)

function home_pagination($query = null) {
    /*if ( !$query ) {
            global $wp_query;
            $query = $wp_query;
    } commented out because do I need this? */

    $big = 999999999; // need an unlikely integer

    $pagination = paginate_links( array(
            'base' => str_replace( $big, '%#%', esc_url( get_pagenum_link( $big ) ) ),
            'format' => '?paged=%#%',
            'current' => max( 1, get_query_var( 'page' ) ),
            'total' => $query->max_num_pages,
            'prev_text' => '&laquo; Previous',
            'next_text' => 'Next &raquo;',

            ) );

    return $pagination;

} 

Now, this was functioning not a week ago but I can’t seem to get it to work now… Here’s what I’ve tried:

  1. Playing around with get_query_var('page')
  2. Using a plugin (Wp_page_navi)
  3. Changing where and when wp_reset_postdata() and wp_reset_query() are used
  4. Using the max_num_pages workaround for next_posts_link() and previous_posts_link()

This is on a static front page and I’m aware that the rules are different with ‘page’ and ‘paged’

Interesting to note: When I change the ‘paged’ variable in the query args, it returns the proper corresponding page.

I’m trying to figure out why the pagination I’m outputting as a part of this query is doing ABSOLUTELY NOTHING when I try
to navigate with it? It’s as if it wants to navigate to /page/2 or whichever number, but then immediately redirects to the site itself…

I’m considering not putting the paginate_links() call in a separate function. There are probably glaring mistakes, but I’ve been
grinding away at pagination that no longer seems to work. Any ideas on how I can best approach this? Is there something huge I’m missing?

3 Answers
3

I’m not quite sure what do you mean by you have extended the visual composer, but I do have a couple of concerns here.

  • SoC -> From your code I detect that you are using a shortcode, which should have its own separate class. You should not extend the class to the visual editor (at least this is how I read your question). Classes should never multi task, they should do one single duty. This keeps your class extentable, short, easy to maintain and reusable and easy to test

  • Extending the previous point, front end and back end functionalities should not be mixed

  • I don’t see the usefulness of using global $post and setting up post data. Doing this and not resetting postdata afterwards will have unexpected influences on any query afterwards and on the main query. You should drop that completely

  • Never ever use extract() in shortcodes ( and any other function for that matter ). It is very unrealiable and untestable and leads to unexpected output. This makes it extremely hard to debug in case of failures. For this very reason, it was completely removed from core. See trac ticket 22400

  • Because you are using multiple category conditions, and to make it more dynamic, I would rather use a tax_query to handle the conditions. The big plus here would be that you will save on db calls as you can get rid of get_category_by_slug

  • There is no need for wp_reset_query(). This is used with query_posts which you should never ever use. It is a page breaker.

  • Front pages and single pages uses the query variable page (get_query_var( 'page' )) for pagination, and not paged (get_query_var( 'paged' )) as other pages. The query argument for WP_Query for both however is the same, it should be paged, not page. The value to the paged parameter/argument should be page for static front pages and paged for all other pages

You can rewrite your method above as follow: (CAVEAT: Untested)

public function renderPosts( $atts ) 
{
    $attributes = shortcode_atts( array(
        'foo'     => 5, //default of 5
        'include' => 'news-views', // Currently news and views.
        'exclude' => 'game-library' // Exclude this category
    ), $atts);

    /* 
     * For getting the Query variable on a static front page, you have
     * to use 'page' and not 'paged'. Weird.
     */ 
    if ( get_query_var('paged') ) {
        $paged = get_query_var('paged'); 
    } elseif ( get_query_var('page') ) { 
        $paged = get_query_var('page'); 
    } else { 
        $paged = 1; 
    }


    /*
     * Args for the custom query
     */
    $query_args = array(
        'posts_per_page' => intval($attributes['foo']),
        'tax_query'      => array(
            array(
                'taxonomy' => 'category',
                'field' => 'slug',
                'terms' => explode( ',', str_replace( ' ', '', $attributes['include'] ) ),
                'include_children' => false
            ),
            array(
                'taxonomy' => 'category',
                'field' => 'slug',
                'terms' => explode( ',', str_replace( ' ', '', $attributes['exclude'] ) ),
                'operator' => 'NOT IN'
            )
        ),
        'paged' => $paged
    );

    $custom_query = new WP_Query($query_args); 

    $output="";
    if ( $custom_query->have_posts() ) {  

        $output .= '<div id="blogroll">'; 

        while ( $custom_query->have_posts() ) { 

            $custom_query->the_post();

            $output .= "<div class="home_post col span_12 clear-both">";
            $output .=      "<div class="col span_3"><a href="" . get_the_permalink() . "">" . get_the_post_thumbnail(get_the_ID(), 'home_post_thumb') . "</a></div>";
            $output .=      "<div class="col span_9 col_last right-edge">";
            $output .=        "<h2 class="home_post_header">";
            $output .=           '<a href="' . get_the_permalink() . '">' . get_the_title() . "</a>";
            $output .=        "</h2>";
            $output .=        get_the_excerpt();
            $output .=      '<a class="home-more-link" href="' . get_the_permalink() . '"><span class="continue-reading">Read More</span></a>';
            $output .=      "</div>";
            $output .= "</div>";

        }

        wp_reset_postdata();

        $output .= '<div id="pagination" class="blogroll-pagination">' . home_pagination( $custom_query ) . '</div></div>';

    }

    return $output;  
}

Just remember, the I have changed the attributes for readability, include takes a comma separated string of category slugs (include="slug-1, slug-2"). This attribute will be used to include categories. exclude works the same (exclude="slug-1, slug-2"), except that it takes a comma separated string of category slugs

EDIT

I have tested my code and fixed a couple of small bugs. It works as expected if I just create a normal shortcode from it.

PROOF OF CONCEPT – SHORTCODE

add_shortcode( 'testcode', function ( $atts )
{
    $attributes = shortcode_atts( array(
        'foo'     => 5, //default of 5
        'include' => 'news-views', // Currently news and views.
        'exclude' => 'game-library' // Exclude this category
    ), $atts);

    /* 
     * For getting the Query variable on a static front page, you have
     * to use 'page' and not 'paged'. Weird.
     */ 
    if ( get_query_var('paged') ) {
        $paged = get_query_var('paged'); 
    } elseif ( get_query_var('page') ) { 
        $paged = get_query_var('page'); 
    } else { 
        $paged = 1; 
    }


    /*
     * Args for the custom query
     */
    $query_args = array(
        'posts_per_page' => intval($attributes['foo']),
        'tax_query'      => array(
            array(
                'taxonomy' => 'category',
                'field' => 'slug',
                'terms' => explode( ',', str_replace( ' ', '', $attributes['include'] ) ),
                'include_children' => false
            ),
            array(
                'taxonomy' => 'category',
                'field' => 'slug',
                'terms' => explode( ',', str_replace( ' ', '', $attributes['exclude'] ) ),
                'operator' => 'NOT IN'
            )
        ),
        'paged' => $paged
    );

    $custom_query = new WP_Query($query_args); 

    $output="";
    if ( $custom_query->have_posts() ) {  

        $output .= '<div id="blogroll">'; 

        while ( $custom_query->have_posts() ) { 

            $custom_query->the_post();

            $output .= "<div class="home_post col span_12 clear-both">";
            $output .=      "<div class="col span_3"><a href="" . get_the_permalink() . "">" . get_the_post_thumbnail(get_the_ID(), 'home_post_thumb') . "</a></div>";
            $output .=      "<div class="col span_9 col_last right-edge">";
            $output .=        "<h2 class="home_post_header">";
            $output .=           '<a href="' . get_the_permalink() . '">' . get_the_title() . "</a>";
            $output .=        "</h2>";
            $output .=        get_the_excerpt();
            $output .=      '<a class="home-more-link" href="' . get_the_permalink() . '"><span class="continue-reading">Read More</span></a>';
            $output .=      "</div>";
            $output .= "</div>";

        }

        wp_reset_postdata();

        $output .= '<div id="pagination" class="blogroll-pagination">' . home_pagination( $custom_query ) . '</div></div>';

    }

    return $output;  
});

which I use as follow

[testcode include="testslug-1, testslug-2" exclude="testslug-3, testslug-4"]

I have tested your pagination function as well and that also works as expected.

function home_pagination( $query = null ) 
{

    $big = 999999999; // need an unlikely integer

    if ( get_query_var('paged') ) {
        $paged = get_query_var('paged'); 
    } elseif ( get_query_var('page') ) { 
        $paged = get_query_var('page'); 
    } else { 
        $paged = 1; 
    }


    $pagination = paginate_links( 
        array(
            'base'      => str_replace( $big, '%#%', esc_url( get_pagenum_link( $big ) ) ),
            'format'    => '?paged=%#%',
            'current'   => $paged,
            'total'     => $query->max_num_pages,
            'prev_text' => '&laquo; Previous',
            'next_text' => 'Next &raquo;',
        ) 
    );

    return $pagination;

} 

EDIT 2

Frm your comments, and as I have already stated, static front pages and single pages uses get_query_var( 'page' ) for pagination while all the other uses get_query_var( 'paged' ). I have updated all the code above with the following

if ( get_query_var('paged') ) {
    $paged = get_query_var('paged'); 
} elseif ( get_query_var('page') ) { 
    $paged = get_query_var('page'); 
} else { 
    $paged = 1; 
}

This will sort the problem with page and paged and make your shortcode and pagination work across all pages without any specific changes made to it

EDIT 3

Here is a sligtly modified version of the code by @ChipBennet which will sort the problem of /page/2

function home_pagination( $query = null ) 
{
    global $wp_rewrite;

    if ( get_query_var('paged') ) {
        $paged = get_query_var('paged'); 
    } elseif ( get_query_var('page') ) { 
        $paged = get_query_var('page'); 
    } else { 
        $paged = 1; 
    }


    $pagination = array(            
        'base'      => @add_query_arg( 'paged', '%#%' ),
        'format'    => '',
        'current'   => $paged,
        'total'     => $query->max_num_pages,
        'prev_text' => '&laquo; Previous',
        'next_text' => 'Next &raquo;',
    );

    if ( $wp_rewrite->using_permalinks() )
        $pagination['base'] = user_trailingslashit( trailingslashit( remove_query_arg( 's', get_pagenum_link( 1 ) ) ).'/%#%/', '' );

    if ( ! empty( $wp_query->query_vars['s'] ) )
        $pagination['add_args'] = array( 's' => get_query_var( 's' ) );

    return paginate_links( $pagination );
} 

Leave a Comment