21

What I am looking to do:

Setup WP_Query pagination in a single-custom-post-type.php template file

What I have done

1) Created a post type called "authors". Each post within that post type is an individual author.

2) Standard edit post screen pages contain a dropdown which lists all posts (authors) from the authors custom post type.

3) Created a single-authors.php template which query's all posts with the author metadata from the dropdown, so the result is a list of posts that have the same author assigned to them (kind of like an archive):

<?php

// set the "paged" parameter (use 'page' if the query is on a static front page)
global $paged;

/*We need this here to add and maintain Pagination if Template is assigned to Front Page*/
if ( get_query_var( 'paged' ) ) {
    $paged = get_query_var('paged');
} elseif ( get_query_var( 'page' ) ) {
    $paged = get_query_var( 'page' );
} else {
    $paged = 1;
}

$args = array(
    'posts_per_page'    =>  10,
    'meta_key'          => 'author_select',
    'meta_value'        => $author_id,
    'paged'             => $paged,
);

$temp = $wp_query;
$wp_query = NULL;

$wp_query = new WP_Query($args);

?>

<?php if( $wp_query->have_posts() ) : ?>    
    <?php while ( $wp_query->have_posts() ) : $wp_query->the_post(); ?>
    <?php // Successfully outputs the results of the above query, so I've omitted the code from this example. ?>

    <?php endwhile; ?>

<div class="single_navigation"> 
    <?php if( get_adjacent_post( false, '', true ) ) { ?>
        <span class="prev"><?php previous_post_link( '&lt; %link' ) ?></span>
    <?php } ?>

    <?php if( get_adjacent_post( false, '', false ) ) { ?>
        <span class="next"><?php next_post_link( '%link &gt;' ) ?></span>
    <?php } ?>
    </div><!--/single navigation-->
<?php endif; ?>

<?php
    $wp_query = null;
    $wp_query = $temp;
    wp_reset_query();
?>

What I am stuck on

The pagination links do not appear. I have done some research and have found that they use the $wp_query variable, however when I changed my query variable to $wp_query, the links appeared, but when clicked did nothing.

Any idea where I am going wrong?

Edit:

To answer some of your questions, my query successfully outputs the posts that I am querying, $author_id already has a value that I did not include in this code snippet. Similarly, I left out the actual content output within the while loop because that part is not the issue. The issue is that I need to paginate the content that already exists on this single post.

The purpose of this functionality was to allow posts to have custom authors (outside of the built in Users system) and this template's purpose is to output all of the posts for a given author (which is already working).

6
  • 3
    An upvote for a well formatted question, though you didnt describe why you're doing this, is it a related posts by the current author box?
    – Tom J Nowell
    May 8, 2014 at 16:44
  • 1
    I really do agree with @TomJNowell. +1 May 8, 2014 at 16:45
  • 2
    Unrelated, but it is not necessary to echo the_title() May 8, 2014 at 16:47
  • 2
    Is 'meta_value' => $author_id being filled from the dropdown mentioned? And when selecting each author via the dropdown, are the first ten posts showing correctly?
    – Stephen S.
    May 8, 2014 at 16:52
  • 1
    Have you made sure that $author_query->have_posts() returns true and that the $author_query actually contains any posts?
    – kaiser
    May 8, 2014 at 17:06

3 Answers 3

25

You have 2 problems.

First problem

The line

$paged = ( get_query_var( 'paged' ) ) ? get_query_var( 'paged' ) : 1;

will fail, because on singular post view, when the URL contains '/page/XX/', the variable WordPress sets is 'page' and not 'paged'.

You may think to use 'page' instead of 'paged', but that will not work either, because once the 'page' variable is intended to be used for multi-page singular post (page separation using <!--nextpage-->) and once the post is not multi-page, WordPress will redirect the request to the URL without '/page/XX/'.

This is what happens when you name your query variable $wp_query.

The solution is to prevent that redirection by removing the function responsible for it, which is 'redirect_canonical' hooked into 'template_redirect':

So, in your functions.php add:

add_action( 'template_redirect', function() {
    if ( is_singular( 'authors' ) ) {
        global $wp_query;
        $page = ( int ) $wp_query->get( 'page' );
        if ( $page > 1 ) {
            // convert 'page' to 'paged'
            $wp_query->set( 'page', 1 );
            $wp_query->set( 'paged', $page );
        }
        // prevent redirect
        remove_action( 'template_redirect', 'redirect_canonical' );
    }
}, 0 ); // on priority 0 to remove 'redirect_canonical' added with priority 10

Now WordPress will not redirect anymore and will set correctly the 'paged' query var.

Second problem

next_posts_link() and previous_posts_link() both check if ( ! is_single() ) to display pagination.

Now, is_single() is true in your case, because you are in a single post of 'author' type, so those functions can't work as you expect.

You have 3 possibilities:

  1. Use query_posts to override the main query (really not recommended)
  2. Use a custom page template instead of a custom post type, because is_single() is false for pages, and your code will work there.
  3. Write your own pagination function and use that

That's the code for solution number #3:

