Handle category name URL rewrite before different post type slugs

I went over a hundred similar topics here which were close to what I need, but unfortunately I wasn’t able to fine tune this so apologies if it’s a duplicate.

I have a several post types (such as Events, Venues, Places) and I also use Posts, where the four post types are connected to the standard WordPress Categories taxonomy.

I’ve been trying to assemble a permalink that looks like that:

http://sitename/venues/61312/california-venue

Where venues is the name of the category; 61312 is the post_id and california-venue is the postname.

All post types should render the same structure:

%category%/%post_id%/%postname%/

That permalink structure seems to work just fine for Posts in Settings -> Permalinks, but doesn’t for the other post types.

The ones with with_front => false return 404, the others work fine, but redirect to post_type/postname.

I have a custom rewrite rule that looks like that:

array( 
  'regex'     => '^([^/]+)/([0-9]+)/([^/]+)?',  
  'query'       => 'category_name=$matches[1]&post_id=$matches[2]&pagename=$matches[3]', 
 )

I tried filtering the post_type_link and see if the redirect would stop, and I also tried the following snippet by adding disable_redirect=1 to my query rules:

add_filter('query_vars', 'my_public_query_vars');
function my_public_query_vars($qv)
{
    $qv[] = 'disable_redirect';
    return $qv;
}
add_filter('wp_redirect', 'my_disable_redirect');
function my_disable_redirect($location)
{
    $disable_redirect = get_query_var('disable_redirect');
    if(!empty($disable_redirect)) return false;
    return $location;
}

That simply caused a WSOD.

How can I prevent the redirect to my single views of each post type (single post view seems to work fine) and keep the %category%/%post_id%/%postname% URL structure?

1 Answer
1

After some brainstorming and a lot of trial and error with T5 Rewrite and HM Rewrite, I went for the simplest possible solution.

The permalink was actually intercepted by WordPress so I didn’t need the additional rewrite rule (even though it didn’t hurt, other than performance). I left %category%/%post_id%/%postname% as the default custom permalink, and it seems to work for all post types so far.

The original slug is handled gracefully too (when leftover links are found), but the new one is the default.

I played with template_redirect, request, redirect_canonical, query_vars, wp_redirect and other hooks in order to find the problem and I ended up with this:

add_filter( 'request', 'sm_permalink_request_handler' );
function sm_permalink_request_handler( $request ) {
    if ( ! isset( $request['p'] ) ) {
        return $request;
    }

    // Get the post type, it's required to prevent the redirect
    $post_id = $request['p'];
    $post = get_post( $post_id );
    $request['post_type'] = $post->post_type;

    return $request;
} 


/**
 * Override get_permalink and prevent redirects
 */
function sm_fix_permalinks_for_post_types( $url, $post ) {
        $taxonomies = get_post_taxonomies();
        // See if categories are assigned to our CPT entry
        if ( ! empty( $taxonomies ) && in_array( 'category', $taxonomies ) ) {

            // Fetch all categories
            $categories = wp_get_post_categories( $post->ID, array() );

            if ( ! empty( $categories ) ) {
                // Get the main category in case there are more assigned
                // Somewhat arbitrary since it pops the last one.
                $main_category_id = array_pop( $categories );

                $main_category = get_category( $main_category_id );

                if( ! empty( $main_category ) ) {
                    $category_slug = $main_category->slug;
                    $post_id = $post->ID;
                    $post_name = $post->post_name;

                    $url = home_url( "$category_slug/$post_id/$post_name" );
                }
            }
        }

        return $url;
}

add_filter( 'post_type_link', 'sm_fix_permalinks_for_post_types', 10, 2 );

The first problem was related to the lack of post_type passed with the request. My permalink was filtering by category name and post ID, but was posttype-agnostic which interfered with WordPress. By fetching the post by its ID and setting the post_type, things went back to normal.

The second problem was related to the permalinks for new posts, in the admin and anywhere around the frontend templates. Another filtering portion did the trick there and it works across the entire site.

Leave a Comment