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.

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.

Have you considered just using Pages? You would get the right permalink structure automatically.
–
Michael HamptonAug 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.
–
DaltonAug 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 CardenAug 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.
–
DaltonAug 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 CardenAug 13 '12 at 20:32

3 Answers
3

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;
}

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.
–
DaltonAug 11 '12 at 21:06

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'.