¿Es posible obtener get_terms por taxonomía Y post_type?

9 abr 2011, 03:38:03
Vistas: 27.8K
Votos: 18

Tengo 2 tipos de entradas personalizadas 'bookmarks' y 'snippets' y una taxonomía compartida 'tag'. Puedo generar una lista de todos los términos en la taxonomía con get_terms(), pero no puedo encontrar cómo limitar la lista al tipo de entrada. Básicamente, lo que estoy buscando es algo como esto:

get_terms(array('taxonomy' => 'tag', 'post_type' => 'snippet'));

¿Hay alguna manera de lograr esto? ¡¡Las ideas son muy apreciadas!!

Ah, estoy usando WP 3.1.1

0
Todas las respuestas a la pregunta 7
5
14

Resultó que necesitaba algo así para un proyecto en el que estoy trabajando. Simplemente escribí una consulta para seleccionar todos los posts de un tipo personalizado, luego verifiqué cuáles son los términos reales de mi taxonomía que están usando.

Luego obtuve todos los términos de esa taxonomía usando get_terms() y luego solo utilicé aquellos que estaban en ambas listas, lo envolví en una función y listo.

Pero luego necesité más que solo los IDs: necesitaba los nombres, así que agregué un nuevo argumento llamado $fields para poder indicarle a la función qué devolver. Después me di cuenta de que get_terms acepta muchos argumentos y mi función estaba limitada a simplemente términos que son utilizados por un tipo de post, así que agregué un condicional if más y ahí lo tienes:

La Función:

/* obtener términos limitados a un tipo de post 
 @ $taxonomies - (string|array) (requerido) Las taxonomías de las cuales recuperar los términos. 
 @ $args  -  (string|array) todos los argumentos posibles de get_terms http://codex.wordpress.org/Function_Reference/get_terms
 @ $post_type - (string|array) tipos de posts para limitar los términos
 @ $fields - (string) Qué devolver (por defecto 'all') acepta ID, name, all, get_terms. 
 si deseas usar argumentos de get_terms entonces $fields debe establecerse en 'get_terms'
*/
function get_terms_by_post_type($taxonomies,$args,$post_type,$fields = 'all'){
    $args = array(
        'post_type' => (array)$post_type,
        'posts_per_page' => -1
    );
    $the_query = new WP_Query( $args );
    $terms = array();
    while ($the_query->have_posts()){
        $the_query->the_post();
        $curent_terms = wp_get_object_terms( $post->ID, $taxonomy);
        foreach ($curent_terms as $t){
          //evitar duplicados
            if (!in_array($t,$terms)){
                $terms[] = $c;
            }
        }
    }
    wp_reset_query();
    //devolver array de objetos de términos
    if ($fields == "all")
        return $terms;
    //devolver array de IDs de términos
    if ($fields == "ID"){
        foreach ($terms as $t){
            $re[] = $t->term_id;
        }
        return $re;
    }
    //devolver array de nombres de términos
    if ($fields == "name"){
        foreach ($terms as $t){
            $re[] = $t->name;
        }
        return $re;
    }
    // obtener términos con argumentos de get_terms
    if ($fields == "get_terms"){
        $terms2 = get_terms( $taxonomies, $args );
        foreach ($terms as $t){
            if (in_array($t,$terms2)){
                $re[] = $t;
            }
        }
        return $re;
    }
}

Uso:

Si solo necesitas una lista de IDs de términos entonces:

$terms = get_terms_by_post_type('tag','','snippet','ID');

Si solo necesitas una lista de nombres de términos entonces:

$terms = get_terms_by_post_type('tag','','snippet','name');

Si solo necesitas una lista de objetos de términos entonces:

$terms = get_terms_by_post_type('tag','','snippet');

Y si necesitas usar argumentos adicionales de get_terms como: orderby, order, hierarchical ...

$args = array('orderby' => 'count', 'order' => 'DESC',  'hide_empty' => 1);
$terms = get_terms_by_post_type('tag',$args,'snippet','get_terms');

¡Disfrútalo!

Actualización:

Para corregir el conteo de términos para un tipo de post específico cambia:

foreach ($current_terms as $t){
          //evitar duplicados
            if (!in_array($t,$terms)){
                $terms[] = $t;
            }
        }

a:

foreach ($current_terms as $t){
    //evitar duplicados
    if (!in_array($t,$terms)){
        $t->count = 1;
        $terms[] = $t;
    }else{
        $key = array_search($t, $terms);
        $terms[$key]->count = $terms[$key]->count + 1;
    }
}
9 abr 2011 04:21:43
Comentarios

¿no sería mejor si usas (array) $args en lugar de una lista de 4 $vars? Esto te permitiría no preocuparte por el orden en que pasas los argumentos, así que algo como get_terms_by_post_type( $args = array( 'taxonomies', 'args', 'post_type', 'fields' => 'all') ) y luego llamarlos dentro de la función con $args['taxonomies']. Esto te ayudaría a evitar añadir valores vacíos y tener que recordar el orden de tus argumentos. También sugeriría usar comillas simples en lugar de dobles. He visto que son hasta cinco veces más rápidas.

