I have just set up a post/parent relationship between a post type "episodes" and a post type "cartoon-series."

I used this bit of code to add in the meta box to assign the parent from another post type:

add_action('admin_menu', function() {
    remove_meta_box('pageparentdiv', 'episodes', 'normal');
});
add_action('add_meta_boxes', function() {
    add_meta_box('episodes-parent', 'Cartoon Series', 'episodes_attributes_meta_box', 'episodes', 'side', 'default');
});

function episodes_attributes_meta_box($post) {
    $post_type_object = get_post_type_object($post->post_type);
    if ( $post_type_object->hierarchical ) {
        $pages = wp_dropdown_pages(array('post_type' => 'cartoon-series', 'selected' => $post->post_parent, 'name' => 'parent_id', 'show_option_none' => __('(no parent)'), 'sort_column'=> 'menu_order, post_title', 'echo' => 0));
        if ( ! empty($pages) ) {
            echo $pages;
        } // end empty pages check
    } // end hierarchical check.
}

That worked on the admin screen in allowing me to set the series as a parent to the episode, but when I try to view the post, I get a 404. The url structure is:

domain/episodes/series-name/episode-name

The url for the series is:

domain/cartoon-series/series-name

I'd like the url for the episode to be:

domain/cartoon-series/series-name/episode-name

What am I missing? Is it possible to make an entire post type the child of another post type? So, then I could even get the url for the episodes list to be:

domain/cartoon-series/series-name/episodes

Thanks! Matt


As requested, here is the code for the two custom post types in question:

$labels = array(
    "name" => "Cartoon Series",
    "singular_name" => "Cartoon Series",
    "menu_name" => "Cartoon Series",
    "all_items" => "All Cartoon Series",
    "add_new" => "Add New",
    "add_new_item" => "Add New Cartoon Series",
    "edit" => "Edit",
    "edit_item" => "Edit Cartoon Series",
    "new_item" => "New Cartoon Series",
    "view" => "View",
    "view_item" => "View Cartoon Series",
    "search_items" => "Search Cartoon Series",
    "not_found" => "No Cartoon Series Found",
    "not_found_in_trash" => "No Cartoon Series Found in Trash",
    "parent" => "Parent Cartoon Series",
    );

$args = array(
    "labels" => $labels,
    "description" => "",
    "public" => true,
    "show_ui" => true,
    "has_archive" => true,
    "show_in_menu" => true,
    "exclude_from_search" => false,
    "capability_type" => "post",
    "map_meta_cap" => true,
    "hierarchical" => true,
    "rewrite" => array( "slug" => "cartoon-series", "with_front" => true ),
    "query_var" => true,
    "supports" => array( "title", "revisions", "thumbnail" ),           );
register_post_type( "cartoon-series", $args );

$labels = array(
    "name" => "Episodes",
    "singular_name" => "Episode",
    );

$args = array(
    "labels" => $labels,
    "description" => "",
    "public" => true,
    "show_ui" => true,
    "has_archive" => true,
    "show_in_menu" => true,
    "exclude_from_search" => false,
    "capability_type" => "post",
    "map_meta_cap" => true,
    "hierarchical" => true,
    "rewrite" => array( "slug" => "episodes", "with_front" => true ),
    "query_var" => true,
    "supports" => array( "title", "revisions", "thumbnail" ),           );
register_post_type( "episodes", $args );

I'm using the CPT UI plugin, so I can't edit that code directly. That is just the export code CPT UI provides.

I don't have any other code that links the two CPTs. Maybe that's what I'm missing. I just found that code online that places the metabox on the page to do the linking. Is it not enough to do the job? Looks like it sets the post_parent.

