Take the 2-minute tour ×
WordPress Development Stack Exchange is a question and answer site for WordPress developers and administrators. It's 100% free, no registration required.

I am trying to set up a multi-level custom post type structure with permalinks that look like authors/books/chapters, with authors, books, and chapters all set up as their own custom post type. For example, a typical URL on this site might look like example.com/authors/stephen-king/the-shining/chapter-3/

Each chapter can only belong to one book, and each book can only belong to one author. I've considered using taxonomies instead of CPTs for authors and books, but I need to associate metadata with each item and I prefer the post interface for this.

I'm most of the way there by simply setting up each custom post as a child of an entry in the CPT one level up. For example, I create "Chapter 3" and assign "The Shining" as a parent using a custom meta-box. "The Shining" in turn has "Stephen King" as a parent. I haven't had any trouble creating these relationships.

I'm using rewrite tags in the CPT slugs and the permalinks want to work, but they're not quite right. Using a re-write analyzer, I can see that the rewrite rules are actually generated, but they don't seem to be in the right order and so other rules are processed first.

Here's a screenshot of my rewrite analyzer.

Here's how I've registered my CPTs:

function cpt_init() {

  $labels = array(
    'name' => 'Authors'
   );

  $args = array(
    'labels' => $labels,
    'public' => true,
    'publicly_queryable' => true,
    'show_ui' => true, 
    'show_in_menu' => true, 
    'query_var' => true,
    'rewrite' => array(
        'slug' => 'author',
        'with_front' => FALSE,
    ),
    'with_front' => false,
    'capability_type' => 'post',
    'has_archive' => false, 
    'hierarchical' => true,
    'menu_position' => null,
    'supports' => array( 'title', 'editor' )
  ); 

  register_post_type('authors',$args);

  $labels = array(
    'name' => 'Books'
  );

  $args = array(
    'labels' => $labels,
    'public' => true,
    'publicly_queryable' => true,
    'show_ui' => true, 
    'show_in_menu' => true, 
    'query_var' => true,
    'rewrite' => array(
        'slug' => 'author/%authors%',
        'with_front' => FALSE,
    ),
    'with_front' => false,
    'capability_type' => 'post',
    'has_archive' => false, 
    'hierarchical' => true,
    'menu_position' => null,
    'supports' => array( 'title', 'editor' )
  ); 

  register_post_type('books',$args);


  $labels = array(
    'name' => 'Chapters'
   );

  $args = array(
    'labels' => $labels,
    'public' => true,
    'publicly_queryable' => true,
    'show_ui' => true, 
    'show_in_menu' => true, 
    'query_var' => true,
    'rewrite' => array(
        'slug' => 'author/%authors%/%books%',
        'with_front' => FALSE,
    ),
    'with_front' => FALSE,
    'capability_type' => 'post',
    'has_archive' => false, 
    'hierarchical' => true,
    'menu_position' => null,
    'supports' => array( 'title', 'editor' )
  ); 

  register_post_type('chapters',$args);

}

add_action( 'init', 'cpt_init' );

So is there any way to change the priority of my rewrite rules so that authors, books, and chapters are all matched first?

I also know that I'm going to have to add a post_type_link filter, but that seems secondary to getting the permalinks right in the first place. If anyone knows where I can find a comprehensive overview of how that filter works, it would be appreciated.

share|improve this question
    
Have you considered just using Pages? You would get the right permalink structure automatically. –  Michael Hampton Aug 11 '12 at 5:55
    
I've definitely considered it. The problem with pages is that we may have 100 grandchildren items for a single author, which will be very hard to manage in the pages admin. Also, we need to be able to query by post type. –  Dalton Aug 11 '12 at 21:08
    
I'm working on a solution but I have a quick question: is it key to have 'author' in front of each permalink? Because that seems to be the stickler. I think it's confusing WordPress a little too much, especially since that's a permalink slug also used for WP author pages. If having 'author' is required, I think the whole thing is still do-able... it will just be more complicated. –  Rachel Carden Aug 13 '12 at 20:04
    
Oops, I didn't realize that author was going to conflict with the built-in WP author slug. No, that's not required, it could be anything. I assumed I needed something there because it's a CPT, but it could just as easily be "writers" or anything else. –  Dalton Aug 13 '12 at 20:23
    
I realized the confusion actually lies with the CPTs sharing 'author' as the base slug. Once you set 'author' as the slug for the CPT 'authors', and then you set 'author/%author%' for the CPT 'books' and 'author/%author%/%book%' for the CPT 'chapters', then WordPress thinks the posts for 'books' and the posts for 'chapters' are literally hierarchical children posts for 'authors'. Does that make sense? In my testing you could keep 'author' as the base for the CPT 'authors' and it works just fine. So replace my previous question with: do you need 'author' or can the slug start with %author%? –  Rachel Carden Aug 13 '12 at 20:32

3 Answers 3

up vote 6 down vote accepted
+50

If you want to keep 'authors' as the base slug in the permalinks, i.e. example.com/authors/stephen-king/ for the 'authors' CPT, example.com/authors/stephen-king/the-shining/ for the 'books' CPT and example.com/authors/stephen-king/the-shining/chapter-3/ for the 'chapters' CPT, WordPress will think pretty much everything is an 'authors' post or a hierarchical child of an 'authors' post and, since that is not the case, WordPress ultimately becomes very confused.

With that said, there's a workaround that is quite basic but as long as your permalink structure always follows the same order, i.e. the word 'authors' is always followed by an author slug, which is always followed by a book slug which is always followed by a chapter slug, then you should be good to go.

