3

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?

4
  • Please add more details to your question: We have many solutions for this on our site (see the related questions in the sidebar). What have you tried, why didn’t it work?
    – fuxia
    Aug 28, 2012 at 15:19
  • I've updated my question as per your suggestion. Hopefully it's more clearer now. And yup, I've searched this site for over an hour but most of the related answers address the CPT/taxonomy/post-name permalink scheme. I tried wrangling the code I did find to suite my needs, but don't really know what I'm doing.
    – bob
    Aug 28, 2012 at 17:53
  • Well, I spent another hour or so proving to myself that I really need to work on my google-fu. I have failed to find anyone document the permalink scheme I'm trying to achieve. Usually that means I'm going about things the wrong way. Maybe I should try to layout my site structure a different, more WordPress-friendly way?
    – bob
    Aug 29, 2012 at 7:26

2 Answers 2

5

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 . '/' . $this->post_type . '/' . $post->slug )
        );
    }
}

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

3
  • Wow, cheers for helping me out. Seems to work as intended. I'll have a go at creating additional cpt's using your code as a template. Many thanks
    – bob
    Aug 29, 2012 at 18:31
  • Doh, when I use your code example it breaks standard posts and other post types I add categories to. Any ideas why?
    – bob
    Sep 4, 2012 at 14:45
  • @bob Interesting. See my update: you have to put the rewrite regex into the slug parameter. Looks a little bit strange, but it works now.
    – fuxia
    Sep 4, 2012 at 21:29
0
add_action( 'init', 'register_my_types' );
function register_my_types() {
    register_post_type( 'recipes',
        array(
            'labels' => array(
                'name' => __( 'Recipes' ),
                'singular_name' => __( 'Recipee' )
            ),
            'public' => true,
            'has_archive' => true,
        )
    );
    register_taxonomy( 'country', array( 'recipes' ), array( 
            'hierarchical' => true, 
            'label' => 'Country'
        )
    );
}
// Add our custom permastructures for custom taxonomy and post
add_action( 'wp_loaded', 'add_clinic_permastructure' );
function add_clinic_permastructure() {
    global $wp_rewrite;
    add_permastruct( 'country', 'recipes/%country%', false );
    add_permastruct( 'recipes', 'country/%country%/recipes/%recipes%', false );
}
// Make sure that all links on the site, include the related texonomy terms
add_filter( 'post_type_link', 'recipe_permalinks', 10, 2 );
function recipe_permalinks( $permalink, $post ) {
    if ( $post->post_type !== 'recipes' )
        return $permalink;
    $terms = get_the_terms( $post->ID, 'country' );

    if ( ! $terms )
        return str_replace( '%country%/', '', $permalink );
    //$post_terms = array();
    foreach ( $terms as $term )
        $post_terms = $term->slug;
    print_r($permalink);
    return str_replace( '%country%',  $post_terms , $permalink );
}
// Make sure that all term links include their parents in the permalinks
add_filter( 'term_link', 'add_term_parents_to_permalinks', 10, 2 );
function add_term_parents_to_permalinks( $permalink, $term ) {
    $term_parents = get_term_parents( $term );
    foreach ( $term_parents as $term_parent )
        $permlink = str_replace( $term->slug, $term_parent->slug . ',' . $term->slug, $permalink );
    return $permlink;
}
// Helper function to get all parents of a term
function get_term_parents( $term, &$parents = array() ) {
    $parent = get_term( $term->parent, $term->taxonomy );

    if ( is_wp_error( $parent ) )
        return $parents;

    $parents[] = $parent;
    if ( $parent->parent )
        get_term_parents( $parent, $parents );
    return $parents;
}

above code paste in theme function.php file and change the permalink option /%category/%postname%/ %

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.