Thanks! Matt

  • I'm sorry but I was wrong. The parent-child relationship is correctly set. The meta box is not using a meta field (that is what confused me at first time), it use parent_id query var and it doesn't need more to code to set the relationship. The problem is that the URL generated is not recognized by WordPress. I've been trying to find a rewrite rule that make it work but I've had not success. I'm now investigating a solution. – cybmeta Mar 14 '15 at 18:19
  • After some investigation, I think you can not get it to work as the way you want. Having a post type as parent of other post type seems not be possible. Well, it is possible, with your code that relationship it is actually set, but seeing the child post dosen't work in the frontend. I've tried rewrite rules and hooking into pre_get_posts to alter the query with no success, someting more complicated is involved that I've not been able to understand. Like having a cat be the parent of a dog. I suggest to use only one hierarchical post type or set the realtionship using meta fields. – cybmeta Mar 14 '15 at 19:12
  • I think that one hirarchical post type fits perfectly your situation. – cybmeta Mar 14 '15 at 19:19
  • 1
    I am really trying NOT to be complicated with this. If a more elegant solution is available, I am all ears. I am new to WP in general and have done pretty well so far, but this one has stumped me. Typically, I would just make the cartoon series a category and assign it to the episode. The problem is, I also have other nested data other than episodes to go under the cartoon series. So, it seems like cartoon series would need to be a CPT as well. It's complicated! :-D Can you explain to me what you mean by using only one hierarchical post type? – Mattaton Mar 14 '15 at 20:40
up vote 7 down vote accepted

Finally I've found a working solution. The cartoon-series can be registered as you did but episodes custom post types can not be hirarchical (I think WordPress expects parent content be the same type as child content if the relationship is set using post_parent in wp_posts database table).

When registering episodes, the rewrite rule must be set to the slug you want, that is cartoon-series/%series_name%. Then we can filter the episodes link to replace %series_name% with actual name of the parent cartoon-series post type and a rewrite rule to say to WordPress when a cartoon-series post type is requested and when is a episodes.

add_action('init', function(){
    $labels = array(
        "name" => "Cartoon Series",
        "singular_name" => "Cartoon Series",
        "menu_name" => "Cartoon Series",
        "all_items" => "All Cartoon Series",
        "add_new" => "Add New",
        "add_new_item" => "Add New Cartoon Series",
        "edit" => "Edit",
        "edit_item" => "Edit Cartoon Series",
        "new_item" => "New Cartoon Series",
        "view" => "View",
        "view_item" => "View Cartoon Series",
        "search_items" => "Search Cartoon Series",
        "not_found" => "No Cartoon Series Found",
        "not_found_in_trash" => "No Cartoon Series Found in Trash",
        "parent" => "Parent Cartoon Series",
    );

    $args = array(
        "labels" => $labels,
         "description" => "",
        "public" => true,
        "show_ui" => true,
        "has_archive" => true,
        "show_in_menu" => true,
        "exclude_from_search" => false,
        "capability_type" => "post",
        "map_meta_cap" => true,
        "hierarchical" => true,
        "rewrite" => array( "slug" => "cartoon-series", "with_front" => true ),
        "query_var" => true,
        "supports" => array( "title", "revisions", "thumbnail" )
    );

    register_post_type( "cartoon-series", $args );

    $labels = array(
        "name" => "Episodes",
        "singular_name" => "Episode",
    );

    $args = array(
        "labels" => $labels,
        "description" => "",
        "public" => true,
        "show_ui" => true,
        "has_archive" => true,
        "show_in_menu" => true,
        "exclude_from_search" => false,
        "capability_type" => "post",
        "map_meta_cap" => true,
        "hierarchical" => false,
        "rewrite" => array( "slug" => "cartoon-series/%series_name%", "with_front" => true ),
        "query_var" => true,
        "supports" => array( "title", "revisions", "thumbnail" )
    );

    register_post_type( "episodes", $args );

});

add_action('add_meta_boxes', function() {
    add_meta_box('episodes-parent', 'Cartoon Series', 'episodes_attributes_meta_box', 'episodes', 'side', 'default');
});