kaiser kaiser
9 abr 2011 13:48:50

@kaiser - Las cadenas entre comillas dobles tienen que ser analizadas, mientras que los valores entre comillas simples siempre se tratan como literales. Cuando usas variables en una cadena tiene sentido y es perfectamente válido usar comillas dobles, pero para valores de cadena sin variables, las comillas simples son más ideales (porque no necesitan ser analizadas) y ligeramente más rápidas (estamos hablando de milisegundos en la mayoría de los casos).

t31os t31os
9 abr 2011 14:35:20

@t31os - Absolutamente correcto. Todavía prefiero 'este es mi estado de ánimo: '.$valor sobre "este es mi estado de ánimo: $valor", por legibilidad. Cuando se trata de velocidad: no es ligeramente - he medido hasta cinco veces. Y cuando usas comillas dobles en todo tu tema en todas partes, se suman rápidamente cuando tienes muchas solicitudes. De todos modos, es bueno que lo hayas aclarado.

kaiser kaiser
9 abr 2011 15:25:26

@t31os A raíz de una discusión volví a medir la velocidad de " frente a ' y me equivoqué. La diferencia está muy lejos de ser algo que alguien pudiera notar.

kaiser kaiser
24 jul 2011 18:24:17

+1 ¡buena función! 2 errores tipográficos: $taxonomies se usa en la función $taxonomy y $terms[] = $c; debería ser $terms[] = $t;

Rob Vermeer Rob Vermeer
5 feb 2012 19:45:27
3
13

Excelente pregunta y respuestas sólidas.

Realmente me gustó el enfoque de @jessica usando el filtro terms_clauses, porque extiende la función get_terms de una manera muy razonable.

Mi código es una continuación de su idea, con algo de sql de @braydon para reducir duplicados. También permite un array de post_types:

/**
 * my_terms_clauses
 *
 * filtra las cláusulas de términos
 *
 * @param $clauses array
 * @param $taxonomy string
 * @param $args array
 * @return array
 **/
function my_terms_clauses($clauses, $taxonomy, $args)
{
  global $wpdb;

  if ($args['post_types'])
  {
    $post_types = implode("','", array_map('esc_sql', (array) $args['post_types']));

    // permite arrays
    if ( is_array($args['post_types']) ) {
      $post_types = implode( "','", $args['post_types'] );
    }
    $clauses['join'] .= " INNER JOIN $wpdb->term_relationships AS r ON r.term_taxonomy_id = tt.term_taxonomy_id INNER JOIN $wpdb->posts AS p ON p.ID = r.object_id";
    $clauses['where'] .= " AND p.post_type IN ('". $post_types. "') GROUP BY t.term_id";
  }
  return $clauses;
}
add_filter('terms_clauses', 'my_terms_clauses', 99999, 3);

Como get_terms no tiene una cláusula para GROUP BY, tuve que agregarla al final de la cláusula WHERE. Nota que tengo la prioridad del filtro muy alta, con la esperanza de que siempre se ejecute al final.

4 abr 2015 06:09:25
Comentarios

Deberías reemplazar $post_types = $args['post_types']; por $post_types = implode("','", array_map('esc_sql', (array) $args['post_types'])); y eliminar esc_sql() de la cláusula IN o esta cláusula terminará con \' entre los tipos de posts cuando proporciones múltiples tipos de posts en $args['post_types']

ZalemCitizen ZalemCitizen
28 sept 2020 10:58:33

El único que me permitió usar get_terms y funcionó para mí en 2022

LBF LBF
28 oct 2022 00:03:13

¿El argumento hide_empty seguirá aplicándose a todos los posts, no solo al tipo de post seleccionado?

Adrian B Adrian B
4 oct 2023 00:43:15
3
12

Aquí hay otra forma de hacer algo similar, con una sola consulta SQL:

static public function get_terms_by_post_type( $taxonomies, $post_types ) {

    global $wpdb;

    $query = $wpdb->prepare(
        "SELECT t.*, COUNT(*) from $wpdb->terms AS t
        INNER JOIN $wpdb->term_taxonomy AS tt ON t.term_id = tt.term_id
        INNER JOIN $wpdb->term_relationships AS r ON r.term_taxonomy_id = tt.term_taxonomy_id
        INNER JOIN $wpdb->posts AS p ON p.ID = r.object_id
        WHERE p.post_type IN('%s') AND tt.taxonomy IN('%s')
        GROUP BY t.term_id",
        join( "', '", $post_types ),
        join( "', '", $taxonomies )
    );

    $results = $wpdb->get_results( $query );

    return $results;

}
26 jul 2011 11:40:10
Comentarios

¡Sí! Esto hace exactamente lo que quiero.

Gavin Hewitt Gavin Hewitt
7 nov 2011 03:33:30

print_r(get_terms_by_post_type(array('category') , array('event') )); muestra Warning: Missing argument 2 for wpdb::prepare()

Anshad Vattapoyil Anshad Vattapoyil
20 jun 2014 05:20:40

