¿Cómo obtener entradas de dos categorías con WP_Query?
Estoy usando WP_Query para crear secciones de 'últimas entradas' y 'entradas populares' en mi página de inicio. Estoy tratando de obtener solo 5 entradas de 2 categorías (9 y 11) pero solo muestra entradas de la categoría 9.
Aquí está el código PHP que estoy usando para 'últimas entradas'-
<ul>
<?php
$cat = array(9,11);
$showposts = 5;
$paged = (get_query_var('paged')) ? get_query_var('paged') : 1;
$args=array(
'category__in' => $cat,
'showposts' => $showposts,
'paged' => $paged,
'orderby' => 'post_date',
'order' => 'DESC',
'post_status' => 'publish',
);
$the_query = new WP_Query ( $args ); //la consulta
$i = 0;while ($the_query->have_posts() ) : $the_query->the_post(); //inicia el bucle
?>
<?php
if($i==0){ //Define la salida para la primera entrada
?>
<li class="first-news">
<div class="post-thumbnail"><a href="<?php the_permalink(); ?>"><?php the_post_thumbnail(350,187); ?></a></div>
<h2 class="post-box-title"><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h2>
<p class="post-meta"><span class="tie-date"><?php the_time('F jS, Y') ?></span></p>
<div class="entry"><?php the_excerpt(); ?></div>
<div><a class="more-link" href="<?php the_permalink(); ?>">Leer Más »</a></div>
</li>
<?php
$i++;
} else { ?>
<li class="other-news rar">
<div class="post-thumbnail"><a href="<?php the_permalink(); ?>"><?php the_post_thumbnail(145,93); ?></a></div>
<h3 class="post-box-title"><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h3>
<p class="post-meta"><span class="tie-date"><?php the_time('F jS, Y') ?></span></p>
</li>
<?php } endwhile; //fin del bucle ?>
<?php wp_reset_postdata(); // reinicia la consulta ?>
</ul>
¿Alguna sugerencia?

