Pagination of a WP_Query Loop in a child-page page template

See edit at bottom.

I have a wp_query loop in a page template for a custom post type (success_stories). The loop itself works fine but I cannot seem to get pagination working. I've tried every solution I could find on these forums and everytime I try to navigate to /page/child-page/page/2/ WordPress sends me back to /page/child-page/ with a 301 redirect.

I have of course flushed the permalinks about 100 times. Here's the code I'm working with:

?php


if ( get_query_var('paged') ) {
    $paged = get_query_var('paged');
} elseif ( get_query_var('page') ) { // 'page' is used instead of 'paged' on Static Front Page
    $paged = get_query_var('page');
} else {
    $paged = 1;
}
$paged = (get_query_var('paged')) ? get_query_var('paged') : 1;

// Query arguments
$args = array(
    'post_type'             = array( 'success_stories' ),
    'posts_per_page'        = '3',
    'paged'                 = $paged,
    'ignore_sticky_posts'   = true,
    'tax_query'             = array(
            array(
                'taxonomy' = 'practice_areas_taxonomy',
                'field'    = 'ID',
                'terms'    = array( 17 ),
            )
    )
);

// Instantiate custom query
$practice_area_query = new WP_Query( $args );

// Pagination fix
$temp_query = $wp_query;
$wp_query   = NULL;
$wp_query   = $practice_area_query;

// Output custom query loop
if ( $practice_area_query-have_posts() ) :

    echo 'div';

    while ( $practice_area_query-have_posts() ) :
        $practice_area_query-the_post(); ?

        div class=?php echo $pa_class_inner; ?

            blockquoteldquo;?php echo get_the_excerpt();?rdquo;/blockquote


        /div

    ?php endwhile;

    echo '/div';

endif;


// Pagination
$total_pages = $wp_query-max_num_pages;
         
if ($total_pages  1) {

    $big = 999999999;
    $current_page = max(1, get_query_var('paged'));

    echo 'nav class=pagination clearfix';

    echo paginate_links(
        array(
            'base' = str_replace($big, '%#%', esc_url(get_pagenum_link($big))),
            'current' = $current_page,
            'total' = $total_pages,
            'prev_text' = 'Prev',
            'next_text' = 'Next',
            'mid_size' = 1,
            'end_size' = 3
        )
    );

    echo '/nav';   
}

wp_reset_postdata();

// Reset main query object
$wp_query = NULL;
$wp_query = $temp_query; ? 

Where am I going wrong? Can this not be done from a (child?) page template?

Edit: adding the following rewrite rules almost resolves the issue:


// Primary Rewrite Rule
add_rewrite_rule( 'practice-areas/family-law/?', 'index.php?post_type=success_stories', 'top' );
// Pagination Rewrite Rule
add_rewrite_rule( 'practice-areas/family-law/page/([0-9]{1,})/?', 'index.php?post_type=success_storiespaged=$matches[1]', 'top' );

The only problem then is that (a) on paginated pages I'm suddenly in a different template (archive.php) and (b) WordPress seems to default to regular wp_query parameters. That is, it's forgotten all my args from the above query, as well as my offset, and I think even the taxonomy query.

Topic page-template child-pages wp-query pagination Wordpress

Category Web


Glad that you managed to figure out a solution, which yes, does work. But you could make it more selectively, i.e. target just the specific pages, by using is_single( '<parent slug>/<child slug>' ), e.g. is_single( 'family-law/success-stories' ) in your case.

Secondly, it's not exactly the redirect_canonical hook which caused the (301) redirect on singular paginated post pages (with URL having the /page/<number>). Instead, it's the $addl_path part in the redirect_canonical() function which fires the redirect_canonical hook. More specifically, that part runs only if ! is_single() is true (and that the current page number is 2 or more), and thus:

  • If the request (i.e. current page) URL was for a CPT at example.com/practice-areas/family-law/success-stories/page/2/ where practice-areas is the CPT slug and success-stories is child of family-law, the redirect/canonical URL would not contain page/2/ because ! is_single() is false, hence $addl_path would be empty.

    The same scenario would also happen on example.com/practice-areas/family-law/page/2/ (i.e. page 2 of the parent post) and other singular CPT pages, including regular posts (in the default post type), e.g. at example.com/hello-world/page/2/, but not on singular Pages (post type page) because ! is_single() is false — because is_single() works with any post type, except attachment and page.

  • So if the request URL does not match the canonical URL, then WordPress runs the (301) redirect, which explains why this happened: "everytime I try to navigate to /page/child-page/page/2/ WordPress sends me back to /page/child-page/ with a 301 redirect".

Also, redirect_canonical() is hooked on template_redirect, so instead of having the function runs the various logic of determining the redirect/canonical URL and yet you eventually return an empty string, you might better off just disable the function like so:

add_action( 'template_redirect', 'my_template_redirect', 1 );
function my_template_redirect() {
    if ( is_paged() && is_single( 'family-law/success-stories' ) ) {
        remove_action( 'template_redirect', 'redirect_canonical' );
    }
}

And yes, no extra rewrite rules are needed because WordPress already generated them (which handle the /page/<number>) for your CPT. But I don't know for sure why WordPress does the above ! is_single() check.

Last but not least, in my original answer and comment as well, I suggested you to remove the $temp_query parts, and your reply was: "I'm gonna keep the $temp_query bit though, since it allows our pagination component to work without the need to rewrite or complicate things" and "I think you were just saying it's not needed".

So yes, you're right with that second sentence. But more precisely, I actually tested your template code where I put it in single-practice-areas.php and then removed the $temp_query parts (and used $total_pages = $practice_area_query->max_num_pages;), and the /page/<number> pagination worked just fine for me with the help of the above my_template_redirect() function.

So I wondered what exactly does the $temp_query fix, or why must you assign the $wp_query to $practice_area_query?

But if you really must keep it, then you need to move the wp_reset_postdata(); to after the $wp_query = $temp_query;, i.e. after you restore the global $wp_query variable back to the actual main query. Because otherwise, if for example you run the_title(), you would see the title of the last post in your custom query and not the current (and the only) one in the main query.


The problem turned out to be the redirect_canonical filter. That was causing the 301 back to the child page itself.

The solution is to selectively disable that filter. Below is what I've done, though it's not quite as selective as I'd like.

But it does allow pagination to work, without the need of any rewrite rules.

// redirect_canonical is preventing pagination on PA child pages. This removes the filter in that context
add_filter( 'redirect_canonical', 'namespace_prevent_redirect_on_pagination', 10 );
function namespace_prevent_redirect_on_pagination( $redirect_url ) {
    if ( is_singular('practice-areas') && !is_admin() && get_post()->post_parent ) {
        return '';
    }
 
    return $redirect_url;
}

About

Geeks Mental is a community that publishes articles and tutorials about Web, Android, Data Science, new techniques and Linux security.