Yes! The following (untested) script ends up with an array of category term objects in $terms
. They represent all category terms which are applied to all published ‘courses’ post types.
//$wpd->prepare() is normally required here, except we are not using any variables in our query
$courses = $wpdb->get_col("SELECT ID FROM $wpdb->posts WHERE post_status ='publish' AND post_type ='courses';", 'ARRAY_N');
$terms = wp_get_object_terms( $courses, 'category');
You would then need to loop through each term object to generate a dropdown form field. Unfortunately, this by itself would not organize the terms hierarchically, we cannot even order the results by parent. To get the organization you want, in the first loop, parse the terms into another array structure where the keys are parent IDs.
Step through the parent 0 (top level) terms in the new array. For each term, output an option block, then loop through any child terms, which are keyed to the top level term’s ID. It is this loop from which the children dropdown option blocks are generated. Something like this, very much untested:
$sorted = array();
foreach ( $terms as $term ) {
$sorted[$term->parent][] = $term;
}
echo "<select>";
foreach ( $sorted[0] as $term ) {
echo "<option value='{$term->term_id}'>{$term->name}</option>";
foreach ( $sorted[$term->term_id] as $child ) {
echo "<option value='{$child->term_id}'>- {$child->name}</option>";
}
}
echo "</select>";
Don’t be surprised if this does not work on first attempt. Some tweaking and debugging is likely required. Still, I believe the underlying logic is sound.