WordPress.org

Make WordPress Core

Opened 9 years ago

Last modified 5 months ago

#15551 reopened defect (bug)

Custom post type pagination redirect

Reported by: cbsad Owned by:
Milestone: Priority: normal
Severity: normal Version: 3.1
Component: Query Keywords:
Focuses: Cc:
PR Number:

Description

We seem to be having a problem with a redirect on a custom post page (e.g., http://www.example.com/show/slug/).

Everything on the page shows correctly. The template pulls in posts with the following query:

<?php query_posts(array('meta_key' => 'show', 'meta_value' => $show->ID, 'paged' => get_query_var('paged'), 'caller_get_posts' => true)); ?>

After the posts we display pagination links (e.g., http://www.example.com/show/slug/page/2/). In our rewrite rules, we include the following rule which should handle these URLs:

'show/([^/]+)/page/([0-9]{1,})/?$' => 'index.php?show=$matches[1]&paged=$matches[2]',

Instead of the page displaying and allowing us to pull in the second page of posts, the browser is being redirected back to the original page (http://www.example.com/show/slug/) with a 301. I see that this is happening inside the redirect_canonical function. The following code in wp-includes/canonical.php replaces /page/2/ with / causing the redirect:

	// paging and feeds
	if ( get_query_var('paged') || is_feed() || get_query_var('cpage') ) {
		if ( !$redirect_url )
		        $redirect_url = $requested_url;
		$paged_redirect = @parse_url($redirect_url);
		while ( preg_match( "#/$wp_rewrite->pagination_base/?[0-9]+?(/+)?$#", $paged_redirect['path'] ) || preg_match( '#/(comments/?)?(feed|rss|rdf|atom|rss2)(/+)?$#', $paged_redirect['path'] ) || preg_match( '#/comment-page-[0-9]+(/+)?$#', $paged_redirect['path'] ) ) {
		        // Strip off paging and feed
		        $paged_redirect['path'] = preg_replace("#/$wp_rewrite->pagination_base/?[0-9]+?(/+)?$#", '/', $paged_redirect['path']); // strip off any existing paging
		        $paged_redirect['path'] = preg_replace('#/(comments/?)?(feed|rss2?|rdf|atom)(/+|$)#', '/', $paged_redirect['path']); // strip off feed endings
		        $paged_redirect['path'] = preg_replace('#/comment-page-[0-9]+?(/+)?$#', '/', $paged_redirect['path']); // strip off any existing comment paging
		}

Change History (20)

#1 @ryan
9 years ago

Calls to query_posts() made within the theme have no affect on pagination. To affect pagination links you need to hook into the parse_query action and manipulate the main query. Paging is done off of the results of the main query.

#2 @tott
9 years ago

A simple workaround could be disabling the canonical redirect for this particular case.

A possible solution could look like this. I'm not sure if this would be the preferred way to do this, though.

function no_canonical( $url ) {
        return false;
}
function adjust_show_request( $request ) {
        if ($request->query_vars['post_type'] === 'show' && $request->is_singular === true && $request->current_post == -1 && $request->is_paged === true ) {
                add_filter( 'redirect_canonical', 'no_canonical' );
        }
        return $request;
}
add_action( 'parse_query', 'adjust_show_request' );

#3 @tott
9 years ago

  • Cc tott added

#4 @lloydbudd
9 years ago

  • Component changed from General to Query
  • Milestone changed from Awaiting Review to 3.1

#5 @nacin
9 years ago

What's the bug here?

#6 @scribu
9 years ago

  • Keywords reporter-feedback added

#7 @ryan
9 years ago

  • Milestone 3.1 deleted
  • Resolution set to invalid
  • Status changed from new to closed

Not sure that there is a bug here. Re-open if I'm missing something.

#8 @ericlewis
8 years ago

  • Resolution invalid deleted
  • Status changed from closed to reopened

I'd like to reopen this one, as I've helped two people with a fix on this issue in two days.

This issue has been discussed in a few forums, with use cases in each:

http://wordpress.org/support/topic/custom-post-type-singular-page-with-pagination?replies=5
http://www.wptavern.com/forum/bbpress/1710-bbpress-alternative-wordpress.html

The main use case is a custom post type that requires pagination on the single page.
This could be used for a few reasons I could imagine:

  1. If you have a static photo gallery post type that has so many photos you want to split it onto two pages without any javascript
  2. A forum that would have tons of threads, which would require pagination

In both of these scenarios developers use the 'redirect_canonical' filter to turn off canonical redirects on the singular post. However, perhaps we can build this into core? I could imagine many developers being thrown for a loop trying to debug this issue, never even tracking it down to canonical redirects.

#9 @jtsternberg
8 years ago

  • Cc justin@… added

#10 @johnjamesjacoby
8 years ago

Isn't this achieved with 'paged' and the <--nextpage--> identifier? Or does that not work with custom post types for some reason?

#11 @toscho
8 years ago

  • Cc info@… added

#12 @SergeyBiryukov
8 years ago

  • Milestone set to Awaiting Review

#13 @wonderboymusic
6 years ago

  • Keywords reporter-feedback removed
  • Milestone Awaiting Review deleted
  • Resolution set to invalid
  • Status changed from reopened to closed

no traction in 18 months - this wasn't convincing to begin with

#14 @ianarmstrong
5 years ago

  • Resolution invalid deleted
  • Status changed from closed to reopened

I can confirm that this is still very much an issue in 3.9.x when generating a custom post type archive, applying pagination, and using permalink rewrites.

Case: a custom post type for items in a collection.

You want this:
../collection/home-decor/page/2/

You click and get this:
../collection/home-decor/

It happens every single time.

It was fixed using:

// Disable canonical urls for pagination
add_filter( 'redirect_canonical','custom_disable_redirect_canonical' );
function custom_disable_redirect_canonical( $redirect_url ){
    global $post;
    $ptype = get_post_type( $post );
    if ( $ptype == 'items' && is_archive() ) $redirect_url = false;
    return $redirect_url;
}

The original loop was basically:

/**
 * The logic for displaying a collection gallery
 */
 
wp_reset_postdata();
global $post;
 
$paged = (get_query_var('paged')) ? get_query_var('paged') : 1;
$my_items = array(
    'post_type' => 'items',
    'numberposts' => -1,
    'orderby' => 'title',
    'order' => 'ASC',
    'posts_per_page'   => 12,
    'paged' => $paged
);
 
$my_postlist = new WP_Query( $my_items );
 
if($my_postlist->have_posts()) : while($my_postlist->have_posts()) : $my_postlist->the_post();
 
    // Build your post loop here
 
endwhile;
 
else :
 
    // Build your contingency content here
 
endif;

The actual loop was using a meta-query out of wp-types but that wasn't the issue. The issue was a forced canonical redirect from the page permalink to the root.

Doesn't matter if I use default permalinks, WP_PageNavi, or Hybrid Core; obviously. While I never have to tear my hair out over this again, it's a horribly user-unfriendly issue.

#15 @wonderboymusic
5 years ago

  • Keywords needs-patch added
  • Milestone set to Future Release

This is super obnoxious. You need to do 2 things when you want to use page/[0-9]+ in the URL for a CPT:

// filter _wp_link_page() in the most ghetto way possible

add_filter( 'wp_link_pages_link', function ($link) {
	if ( preg_match( '#whatever/([^/]+)/([0-9]+?)/#', $link, $match ) ) {
		$link = str_replace( $match[0], "whatever/{$match[1]}/page/{$match[2]}/", $link );
	}
	return $link;
} );

// shove your rules in there somehow

add_filter( 'rewrite_rules_array', function ($rules) {
	$mine = array(
		'whatever/([^/]+)/page/([0-9]+?)/?$' => 'index.php?whatever=$matches[1]&page=$matches[2]',
	);

	return array_merge( $mine, $rules );
} );

Notice, the query var has to be page, not paged - after that, you can at least view the page, but it will redirected from page/2/ to 2/

If someone works through the headache and creates a patch, we can do this in 4.0

#16 @chriscct7
4 years ago

  • Keywords needs-unit-tests added

#17 @DzeryCZ
4 years ago

I have this problem too.

I have set custom page as Front page, and that page has set custom template with posts loop.

When the permalinks are set to different value than "Default", paged URL is redirected to homepage. (E.g. www.example.com/page/3 -> www.example.com)


Just question: Why are in WordPress used two different query vars for pagination?

I mean get_query_var('page') vs get_query_var('pages') I know one is used on is_home() (main blog) and second one is used in archives. But why you cannot merge it up?

I've tried to add this code:

<?php
if(!!get_query_var('page'))
        set_query_var('paged', get_query_var('page'));

into /wp-includes/canonical.php - function redirect_canonical() (line 45) and it helps. But I don't know whether it cause problem somewhere else.

+

In query in my custom page template cannot be set "offset".

<?php
if (is_front_page()) {
    unset($args['offset']);
}
$wp_query = new WP_Query($args);

Many thanks for any answers.

#18 @moonomo
3 years ago

In the single post for custom post type I had to show some extra data that requires pagination. WordPress redirects any sub-page requests to the single page (WordPress 4.5.3). Following code (taken from previous comment, and updated a little) removes the redirection, and pagination finally works:

<?php
function fix_request_redirect( $request ) {

        if ( isset( $request->query_vars['post_type'] )
             && 'custom_type' === $request->query_vars['post_type']
             && true === $request->is_singular
             && - 1 == $request->current_post
             && true === $request->is_paged
        ) {
                add_filter( 'redirect_canonical', '__return_false' );
        }

        return $request;
}

add_action( 'parse_query', 'fix_request_redirect' );

#19 follow-up: @yuranikolaev
3 years ago

  • Keywords needs-patch needs-unit-tests removed

Can someone explain, how to make it work with an array of post types?
For example, we got a few post types - band, person, release, cinema, place etc...

I tried this one

<?php
function fix_request_redirect( $request ) {

        if ( isset( $request->query_vars['post_type'] )
             && 'place' === $request->query_vars['post_type'] OR 'org' === $request->query_vars['post_type'] OR 'label' === $request->query_vars['post_type'] OR 'band' === $request->query_vars['post_type'] OR 'person' === $request->query_vars['post_type'] OR 'company' === $request->query_vars['post_type'] OR 'fest' === $request->query_vars['post_type'] OR 'release' === $request->query_vars['post_type']
             && true === $request->is_singular
             && - 1 == $request->current_post
             && true === $request->is_paged
        ) {
                add_filter( 'redirect_canonical', '__return_false' );
        }

        return $request;
}

add_action( 'parse_query', 'fix_request_redirect' );

and it works!
But it also make errors in php logs, and, I think, not so perfect

I also playing with arrays, but cannot figure how to make it work properly

Is it possible at all?

Thanks in advance

#20 in reply to: ↑ 19 @elvismdev
3 years ago

Use in_array http://php.net/manual/en/function.in-array.php

Your example will look like:

<?php
function fix_request_redirect( $request ) {

        $post_types = array(
                'place',
                'org',
                'label',
                'person',
                'company',
                'fest',
                'release'
                );

        if ( isset( $request->query_vars['post_type'] )
                && in_array( $request->query_vars['post_type'], $post_types )
                && true === $request->is_singular
                && - 1 == $request->current_post
                && true === $request->is_paged 
                ) 
        {
                add_filter( 'redirect_canonical', '__return_false' );
        }

        return $request;
}
add_action( 'parse_query', 'fix_request_redirect' );

Replying to yuranikolaev:

Can someone explain, how to make it work with an array of post types?
For example, we got a few post types - band, person, release, cinema, place etc...

I tried this one

<?php
function fix_request_redirect( $request ) {

        if ( isset( $request->query_vars['post_type'] )
             && 'place' === $request->query_vars['post_type'] OR 'org' === $request->query_vars['post_type'] OR 'label' === $request->query_vars['post_type'] OR 'band' === $request->query_vars['post_type'] OR 'person' === $request->query_vars['post_type'] OR 'company' === $request->query_vars['post_type'] OR 'fest' === $request->query_vars['post_type'] OR 'release' === $request->query_vars['post_type']
             && true === $request->is_singular
             && - 1 == $request->current_post
             && true === $request->is_paged
        ) {
                add_filter( 'redirect_canonical', '__return_false' );
        }

        return $request;
}

add_action( 'parse_query', 'fix_request_redirect' );

and it works!
But it also make errors in php logs, and, I think, not so perfect

I also playing with arrays, but cannot figure how to make it work properly

Is it possible at all?

Thanks in advance

Note: See TracTickets for help on using tickets.