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