Come ottenere tutti i figli e nipoti di un custom post type gerarchico?
Ho bisogno di ottenere tutti i sotto-post di un ID genitore specifico (root).
get_posts( array( 'numberposts' => -1, 'post_status' => 'publish', 'post_type' => 'microsite', 'post_parent' => $root_parent_id, 'suppress_filters' => false ) );
WP-Codex: la funzione get_post() ha il parametro post_parent ma non child_of.
Il vantaggio della funzione get_pages() in combinazione con il parametro child_of è "... Nota che il parametro child_of recupererà anche i 'nipoti' dell'ID dato, non solo i discendenti diretti."*

Dovrai iterare su quei post e poi eseguire più query per ogni post, ripetendo finché non trovi più post in una query.
Ad esempio:
function get_posts_children($parent_id){
$children = array();
// recupera i figli del post
$posts = get_posts( array( 'numberposts' => -1, 'post_status' => 'publish', 'post_type' => 'microsite', 'post_parent' => $parent_id, 'suppress_filters' => false ));
// ora recupera i nipoti
foreach( $posts as $child ){
// ricorsione!! evviva
$gchildren = get_posts_children($child->ID);
// unisci i nipoti nell'array dei figli
if( !empty($gchildren) ) {
$children = array_merge($children, $gchildren);
}
}
// unisci i discendenti diretti trovati prima
$children = array_merge($children,$posts);
return $children;
}
// esempio di utilizzo, chiamiamola e stampiamo i risultati
$descendants = get_posts_children($post->ID);
echo '<pre>';
print_r($descendants);
echo '</pre>';
Sì, la funzione sopra richiama sé stessa, è una funzione ricorsiva. Continuerà a chiamarsi finché non raggiunge un punto in cui il post esaminato non ha figli, poi tornerà senza chiamarsi e l'intero stack risalirà costruendo l'array dei figli. Faresti bene a fare ulteriori ricerche in questo ambito.
Nota che c'è un costo intrinseco in ciò che vuoi fare, indipendentemente dal fatto che tu usi funzioni ricorsive o meno, legato a quanti livelli di post hai. 5 livelli di post saranno più costosi di 2, e la scalabilità non è lineare. Potresti voler usare i transient per memorizzare nella cache l'output a seconda di come lo implementi.
Un altro modo per ridurre il costo è limitare la profondità dell'albero dei post da esaminare, ad esempio solo fino ai nipoti ma non ai pronipoti. Questo può essere fatto passando un parametro di profondità e decrementandolo ad ogni chiamata ricorsiva, assicurandosi di restituire un array vuoto all'inizio se la profondità è 0 o inferiore. Molti tutorial sulle funzioni ricorsive usano questo come esempio.

Il problema con questo codice è che non fornisce un ordinamento corretto; ottengo prima tutti gli elementi di primo livello, poi tutti quelli di secondo livello in un unico array appiattito. Qualche idea su come risolvere?

Avevo un requisito simile ma dovevo preservare la gerarchia, quindi la risposta di Tom mi ha dato un buon punto di partenza:
function get_post_offspring($post)
{
// Funzione per scorrere programmaticamente i discendenti di un post
// Ottieni i figli diretti del post corrente
// e aggiungili a un nuovo elemento dell'oggetto chiamato children
$post->children = get_children(array("post_parent" => $post->ID, "post_type" => "page", "post_status" => "publish"));
// se il post non ha figli, restituisci semplicemente il post
// con children come array vuoto
if (empty($post->children)) {
return $post;
}
foreach ($post->children as $child) {
// Per ogni figlio di questo post...ottieni i figli
$children = get_children(array("post_parent" => $child->ID, "post_type" => "page", "post_status" => "publish"));
if (!empty($children)) {
// se questo post ha figli...allora chiama nuovamente questa funzione
// per assegnare l'elemento children a questo post e scendere ulteriormente
// nella discendenza di questo post
$child = get_post_offspring($child);
}
}
return $post;
}
Ora avevo anche bisogno di attraversare la lista con un mantenendo l'ordine e il livello.
function render_list_items($children)
{
foreach ($children as $child) {
$has_children = !empty($child->children);
echo "<li>";
echo "<a href='" . get_permalink($child) . "'>" . $child->post_title . "</a";
if ($has_children) {
// Se il post ha figli, crea un nuovo <ul> dentro l'attuale
// <li> e richiama la funzione nuovamente
echo "<ul>";
echo render_list_items($child->children);
echo "</ul>";
}
echo "</li>";
}
}
Quindi, per mettere tutto insieme ho qualcosa come:
global $post;
$post_id = $post->ID;
// Ottieni gli antenati del post corrente dove l'ultimo elemento dell'array
// è il genitore più in alto
$ancestors = get_post_ancestors($post->ID);
$root_id = array_pop($ancestors);
// Ora abbiamo il post radice di questo post e scendiamo per prendere tutta la sua discendenza
$root_post = get_post($root_id);
$root_children = get_post_offspring($root_post);

Basta usare get_page_children()
. Funziona per ogni tipo di post (non solo per le pagine) ed è essenzialmente quello che @TomJNowell ha mostrato nell'altra domanda, ma già implementato nel core.
$children = get_page_children( $post->ID, $GLOBALS['wp_query'] );
L'esempio sopra è come nel Codex. Ecco perché puoi semplicemente prendere l'oggetto query globale (o qualsiasi altro oggetto query) da usare come base di ricerca.

Utilizza questo shortcode per visualizzare tutti i figli e nipoti in una vista gerarchica. Utilizzo: [my_children_list] oppure [my_children_list page_id=123]
function my_children_list_func($atts, $content = null) {
global $post;
$a = shortcode_atts( array(
'page_id' => ''
), $atts );
$args = array(
'numberposts' => -1,
'post_status' => 'publish',
'post_type' => 'microsite',
'post_parent' => (isset($a['page_id']) && $a['page_id']) ? $a['page_id'] : $post->ID,
'suppress_filters' => false
);
$parent = new WP_Query( $args );
ob_start();
if ( $parent->have_posts() ) :?>
<ul>
<?php while ( $parent->have_posts() ) : $parent->the_post(); ?>
<li><a href="<?php the_permalink(); ?>" title="<?php the_title(); ?>"><?php the_title(); ?></a>
<?php echo do_shortcode('[tadam_children_list page_id='.get_the_ID().']') ?>
</li>
<?php endwhile;?>
</ul>
<?php endif; wp_reset_postdata();
return ob_get_clean();
}
add_shortcode( 'my_children_list', 'my_children_list_func' );
