So führen Sie zwei Abfragen zusammen

10

Ich versuche, die Beiträge in einer Kategorie zu ordnen, indem ich zuerst die Beiträge mit Bildern und dann zuletzt die Beiträge ohne Bilder anzeige. Ich habe es geschafft, indem ich zwei Abfragen ausgeführt habe, und jetzt möchte ich die beiden Abfragen zusammenführen.

Ich habe folgendes:

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

Wenn ich jedoch versuche, die Seite anzuzeigen, wird der folgende Fehler angezeigt:

 Fatal error: Call to a member function have_posts() on a non-object in...

Ich habe dann versucht, array_merge in ein Objekt umzuwandeln, aber ich habe den folgenden Fehler erhalten:

Fatal error: Call to undefined method stdClass::have_posts() in...

Wie kann ich diesen Fehler beheben?

Howli
quelle

Antworten:

8

Eine einzelne Abfrage

Ich habe ein bisschen mehr darüber nachgedacht und es besteht die Möglichkeit, dass Sie mit einer einzelnen / der Hauptabfrage arbeiten können. Oder mit anderen Worten: Sie benötigen keine zwei zusätzlichen Abfragen, wenn Sie mit der Standardabfrage arbeiten können. Und falls Sie nicht mit einer Standardabfrage arbeiten können, benötigen Sie nicht mehr als eine einzelne Abfrage, unabhängig davon, wie viele Schleifen Sie die Abfrage aufteilen möchten.

Voraussetzungen

Zuerst müssen Sie (wie in meiner anderen Antwort gezeigt) die erforderlichen Werte in einem pre_get_postsFilter festlegen . Dort wirst du wahrscheinlich setzen posts_per_pageund cat. Beispiel ohne den pre_get_postsFilter:

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

Eine Basis bauen

Das nächste, was wir brauchen, ist ein kleines benutzerdefiniertes Plugin (oder functions.phpfügen Sie es einfach in Ihre Datei ein, wenn es Ihnen nichts ausmacht, es während Updates oder Themenänderungen zu verschieben):

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

Dieses Plugin macht eines: Es verwendet die PHP SPL (Standard PHP Library) und ihre Schnittstellen und Iteratoren. Was wir jetzt haben, ist ein FilterIterator, mit dem wir bequem Gegenstände aus unserer Schleife entfernen können. Es erweitert den PHP SPL Filter Iterator, sodass wir nicht alles einstellen müssen. Der Code ist gut kommentiert, aber hier sind einige Hinweise:

  1. Mit dieser accept()Methode können Kriterien definiert werden, die das Durchlaufen des Elements ermöglichen - oder auch nicht.
  2. Innerhalb dieser Methode verwenden wir WP_Query::the_post(), sodass Sie einfach jedes Vorlagen-Tag in Ihrer Vorlagendateischleife verwenden können.
  3. Außerdem überwachen wir die Schleife und spulen die Beiträge zurück, wenn wir das letzte Element erreichen. Dies ermöglicht es, eine unendliche Anzahl von Schleifen zu durchlaufen, ohne unsere Abfrage zurückzusetzen.
  4. Es gibt eine benutzerdefinierte Methode, die nicht Teil der FilterIteratorSpezifikationen ist : deny(). Diese Methode ist besonders praktisch, da sie nur unsere "Prozess oder nicht" -Statement enthält und wir sie in späteren Klassen leicht überschreiben können, ohne etwas anderes als WordPress-Vorlagen-Tags wissen zu müssen.

Wie schleife ich?

Mit diesem neuen Iterator brauchen wir if ( $customQuery->have_posts() )und while ( $customQuery->have_posts() )nicht mehr. Wir können mit einer einfachen foreachAussage gehen, da alle erforderlichen Prüfungen bereits für uns durchgeführt wurden. Beispiel:

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

Schließlich brauchen wir nichts weiter als eine Standardschleife foreach. Wir können sogar the_post()alle Vorlagen-Tags löschen und trotzdem verwenden. Das globale $postObjekt bleibt immer synchron.

foreach ( $primaryQuery as $post )
{
    var_dump( get_the_ID() );
}

Nebenschleifen

Das Schöne ist nun, dass jeder spätere Abfragefilter ganz einfach zu handhaben ist: Definieren deny()Sie einfach die Methode und schon können Sie mit der nächsten Schleife beginnen. $this->current()wird immer auf unseren aktuell geloopten Beitrag verweisen.

class NoThumbnailFilter extends ThumbnailFilter
{
    public function deny()
    {
        return has_post_thumbnail( $this->current()->ID );
    }
}

Da wir definiert haben, dass wir jetzt deny()jeden Beitrag mit einer Miniaturansicht schleifen, können wir sofort alle Beiträge ohne Miniaturansicht schleifen:

foreach ( $secondaryQuery as $post )
{
    var_dump( get_the_title( get_the_ID() ) );
}

Probier es aus.

Das folgende Test-Plugin ist als Gist auf GitHub verfügbar. Einfach hochladen und aktivieren. Es gibt die ID jedes geloopten Posts als Rückruf für die loop_startAktion aus / gibt sie aus. Dies bedeutet, dass abhängig von Ihrem Setup, der Anzahl der Beiträge und der Konfiguration möglicherweise einiges ausgegeben wird. Bitte fügen Sie einige Abbruchanweisungen hinzu und ändern Sie das var_dump()s am Ende so, wie Sie es sehen möchten und wo Sie es sehen möchten. Es ist nur ein Proof of Concept.

Kaiser
quelle
6

