¿Es posible obtener get_terms por taxonomía Y post_type?
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

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

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

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

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.

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

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

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

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.

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

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.

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

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

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

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: Ah sí, me preguntaba cómo añadir algo más. No pensé en editar mi pregunta. ¡Gracias!

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