Handle category name URL rewrite before different post type slugs

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