Dies ist zwar nicht der beste Weg, um dieses Problem zu lösen (die Antwort von @ kaiser lautet), aber um die Frage direkt zu beantworten, werden die tatsächlichen Abfrageergebnisse angezeigt $loop->postsund $loop2->posts...

$mergedloops = array_merge($loop->posts, $loop2->posts);

... sollte funktionieren, aber Sie müssten eine foreachSchleife und nicht die WP_Querybasierte Standardschleifenstruktur verwenden, da durch das Zusammenführen solcher Abfragen die WP_Query"Metadaten" des Objekts über die Schleife beschädigt werden.

Sie können dies auch tun:

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

Natürlich stellen diese Lösungen mehrere Abfragen dar, weshalb @ Kaiser's der bessere Ansatz für Fälle wie diesen ist, in denen WP_Querydie erforderliche Logik verarbeitet werden kann.

s_ha_dum
quelle
3

Tatsächlich gibt es meta_query(oder WP_Meta_Query) - das ein Array von Arrays benötigt - wo Sie nach den _thumbnail_idZeilen suchen können . Wenn Sie dann nach suchen EXISTS, können Sie nur diejenigen erhalten, die dieses Feld haben. Wenn Sie dies mit dem catArgument kombinieren, erhalten Sie nur Beiträge, die der Kategorie mit der ID von zugewiesen sind 1und an die ein Miniaturbild angehängt ist. Wenn Sie sie dann nach dem bestellen, bestellen Sie sie meta_value_numtatsächlich nach der niedrigsten bis höchsten Miniatur-ID (wie mit orderund angegeben ASC). Sie müssen das nicht angeben, valuewenn Sie es EXISTSals compareWert verwenden.

$thumbsUp = new WP_Query( array( 
    'cat'        => 1,
    'meta_query' => array( 
        array(
            'key'     => '_thumbnail_id',
            'compare' => 'EXISTS',
        ),
    ),
    'orderby'    => 'meta_value_num',
    'order'      => 'ASC',
) );

Wenn Sie sie jetzt durchlaufen, können Sie alle IDs sammeln und in einer exklusiven Anweisung für die Nebenabfrage verwenden:

$postsWithThumbnails = array();
if ( $thumbsUp->have_posts() )
{
    while ( $thumbsUp->have_posts() )
    {
        $thumbsUp->the_post();

        // collect them
        $postsWithThumbnails[] = get_the_ID();

        // do display/rendering stuff here
    }
}

Jetzt können Sie Ihre zweite Abfrage hinzufügen. Keine Notwendigkeit wp_reset_postdata()hier - alles ist in der Variablen und nicht in der Hauptabfrage.

$noThumbnails = new WP_Query( array(
    'cat'          => 1,
    'post__not_in' => $postsWithThumbnails
) );
// Loop through this posts

Natürlich können Sie viel schlauer sein und einfach die darin enthaltene SQL-Anweisung ändern, um pre_get_postsdie Hauptabfrage nicht zu verschwenden. Sie können auch einfach die erste Abfrage ( $thumbsUpoben) innerhalb eines pre_get_postsFilterrückrufs durchführen.

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

Dadurch wurde die Hauptabfrage geändert, sodass nur Beiträge mit einem Miniaturbild angezeigt werden. Jetzt können wir (wie in der ersten Abfrage oben gezeigt) die IDs während der Hauptschleife sammeln und dann eine zweite Abfrage hinzufügen, die den Rest der Beiträge anzeigt (ohne Miniaturansicht).

Abgesehen davon können Sie noch intelligenter werden und posts_clausesdie Abfrage direkt nach dem Metawert ändern und modifizieren. Schauen Sie sich diese Antwort an, da die aktuelle nur ein Ausgangspunkt ist.

Kaiser
quelle
3

Was Sie brauchen, ist tatsächlich eine dritte Abfrage, um alle Beiträge auf einmal zu erhalten. Anschließend ändern Sie Ihre ersten beiden Abfragen so, dass nicht die Beiträge zurückgegeben werden, sondern nur die Beitrags-IDs in einem Format, mit dem Sie arbeiten können.

Der 'fields'=>'ids'Parameter bewirkt, dass eine Abfrage tatsächlich ein Array übereinstimmender Post-ID-Nummern zurückgibt. Wir wollen aber nicht das gesamte Abfrageobjekt, also verwenden wir stattdessen get_posts für diese.

Holen Sie sich zuerst die Post-IDs, die wir benötigen:

$imageposts = get_posts( array('fields'=>'ids', 'meta_key' => '_thumbnail_id', 'cat' => 1 ) );
$nonimageposts = get_posts( array('fields'=>'ids', 'meta_key' => '', 'cat' => 1 ) );

$ imageposts und $ nonimageposts sind jetzt beide ein Array von Post-ID-Nummern, daher führen wir sie zusammen

$mypostids = array_merge( $imageposts, $nonimageposts );

Beseitigen Sie die doppelten ID-Nummern ...

$mypostids = array_unique( $mypostids );

Stellen Sie nun eine Abfrage, um die tatsächlichen Beiträge in der angegebenen Reihenfolge abzurufen:

$loop = new WP_Query( array('post__in' => $mypostids, 'ignore_sticky_posts' => true, 'orderby' => 'post__in' ) );

Die Variable $ loop ist jetzt ein WP_Query-Objekt mit Ihren Posts darin.

Otto
quelle
Danke dafür. Es wurde festgestellt, dass dies die am wenigsten komplizierte Lösung ist, um eine Schleifenstruktur und unkomplizierte Paginierungsberechnungen beizubehalten.
Jay Neely