¿Cómo mostrar una lista jerárquica de términos?

13 abr 2011, 17:23:30
Vistas: 69.2K
Votos: 49

Tengo una taxonomía jerárquica llamada 'ubicaciones geográficas'. Contiene continentes en el primer nivel, y luego los países para cada uno. Ejemplo:

Europa
- Irlanda
- España
- Suecia
Asia
- Laos
- Tailandia
- Vietnam

etc.

Usando get_terms() logré mostrar la lista completa de términos, pero los continentes se mezclan con los países en una gran lista plana.

¿Cómo puedo mostrar una lista jerárquica como la de arriba?

1
Comentarios

En caso de que alguien necesite una LISTA DE VERIFICACIÓN jerárquica (no es la pregunta aquí pero está relacionada para personas que construyen interfaces personalizadas para taxonomías jerárquicas), la mejor respuesta es usar wp_terms_checklist() con tu taxonomía personalizada.

jerclarke jerclarke
23 feb 2016 18:27:48
Todas las respuestas a la pregunta 12
3
64

Me doy cuenta de que esta es una pregunta muy antigua, pero si necesitas construir una estructura real de términos, este podría ser un método útil para ti:

/**
 * Ordena recursivamente un array de términos de taxonomía jerárquicamente. Las categorías hijas se
 * colocarán bajo un miembro 'children' de su término padre.
 * @param Array   $cats     objetos de términos de taxonomía para ordenar
 * @param Array   $into     array de resultado donde colocarlos
 * @param integer $parentId el ID padre actual donde colocarlos
 */
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);
    }
}

El uso es el siguiente:

$categories = get_terms('my_taxonomy_name', array('hide_empty' => false));
$categoryHierarchy = array();
sort_terms_hierarchically($categories, $categoryHierarchy);

var_dump($categoryHierarchy);
15 may 2013 09:54:30
Comentarios

Esto es realmente bueno. Cambiaría una cosa: $into[$cat->term_id] = $cat; por $into[] = $cat; Tener el ID del término como clave del array es molesto (no puedes obtener el primer elemento fácilmente usando la clave 0) e innecesario (ya estás almacenando el objeto $cat y puedes obtener el id usando la propiedad term_id.

Nahuel Nahuel
7 mar 2017 17:03:31

Si como yo estás intentando aplicar esta función a un subnivel de categorías, necesitarás pasar el ID del nivel en el que te encuentras actualmente para que funcione. Pero funciona muy bien, gracias @popsi.

Ben Everard Ben Everard
16 ago 2018 13:19:47

funciona, gracias

Luca Reghellin Luca Reghellin
12 nov 2019 18:35:57
1
24

Usa wp_list_categories con el argumento 'taxonomy' => 'taxonomía', está diseñado para crear listas jerárquicas de categorías pero también soporta el uso de taxonomías personalizadas.

Ejemplo del Codex:
Mostrar términos en una taxonomía personalizada

Si la lista aparece plana, es posible que solo necesites un poco de CSS para agregar relleno a las listas y así poder ver su estructura jerárquica.

13 abr 2011 17:38:04
Comentarios

¿Se podría revertir esto? Mostrar los hijos primero...

Arg Geo Arg Geo
6 mar 2016 09:57:09
3
14

No conozco ninguna función que haga exactamente lo que buscas, pero puedes construir algo como esto:

<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>

No he probado este código pero puedes entender la idea. Lo que hace el código anterior es mostrarte solo dos niveles de términos.

EDITADO: ah sí, puedes usar wp_list_categories() para lograr lo que buscas.

13 abr 2011 17:39:21
Comentarios

De hecho, esto es bastante útil, ya que necesito tener enlaces personalizados (con un parámetro GET) en los enlaces de términos, lo cual no parece posible con la forma de hacerlo de wp_list_categories().

mike23 mike23
13 abr 2011 18:04:23

Sí, este método te dará más control sobre tu salida. Pero podrías hacer un buen trabajo de buscar y reemplazar en la salida de wp_list_categories() para añadir tus parámetros GET. O incluso mejor, construir un filtro para la función y añadir las partes que quieras. No me preguntes cómo se hace eso porque todavía no he podido entenderlo :(

Scott Scott
13 abr 2011 18:19:04

Yo sugeriría usar un walker de categorías personalizado con wp_list_categories si quieres mayor control sobre la salida, hará que tu código sea mucho más reutilizable.

t31os t31os
13 abr 2011 19:09:53
0

Puedes usar wp_list_categories(), con el argumento 'taxonomy'.

13 abr 2011 17:37:03
0

El siguiente código generará un menú desplegable con términos, pero también puede generar cualquier otro elemento/estructura editando la variable $outputTemplate y modificando las líneas de str_replace:

function get_terms_hierarchical($terms, $output = '', $parent_id = 0, $level = 0) {
    //Plantilla de salida
    $outputTemplate = '<option value="%ID%">%PADDING%%NAME%</option>';

    foreach ($terms as $term) {
        if ($parent_id == $term->parent) {
            //Reemplazando las variables de la plantilla
            $itemOutput = str_replace('%ID%', $term->term_id, $outputTemplate);
            $itemOutput = str_replace('%PADDING%', str_pad('', $level*12, '&nbsp;&nbsp;'), $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>';  
13 ene 2016 13:18:11
0

Utilicé el código de @popsi que funcionaba muy bien y lo hice más eficiente y fácil de leer:

/**
 * Ordena recursivamente un array de términos de taxonomía de forma jerárquica. Las categorías hijas se
 * colocarán bajo un miembro 'children' de su término padre.
 * @param Array   $cats     objetos de términos de taxonomía para ordenar
 * @param integer $parentId el ID padre actual para colocarlos
 */
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;
}

Uso:

$sorted_terms = sort_terms_hierarchicaly($terms);
9 jul 2018 15:44:10
0

Mientras buscaba lo mismo pero para obtener los términos de una entrada, finalmente compilé esto y me funciona.

Lo que hace:
• Obtiene todos los términos de una taxonomía específica para una entrada determinada.
• Para una taxonomía jerárquica con dos niveles (ej: nivel1:'país' y nivel2:'ciudades'), crea un h4 con el nivel1 seguido de una lista ul de nivel2, y esto para todos los elementos de nivel1.
• Si la taxonomía no es jerárquica, creará solo una lista ul con todos los elementos. Aquí está el código (lo escribí para mí, así que intenté que fuera lo más genérico posible, pero...):

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>';
    };
};

Finalmente, llamas a la función con esto (obviamente, reemplazas my_taxonomy por el tuyo): finishingLister('my_taxonomy');

No pretendo que sea perfecto, pero como dije, funciona para mí.

21 feb 2013 17:55:39
0

Tuve este problema y ninguna de las respuestas aquí funcionó para mí, por una razón u otra.

Aquí está mi versión actualizada y que funciona.

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;
}

Y su uso:

$selector = locationSelector('locationSelectClass');
echo $selector;
30 nov 2013 12:35:57
0

Esta solución es menos eficiente que el código de @popsi, ya que realiza una nueva consulta por cada término, pero también es más fácil de usar en una plantilla. Si tu sitio web utiliza caché, puede que, como yo, no te importe la pequeña sobrecarga en la base de datos.

No necesitas preparar un array que se llenará recursivamente con términos. Simplemente lo llamas de la misma forma que llamarías a get_terms() (la forma no obsoleta con solo un array como argumento). Devuelve un array de objetos WP_Term con una propiedad adicional llamada children.

function get_terms_tree( Array $args ) {
    $new_args = $args;
    $new_args['parent'] = $new_args['parent'] ?? 0;
    $new_args['fields'] = 'all';

    // Los términos para este nivel
    $terms = get_terms( $new_args );

    // Los hijos de cada término en este nivel
    foreach( $terms as &$this_term ) {
        $new_args['parent'] = $this_term->term_id;
        $this_term->children = get_terms_tree( $new_args );
    }

    return $terms;
}

El uso es sencillo:

$terms = get_terms_tree([ 'taxonomy' => 'my-tax' ]);
4 may 2020 23:45:56
0

Un último golpe final a este caballo (similar a las respuestas anteriores) con un poco más de refinamiento... permitiéndote también transformar la lista en una lista jerárquicamente plana con un parámetro depth.

Adicionalmente, esta función utiliza clone para evitar modificar el arreglo original.

/**
 * Ordena una lista de objetos de términos jerárquicamente o de forma jerárquico-plana.
 */
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 );
}

