This only takes care of two levels. You could make a recursive function, if you have 'unlimited' levels.
$custom_terms = get_terms('custom_taxonomy');
/**
* Loop through all taxonomies
*/
foreach($custom_terms as $custom_term):
// Reset query (for good measures sake)
wp_reset_query();
// Set the args (getting ready to get posts that are in the given taxonomy)
$outer_args = array(
'post_type' => 'custom_post_type',
'tax_query' => array(
array(
'taxonomy' => 'custom_taxonomy',
'field' => 'slug',
'terms' => $custom_term->slug,
'post_parent' => 0 // This means that it's only top-level taxonomies included
)
)
);
// Get the information for the outer loop.
$outer_loop = new WP_Query( $outer_args );
if($outer_loop->have_posts()):
echo '<h2>'.$custom_term->name.'</h2>';
// Loop through outer loop
while( $outer_loop->have_posts() ):
$outer_loop->the_post();
$outer_loop_ID = get_the_ID();
// Display OUTER loop info:
echo '<a href="'.get_permalink().'">'.get_the_title().'</a><br>';
/**
* Inner loop
*/
wp_reset_query();
$inner_args = array('post_type' => 'custom_post_type',
'tax_query' => array(
array(
'taxonomy' => 'custom_taxonomy',
'field' => 'slug',
'terms' => $custom_term->slug,
'post_parent' => $outer_loop_ID // This gets the posts that has the outer loops post as parent
)
)
);
$inner_loop = new WP_Query($inner_args);
if($inner_loop->have_posts()):
// Display inner loop information
echo '<h2>'.$custom_term->name.'</h2>';
while($inner_loop->have_posts()) :
$inner_loop->the_post();
// Display INNER loop info:
echo '<a href="'.get_permalink().'">'.get_the_title().'</a><br>';
endwhile; // Inner loop
endif; // if($inner_loop->have_posts()) {
endwhile; // Outer loop
endif; // if($outer_loop->have_posts()):
endforeach; // foreach($custom_terms as $custom_term):