Come mostrare una lista gerarchica di termini?
Ho una tassonomia gerarchica chiamata 'geographical locations'. Contiene continenti al primo livello e poi i paesi per ciascuno di essi. Esempio:
Europa
- Irlanda
- Spagna
- Svezia
Asia
- Laos
- Thailandia
- Vietnam
ecc.
Usando get_terms() sono riuscito a mostrare l'elenco completo dei termini, ma i continenti si mescolano con i paesi in un'unica lista piatta.
Come posso generare una lista gerarchica come quella mostrata sopra?

Capisco che questa sia una domanda molto vecchia, ma se hai la necessità di costruire una vera e propria struttura di termini, questo potrebbe essere un metodo utile per te:
/**
* Ordina ricorsivamente un array di termini tassonomici in modo gerarchico. Le categorie figlie verranno
* posizionate sotto un membro 'children' del loro termine genitore.
* @param Array $cats oggetti dei termini tassonomici da ordinare
* @param Array $into array risultato in cui inserirli
* @param integer $parentId l'ID del genitore corrente in cui inserirli
*/
function sort_terms_hierarchically(Array &$cats, Array &$into, $parentId = 0)
{
foreach ($cats as $i => $cat) {
if ($cat->parent == $parentId) {
$into[$cat->term_id] = $cat;
unset($cats[$i]);
}
}
foreach ($into as $topCat) {
$topCat->children = array();
sort_terms_hierarchically($cats, $topCat->children, $topCat->term_id);
}
}
L'utilizzo è il seguente:
$categories = get_terms('my_taxonomy_name', array('hide_empty' => false));
$categoryHierarchy = array();
sort_terms_hierarchically($categories, $categoryHierarchy);
var_dump($categoryHierarchy);