Podría estar equivocado, pero de entrada, no creo que esas sentencias 'join' funcionen - es decir, solo funcionarían si se pasan arrays de un solo valor. Esto se debe a que la función prepare escaparía todas las comillas simples generadas y consideraría cada sentencia 'join' completa como una cadena.

Codesmith Codesmith
12 mar 2019 19:19:43
0
10

Escribí una función que te permite pasar post_type en el array $args a la función get_terms():

HT a @braydon por escribir el SQL.

/**
 * terms_clauses
 *
 * filtra las cláusulas de términos
 *
 * @param $clauses array
 * @param $taxonomy string
 * @param $args array
 * @return array
**/
function terms_clauses($clauses, $taxonomy, $args)
{
    global $wpdb;

    if ($args['post_type'])
    {
        $clauses['join'] .= " INNER JOIN $wpdb->term_relationships AS r ON r.term_taxonomy_id = tt.term_taxonomy_id INNER JOIN $wpdb->posts AS p ON p.ID = r.object_id";
        $clauses['where'] .= " AND p.post_type='{$args['post_type']}'"; 
    }
    return $clauses;
}
add_filter('terms_clauses', 'terms_clauses', 10, 3);
8 jun 2012 08:14:39
1

No pude hacer que los argumentos de get_terms funcionaran con la versión del código de Gavin mencionada anteriormente, pero finalmente lo logré cambiando

$terms2 = get_terms( $taxonomy );

por

$terms2 = get_terms( $taxonomy, $args );

tal como estaba en la función original de Bainternet.

10 abr 2011 05:16:13
Comentarios

Corregido en la versión actual

Gavin Hewitt Gavin Hewitt
10 abr 2011 14:06:01
5

@Bainternet: ¡Gracias! Tuve que modificar ligeramente la función porque no funcionaba (algunos errores tipográficos). El único problema ahora es que el conteo de términos no es correcto. El conteo no está considerando el tipo de post, así que no creo que puedas usar get_terms() en esto.

function get_terms_by_post_type($post_type,$taxonomy,$fields='all',$args){
    $q_args = array(
        'post_type' => (array)$post_type,
        'posts_per_page' => -1
    );
    $the_query = new WP_Query( $q_args );

    $terms = array();

    while ($the_query->have_posts()) { $the_query->the_post();

        global $post;

        $current_terms = get_the_terms( $post->ID, $taxonomy);

        foreach ($current_terms as $t){
            //evitar duplicados
            if (!in_array($t,$terms)){
                $t->count = 1;
                $terms[] = $t;
            }else{
                $key = array_search($t, $terms);
                $terms[$key]->count = $terms[$key]->count + 1;
            }
        }
    }
    wp_reset_query();

    //retornar array de objetos de términos
    if ($fields == "all")
        return $terms;
    //retornar array de IDs de términos
    if ($fields == "ID"){
        foreach ($terms as $t){
            $re[] = $t->term_id;
        }
        return $re;
    }
    //retornar array de nombres de términos
    if ($fields == "name"){
        foreach ($terms as $t){
            $re[] = $t->name;
        }
        return $re;
    }
    // obtener términos con argumentos de get_terms
    if ($fields == "get_terms"){
        $terms2 = get_terms( $taxonomy, $args );

        foreach ($terms as $t){
            if (in_array($t,$terms2)){
                $re[] = $t;
            }
        }
        return $re;
    }
}

EDITADO: Añadí la(s) corrección(es). Pero de alguna manera todavía no funciona para mí. El conteo sigue mostrando un valor incorrecto.

9 abr 2011 14:45:03
Comentarios

Esa es una historia diferente, pero puedes contar cuando evitas duplicados en el bucle while.

Bainternet Bainternet
9 abr 2011 16:34:55

Actualicé mi respuesta con una corrección para el conteo de términos.

Bainternet Bainternet
9 abr 2011 17:25:26

Por favor no agregues seguimientos como respuestas, a menos que estés específicamente respondiendo tu propia pregunta, las adiciones deberían hacerse en la pregunta original.

t31os t31os
10 abr 2011 13:06:44

@t31os: Ah sí, me preguntaba cómo añadir algo más. No pensé en editar mi pregunta. ¡Gracias!

Gavin Hewitt Gavin Hewitt
10 abr 2011 13:58:36

¿Cómo puedo llamar a esto? print_r(get_terms_by_post_typea(array('event','category','',array())); este da Warning: Invalid argument supplied for foreach() para la línea foreach ($current_terms as $t){

Anshad Vattapoyil Anshad Vattapoyil
20 jun 2014 05:32:21
1

Evitar duplicados:

//evitar duplicados
    $mivalor=$t->term_id;
    $arr=array_filter($terms, function ($item) use ($mivalor) {return isset($item->term_id) && $item->term_id == $mivalor;});

    if (empty($arr)){
    $t->count=1;
            $terms[] = $t;
        }else{
            $key = array_search($t, $terms);
            $terms[$key]->count = $terms[$key]->count + 1;
        }
26 feb 2013 19:10:19
Comentarios

¿Puedes explicar por qué esto resuelve el problema? Ver [respuesta].

brasofilo brasofilo
26 feb 2013 19:38:46