Taxonomía personalizada, get_the_terms, listado en orden de padre > hijo
Tengo una taxonomía jerárquica personalizada que puedo mostrar usando print_r(get_the_terms( $post->ID, 'taxonomic_rank' ));
:
Array
(
[46] => stdClass Object
(
[term_id] => 46
[name] => Aplocheilidae
[slug] => aplocheilidae
[term_group] => 0
[term_taxonomy_id] => 53
[taxonomy] => taxonomic_ranks
[description] =>
[parent] => 39
[count] => 1
[object_id] => 443
)
[47] => stdClass Object
(
[term_id] => 47
[name] => Aplocheilus
[slug] => aplocheilus
[term_group] => 0
[term_taxonomy_id] => 54
[taxonomy] => taxonomic_ranks
[description] =>
[parent] => 46
[count] => 1
[object_id] => 443
)
[39] => stdClass Object
(
[term_id] => 39
[name] => Cyprinodontiformes
[slug] => cyprinodontiformes
[term_group] => 0
[term_taxonomy_id] => 52
[taxonomy] => taxonomic_ranks
[description] =>
[parent] => 0
[count] => 1
[object_id] => 443
)
)
Esta taxonomía siempre tendrá la siguiente forma: Orden (padre) > Familia (hijo del Orden) > Sub-familia (hijo de la Familia)
¿Hay alguna manera rápida y fácil de mostrar estas taxonomías en el orden correcto, para que pueda imprimir la siguiente línea? Orden: <orden>, Familia: <familia>, Sub-familia: <sub-familia>
Gracias de antemano

Probablemente hay algunas formas mejores de hacer esto, pero siempre puedes usar tres simples bucles foreach
.
Escribí una función de ejemplo que hace bien el trabajo y debería servirte como un buen punto de partida:
function print_taxonomic_ranks( $terms = '' ){
// verificar entrada
if ( empty( $terms ) || is_wp_error( $terms ) || ! is_array( $terms ) )
return;
// establecer variables de id en 0 para fácil verificación
$order_id = $family_id = $subfamily_id = 0;
// obtener orden
foreach ( $terms as $term ) {
if ( $order_id || $term->parent )
continue;
$order_id = $term->term_id;
$order = $term->name;
}
// obtener familia
foreach ( $terms as $term ) {
if ( $family_id || $order_id != $term->parent )
continue;
$family_id = $term->term_id;
$family = $term->name;
}
// obtener subfamilia
foreach ( $terms as $term ) {
if ( $subfamily_id || $family_id != $term->parent )
continue;
$subfamily_id = $term->term_id;
$subfamily = $term->name;
}
// salida
echo "Orden: $order, Familia: $family, Subfamilia: $subfamily";
}
Colócala en tu archivo functions.php
y úsala en tus plantillas así:
print_taxonomy_ranks( get_the_terms( $post->ID, 'taxonomic_rank' ) );
NOTA: Recorrer el mismo array tres veces puede sonar un poco tonto, pero por otro lado es una solución rápida y fácil que es simple de leer, extender y mantener.

No podría estar más en deuda contigo. Es una respuesta perfecta, ¡y ojalá pudiera darte más reputación!! Si alguien pasa por esta publicación, por favor +1 para que Maugly pueda ser recompensado tanto como me gustaría :)

No hay problema. Me alegra haber podido ayudar :) También he actualizado el código en mi respuesta y agregué una verificación simple de entrada...

Maugly, gracias por tu respuesta, es casi exactamente lo que necesitaba. ¿Tienes alguna idea sobre cómo usarlo con los enlaces de archivo todavía adjuntos a los términos? Gracias de nuevo.

@Adam Echa un vistazo a get_term_link()