function episodes_attributes_meta_box($post) {
        $pages = wp_dropdown_pages(array('post_type' => 'cartoon-series', 'selected' => $post->post_parent, 'name' => 'parent_id', 'show_option_none' => __('(no parent)'), 'sort_column'=> 'menu_order, post_title', 'echo' => 0));
        if ( ! empty($pages) ) {
            echo $pages;
        } // end empty pages check
}

add_action( 'init', function() {

    add_rewrite_rule( '^cartoon-series/(.*)/([^/]+)/?$','index.php?episodes=$matches[2]','top' );

});

add_filter( 'post_type_link', function( $link, $post ) {
    if ( 'episodes' == get_post_type( $post ) ) {
        //Lets go to get the parent cartoon-series name
        if( $post->post_parent ) {
            $parent = get_post( $post->post_parent );
            if( !empty($parent->post_name) ) {
                return str_replace( '%series_name%', $parent->post_name, $link );
            }
        } else {
            //This seems to not work. It is intented to build pretty permalinks
            //when episodes has not parent, but it seems that it would need
            //additional rewrite rules
            //return str_replace( '/%series_name%', '', $link );
        }

    }
    return $link;
}, 10, 2 );

NOTE: Remember to flush rewrite rules after saving the above code and before to try it. Go to wp-admin/options-permalink.php and click save to regerenate the rewrite rules.

NOTE 2: It is probably that more rewrite rules have to be added, for example to work for paginate posts. Also it may need some more work in order to have a complete solution, for example, when deleting a cartoon-series delete also all child episodes? Add a filter in admin edit screen to filter episodes by post parent? Modify the episodes title in admin edit screen to show the parent series name?

  • Thanks for looking into this! It seems that the code you have posted is dropping the name of the cartoon series from the url. Instead of replacing %series_name% with the name of the episode, %series_name% should be the name of the parent of the episode. The episode name would be after that. For some reason, the Cartoon Series box is not being populated for me to select a parent. That's why I thought episodes needed to be hierarchical. Trying to figure out why. – Mattaton Mar 14 '15 at 20:58
  • Yes, episodes has to be hierarchical in order for the Cartoon Series meta box to be populated. – Mattaton Mar 14 '15 at 20:59
  • With episodes hierarchical so I can set the parent, the url just got worse. With the slug as you suggested, I get the series name in the url twice. So, instead of domain/episodes/series-name/episode-name like before, I got domain/episodes/series-name/series-name/episode-name – Mattaton Mar 14 '15 at 21:03
  • As I said, episodes can not be hierarchical. I modified the meta box code to be populated with non-hierarchical post types. Use the exact code I posted, I tested it and it is working. If you use another code I can not know what is wrong. Just copy and paste the code from the answer and test it. You may need to disable CPT UI plugin or, at least, delete the custom post types from the plugin as they are registered in the code. – cybmeta Mar 14 '15 at 21:06
  • Ah, my apologies, I had quickly scanned and thought that part was the same. You're right, the page does load now and the url looks correct. – Mattaton Mar 14 '15 at 21:10

No need for HARD-CODING in this case... You can simply use this plugin:

https://wordpress.org/plugins/add-hierarchy-parent-to-post/

You can even grab code from it. However, it may not be a full solution.

You will need to write your own URL parsing code for that as wordpress needs to know the type of the post it tries to retrieve from the DB based on the url structure and your url structure do no give any hint to this.

This is not something that is very easy to do with the rewrite rules API of wordpress, but there is nothing that prevents you bypassing the rewrite mechanism and parsing the urls by yourself. Something like 1. run wordpress rewite rules. If a content was found display it and exit 2. get the first part of the url, check if there is a post matching that slug with the expected post type 3. loop on the rest parts of the URL verify that the posts exist and are in the right type. 4. if everything matches display the last post found, else display a 404 page

Your Answer

 
discard

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

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