I am trying to use categories to provide the structure for my site. I will create sections on my site called England, Wales, Scotland and Ireland . These will be categories. My posts will use custom post types – Video, Guide, Event, Clubs etc.

So, if I create a post called ‘Trouble down south’ using the ‘Video’ CPT, attach it to the ‘England’ category, I would like the slug format to be:

category-name/custom-post-type-name/post-name

e.g. mysite.com/england/video/trouble-down-south

I would also like the following permalinks to work like so –

mysite.com/england/ – show all posts in that category regardless of CPT

mysite.com/video/ – show all Video CPT posts regardless of category

Sticking with my example, I create my Video CPT like so:

add_action( 'init', 'register_cpt_video' );

function register_cpt_video() {

    $labels = array( 
        'name' => _x( 'videos', 'video' ),

        some code...

    );

    $args = array( 

        some code...

        'taxonomies' => array( 'category' ),

        some code...

        'has_archive' => true,
        'rewrite' => array( 
            'slug' => 'video', 
            'with_front' => false,
            'feeds' => true,
            'pages' => true
        ),
        'capability_type' => 'post'
    );

    register_post_type( 'video', $args );
}

This almost does what I need. I get the correct listings at

mysite.com/england (i.e category archive page)

and

mysite.com/video (i.e CPT archive page)

Unfortunately when I click through to the actual post, the slug shows as

mysite.com/video/trouble-down-south

I would like

mysite.com/england/video/trouble-down-south

I’ve tried using the following rewrite slug

'slug' => '%category%/video',

This does not work. %category% gets literally inserted into the url without being converted into the actual category name.

I have searched this site for a solution but haven’t found any. Most related answers seem to address appending taxonomies to the CPT slug.

i.e mysite.com/video/england/trouble-down-south

They seem to achieve this by explicitly prefixing the CPT name to the taxonomy rewrite slug which exploits the cascade-like nature of the rewrite rules (plus some other wizardry I don’t understand.)

Anyhow, nothing I have found has addressed my requirement of category-name/custom-post-type-name/post-name, plus the archive pages I require.

Any ideas how to achieve my desired permalink structure?

2 Answers
2

You have to filter 'post_type_link' to create the appropriate permalink. See this answer for a similar example.

The following code works:

add_action( 'init', array ( 'WPSE_63343', 'init' ) );

class WPSE_63343
{
    protected $post_type="video";

    public static function init()
    {
        new self;
    }

    public function __construct()
    {
        $args = array (
            'public' => TRUE,
            'rewrite' => array(
                'slug' => '[^/]+/' . $this->post_type .'/([^/]+)/?$'
            ),
            'has_archive' => TRUE,
            'supports' => array( 'title', 'editor' ),
            'taxonomies' => array( 'category' ),
            'labels' => array (
                'name' => 'Video',
                'singular_name' => 'Video'
            )
        );
        register_post_type( $this->post_type, $args );

        add_rewrite_rule(
            '[^/]+/' . $this->post_type .'/([^/]+)/?$',
            'index.php?post_type=" . $this->post_type . "&pagename=$matches[1]',
            'top'
        );

        // Inject our custom structure.
        add_filter( 'post_type_link', array ( $this, 'fix_permalink' ), 1, 2 );

        # uncomment for debugging
        # add_action( 'wp_footer', array ( $this, 'debug' ) );
    }

    /**
     * Print debug data into the 404 footer.
     *
     * @wp-hook wp_footer
     * @since   2012.09.04
     * @return  void
     */
    public function debug()
    {
        if ( ! is_404() )
        {
            return;
        }

        global $wp_rewrite, $wp_query;

        print '<pre>' . htmlspecialchars( print_r( $wp_rewrite, TRUE ) ) . '</pre>';
        print '<pre>' . htmlspecialchars( print_r( $wp_query, TRUE ) ) . '</pre>';
    }

    /**
     * Filter permalink construction.
     *
     * @wp-hook post_type_link
     * @param  string $post_link default link.
     * @param  int    $id Post ID
     * @return string
     */
    public function fix_permalink( $post_link, $id = 0 )
    {
        $post = &get_post( $id );
        if ( is_wp_error($post) || $post->post_type != $this->post_type )
        {
            return $post_link;
        }
        // preview
        empty ( $post->slug )
        and ! empty ( $post->post_title )
        and $post->slug = sanitize_title_with_dashes( $post->post_title );

        $cats = get_the_category( $post->ID );

        if ( ! is_array( $cats ) or ! isset ( $cats[0]->slug ) )
        {
            return $post_link;
        }

        return home_url(
            user_trailingslashit( $cats[0]->slug . "https://wordpress.stackexchange.com/" . $this->post_type . "https://wordpress.stackexchange.com/" . $post->slug )
        );
    }
}

Don’t forget to visit the permalink settings once to refresh the stored rewrite rules.

Leave a Reply

Your email address will not be published. Required fields are marked *