Tema un poco antiguo pero creo que sigue siendo relevante porque sigue siendo un verdadero dolor de cabeza.
Estoy usando esta función recursiva que toma dos arrays como referencias. Creará un array con la estructura:
[term_id] => term_object->children->child_terms_array->children->child_terms_array
.
<?php
function sort_terms_hierarchically( array &$terms, array &$into, $parent_id = 0 ) {
foreach ( $terms as $i => $term ) {
if ( $term->parent == $parent_id ) {
$into[$term->term_id] = $term;
unset( $terms[ $i ] );
}
}
foreach ( $into as $top_term ) {
$top_term->children = array();
$this->sort_terms_hierarchically( $terms, $top_term->children, $top_term->term_id );
}
}
$terms = get_the_terms( 'taxslug', $post );
$sorted_terms = array();
sort_terms_hierarchically( $terms, $sorted_terms );
// Registrará los arrays anidados de objetos de términos.
error_log( print_r( $sorted_terms, true ) );
Esta es la única solución que he encontrado que mantiene los objetos de términos y funciona con cualquier nivel de anidamiento.

Aunque el enfoque de Maugly parece un poco más legible, ejecutar un bucle 3 veces sobre el array no me parece correcto. Así que aquí hay otro enfoque que podría ser menos legible para algunos, pero funciona sin ejecutar el bucle 3 veces.
function print_taxonomy_ranks( $terms ) {
// si los términos no son un array o está vacío, no continuar
if ( ! is_array( $terms ) || empty( $terms ) ) {
return false;
}
foreach ( $terms as $term ) {
// si el término tiene un padre, establecer el término hijo como atributo en el término padre
if ( $term->parent != 0 ) {
$terms[$term->parent]->child = $term;
} else {
// registrar el término padre
$parent = $term;
}
}
echo "Orden: $parent->name, Familia: {$parent->child->name}, Sub-Familia: {$parent->child->child->name}";
}

¡Buen trabajo! Sabía que era posible, simplemente no podía entenderlo en ese momento :) ¡Me gusta tu solución!

Advertencia de voto negativo: Creando objeto por defecto desde un valor vacío

@Dev ¿podrías explicar más? ¿De qué objeto estás hablando? Aunque es una respuesta de hace 9 años, me encantaría saber a qué objeto te refieres

Tuve una situación donde una publicación podía tener múltiples grupos de categorías, y múltiples hijos dentro de categorías padre, por lo que quería que mi jerarquía reflejara eso. También solo quería unas pocas líneas de código:
$terms = get_the_terms($id, 'department_categories');
foreach($terms as $key => $term){
if($term->parent != 0){
$terms[$term->parent]->children[] = $term;
unset($terms[$key]);
}
}
Básicamente, después de encontrar el padre de una categoría, lo mueve al objeto padre como un hijo, y luego lo elimina de su posición original en el array. He probado esto usando múltiples hermanos, hijos y diferentes niveles de categorías.
¡Espero que alguien más encuentre esto útil en caso de que solo estén buscando orientación lógica en lugar de un "plugin"!

@LucasB Intentaré recordar :P Tenemos un post, y sabemos que su $post->ID es $id y "department_categories" sería la taxonomía.
Simplemente obtiene todos los términos asociados al post y reestructura el array de resultados para que tenga más sentido jerárquico. Una vez reestructurado, puedes mostrarlo como prefieras.

Inspirado por la respuesta de Michal Mau y la respuesta de Manny Fleurmond, aquí está mi solución: Tengo el mismo problema que @Lucas Bustamante: ¿Qué pasa si la publicación tiene dos categorías principales?
Mi solución fue crear otro array de objetos, comparando el term_id
y verificando la clave parent
function print_taxonomic_ranks( $terms ){
if ( ! is_array( $terms ) || empty( $terms ) ) {
return false;
}
$parent_terms = array();
// obtener solo padres
foreach ( $terms as $term ) {
if ($term->parent === 0) {
$term->child = Array();
$parent_terms[] = $term;
}
}
// comparar y anidar
foreach ( $terms as $term ) {
if ($term->parent != 0) {
foreach ($parent_terms as $key => $value) {
if ($term->parent === $value->term_id) {
$parent_terms[$key]->child[] = $term;
}
}
}
}
// mostrar resultados
foreach ( $parent_terms as $term ) {
// término padre
echo '<span class="d-block">'.$term->name.'';
if ($term->child) {
$i = 1;
foreach ( $term->child as $child ) {
//echo '<span class="text-danger">'.$i.'</span>';
echo ($i <= 1)? ": " : "";
echo '<span class="font-weight-normal">'.$child->name.'</span>';
echo ($i < count($term->child))? ", " : "";
$i++;
}
}
echo '.<span>';
}
}
y se verá algo así:
¡Saludos!