En mi experiencia usando filtros 'posts_*'
('posts_request'
, 'posts_where'
...) en combinación con str_replace
/ preg_replace
no es confiable y poco flexible:
No es confiable porque si otro filtro modifica alguno de esos filtros, en el mejor caso se obtienen resultados inesperados, en el peor se obtienen errores SQL.
Poco flexible porque cambiar un argumento, como 'include_children' para categorías, o reutilizar el código para por ejemplo 3 términos en lugar de 2 requiere mucho trabajo.
Además, adaptar el código para que sea compatible con multisitio requiere editar manualmente el SQL.
Por eso, a veces, incluso si no es la mejor solución en cuanto a rendimiento, un enfoque más canónico es el mejor y más flexible.
Y el rendimiento puede mejorarse con algunos trucos de caché...
Mi propuesta:
- escribir una función que use
usort
para ordenar posts que vienen de diferentes consultas (ej. una por término) - escribir una función que en la primera ejecución ejecute consultas separadas, combine resultados, los ordene, los guarde en caché y los devuelva. En solicitudes posteriores simplemente devuelva los resultados en caché
- manejar la invalidación del caché cuando sea necesario
##Código##
Primero la función que ordena los posts:
function my_date_terms_posts_sort( Array $posts, $order = 'DESC' ) {
if ( ! empty( $posts ) ) {
usort( $posts, function( WP_Post $a, WP_Post $b ) use ( $order ) {
$at = (int) mysql2date( 'U', $a->post_date );
$bt = (int) mysql2date( 'U', $b->post_date );
$orders = strtoupper($order) === 'ASC' ? array( 1, -1 ) : array( -1, 1 );
return $at === $bt ? 0 : ( $at > $bt ) ? $orders[0] : $orders[1];
} );
}
return $posts;
}
Luego la función que obtiene resultados sin caché:
function my_fresh_terms_get_posts( $args, $terms, $tax_query_args = NULL ) {
$posts = array();
// necesitamos saber al menos la taxonomía
if ( ! is_array( $tax_query_args ) || ! isset( $tax_query_args['taxonomy'] ) ) return;
// manejar tax_query base
$base_tax_query = isset( $args['tax_query'] ) ? $args['tax_query'] : array();
// ejecutar una consulta por cada término
foreach ( $terms as $term ) {
$term_tax_query = wp_parse_args( array(
'terms' => array( $term ),
'field' => is_numeric( $term ) ? 'term_id' : 'slug'
), $tax_query_args );
$args['tax_query'] = array_merge( $base_tax_query, array($term_tax_query) );
$q = new WP_Query( $args );
if ( $q->have_posts() ) {
// combinar posts recuperados en array $posts
// previniendo duplicados usando ID como claves del array
$ids = wp_list_pluck( $q->posts, 'ID' );
$keyed = array_combine( $ids, array_values( $q->posts ) );
$posts += $keyed;
}
}
return $posts;
}
Ahora la función que verifica el caché y lo devuelve si está disponible o devuelve resultados sin caché
function my_terms_get_posts( $args, $terms, $tax_query_args = NULL, $order = 'DESC' ) {
// necesitamos saber al menos la taxonomía
if ( ! is_array( $tax_query_args ) || ! isset( $tax_query_args['taxonomy'] ) ) return;
$tax = $tax_query_args['taxonomy'];
// obtener resultados en caché
$cached = get_transient( "my_terms_get_posts_{$tax}" );
if ( ! empty( $cached ) ) return $cached;
// no hay resultados en caché, obtener posts 'frescos'
$posts = my_fresh_terms_get_posts( $args, $terms, $tax_query_args );
if ( ! empty($posts) ) {
// ordenar posts y guardarlos en caché
$posts = my_date_terms_posts_sort( $posts, $order );
set_transient( "my_terms_get_posts_{$tax}", $posts, DAY_IN_SECONDS );
}
return $posts;
}
El caché se limpia automáticamente cada día, sin embargo, es posible invalidarlo cada vez que se añade o actualiza un nuevo post en una taxonomía específica. Eso puede hacerse añadiendo una función de limpieza de caché en 'set_object_terms'
add_action( 'set_object_terms', function( $object_id, $terms, $tt_ids, $taxonomy ) {
$taxonomies = get_taxonomies( array( 'object_type' => array('post') ), 'names' );
if ( in_array( $taxonomy, (array) $taxonomies ) ) {
delete_transient( "my_terms_get_posts_{$taxonomy}" );
}
}, 10, 4 );
##Uso##
// el número de posts a recuperar por cada término
// el total de posts recuperados será igual a este número x el número de términos pasados
// a la función my_terms_get_posts
$perterm = 5;
// primero definir argumentos generales:
$paged = ( get_query_var('paged') ) ? get_query_var('paged') : 1;
$args = array(
'posts_per_page' => $perterm,
'paged' => $paged,
);
// argumentos de taxonomía
$base_tax_args = array(
'taxonomy' => 'category'
);
$terms = array( 9, 11 ); // también es posible usar slugs
// obtener posts
$posts = my_terms_get_posts( $args, $terms, $base_tax_args );
// loop
if ( ! empty( $posts ) ) {
foreach ( $posts as $_post ) {
global $post;
setup_postdata( $_post );
//-------------------------> aquí va el código del loop
}
wp_reset_postdata();
}
Las funciones son lo suficientemente flexibles para usar consultas complejas, incluso consultas para taxonomías adicionales:
EJEMPLO:
$args = array(
'posts_per_page' => $perterm,
'paged' => $paged,
'post_status' => 'publish',
'tax_query' => array(
'relation' => 'AND',
array(
'taxonomy' => 'another_taxonomy',
'terms' => array( 'foo', 'bar' ),
'field' => 'id'
)
)
);
$base_tax_args = array(
'taxonomy' => 'category',
'include_children' => FALSE
);
$terms = array( 'a-category', 'another-one' );
$posts = my_terms_get_posts( $args, $terms, $base_tax_args );
De esta manera el 'tax_query' establecido en el array $args
se combinará dinámicamente con el argumento de tax query en $base_tax_args
, para cada uno de los términos en el array $terms
.
También es posible ordenar los posts en orden ascendente:
$posts = my_terms_get_posts( $args, $terms, $base_tax_args, 'ASC' );
Por favor tenga en cuenta:
- IMPORTANTE: Si algunos posts pertenecen a más de una categoría entre las pasadas a la función (ej. en el caso de OP algunos posts tienen ambas categorías 9 y 11) el número de posts recuperados no será el esperado, porque la función devolverá esos posts una sola vez
- El código requiere PHP 5.3+

