J'essaie de classer les articles dans une catégorie en affichant d'abord les articles avec des images, puis les articles sans images en dernier. J'ai réussi à le faire en exécutant deux requêtes et je souhaite maintenant fusionner les deux requêtes ensemble.
J'ai le suivant:
<?php
$loop = new WP_Query( array('meta_key' => '_thumbnail_id', 'cat' => 1 ) );
$loop2 = new WP_Query( array('meta_key' => '', 'cat' => 1 ) );
$mergedloops = array_merge($loop, $loop2);
while($mergedloops->have_posts()): $mergedloops->the_post(); ?>
Mais quand j'essaie de voir la page, j'obtiens l'erreur suivante:
Fatal error: Call to a member function have_posts() on a non-object in...
J'ai ensuite essayé de transtyper array_merge sur un objet, mais j'ai eu l'erreur suivante:
Fatal error: Call to undefined method stdClass::have_posts() in...
Comment puis-je réparer cette erreur?
Si vous y réfléchissez un peu plus, il est possible que vous puissiez utiliser une requête unique/principale. Ou en d'autres termes: vous n'avez pas besoin de deux requêtes supplémentaires lorsque vous pouvez utiliser la requête par défaut. Et si vous ne pouvez pas utiliser une configuration par défaut, vous n'aurez pas besoin de plus d'une requête, peu importe le nombre de boucles que vous souhaitez fractionner.
Tout d'abord, vous devez définir (comme indiqué dans mon autre réponse) les valeurs nécessaires dans un filtre pre_get_posts
. Là, vous allez probablement définir posts_per_page
et cat
. Exemple sans le pre_get_posts
- Filter:
$catID = 1;
$catQuery = new WP_Query( array(
'posts_per_page' => -1,
'cat' => $catID,
) );
// Add a headline:
printf( '<h1>%s</h1>', number_format_i18n( $catQuery->found_posts )
.__( " Posts filed under ", 'YourTextdomain' )
.get_cat_name( $catID ) );
La prochaine chose dont nous avons besoin est un petit plugin personnalisé (ou simplement le mettre dans votre fichier functions.php
si cela ne vous dérange pas de le déplacer pendant les mises à jour ou les changements de thèmes):
<?php
/**
* Plugin Name: (#130009) Merge Two Queries
* Description: "Merges" two queries by using a <code>RecursiveFilterIterator</code> to divide one main query into two queries
* Plugin URl: http://wordpress.stackexchange.com/questions/130009/how-to-merge-two-queries-together
*/
class ThumbnailFilter extends FilterIterator implements Countable
{
private $wp_query;
private $allowed;
private $counter = 0;
public function __construct( Iterator $iterator, WP_Query $wp_query )
{
NULL === $this->wp_query AND $this->wp_query = $wp_query;
// Save some processing time by saving it once
NULL === $this->allowed
AND $this->allowed = $this->wp_query->have_posts();
parent::__construct( $iterator );
}
public function accept()
{
if (
! $this->allowed
OR ! $this->current() instanceof WP_Post
)
return FALSE;
// Switch index, Setup post data, etc.
$this->wp_query->the_post();
// Last WP_Post reached: Setup WP_Query for next loop
$this->wp_query->current_post === $this->wp_query->query_vars['posts_per_page'] -1
AND $this->wp_query->rewind_posts();
// Doesn't meet criteria? Abort.
if ( $this->deny() )
return FALSE;
$this->counter++;
return TRUE;
}
public function deny()
{
return ! has_post_thumbnail( $this->current()->ID );
}
public function count()
{
return $this->counter;
}
}
Ce plugin fait une chose: Il utilise la PHP SPL (bibliothèque standard PHP Bibliothèque) } _ et ses interfaces et itérateurs. Ce que nous avons maintenant est une FilterIterator
qui nous permet de supprimer facilement des éléments de notre boucle. Il étend l'itérateur de filtre SPL PHP afin que nous n'ayons pas à tout définir. Le code est bien commenté, mais voici quelques notes:
accept()
permet de définir des critères autorisant ou non la mise en boucle de l’élément.WP_Query::the_post()
afin que vous puissiez simplement utiliser chaque balise de modèle dans votre boucle de fichiers de modèle.FilterIterator
: deny()
. Cette méthode est particulièrement pratique car elle ne contient que notre instruction "process or not" et nous pouvons facilement l'écraser dans les classes ultérieures sans avoir à rien connaître en dehors des balises de modèle WordPress.Avec ce nouvel itérateur, nous n’avons plus besoin de if ( $customQuery->have_posts() )
et while ( $customQuery->have_posts() )
. Nous pouvons utiliser une simple déclaration foreach
car toutes les vérifications nécessaires sont déjà effectuées pour nous. Exemple:
global $wp_query;
// First we need an ArrayObject made out of the actual posts
$arrayObj = new ArrayObject( $wp_query->get_posts() );
// Then we need to throw it into our new custom Filter Iterator
// We pass the $wp_query object in as second argument to keep track with it
$primaryQuery = new ThumbnailFilter( $arrayObj->getIterator(), $wp_query );
Enfin, nous n’avons besoin de rien de plus qu’une boucle foreach
par défaut. Nous pouvons même supprimer the_post()
et continuer à utiliser toutes les balises de modèle. L'objet global $post
restera toujours synchronisé.
foreach ( $primaryQuery as $post )
{
var_dump( get_the_ID() );
}
Maintenant, ce qui est bien, c’est que tous les filtres de requête ultérieurs sont assez faciles à manipuler: définissez simplement la méthode deny()
et vous êtes prêt pour votre prochaine boucle. $this->current()
désignera toujours notre publication en boucle.
class NoThumbnailFilter extends ThumbnailFilter
{
public function deny()
{
return has_post_thumbnail( $this->current()->ID );
}
}
Comme nous avons défini que nous avons maintenant deny()
boucler chaque publication qui a une vignette, nous pouvons alors boucler instantanément toutes les publications sans vignette:
foreach ( $secondaryQuery as $post )
{
var_dump( get_the_title( get_the_ID() ) );
}
Le test plugin } suivant est disponible en tant que Gist sur GitHub. Il suffit de télécharger et de l'activer. Il génère/affiche l'ID de chaque publication en boucle en tant que rappel sur l'action loop_start
. Cela signifie que la sortie peut être assez lourde en fonction de votre configuration, du nombre de publications et de la configuration. Ajoutez quelques instructions d'abandon et modifiez les var_dump()
s à la fin en ce que vous voulez voir et où vous voulez le voir. C'est juste une preuve de concept.
Bien que ce ne soit pas la meilleure façon de résoudre ce problème (la réponse de @ kaiser est:), pour répondre directement à la question, les résultats de la requête seront en $loop->posts
et $loop2->posts
, donc ...
$mergedloops = array_merge($loop->posts, $loop2->posts);
... devrait fonctionner, mais vous devez utiliser une boucle foreach
et non la structure de boucle standard basée sur WP_Query
, car la fusion de requêtes de ce type rompra les données "méta" de l'objet WP_Query
concernant la boucle.
Vous pouvez aussi faire ceci:
$loop = new WP_Query( array('fields' => 'ids','meta_key' => '_thumbnail_id', 'cat' => 1 ) );
$loop2 = new WP_Query( array('fields' => 'ids','meta_key' => '', 'cat' => 1 ) );
$ids = array_merge($loop->posts, $loop2->posts);
$merged = new WP_Query(array('post__in' => $ids,'orderby' => 'post__in'));
Bien entendu, ces solutions représentent plusieurs requêtes. C'est pourquoi @ Kaiser est la meilleure approche pour les cas de ce type où WP_Query
peut gérer la logique nécessaire.
Ce dont vous avez besoin est en fait une troisième requête pour obtenir tous les messages en même temps. Ensuite, vous modifiez vos deux premières requêtes pour ne pas renvoyer les publications, mais uniquement les identifiants de publication dans un format avec lequel vous pouvez travailler.
Le paramètre 'fields'=>'ids'
fera en sorte qu'une requête retourne en réalité un tableau de numéros d'ID de publication correspondants. Mais nous ne voulons pas de l'objet de requête entier, nous utilisons donc get_posts pour ceux-ci.
D'abord, obtenez les identifiants de poste dont nous avons besoin:
$imageposts = get_posts( array('fields'=>'ids', 'meta_key' => '_thumbnail_id', 'cat' => 1 ) );
$nonimageposts = get_posts( array('fields'=>'ids', 'meta_key' => '', 'cat' => 1 ) );
$ imageposts et $ nonimageposts constitueront désormais un tableau de numéros d'identification, nous les fusionnons
$mypostids = array_merge( $imageposts, $nonimageposts );
Éliminez les numéros d'identification en double ...
$mypostids = array_unique( $mypostids );
Maintenant, faites une requête pour obtenir les publications réelles dans l'ordre spécifié:
$loop = new WP_Query( array('post__in' => $mypostids, 'ignore_sticky_posts' => true, 'orderby' => 'post__in' ) );
La variable $ loop est maintenant un objet WP_Query contenant vos publications.
En fait, il existe meta_query
(ou WP_Meta_Query
) - qui prend un tableau de tableaux - dans lequel vous pouvez rechercher les lignes _thumbnail_id
. Si vous recherchez ensuite EXISTS
, vous ne pouvez obtenir que ceux qui possèdent ce champ. En combinant ceci avec l'argument cat
, vous obtiendrez uniquement les publications attribuées à la catégorie avec l'ID 1
et auxquelles une vignette est jointe. Si vous les commandez ensuite à l'aide du meta_value_num
, vous les classerez par l'ID de vignette le plus petit au plus élevé (comme indiqué avec order
et ASC
). Il n'est pas nécessaire de spécifier la valeur value
lorsque vous utilisez EXISTS
en tant que valeur compare
.
$thumbsUp = new WP_Query( array(
'cat' => 1,
'meta_query' => array(
array(
'key' => '_thumbnail_id',
'compare' => 'EXISTS',
),
),
'orderby' => 'meta_value_num',
'order' => 'ASC',
) );
Désormais, lorsque vous passez en boucle entre eux, vous pouvez collecter tous les ID et les utiliser dans une instruction exclusive pour la requête subsidiaire:
$postsWithThumbnails = array();
if ( $thumbsUp->have_posts() )
{
while ( $thumbsUp->have_posts() )
{
$thumbsUp->the_post();
// collect them
$postsWithThumbnails[] = get_the_ID();
// do display/rendering stuff here
}
}
Vous pouvez maintenant ajouter votre seconde requête. Pas besoin de wp_reset_postdata()
ici - tout est dans la variable et non dans la requête principale.
$noThumbnails = new WP_Query( array(
'cat' => 1,
'post__not_in' => $postsWithThumbnails
) );
// Loop through this posts
Bien sûr, vous pouvez être beaucoup plus intelligent et simplement modifier l'instruction SQL dans pre_get_posts
pour ne pas gaspiller la requête principale. Vous pouvez aussi simplement effectuer la première requête ($thumbsUp
ci-dessus) dans un rappel de filtre pre_get_posts
.
add_filter( 'pre_get_posts', 'wpse130009excludeThumbsPosts' );
function wpse130009excludeThumbsPosts( $query )
{
if ( $query->is_admin() )
return $query;
if ( ! $query->is_main_query() )
return $query;
if ( 'post' !== $query->get( 'post_type' ) )
return $query;
// Only needed if this query is for the category archive for cat 1
if (
$query->is_archive()
AND ! $query->is_category( 1 )
)
return $query;
$query->set( 'meta_query', array(
array(
'key' => '_thumbnail_id',
'compare' => 'EXISTS',
),
) );
$query->set( 'orderby', 'meta_value_num' );
// In case we're not on the cat = 1 category archive page, we need the following:
$query->set( 'category__in', 1 );
return $query;
}
Cela a modifié la requête principale, nous n'obtiendrons que les publications auxquelles une vignette est jointe. Nous pouvons maintenant (comme indiqué dans la 1ère requête ci-dessus) collecter les identifiants lors de la boucle principale, puis ajouter une seconde requête qui affiche le reste des publications (sans vignette).
En dehors de cela, vous pouvez être encore plus intelligent et modifier posts_clauses
et modifier la requête directement en fonction de la méta-valeur. Jetez un coup d'œil à cette réponse , car l'actuel n'est qu'un point de départ.