Questa è davvero una buona soluzione. Cambierei solo una cosa: $into[$cat->term_id] = $cat;
in $into[] = $cat;
Avere l'ID del termine come chiave dell'array è fastidioso (non puoi ottenere facilmente il primo elemento usando la chiave 0) e inutile (stai già memorizzando l'oggetto $cat
e puoi ottenere l'id usando la proprietà term_id
.

Se come me stai cercando di applicare questa funzione a un sotto-livello di categorie, dovrai passare l'ID del livello in cui ti trovi attualmente per farla funzionare. Ma funziona benissimo, grazie @popsi.

Utilizza wp_list_categories
con l'argomento 'taxonomy' => 'taxonomy'
, è costruito per creare liste gerarchiche di categorie ma supporta anche l'uso di una tassonomia personalizzata.
Esempio dal Codex:
Visualizza termini in una tassonomia personalizzata
Se la lista risulta piatta, è possibile che tu abbia bisogno di un po' di CSS per aggiungere spaziatura alle liste, in modo da poter vedere la loro struttura gerarchica.

Non conosco alcuna funzione che faccia quello che vuoi, ma puoi costruire qualcosa di simile a questo:
<ul>
<?php $hiterms = get_terms("my_tax", array("orderby" => "slug", "parent" => 0)); ?>
<?php foreach($hiterms as $key => $hiterm) : ?>
<li>
<?php echo $hiterm->name; ?>
<?php $loterms = get_terms("my_tax", array("orderby" => "slug", "parent" => $hiterm->term_id)); ?>
<?php if($loterms) : ?>
<ul>
<?php foreach($loterms as $key => $loterm) : ?>
<li><?php echo $loterm->name; ?></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
</li>
<?php endforeach; ?>
</ul>
Non ho testato questo codice ma puoi capire l'idea. Quello che il codice sopra farà è darti solo due livelli
MODIFICA: ah sì, puoi usare wp_list_categories() per fare quello che cerchi.

In realtà questo è piuttosto utile, dato che ho bisogno di avere link personalizzati (con un parametro GET) sui link dei termini, cosa che non sembra possibile con il metodo wp_list_categories()
.

Sì, questo metodo ti darà più controllo sull'output. Ma potresti fare un bel po' di trova e sostituisci sull'output di wp_list_categories()
per aggiungere i tuoi parametri GET. O ancora meglio, creare un filtro per la funzione per aggiungere le parti che desideri. Non chiedermi come si fa perché non sono ancora riuscito a capirlo :(

Suggerirei di usare un walker di categorie personalizzato con wp_list_categories
se vuoi un maggiore controllo sull'output, renderà il tuo codice molto più riutilizzabile.

Il seguente codice genererà un menu a tendina con i termini, ma può anche generare qualsiasi altro elemento/struttura modificando la variabile $outputTemplate e le righe str_replace:
function get_terms_hierarchical($terms, $output = '', $parent_id = 0, $level = 0) {
//Template di output
$outputTemplate = '<option value="%ID%">%PADDING%%NAME%</option>';
foreach ($terms as $term) {
if ($parent_id == $term->parent) {
//Sostituzione delle variabili del template
$itemOutput = str_replace('%ID%', $term->term_id, $outputTemplate);
$itemOutput = str_replace('%PADDING%', str_pad('', $level*12, ' '), $itemOutput);
$itemOutput = str_replace('%NAME%', $term->name, $itemOutput);
$output .= $itemOutput;
$output = get_terms_hierarchical($terms, $output, $term->term_id, $level + 1);
}
}
return $output;
}
$terms = get_terms('taxonomy', array('hide_empty' => false));
$output = get_terms_hierarchical($terms);
echo '<select>' . $output . '</select>';

Ho utilizzato il codice di @popsi che funzionava molto bene e l'ho reso più efficiente e facile da leggere:
/**
* Ordina ricorsivamente un array di termini tassonomici in modo gerarchico. Le categorie figlie verranno
* posizionate sotto un membro 'children' del loro termine genitore.
* @param Array $cats oggetti dei termini tassonomici da ordinare
* @param integer $parentId l'ID del genitore corrente in cui inserirli
*/
function sort_terms_hierarchicaly(Array $cats, $parentId = 0)
{
$into = [];
foreach ($cats as $i => $cat) {
if ($cat->parent == $parentId) {
$cat->children = sort_terms_hierarchicaly($cats, $cat->term_id);
$into[$cat->term_id] = $cat;
}
}
return $into;
}
Utilizzo :
$sorted_terms = sort_terms_hierarchicaly($terms);

Mentre cercavo la stessa cosa ma per ottenere i termini di un post, alla fine ho compilato questo codice, che funziona per me.
Cosa fa:
• Ottiene tutti i termini di una tassonomia specifica per un determinato post.
• Per una tassonomia gerarchica con due livelli (es: livello1:'paese' e livello2:'città'), crea un h4 con il livello1 seguito da una lista ul di livello2 e questo per tutti gli elementi di livello1.
• Se la tassonomia non è gerarchica, crea solo una lista ul di tutti gli elementi.
Ecco il codice (l'ho scritto per me quindi ho cercato di renderlo il più generico possibile ma...):
function finishingLister($heTerm){
$myterm = $heTerm;
$terms = get_the_terms($post->ID,$myterm);
if($terms){
$count = count($terms);
echo '<h3>'.$myterm;
echo ((($count>1)&&(!endswith($myterm, 's')))?'s':"").'</h3>';
echo '<div class="'.$myterm.'Wrapper">';
foreach ($terms as $term) {
if (0 == $term->parent) $parentsItems[] = $term;
if ($term->parent) $childItems[] = $term;
};
if(is_taxonomy_hierarchical( $heTerm )){
foreach ($parentsItems as $parentsItem){
echo '<h4>'.$parentsItem->name.'</h4>';
echo '<ul>';
foreach($childItems as $childItem){
if ($childItem->parent == $parentsItem->term_id){
echo '<li>'.$childItem->name.'</li>';
};
};
echo '</ul>';
};
}else{
echo '<ul>';
foreach($parentsItems as $parentsItem){
echo '<li>'.$parentsItem->name.'</li>';
};
echo '</ul>';
};
echo '</div>';
};
};
Quindi alla fine chiami la funzione con questo (ovviamente, sostituisci my_taxonomy con la tua): finishingLister('my_taxonomy');
Non pretendo che sia perfetto ma come ho detto funziona per me.

Ho avuto questo problema e nessuna delle risposte qui ha funzionato per me, per un motivo o per l'altro.
Ecco la mia versione aggiornata e funzionante.
function locationSelector( $fieldName ) {
$args = array('hide_empty' => false, 'hierarchical' => true, 'parent' => 0);
$terms = get_terms("locations", $args);
$html = '';
$html .= '<select name="' . $fieldName . '"' . 'class="chosen-select ' . $fieldName . '"' . '>';
foreach ( $terms as $term ) {
$html .= '<option value="' . $term->term_id . '">' . $term->name . '</option>';
$args = array(
'hide_empty' => false,
'hierarchical' => true,
'parent' => $term->term_id
);
$childterms = get_terms("locations", $args);
foreach ( $childterms as $childterm ) {
$html .= '<option value="' . $childterm->term_id . '">' . $term->name . ' > ' . $childterm->name . '</option>';
$args = array('hide_empty' => false, 'hierarchical' => true, 'parent' => $childterm->term_id);
$granchildterms = get_terms("locations", $args);
foreach ( $granchildterms as $granchild ) {
$html .= '<option value="' . $granchild->term_id . '">' . $term->name . ' > ' . $childterm->name . ' > ' . $granchild->name . '</option>';
}
}
}
$html .= "</select>";
return $html;
}
E utilizzo:
$selector = locationSelector('locationSelectClass');
echo $selector;

Questa soluzione è meno efficiente del codice di @popsi, poiché effettua una nuova query per ogni termine, ma è anche più semplice da utilizzare in un template. Se il tuo sito utilizza la cache, potresti, come me, non preoccuparti del leggero sovraccarico sul database.
Non è necessario preparare un array che verrà riempito ricorsivamente con i termini. Basta chiamarla nello stesso modo in cui chiameresti get_terms() (la forma non deprecata con solo un array come argomento). Restituisce un array di oggetti WP_Term
con una proprietà aggiuntiva chiamata children
.
function get_terms_tree( Array $args ) {
$new_args = $args;
$new_args['parent'] = $new_args['parent'] ?? 0;
$new_args['fields'] = 'all';
// I termini per questo livello
$terms = get_terms( $new_args );
// I figli di ogni termine su questo livello
foreach( $terms as &$this_term ) {
$new_args['parent'] = $this_term->term_id;
$this_term->children = get_terms_tree( $new_args );
}
return $terms;
}
L'utilizzo è semplice:
$terms = get_terms_tree([ 'taxonomy' => 'my-tax' ]);

Un ultimo colpo di grazia a questo cavallo (simile alle risposte sopra) con un po' più di raffinatezza... che ti permette anche di trasformare la lista in una lista gerarchico-piatta con un parametro depth
.
Inoltre, questa funzione utilizza clone
per evitare di modificare l'array originale.
/**
* Ordina una lista di oggetti termini in modo gerarchico o gerarchico-piatto.
*/
function sort_terms_hierarchically( $terms, $parent_id = 0, $flat = FALSE, $depth = 0 ) {
$data = [];
foreach ( $terms as $term ) {
if ( $parent_id != $term->parent ) continue;
$data_term = clone $term;
$data_term->depth = $depth;
$children = sort_terms_hierarchically( $terms, $term->term_id, $flat, $depth + 1 );
if ( ! $flat ) $data_term->children = $children;
$data[] = $data_term;
if ( $flat ) foreach( $children as $child ) $data[] = $child;
}
return $data;
}
function sort_terms_hierarchically_flat( $terms, $parent_id = 0 ) {
return sort_terms_hierarchically( $terms, $parent_id, TRUE );
}
L'utilizzo è il seguente:
$terms = get_terms( [
'taxonomy' => 'regioni',
'hide_empty' => FALSE,
] );
$terms_sorted = sort_terms_hierarchically( $terms );
$terms_sorted_flat = sort_terms_hierarchically_flat( $terms );

Assicurati che hierarchical=true
sia passato alla tua chiamata get_terms()
.
Nota che hierarchical=true
è il valore predefinito, quindi in realtà assicurati solo che non sia stato sovrascritto con false
.

Commenti una risposta lasciata quasi due anni fa? Davvero? In realtà, è una risposta proposta, anche se formulata come domanda. Devo modificarla per farla diventare un'affermazione, piuttosto che una domanda?

Qui ho una lista dropdown a quattro livelli con il primo elemento nascosto
<select name="lokalizacja" id="ucz">
<option value="">Tutte le località</option>
<?php
// Ottiene il termine escluso tramite slug 'podroze'
$excluded_term = get_term_by('slug', 'podroze', 'my_travels_places');
$args = array(
'orderby' => 'slug',
'hierarchical' => 'true',
'exclude' => $excluded_term->term_id,
'hide_empty' => '0',
'parent' => $excluded_term->term_id,
);
$hiterms = get_terms("my_travels_places", $args);
foreach ($hiterms AS $hiterm) :
echo "<option value='".$hiterm->slug."'".($_POST['my_travels_places'] == $hiterm->slug ? ' selected="selected"' : '').">".$hiterm->name."</option>\n";
// Ottiene i termini di secondo livello
$loterms = get_terms("my_travels_places", array("orderby" => "slug", "parent" => $hiterm->term_id,'hide_empty' => '0',));
if($loterms) :
foreach($loterms as $key => $loterm) :
echo "<option value='".$loterm->slug."'".($_POST['my_travels_places'] == $loterm->slug ? ' selected="selected"' : '')."> - ".$loterm->name."</option>\n";
// Ottiene i termini di terzo livello
$lo2terms = get_terms("my_travels_places", array("orderby" => "slug", "parent" => $loterm->term_id,'hide_empty' => '0',));
if($lo2terms) :
foreach($lo2terms as $key => $lo2term) :
echo "<option value='".$lo2term->slug."'".($_POST['my_travels_places'] == $lo2term->slug ? ' selected="selected"' : '')."> - ".$lo2term->name."</option>\n";
endforeach;
endif;
endforeach;
endif;
endforeach;
?>
</select>
<label>Seleziona il tipo di luogo</label>
<select name="rodzaj_miejsca" id="woj">
<option value="">Tutti i tipi</option>
<?php
// Ottiene tutti i termini della tassonomia my_travels_places_type
$theterms = get_terms('my_travels_places_type', 'orderby=name');
foreach ($theterms AS $term) :
echo "<option value='".$term->slug."'".($_POST['my_travels_places_type'] == $term->slug ? ' selected="selected"' : '').">".$term->name."</option>\n";
endforeach;
?>
</select>

Penso che la logica sia che si tratta di un problema correlato. Ho trovato questo post cercando di capire come ottenere una lista di controllo gerarchica simile alle categorie e sono tentato di aggiungere una risposta qui ora che ho capito come fare. Non lo farò però perché, come hai sottolineato, non risponde alla domanda originale.
