Obtener get_terms por tipo de contenido personalizado
Tengo dos tipos de contenido personalizados 'country' y 'city' y una taxonomía compartida 'flag'.
Si uso:
<?php $flags = get_terms('flag', 'orderby=name&hide_empty=0');
Obtengo una lista de todos los términos en la taxonomía, pero quiero limitar la lista al tipo de contenido 'country'.
¿Cómo puedo hacerlo?
Usando la nueva solución
<?php
$flags = wpse57444_get_terms('flags',array('parent' => 0,'hide_empty' => 1,'post_types' =>array('country')));
foreach ($flags as $flag) {
$childTerms = wpse57444_get_terms('flags',array('parent' => $flag->term_id,'hide_empty' => 1,'post_types' =>array('country')));
foreach ($childTerms as $childTerm) {
echo $childTerm->name.'<br />';
}
}
?>
No puedo mostrar $childTerm->name. ¿Por qué?

Me temo que esto no es posible de forma nativa (¿aún?). Ver este ticket: http://core.trac.wordpress.org/ticket/18106
De manera similar, en la página de administración de taxonomías, el conteo de publicaciones refleja todos los tipos de publicaciones. (Estoy bastante seguro de que hay un ticket para eso también)
http://core.trac.wordpress.org/ticket/14084
Ver también, esta publicación relacionada.
Nueva solución
Habiendo escrito la solución de abajo, he descubierto una forma mucho mejor (al menos en el sentido de que puedes hacer más) es usar los filtros proporcionados en la llamada get_terms()
. Puedes crear una función envolvente que use get_terms
y (condicionalmente) agregue un filtro para manipular la consulta SQL (para restringir por tipo de publicación).
La función toma los mismos argumentos que get_terms($taxonomies, $args)
. $args
toma el argumento adicional de post_types
que acepta un array|string de tipos de publicaciones.
Pero no puedo garantizar que todo funcione 'como se espera' (estoy pensando en el relleno del conteo). Parece funcionar usando solo los argumentos predeterminados de $args
para get_terms
.
function wpse57444_get_terms( $taxonomies, $args=array() ){
// Analizar $args en caso de que sea una cadena de consulta.
$args = wp_parse_args($args);
if( !empty($args['post_types']) ){
$args['post_types'] = (array) $args['post_types'];
add_filter( 'terms_clauses','wpse_filter_terms_by_cpt',10,3);
function wpse_filter_terms_by_cpt( $pieces, $tax, $args){
global $wpdb;
// No usar conteo de base de datos
$pieces['fields'] .=", COUNT(*) " ;
// Unir tablas adicionales para restringir por tipo de publicación.
$pieces['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 ";
// Restringir por tipo de publicación y agrupar por term_id para el conteo.
$post_types_str = implode(',',$args['post_types']);
$pieces['where'].= $wpdb->prepare(" AND p.post_type IN(%s) GROUP BY t.term_id", $post_types_str);
remove_filter( current_filter(), __FUNCTION__ );
return $pieces;
}
} // endif post_types establecido
return get_terms($taxonomies, $args);
}
Uso
$args =array(
'hide_empty' => 0,
'post_types' =>array('country','city'),
);
$terms = wpse57444_get_terms('flag',$args);
Solución original
Inspirado en el ticket anterior, (probado y funciona para mí)
function wpse57444_filter_terms_by_cpt($taxonomy, $post_types=array() ){
global $wpdb;
$post_types=(array) $post_types;
$key = 'wpse_terms'.md5($taxonomy.serialize($post_types));
$results = wp_cache_get($key);
if ( false === $results ) {
$where =" WHERE 1=1";
if( !empty($post_types) ){
$post_types_str = implode(',',$post_types);
$where.= $wpdb->prepare(" AND p.post_type IN(%s)", $post_types_str);
}
$where .= $wpdb->prepare(" AND tt.taxonomy = %s",$taxonomy);
$query = "
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
GROUP BY t.term_id";
$results = $wpdb->get_results( $query );
wp_cache_set( $key, $results );
}
return $results;
}
Uso
$terms = wpse57444_filter_terms_by_cpt('flag',array('country','city'));
o
$terms = wpse57444_filter_terms_by_cpt('flag','country');

Funciona, pero ¿qué puedo hacer con mis $args? Me refiero a... parent=0&orderby=name&hide_empty=0

no - esto debe ser un array: $args = array('parent'=>0,'orderby'=>'name','hide_empty'=>0);
. Voy a editar esto para permitir cadenas de consulta...

¿Dónde puedo poner mis $args en este ejemplo: $terms = wpse57444_filter_terms_by_cpt('flag',array('country','city'));
?

No puedes en ese, solo en la nueva solución:wpse57444_get_terms()

@user1443216 $args
es el segundo argumento. Allí solo pones wpse57444_get_terms( 'flag', array( 'country', 'city' ) );

@StephenHarris Incluso el núcleo intenta eliminar todo lo que parezca una cadena de consulta. Solo añade peso innecesario. Ya eliminé este tipo de funcionalidad de todo mi código después de una conversación con uno de los desarrolladores principales.

Si uso esto: $flags = wpse57444_get_terms('flags',array('parent' => 0,'hide_empty' => 1,'post_types' =>array('country')));
foreach ($flags as $flag) {
$childTerms = wpse57444_get_terms('flags',array('parent' => $flag->term_id,'hide_empty' => 1,'post_types' =>array('country')));
foreach ($childTerms as $childTerm) {
echo $childTerm->name.'<br />';
}
}
no puedo hacer echo de $childTerm->name

para obtener un conteo correcto tuve que actualizar esta línea a:
$pieces['fields'] .= ", COUNT(*) as count ";

La solución anterior funciona muy bien con 1 tipo de publicación. Con muchos tipos de publicaciones tenemos un problema con prepare. Parece que no sanitiza el $post_types_str
que generará IN( 'whitepaper, blog' )
en lugar de IN( 'whitepaper', 'blog' )
. Por favor revisa este artículo para una solución alternativa enlace

Dos tipos de entradas personalizadas 'country' y 'city' y una taxonomía compartida 'flag'. Quieres limitar la lista al tipo de entrada 'country'.
Aquí hay una solución más simple:
$posts_in_post_type = get_posts( array(
'fields' => 'ids',
'post_type' => 'country',
'posts_per_page' => -1,
) );
$terms = wp_get_object_terms( $posts_in_post_type, 'flag', array( 'ids' ) ); ?>

Esta parece ser la forma recomendada según este ticket de trac: https://core.trac.wordpress.org/ticket/18106#comment:7. Ten en cuenta que esto puede ser muy lento, por lo que probablemente necesitarás implementar algún tipo de caché si tu sitio tiene muchas publicaciones.

La respuesta de @stephen-harris anterior solo funcionó parcialmente para mí. Si intentaba usarla dos veces en la página, no funcionaba. Además, la idea de enterrar consultas mysql de esa manera me preocupa: creo que es mejor práctica usar métodos principales para lograr una solución, para evitar conflictos con futuras actualizaciones de WP. Aquí está mi solución, basada en el comentario #7 en el ticket Trac que él referenció
function get_terms_by_custom_post_type( $post_type, $taxonomy ){
$args = array( 'post_type' => $post_type);
$loop = new WP_Query( $args );
$postids = array();
// construir un array de IDs de posts
while ( $loop->have_posts() ) : $loop->the_post();
array_push($postids, get_the_ID());
endwhile;
// obtener valores de taxonomía basados en el array de IDs
$regions = wp_get_object_terms( $postids, $taxonomy );
return $regions;
}
Uso:
$terms = get_terms_by_custom_post_type('country','flag');
Esto funciona solo para un tipo de post y una taxonomía, porque eso es lo que necesitaba, pero no sería muy difícil modificar esto para aceptar múltiples valores.
Hubo alguna mención en ese hilo de Trac de que esto podría no escalar bien, pero estoy trabajando en una escala bastante pequeña y no he tenido problemas con la velocidad.

esta solución me parece más "nativa" - de cualquier forma ->
deberías llamar a "wp_reset_postdata()" justo después del "endwhile" del bucle: https://wordpress.stackexchange.com/questions/144343/wp-reset-postdata-or-wp-reset-query-after-a-custom-loop

[editar] Este es un comentario sobre la excelente respuesta de Stephen Harris.
No devuelve ningún término si se usa con múltiples tipos de publicación como $flags = wpse57444_get_terms('flags', array('post_types' => array('country','city')));
. Esto ocurre porque $wpdb->prepare sanitiza la cadena $post_types_str a p.post_type IN('country,city')
cuando debería ser p.post_type IN('country','city')
. Consulte este ticket: 11102. Utilice la solución de este tema para solucionarlo: https://stackoverflow.com/a/10634225

También intenté usar la respuesta de @Stephen Harris, pero la consulta que necesitaba era bastante difícil de escribir como una sola consulta y utilizando los filtros.
Además, también necesitaba usar esa función múltiples veces en la misma página y resolví el problema declarando la función wpse_filter_terms_by_cpt
fuera de la función wrapper.
De cualquier manera, en mi opinión la respuesta de @Mark Pruce encaja mejor, por las mismas razones que él mencionó, aunque requiere que hagas una consulta adicional (y el bucle relacionado) para preparar los argumentos para la función wp_get_object_terms
.
