WP_Query and Pagination on a Static front page

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