Gracias Maugly,
Aquí está mi versión modificada de tu código que incluye los enlaces permanentes de términos por si alguien los necesita
function print_show_location( $terms = '' ){
// verificar entrada
if ( empty( $terms ) || is_wp_error( $terms ) || ! is_array( $terms ) )
return;
// establecer variables de id a 0 para fácil verificación
$country_id = $state_id = $city_id = 0;
// obtener país
foreach ( $terms as $term ) {
if ( $country_id || $term->parent )
continue;
$country_id = $term->term_id;
$country_slug = $term->slug;
$country = '<a href="'.get_term_link($country_slug, 'location').'">'.$term->name.'</a>';
}
// obtener estado/provincia
foreach ( $terms as $term ) {
if ( $state_id || $country_id != $term->parent )
continue;
$state_id = $term->term_id;
$state_slug = $term->slug;
$state = '<a href="'.get_term_link($state_slug, 'location').'">'.$term->name.'</a>';
}
// obtener ciudad
foreach ( $terms as $term ) {
if ( $city_id || $state_id != $term->parent )
continue;
$city_id = $term->term_id;
$city_slug = $term->slug;
$city = '<a href="'.get_term_link($city_slug, 'location').'">'.$term->name.'</a>';
}
// salida
echo "$city, $state - $country";
}

Hay un par de funciones recursivas que uso dependiendo de mis necesidades. Ambas requieren la lista de términos, un ID de padre inicial y un array inicial (generalmente vacío) donde colocar la lista resultante.
PRIMERA VERSIÓN
$terms = [una lista de objetos de términos, usa get_terms o lo que sea]
$ordered_terms = array(); // aquí encontrarás tus términos ordenados, desde la raíz hasta el hijo final
function list_terms_by_parent($parent_id = 0, &$terms, &$ordered_terms){
$root_parent = $parent_id;
foreach($terms as $index => $term){
if($term->parent == (int) $parent_id){
$ordered_terms[$term->term_id] = $term;
$root_parent = $term->term_id;
unset($terms[$index]);
}
}
if(!empty($terms)) list_terms_by_parent($root_parent, $terms, $ordered_terms);
}
SEGUNDA VERSIÓN
$term_ids = [debería ser una lista de IDs en la forma child_id => parent_id]
// una forma rápida de obtener ese tipo de lista es usar WP_Term_query, ejemplo:
//$terms_query = new WP_Term_Query(array(
// 'taxonomy' => 'product_categories'
// ,'object_ids' => $post->ID
// ,'hide_empty' => false
// ,'fields' => 'id=>parent'
//));
$ordered_terms = array(); // aquí encontrarás tus términos ordenados, desde la raíz hasta el hijo final
function list_term_ids_by_parent($parent_id = 0, &$term_ids, &$ordered_terms){
$child_id = array_search($parent_id, $term_ids);
if($child_id){
$ordered_terms[] = $child_id;
unset($term_ids[$child_id]);
}
if(!empty($term_ids)) order_terms($child_id, $term_ids, $ordered_terms);
}
TERCERA VERSIÓN
Esta no es mía, la encontré en algún lugar, probablemente aquí o en stackoverflow. Esta es un poco diferente en la salida, ya que generará una lista de términos en cascada, con solo una raíz, que tiene una propiedad children, que contendrá el siguiente término con su propiedad children y así sucesivamente...
// $ordered_terms se supone que es un array vacío como arriba
function sort_terms_hierarchically(&$terms, &$ordered_terms, $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);
}
}