function my_pagination_link( $label = NULL, $dir = 'next', WP_Query $query = NULL ) {
    if ( is_null( $query ) ) {
        $query = $GLOBALS['wp_query'];
    }
    $max_page = ( int ) $query->max_num_pages;
    // only one page for the query, do nothing
    if ( $max_page <= 1 ) {
        return;
    }
    $paged = ( int ) $query->get( 'paged' );
    if ( empty( $paged ) ) {
        $paged = 1;
    }
    $target_page = $dir === 'next' ?  $paged + 1 : $paged - 1;
    // if 1st page requiring previous or last page requiring next, do nothing
    if ( $target_page < 1 || $target_page > $max_page ) {
        return;
    }
    if ( null === $label ) {
        $label = __( 'Next Page &raquo;' );
    }

    $label = preg_replace( '/&([^#])(?![a-z]{1,8};)/i', '&#038;$1', $label );
    printf( '<a href="%s">%s</a>', get_pagenum_link( $target_page ), esc_html( $label ) );
}

and use it like so in the single-authors.php:

my_pagination_link( 'Older Entries', 'next', $author_query );
my_pagination_link( 'Newer Entries', 'prev', $author_query );
8
  • Very detailed answer, thank you! This is working for the English side of the site, but on the French side the pagination links don't do anything, they only refresh the page. I'm using WPML.
    – Shane
    May 8, 2014 at 20:09
  • 1
    @Shane so seems that WPML run some filter that prevent code to work. But is a so big plugin, and so I can't dig thousands lines of code to understand where is the confict. Try ask WPML support.
    – gmazzap
    May 8, 2014 at 22:30
  • @Shane The WPML Support forums - and if above works for (which I assume), then please mark it as solution. The WPML conflict is off topic/out of scope of this site anyway.
    – kaiser
    May 9, 2014 at 1:03
  • Fantastic answer, mainly because it helped me fix my pagination problem. I have been doing a similar task and have created a second WP_Query to loop over. I called the_posts_pagination but had to take a copy of the original wp_query as outlined in this question: wordpress.stackexchange.com/questions/216821/… Jan 30, 2017 at 9:15
  • 1
    You're correct @HongPong, fixed, thanks.
    – gmazzap
    Sep 9, 2019 at 6:12
0

Based on another similar WPSE question "Pagination is not working wp_query custom fields values", I would suggest adding global $paged; to the beginning of your template file:

global $paged;
$paged = ( get_query_var( 'paged' ) ) ? get_query_var( 'paged' ) : 1;
6
  • 1
    Thanks for the response! Unfortunately it doesn't appear to make any difference. The pagination links do not appear
    – Shane
    May 8, 2014 at 18:32
  • 1
    Stephen, why should that help? Yes, this would bring it in from the global context, but so far you aren't doing anything with it. If you want to override the global, then use $GLOBALS['paged'] = get_query_var( 'paged' );. Else just use the globals content.
    – kaiser
    May 8, 2014 at 18:34
  • @Shane Hmm, is the template page being used as a static front page?
    – Stephen S.
    May 8, 2014 at 18:36
  • No, it is only being used as a single post template for the custom post type. I did try changing my query from $author_query to $wp_query and that seemed to make the pagination appear, but when I click on it the page refreshes and doesn't actually go to the next page, even though there are multiple pages. I've updated my answer to reflect those changes.
    – Shane
    May 8, 2014 at 18:38
  • 1
    The php.net explanation isn't that bad. I'd suggest just playing around with global and $GLOBALS. A bit of var_dump(), some functions and you're fine. To really understand it with a bit of try/error you will approx. need half an hour. This will help you much better understand functions vs. classes and the OOP approach as a whole as well. And it will clearly bring you closer to understanding namespaces :)
    – kaiser
    May 8, 2014 at 18:45
0

gmazzap provides a detailed answer, but I found several portions were not necessary for my implementation, which is very similar to yours. The vital portion was to rearrange my template and use a page template, rather than a custom post type.

In my query args, I did the following:

$paged = (get_query_var('paged')) ? get_query_var('paged') : 1;

$get_posts_args = array(
        'numberposts' => -1,
        'posts_per_page' => 5,
        'paged' => $paged,
        'page' => $paged,
        'post_type' => array('dvposts','post'),
        'post_status' => 'publish',);

Passing $paged to both the paged and page keys allowed both the links to point to the correct url and the results to actually move through the pages.

Then anywhere after you initialize your query, you use paginate_links with with at least these args:

$big = 999999999; // need an unlikely integer
echo paginate_links( array(
    'base' => str_replace( $big, '%#%', esc_url( get_pagenum_link( $big ) ) ),
    'format' => '?paged=%#%',
    'current' => max( 1, get_query_var('paged') ),
    'total' => $qwer->max_num_pages,
) );

This example is actually at the bottom of the WP codex page.

I wanted the page numbers to show, but if you don't, you can use previous_post_link and nest_post_link all the same.

I can't say I fully understand it, but it seems like there might be issues with 'current' => max( 1, get_query_var('paged') ). That may not be the same as $paged that was initialized earlier. I changed it to 'current' => max( 1, $paged ), and it worked the same, as expected.

Your Answer

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

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