4

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?

2
  • 1
    Please add your code inside yoir question. Do not use third party services. Two reasons, if the link dies, your context is gone and therefor your question will be useless to others, secondly, most users like me simply ignore and skip these questions as it is really frustrating having to open multiple screens just to read one post :-) Apr 23, 2015 at 4:27
  • Sorry about that! Made the changes! :) Apr 23, 2015 at 13:45

3 Answers 3

4
+50

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 );
} 
9
  • I used your code, I'm glad to hear my paginate_links function is working. I looked into permalink structure, and I can confirm that it works when I use the 'default' setting for permalinks. Currently, the structure is set to be Day and name, is there a reason why this doesn't function properly with /page/4 type URLS? is there some incompatibility I'm not understanding? Apr 29, 2015 at 18:29
  • 1
    I'm done for the day, of to bed now. If you have other concerns, let me know. Will look at them tommorow. :-) Apr 29, 2015 at 19:01
  • Will do! I'll report back here in a bit. thank you so much, Pieter :) Apr 29, 2015 at 19:09
  • Update So, there's good and there's bad news. The good news is that the Query Pagination works on the front page! The bad news is that the category pages for this website don't? Which is confusing: They all pull from the same code to output posts... The major difference I'm spotting is in the URL: old.pixelkin.org/?paged=4 Vs. old.pixelkin.org/tips-for-parents/?paged=4... Is this a special case? "Tips for Parents" is a Page, by the way, but it uses the same kind of setup the homepage does, using the renderPosts() function after being specified a category... Any ideas? Apr 29, 2015 at 19:51
  • 2
    After the update to the code, I was able to establish conditionals for whether or not it was the front page. Pieter, thank you so much! I'll be going though this post again and again to see what I can improve upon. Thanks for all your help, a heard earned 50 bounty for YOU! :) Apr 30, 2015 at 19:21
0

According to the codex

If the pagination is broken on a static front page you have to add the "paged" parameter this way:

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

query_posts('posts_per_page=3&paged=' . $paged); 

Page parameter is changed to Paged, update function code as follows

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('paged') ) ? get_query_var('paged') : 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,
       'paged' => $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;  
 }
3
  • 3
    The page you've linked to is a pile of crap, yet another useless page in the codex. query_posts should never be used. It break the pagination you are trying to fix, it also breaks the main query object which is used by many plugins and custom code. Also, extract() should also never be used. Make yourself an evil list and add these two functions at the top and make a big note, never ever use them Apr 29, 2015 at 18:09
  • Yeah, apparently query_posts() has become the new WP no-no. Or at least it has been for a while. I appreciate your answer nonetheless, but I think Pieter is teaching me a lot more about this particular issue. Apr 29, 2015 at 18:30
  • I totally agree with it, but the concern here is to resolve pagination issue that should be tried with paged parameter. codex.wordpress.org/Class_Reference/…
    – Vikram
    Apr 29, 2015 at 18:34
-1

Well if i need to use Pagination i will go with WP-PageNavi plugin.

$paged = get_query_var('paged') ? get_query_var('paged') : 1;
$args = array('post_type' => 'post', 'posts_per_page' => 5, 'paged' => $paged);
$loop = new WP_Query( $args );
while ( $loop->have_posts() ) : $loop->the_post();

    // Your Custom Loop Code Here

endwhile; ?>

<?php wp_pagenavi( array( 'query' => $loop ) ); ?>

WP-PageNavi will render pagination according to your query.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.