In this solution, there's no need to define the rewrite slug in the custom post type definition for 'chapters' and 'books', but set the 'authors' rewrite slug as simply 'authors', place the following code in your functions.php file and "flush" your rewrite rules.

add_action( 'init', 'my_website_add_rewrite_tag' );
function my_website_add_rewrite_tag() {
    // defines the rewrite structure for 'chapters', needs to go first because the structure is longer
    // says that if the URL matches this rule, then it should display the 'chapters' post whose post name matches the last slug set
    add_rewrite_rule( '^authors/([^/]*)/([^/]*)/([^/]*)/?','index.php?chapters=$matches[3]','top' );
    // defines the rewrite structure for 'books'
    // says that if the URL matches this rule, then it should display the 'books' post whose post name matches the last slug set
    add_rewrite_rule( '^authors/([^/]*)/([^/]*)/?','index.php?books=$matches[2]','top' );   
}

// this filter runs whenever WordPress requests a post permalink, i.e. get_permalink(), etc.
// we will return our custom permalink for 'books' and 'chapters'. 'authors' is already good to go since we defined its rewrite slug in the CPT definition.
add_filter( 'post_type_link', 'my_website_filter_post_type_link', 1, 4 );
function my_website_filter_post_type_link( $post_link, $post, $leavename, $sample ) {
    switch( $post->post_type ) {

        case 'books':

            // I spoke with Dalton and he is using the CPT-onomies plugin to relate his custom post types so for this example, we are retrieving CPT-onomy information. this code can obviously be tweaked with whatever it takes to retrieve the desired information.
            // we need to find the author the book belongs to. using array_shift() makes sure only one author is allowed
            if ( $author = array_shift( wp_get_object_terms( $post->ID, 'authors' ) ) ) {
                if ( isset( $author->slug ) ) {
                    // create the new permalink
                    $post_link = home_url( user_trailingslashit( 'authors/' . $author->slug . '/' . $post->post_name ) );
                }
            }

            break;

        case 'chapters':

            // I spoke with Dalton and he is using the CPT-onomies plugin to relate his custom post types so for this example, we are retrieving CPT-onomy information. this code can obviously be tweaked with whatever it takes to retrieve the desired information.
            // we need to find the book it belongs to. using array_shift() makes sure only one book is allowed
            if ( $book = array_shift( wp_get_object_terms( $post->ID, 'books' ) ) ) {

                // now to find the author the book belongs to. using array_shift() makes sure only one author is allowed
                $author = array_shift( wp_get_object_terms( $book->term_id, 'authors' ) );

                if ( isset( $book->slug ) && $author && isset( $author->slug ) ) {
                    // create the new permalink
                    $post_link = home_url( user_trailingslashit( 'authors/' . $author->slug . '/' . $book->slug . '/' . $post->post_name ) );
                }

            }

            break;

    }
    return $post_link;
}

Learn more about the CPT-onomies plugin

share|improve this answer
    
This works perfectly, thank you! I feel like I just learned so much. The CPT-onomies plugin is really cool, too. wordpress.org/extend/plugins/cpt-onomies –  Dalton Aug 14 '12 at 9:50
    
I have a feeling you might come across some obstacles as your "library" grows but I already have some code in mind so, if you do, just let me know. –  Rachel Carden Aug 14 '12 at 14:04
    
@RachelCarden What do you do when two books have the same title but different authors? There will be a collision in the rewritten URL! How do you solve this? –  Segfault May 29 '13 at 13:58
1  
@Segfault You will have to retrieve all of the author slugs so you can hard code them into the rewrite rule(s): foreach( $author_slugs as $author_slug ) { add_rewrite_rule( '^authors/' . $author_slug . '/([^/]*)/([^/]*)/?','index.php?authors=' . $author_slug . '&chapters=$matches[2]','top' ); add_rewrite_rule( '^authors/' . $author_slug . '/([^/]*)/?','index.php?authors=' . $author_slug . '&books=$matches[1]','top' ); } –  Rachel Carden May 29 '13 at 14:05
    
@Segfault You can use get_terms(), if you're using a CPT-onomy, or get_posts() to retrieve the post names/slugs. –  Rachel Carden May 29 '13 at 14:15

The rules will get added to the extra_rules_top of WP_Rewrite in the order that the extra permastructs are added. So, switching the order that you register the post types will switch the order of the rewrite rules get generated making the chapter rewrite get matched first. However, since you're using the query_var from the other post_types the wp_query may end up matching one of those as the queried post name before matching the chapter like you want.

I would create new rewrite tags to represent the placeholders for the parent author and parent-book, ie:

add_rewrite_tag('%parent-book%', '([^/]+)', 'parent_book=');

When doing this, you'll have to filter 'query_vars' to make 'parent_book' public. Then you'll need to add a filter to pre_get_posts that will convert the name set as the parent_book query_var into the post_id and set it as the 'post_parent'.

share|improve this answer
    
Could you please provide code examples for the filters you mentioned? Also, what would the rewrite tag look like for the grandchild CPT? –  Dalton Aug 11 '12 at 21:10

I don't have personal experience with such a scenario, but Randy Hoyt did a presentation at WordCamp San Fran last weekend about "Subordinate Post Types" that sounds like what you're talking about.

Here's his page for the talk that includes his presentation slides and links to a plugin he built for working with subordinate post types: http://randyhoyt.com/wordpress/subordinate-post-types/

share|improve this answer
    
Thanks, this looks like a good resource. It's not clear whether this supports grandchildren relationships, though (it doesn't seem to in my testing) and it doesn't actually help with the permalinks. I've already figured out a way to set up my child/parent relationships (although this is a pretty nice way to do it), but the permalinks are really the issue I'm stuck on now. –  Dalton Aug 11 '12 at 21:06

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.