Custom post type structure for posts with multiple child posts

I’m developing a site for an African language translation company and am currently trying to think of the best way to structure the site

I have a custom post type of “Languages” set up, with posts like Arabic, Somali, Swahili etc but each of these posts need “sub” posts within them i.e History, Where is it spoken, Dialects, Alphabet & Writing system

Read More

What would be the best way of setting this up? There are going to be around 140 languages so I don’t think setting up individual posts for the 3/4 sub pages for each language is a good way to go as we’d be looking at over 500+ posts

I’m wondering if I can set up “base” posts for the 3/4 sub pages seeing as the titles will always be unique, just the content will be different

Related posts

1 comment

  1. I’ve waited for quite a while for an answer to this and have had to continue on with the project so thought I would answer myself

    First off I set up the custom post type of language, then hooked in to the publish_language action to programatically add child posts like so:

    function ta_insert_child_posts($post_id) {  
        if(($_POST['post_status'] == 'publish') && ($_POST['original_post_status'] != 'publish')) {
            $post = get_post($post_id);
    
            // Make sure it's a top level language being published
            if($post->post_parent == 0) {
                // Create our array of child post titles
                $child_posts = array('History', 'Where is it spoken', 'Also known as', 'Dialects', 'Alphabet & Writing System');
    
                foreach($child_posts as $child_post_title) {
                    // Insert each new post as a child of the new language
                    wp_insert_post(array(
                        'post_title' => $child_post_title,
                        'post_parent' => $post_id,
                        'post_type' => 'language',
                        'post_status' => $post->post_status 
                    ));
                }
            }
        }
    }
    add_action('publish_language', 'ta_insert_child_posts');
    

    Next, I had to add in logic to delete/trash child posts when their parent was deleted/trashed by hooking in to before_delete_post and trash_language

    function ta_delete_child_posts($post_id) {
        global $post_type;
    
        if($post_type != 'language') return;
    
        $child_posts = get_posts(array('post_parent' => $post_id, 'post_type' => 'language'));
    
        if(is_array($child_posts)) {
            foreach($child_posts as $child_post) {
                wp_delete_post($child_post->ID, true);
            }
        }
    }
    add_action('before_delete_post', 'ta_delete_child_posts');
    
    function ta_trash_child_posts($post_id) {
        $child_posts = get_posts(array('post_parent' => $post_id, 'post_type' => 'language'));
    
        if(is_array($child_posts)) {
            foreach($child_posts as $child_post) {
                wp_trash_post($child_post->ID);
            }
        }
    }
    add_action('trash_language', 'ta_trash_child_posts');
    

    Ok so we now have child posts being published and deleted in sync with their parent language. Next I had to ensure only top level languages were being pulled through in the admin ui language list so I hooked in to the request action:

    function ta_modify_request($request) {
        if(is_admin()) {
            $screen = get_current_screen();
    
            // We only want to retrieve top level language posts in the main request
            if($screen->post_type == 'language') {
               $request['post_parent'] = 0;
            }
        }
    
        return $request;
    }
    add_action('request', 'ta_modify_request');
    

    Lastly, I had to inject some custom CSS and JavaScript by hooking in to admin_footer which added an expand/contract link to each language, with an ajax call to a function which gets child posts of the selected language and displays them in the standard wordpress table format:

    function ta_child_posts_scripts() {
        $screen = get_current_screen();
    
        if($screen->post_type == 'language') {
    ?>
        <style type="text/css">
            #the-list tr .sorting-indicator {top:10px;position:relative;margin-top:0;cursor:pointer}
            #the-list tr .sorting-indicator.show:before {content:''}
            #the-list tr:hover .sorting-indicator {display:inline-block}
        </style>
        <script type="text/javascript">
            jQuery(function($) {
                $('#the-list tr .row-title').each(function() {
                    $(this).after('<span class="sorting-indicator show" title="Show Child Posts"></span>');
                });
    
                $('#the-list tr .sorting-indicator').on('click', function() {
                    var tr = $(this).parents('tr');
    
                    if($(this).hasClass('show')) {
                        var data = {
                            action: 'ta_child_posts',
                            post_id: tr.attr('id')
                        };
    
                        $.post(ajaxurl, data, function(response) {
                            $(response).hide().insertAfter(tr).fadeIn();
                        });
    
                        $(this).removeClass('show').addClass('hide');
    
                    } else {
                        tr.nextUntil('.level-0').fadeOut(function() { $(this).remove(); });
    
                        $(this).removeClass('hide').addClass('show');
                    }
                });           
    
            });
        </script>
    <?php
        }
    }
    
    add_action('admin_footer', 'ta_child_posts_scripts');
    

    With this in place, all I had left to do was the add in the ajax callback function to get the child posts based on the selected language:

    if(is_admin() && !class_exists('WP_List_Table')){
        require_once( ABSPATH . 'wp-admin/includes/class-wp-list-table.php' );
        require_once( ABSPATH . 'wp-admin/includes/class-wp-posts-list-table.php' );
    }
    
    function ta_get_child_posts() {
    
        if(empty($_POST['post_id'])) return;
    
        $post_id = explode('-', $_POST['post_id']);
    
        if(!isset($post_id[1])) return;
    
        $post_id = (int)$post_id[1];
    
        // Get child posts of the selected post
        $child_posts = get_posts(array('post_parent' => $post_id, 'post_type' => 'language'));
    
        set_current_screen('language');
    
        $ta_table = new WP_Posts_List_Table(array('screen' => get_current_screen()));
    
        $ta_table->prepare_items();
    
        // Since WP_List_Table provides no way to return its data we print the output with display_rows but catch it in an output buffer
        ob_start();
    
        $ta_table->display_rows($child_posts, 1);
        $rows = ob_get_clean();
    
        // Return the rows to the ajax callback
        die(print($rows));
    }
    
    add_action('wp_ajax_ta_child_posts', 'ta_get_child_posts');
    

    I hope this helps out a future googler with a similar issue or anyone else browsing this website

Comments are closed.