Según el Codex:
Mostrar publicaciones de varias categorías (Mostrar publicaciones que tengan estas categorías, usando el id de categoría) sería:
$query = new WP_Query( 'cat=9,11' );
Y, los Parámetros de Paginación del Codex indican que 'showposts'
está obsoleto y ha sido reemplazado por 'posts_per_page'
.
posts_per_page
(int) - número de publicaciones a mostrar por página (disponible desde la Versión 2.1, reemplaza el parámetroshowposts
).

Gracias por tu respuesta, voy a probar eso después de intentar la de s_ha_dum. (posts per page no estaba funcionando para mí antes por alguna extraña razón)

Lo que estás preguntando es casi un duplicado de esta pregunta: ¿Cómo creo mi propio meta_query anidado usando posts_where / posts_join?
El problema, como sugiere @fischi, es casi seguro que los resultados provienen de una categoría u otra y alcanzan el límite de publicaciones antes de que ambas estén representadas por igual. Para que esto funcione, necesitas un UNION. WP_Query
no es capaz de esa lógica, pero con hooks puedes hacer que funcione.
$cat = 9; // tu primera categoría
$showposts = 5; // los resultados reales son el doble de este valor
$paged = (get_query_var('paged')) ? get_query_var('paged') : 1;
$args=array(
'cat' => $cat,
'posts_per_page' => $showposts,
'paged' => $paged,
'orderby' => 'post_date',
'order' => 'DESC',
'post_status' => 'publish',
);
function create_cat_union($clauses) {
remove_filter('posts_request','create_cat_union',1);
$clauses = str_replace('SQL_CALC_FOUND_ROWS','',$clauses,$scfr);
$scfr = (0 < $scfr) ? 'SQL_CALC_FOUND_ROWS' : '';
$pattern = 'wp_term_relationships.term_taxonomy_id IN \(([0-9,]+)\)';
$clause2 = preg_replace('|'.$pattern.'|','wp_term_relationships.term_taxonomy_id IN (11)',$clauses); // manipula esto
return "SELECT {$scfr} u.* FROM (({$clauses}) UNION ({$clause2})) as u";
}
add_filter('posts_request','create_cat_union',1,2);
$the_query = new WP_Query ( $args ); // la consulta
var_dump($the_query->posts);
Un par de notas: WP_Query
analizará el argumento de categoría y encontrará las categorías hijas, por lo que el primer UNION
incluirá todas las hijas de la categoría 9. No he duplicado esa lógica para la categoría 11. Si necesitas esa funcionalidad, puedes consultar el código fuente de WP_Query
y reproducir el efecto.

Oh, lo siento, debería haber buscado mejor para encontrar la respuesta =/

No es necesario disculparse. La pregunta a la que se refería te habría acercado a la solución, y si tienes buen conocimiento de PHP/MySQL podrías haber llegado a la respuesta, pero no es un duplicado exacto.

Gracias por tu respuesta, voy a intentarlo. No había escuchado sobre UNION antes, algo nuevo para investigar. Aunque los 5 posts más recientes definitivamente son de ambas categorías.

Ahh, mi MySQL todavía no es muy bueno, ¡tengo mucho que aprender! Gracias por tu ayuda =)
