Come ottenere articoli da due categorie con WP_Query?
Sto usando WP_Query per creare sezioni 'ultimi articoli' e 'articoli popolari' sulla mia home page. Sto cercando di estrarre solo 5 articoli da 2 categorie (9 e 11) ma mostra solo gli articoli della categoria 9.
Ecco il PHP che sto usando per 'ultimi articoli'-
<ul>
<?php
// Array delle categorie
$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 query
$i = 0;while ($the_query->have_posts() ) : $the_query->the_post(); //inizia il loop
?>
<?php
if($i==0){ //Imposta l'output per il primo articolo
?>
<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(); ?>">Leggi di più »</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; //fine del loop ?>
<?php wp_reset_postdata(); // reset della query ?>
</ul>
Qualche suggerimento?
Nella mia esperienza, l'utilizzo dei filtri 'posts_*'
('posts_request'
, 'posts_where'
...) in combinazione con str_replace
/ preg_replace
è inaffidabile e poco flessibile:
Inaffidabile perché se un altro filtro modifica uno di questi filtri, nel migliore dei casi si ottengono risultati imprevisti, nel peggiore si generano errori SQL.
Poco flessibile perché modificare un argomento, ad esempio 'include_children' per le categorie, o riutilizzare il codice per 3 termini invece di 2 richiede molto lavoro.
Inoltre, adattare il codice per renderlo compatibile con il multisite richiede modifiche manuali al SQL.
Quindi, a volte, anche se non è la soluzione migliore in termini di prestazioni, un approccio più canonico è la soluzione migliore e più flessibile.
E le prestazioni possono essere migliorate con alcuni trucchi di caching...
La mia proposta:
- scrivere una funzione che utilizzi
usort
per ordinare i post provenienti da query diverse (ad esempio una per termine) - scrivere una funzione che alla prima esecuzione esegua query separate, unisca i risultati, li ordini, li memorizzi nella cache e li restituisca. Nelle richieste successive restituisca semplicemente i risultati memorizzati
- gestire l'invalidamento della cache quando necessario
##Codice##
Innanzitutto la funzione che ordina i post:
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;
}
Poi la funzione che ottiene i risultati non memorizzati:
function my_fresh_terms_get_posts( $args, $terms, $tax_query_args = NULL ) {
$posts = array();
// abbiamo bisogno di conoscere almeno la tassonomia
if ( ! is_array( $tax_query_args ) || ! isset( $tax_query_args['taxonomy'] ) ) return;
// gestisce il tax_query di base
$base_tax_query = isset( $args['tax_query'] ) ? $args['tax_query'] : array();
// esegue una query per ogni termine
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() ) {
// unisce i post recuperati nell'array $posts
// prevenendo duplicati utilizzando gli ID come chiavi dell'array
$ids = wp_list_pluck( $q->posts, 'ID' );
$keyed = array_combine( $ids, array_values( $q->posts ) );
$posts += $keyed;
}
}
return $posts;
}
Ora la funzione che verifica la cache e la restituisce se disponibile o restituisce i risultati non memorizzati
function my_terms_get_posts( $args, $terms, $tax_query_args = NULL, $order = 'DESC' ) {
// abbiamo bisogno di conoscere almeno la tassonomia
if ( ! is_array( $tax_query_args ) || ! isset( $tax_query_args['taxonomy'] ) ) return;
$tax = $tax_query_args['taxonomy'];
// ottiene i risultati memorizzati
$cached = get_transient( "my_terms_get_posts_{$tax}" );
if ( ! empty( $cached ) ) return $cached;
// nessun risultato memorizzato, ottiene i post 'freschi'
$posts = my_fresh_terms_get_posts( $args, $terms, $tax_query_args );
if ( ! empty($posts) ) {
// ordina i post e li memorizza nella cache
$posts = my_date_terms_posts_sort( $posts, $order );
set_transient( "my_terms_get_posts_{$tax}", $posts, DAY_IN_SECONDS );
}
return $posts;
}
La cache viene pulita automaticamente ogni giorno, tuttavia, è possibile invalidarla ogni volta che un nuovo post viene aggiunto o aggiornato in una specifica tassonomia. Questo può essere fatto aggiungendo una funzione di pulizia della cache su '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 );
##Utilizzo##
// il numero di post da recuperare per ogni termine
// il totale dei post recuperati sarà uguale a questo numero x il numero di termini passati
// alla funzione my_terms_get_posts
$perterm = 5;
// prima definisci gli argomenti generali:
$paged = ( get_query_var('paged') ) ? get_query_var('paged') : 1;
$args = array(
'posts_per_page' => $perterm,
'paged' => $paged,
);
// argomenti della tassonomia
$base_tax_args = array(
'taxonomy' => 'category'
);
$terms = array( 9, 11 ); // è possibile utilizzare anche gli slug
// ottiene i post
$posts = my_terms_get_posts( $args, $terms, $base_tax_args );
// loop
if ( ! empty( $posts ) ) {
foreach ( $posts as $_post ) {
global $post;
setup_postdata( $_post );
//-------------------------> il codice del loop va qui
}
wp_reset_postdata();
}
Le funzioni sono abbastanza flessibili da poter utilizzare query complesse, anche query per tassonomie aggiuntive:
ESEMPIO:
$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 );
In questo modo il 'tax_query' impostato nell'array $args
verrà unito dinamicamente con l'argomento della query tassonomica in $base_tax_args
, per ognuno dei termini nell'array $terms
.
È anche possibile ordinare i post in ordine crescente:
$posts = my_terms_get_posts( $args, $terms, $base_tax_args, 'ASC' );
Nota bene:
- IMPORTANTE: Se alcuni post appartengono a più di una categoria tra quelle passate alla funzione (ad esempio nel caso dell'OP alcuni post hanno sia la categoria 9 che la 11) il numero di post recuperati non sarà quello previsto, perché la funzione restituirà quei post una sola volta
- Il codice richiede PHP 5.3+

Secondo il Codex:
Mostrare Post da Diverse Categorie (Visualizza i post che appartengono a queste categorie, utilizzando l'id della categoria) sarebbe:
$query = new WP_Query( 'cat=9,11' );
Inoltre, i Parametri di Paginazione del Codex indicano che 'showposts'
è deprecato e sostituito con 'posts_per_page'
.
posts_per_page
(int) - numero di post da mostrare per pagina (disponibile dalla Versione 2.1, ha sostituito il parametroshowposts
).

Grazie per la tua risposta, proverò questa soluzione dopo aver provato quella di s_ha_dum. (posts per page non funzionava davvero per me prima per qualche strana ragione)

Quello che stai chiedendo è quasi un duplicato di questa domanda: Come posso creare la mia meta_query annidata utilizzando posts_where / posts_join?
Il problema, come suggerisce @fischi, è quasi sicuramente che i risultati provengono da una categoria o dall'altra e raggiungono il limite di post prima che entrambe siano equamente rappresentate. Per far funzionare questo, hai bisogno di un UNION. WP_Query
non è in grado di gestire quella logica, ma con gli hook puoi farlo funzionare.
$cat = 9; // la tua prima categoria
$showposts = 5; // i risultati effettivi sono il doppio di questo valore
$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); // manipola questo
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 query
var_dump($the_query->posts);
Un paio di note: WP_Query
analizzerà l'argomento della categoria e troverà le categorie figlie, quindi il primo UNION
includerà tutti i figli della categoria 9. Non ho duplicato quella logica per la categoria 11. Se hai bisogno di quella funzionalità, puoi fare riferimento al sorgente di WP_Query
e riprodurre l'effetto.

Non c'è bisogno di scuse. La domanda a cui facevi riferimento ti avrebbe avvicinato alla soluzione, e se conosci bene PHP/MySQL avresti potuto arrivare alla risposta, ma non è un vero duplicato.

Grazie per la tua risposta, proverò a implementarla. Non avevo mai sentito parlare di UNION prima d'ora, una cosa nuova da approfondire per me. Anche se i 5 post più recenti provengono sicuramente da entrambe le categorie.

Ahh, il mio MySQL non è ancora il massimo - ho ancora molto da imparare! Grazie per il tuo aiuto =)