El uso es el siguiente:

$terms = get_terms( [
    'taxonomy' => 'regiones',
    'hide_empty' => FALSE,
] );

$terms_sorted = sort_terms_hierarchically( $terms );
$terms_sorted_flat = sort_terms_hierarchically_flat( $terms );
22 ago 2023 18:48:33
4
-1

Asegúrate de que hierarchical=true se pase en tu llamada a get_terms().

Ten en cuenta que hierarchical=true es el valor por defecto, así que en realidad, solo asegúrate de que no haya sido sobrescrito para que sea false.

13 abr 2011 17:31:53
Comentarios

Hola Chip, sí, 'hierarchical' es 'true' por defecto.

mike23 mike23
13 abr 2011 17:37:19

¿Puedes proporcionar un enlace a un ejemplo en vivo de la salida?

Chip Bennett Chip Bennett
13 abr 2011 17:44:33

¿Comentando una respuesta dejada hace casi dos años? ¿En serio? En realidad, es una respuesta propuesta, aunque esté formulada como una pregunta. ¿Debería editarla para que sea una afirmación en lugar de una pregunta?

Chip Bennett Chip Bennett
12 feb 2013 01:22:18

get_terms() devolverá una lista completa de los términos (como mencionó el OP) pero no una lista jerárquica que muestre la relación padre/hijo como se solicitó.

jdm2112 jdm2112
17 mar 2016 07:00:22
2
-1

Aquí tengo una lista desplegable de cuatro niveles con el primer elemento oculto

<select name="lokalizacja" id="ucz">
            <option value="">Todas las ubicaciones</option>
            <?php
            // Obtener término excluido por slug
            $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,
            );              
            // Obtener términos de nivel superior
            $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";

                // Obtener términos de segundo nivel
                $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"' : '').">&nbsp;-&nbsp;".$loterm->name."</option>\n";

                    // Obtener términos de tercer nivel
                    $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"' : '').">&nbsp;&nbsp;&nbsp;&nbsp;-&nbsp;".$lo2term->name."</option>\n";



                        endforeach;
                    endif;

                    endforeach;
                endif;

            endforeach;
            ?>
         </select>
        <label>Selecciona el tipo de lugar</label>
        <select name="rodzaj_miejsca" id="woj">
            <option value="">Todos los tipos</option>
            <?php
            // Obtener todos los términos del tipo de lugar
            $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>
25 mar 2013 11:31:18
Comentarios

Por favor explica por qué eso podría resolver el problema.

fuxia fuxia
25 mar 2013 12:00:39

Creo que la lógica es que es un problema relacionado. Encontré esta publicación intentando descubrir cómo obtener una lista de verificación jerárquica al estilo de categorías y estoy tentado a agregar una respuesta aquí ahora que lo he descubierto. No lo haré porque, como señalas, no responde a la pregunta original.

jerclarke jerclarke
23 feb 2016 18:26:24