it-swarm.com.de

Verwendung von pre_get_posts auf True Pages und statischen Front Pages

Ich habe ziemlich ausgiebig nachgeforscht, wie man pre_get_posts auf echten Seiten und den statischen Titelseiten verwendet, und es scheint, dass es keine narrensichere Methode gibt.

Die beste Option, die ich bisher gefunden habe, war aus einem Beitrag von @birgire auf Stackoverflow . Ich habe es in eine Demo-Klasse umgeschrieben und den Code etwas dynamischer gemacht

class PreGeTPostsForPages
{
    /**
     * @var string|int $pageID
     * @access protected     
     * @since 1.0.0
     */
    protected $pageID;

    /**
     * @var bool $injectPageIntoLoop
     * @access protected     
     * @since 1.0.0
    */
    protected $injectPageIntoLoop;

    /**
     * @var array $args
     * @access protected     
     * @since 1.0.0
     */
    protected $args;

    /**
     * @var int $validatedPageID
     * @access protected     
     * @since 1.0.0
     */
    protected $validatedPageID = 0;

    /**
     * Constructor
     *
     * @param string|int $pageID = NULL
     * @param bool $injectPageIntoLoop = false
     * @param array| $args = []
     * @since 1.0.0
     */     
    public function __construct( 
        $pageID             = NULL, 
        $injectPageIntoLoop = true, 
        $args               = [] 
    ) { 
        $this->pageID             = $pageID;
        $this->injectPageIntoLoop = $injectPageIntoLoop;
        $this->args               = $args;
    }

    /**
     * Private method validatePageID()
     *
     * Validates the page ID passed
     *
     * @since 1.0.0
     */
    private function validatePageID()
    {
        $validatedPageID       = filter_var( $this->pageID, FILTER_VALIDATE_INT );
        $this->validatedPageID = $validatedPageID;
    }

    /**
     * Public method init()
     *
     * This method is used to initialize our pre_get_posts action
     *
     * @since 1.0.0
     */
    public function init()
    {
        // Load the correct actions according to the value of $this->keepPageIntegrity
        add_action( 'pre_get_posts', [$this, 'preGetPosts'] );
    }

    /**
     * Protected method pageObject()
     *
     * Gets the queried object to use that as page object
     *
     * @since 1.0.0
     */
    protected function pageObject()
    {
        global $wp_the_query;
        return $wp_the_query->get_queried_object();
    }

    /**
     * Public method preGetPosts()
     *
     * This is our call back method for the pre_get_posts action.
     * 
     * The pre_get_posts action will only be used if the page integrity is
     * not an issue, which means that the page will be altered to work like a
     * normal archive page. Here you have the option to inject the page object as
     * first post through the_posts filter when $this->injectPageIntoLoop === true
     *
     * @since 1.0.0
     */
    public function preGetPosts( \WP_Query $q )
    {
        // Make sure that we are on the main query and the desired page
        if (    is_admin() // Only run this on the front end
             || !$q->is_main_query() // Only target the main query
             || !is_page( $this->validatedPageID ) // Run this only on the page specified
        )
            return;

        // Remove the filter to avoid infinte loops
        remove_filter( current_filter(), [$this, __METHOD__] );

        // METHODS:
        $this->validatePageID();
        $this->pageObject();

        $queryArgs             = $this->args;

        // Set default arguments which cannot be changed 
        $queryArgs['pagename'] = NULL;

        // We have reached this point, lets do what we need to do
        foreach ( $queryArgs as $key=>$value ) 
            $q->set( 
                filter_var( $key, FILTER_SANITIZE_STRING ),
                $value // Let WP_Query handle the sanitation of the values accordingly
            );

        // Set $q->is_singular to 0 to get pagination to work
        $q->is_singular = false;

        // FILTERS:
        add_filter( 'the_posts',        [$this, 'addPageAsPost'],   PHP_INT_MAX );
        add_filter( 'template_include', [$this, 'templateInclude'], PHP_INT_MAX );  
    }

    /**
     * Public callback method hooked to 'the_posts' filter
     * This will inject the queried object into the array of posts
     * if $this->injectPageIntoLoop === true
     *
     * @since 1.0.0
     */
    public function addPageAsPost( $posts )
    {
        // Inject the page object as a post if $this->injectPageIntoLoop == true
        if ( true === $this->injectPageIntoLoop )
            return array_merge( [$this->pageObject()], $posts );

        return $posts;
    }

    /**
     * Public call back method templateInclude() for the template_include filter
     *
     * @since 1.0.0
     */
    public function templateInclude( $template )
    {
        // Remove the filter to avoid infinte loops
        remove_filter( current_filter(), [$this, __METHOD__] );

        // Get the page template saved in db
        $pageTemplate = get_post_meta( 
            $this->validatedPageID, 
            '_wp_page_template', 
            true 
        );

        // Make sure the template exists before we load it, but only if $template is not 'default'
        if ( 'default' !== $pageTemplate ) {
            $locateTemplate = locate_template( $pageTemplate );
            if ( $locateTemplate )
                return $template = $locateTemplate;
        }

        /**
         * If $template returned 'default', or the template is not located for some reason,
         * we need to get and load the template according to template hierarchy
         *
         * @uses get_page_template()
         */
        return $template = get_page_template();
    }
}

$init = new PreGeTPostsForPages(
    251, // Page ID
    false,
    [
        'posts_per_page' => 3,
        'post_type'      => 'post'
    ]
);
$init->init();

Dies funktioniert gut und funktioniert wie erwartet mit meiner eigenen Paginierungsfunktion .

PROBLEME:

Aufgrund der Funktion verliere ich die Seitenintegrität, was andere Funktionen betrifft, die sich auf das in $post gespeicherte Seitenobjekt stützen. $post bevor die Schleife auf den ersten Beitrag in der Schleife gesetzt wird und $post auf den letzten Beitrag in der Schleife nach der Schleife gesetzt wird, was erwartet wird. Was ich brauche, ist, dass $post auf das aktuelle Seitenobjekt gesetzt ist, dh das abgefragte Objekt.

Außerdem enthalten $wp_the_query->post und $wp_query->post den ersten Beitrag in der Schleife und nicht das abgefragte Objekt wie auf einer normalen Seite

Ich benutze das folgende (außerhalb meiner Klasse), um meine Globals vor und nach der Schleife zu überprüfen

add_action( 'wp_head',   'printGlobals' );
add_action( 'wp_footer', 'printGlobals' );
function printGlobals()
{
    $global_test  = 'QUERIED OBJECT: ' . $GLOBALS['wp_the_query']->queried_object_id . '</br>';
    $global_test .= 'WP_THE_QUERY: ' . $GLOBALS['wp_the_query']->post->ID . '</br>';
    $global_test .= 'WP_QUERY: ' . $GLOBALS['wp_query']->post->ID . '</br>';
    $global_test .= 'POST: ' . $GLOBALS['post']->ID . '</br>';
    $global_test .= 'FOUND_POSTS: ' . $GLOBALS['wp_query']->found_posts . '</br>';
    $global_test .= 'MAX_NUM_PAGES: ' . $GLOBALS['wp_query']->max_num_pages . '</br>';

    ?><pre><?php var_dump( $global_test ); ?></pre><?php
}

VOR DER SCHLEIFE:

Vor der Schleife wird das Problem teilweise gelöst, indem $injectPageIntoLoop auf true gesetzt wird, wodurch das Seitenobjekt als erste Seite in die Schleife eingefügt wird. Dies ist sehr nützlich, wenn Sie die Seiteninformationen vor den angeforderten Posts anzeigen müssen, aber wenn Sie das nicht möchten, sind Sie fertig.

Ich kann das Problem vor der Schleife lösen, indem ich die Globals direkt hacke, was mir nicht wirklich gefällt. Ich verbinde die folgende Methode mit wp in meiner preGetPosts-Methode

public function wp()
{
    $page                          = get_post( $this->pageID );
    $GLOBALS['wp_the_query']->post = $page;
    $GLOBALS['wp_query']           = $GLOBALS['wp_the_query'];
    $GLOBALS['post']               = $page;
}

und innerhalb der preGetPosts Methode

add_action( 'wp', [$this, 'wp'] );

Von hier aus enthalten $wp_the_query->post, $wp_query->post und $post alle das Seitenobjekt.

Nach der Schleife

Hier liegt mein großes Problem nach der Schleife. Nachdem Sie die Globals mit dem Hook und der Methode wp gehackt haben,

  • $wp_the_query->post und $wp_query->post werden wie erwartet auf den ersten Beitrag in der Schleife zurückgesetzt

  • $post wird auf den letzten Beitrag in der Schleife gesetzt.

Was ich brauche ist, dass alle drei auf das abgefragte Objekt/aktuelle Seitenobjekt zurückgesetzt werden.

Ich habe versucht, die Methode wp mit der Aktion loop_end zu verknüpfen, was nicht funktioniert. Das Verknüpfen der Methode wp mit der Aktion get_sidebar funktioniert, aber es ist zu spät.

add_action( 'get_sidebar', [$this, 'wp'] );

Wenn Sie printGlobals() direkt nach der Schleife in der Vorlage ausführen, wird bestätigt, dass $wp_the_query->post und $wp_query->post immer noch auf den ersten Beitrag und $post auf den letzten Beitrag gesetzt sind.

Ich kann den Code manuell in die wp-Methode nach der Schleife in der Vorlage einfügen, aber die Idee ist nicht, die Vorlagendateien direkt zu ändern, da die Klasse in einem Plugin zwischen Themen übertragbar sein sollte.

Gibt es eine geeignete Möglichkeit, dieses Problem zu lösen, wenn pre_get_posts auf einer True-Page- und einer statischen Frontpage ausgeführt wird und die Integrität von $wp_the_query->post, $wp_query->post und $post (wobei diese auf das abgefragte Objekt gesetzt sind) vor und beibehalten wird? nach der Schleife.

BEARBEITEN

Es scheint Verwirrung darüber zu geben, was ich brauche und warum ich es brauche

Was ich brauche

Ich muss die Werte von $wp_the_query->post, $wp_query->post und $post unabhängig von der Vorlage beibehalten, und dieser Wert sollte das abgefragte Objekt sein. In diesem Stadium enthalten die Werte dieser drei Variablen mit dem von mir veröffentlichten Code nicht das Seitenobjekt, sondern veröffentlichen Objekte von Posts in der Schleife. Ich hoffe das ist klar genug.

Ich habe Code gepostet, mit dem Sie diese Variablen testen können

Warum ich es brauche

Ich benötige eine zuverlässige Methode, um Posts über pre_get_posts zu Seitenvorlagen und statischen Titelseiten hinzuzufügen, ohne die Funktionalität der gesamten Seite zu ändern. In diesem Stadium unterbricht der fragliche Code nach der Schleife mein Breadcrumb-Feature und das zugehörige Seiten-Feature, da $post das "falsche" Post-Objekt enthält.

Vor allem möchte ich die Seitenvorlagen nicht direkt ändern. Ich möchte Beiträge zu einer Seitenvorlage hinzufügen können, ohne an der Vorlage zu ändern

19
Pieter Goosen

Ich habe es endlich zum Laufen gebracht, aber nicht mit dem Code in meiner Frage. Ich habe die ganze Idee komplett verworfen und neu angefangen, in eine neue Richtung zu gehen.

HINWEIS:

Wenn jemand jemals in der Lage ist, die Probleme in meiner Frage zu klären, können Sie gerne eine Antwort posten. Wenn Sie andere Lösungen haben, können Sie auch eine Antwort schreiben.

ÜBERARBEITETE KLASSE UND LÖSUNG:

Was ich hier versucht habe, war, Post-Injection zu verwenden, anstatt die Hauptabfrage komplett zu ändern und bei all den obigen Problemen festzuhalten, einschließlich (a) direktes Ändern von Globalen, (b) Auflaufen auf das globale Wertproblem und (c) Seitenvorlagen neu zuweisen.

Durch die Verwendung der Nacheinspritzung kann ich die vollständige Integrität der Nacheinspritzung gewährleisten, sodass $wp_the_query->post, $wp_query->post, $posts und $post in der gesamten Vorlage konstant bleiben. Jede dieser Variablen verweist auf das aktuelle Seitenobjekt (wie dies bei echten Seiten der Fall ist). Auf diese Weise wissen Funktionen wie Breadcrumbs, dass die aktuelle Seite eine echte Seite und keine Art Archiv ist.

Ich musste die Hauptabfrage leicht ändern (durch Filter und Aktionen), um die Paginierung anzupassen, aber wir werden darauf zurückkommen.

ABFRAGE NACH EINSPRITZUNG

Um die Nachinjektion durchzuführen, habe ich eine benutzerdefinierte Abfrage verwendet, um die für die Injektion erforderlichen Beiträge zurückzugeben. Ich habe auch die Eigenschaft $found_pages der benutzerdefinierten Abfrage verwendet, um die der Hauptabfrage anzupassen, damit die Paginierung der Hauptabfrage funktioniert. Beiträge werden durch die Aktion loop_end in die Hauptabfrage eingefügt.

Um die benutzerdefinierte Abfrage außerhalb der Klasse zugänglich und verwendbar zu machen, habe ich einige Aktionen eingeführt.

  • Paginierungs-Haken um Paginierungs-Funktionen einzuhängen:

    • pregetgostsforgages_before_loop_pagination

    • pregetgostsforgages_after_loop_pagination

  • Benutzerdefinierter Zähler der die Beiträge in der Schleife zählt. Diese Aktionen können verwendet werden, um zu ändern, wie Beiträge in der Schleife entsprechend der Beitragsnummer angezeigt werden.

    • pregetgostsforgages_counter_before_template_part

    • pregetgostsforgages_counter_after_template_part

  • Allgemeiner Hook für den Zugriff auf das Abfrageobjekt und das aktuelle Beitragsobjekt

    • pregetgostsforgages_current_post_and_object

Mit diesen Hooks haben Sie ein absolutes Hands-Off-Erlebnis, da Sie nichts an der Seitenvorlage selbst ändern müssen, was von Anfang an meine Absicht war. Eine Seite kann vollständig von einem Plugin oder einer Funktionsdatei geändert werden, wodurch diese Lösung sehr dynamisch wird.

Ich habe auch get_template_part() verwendet, um einen Vorlagenteil zu laden, der zum Anzeigen der Beiträge verwendet wird. Die meisten Themen verwenden heutzutage Schablonenteile, was dies in der Klasse sehr nützlich macht. Wenn Ihr Design content.php verwendet, können Sie content einfach an $templatePart übergeben, um content.php zu laden.

Wenn Sie Post-Format-Unterstützung für Vorlagenteile benötigen, können Sie einfach content an $templatePart übergeben und $postFormatSupport an true setzen. Infolgedessen wird der Vorlagenteil content-video.php für einen Beitrag mit dem Beitragsformat video geladen.

Die Hauptfrage

Die folgenden Änderungen wurden an der Hauptabfrage durch die entsprechenden Filter und Aktionen vorgenommen:

  • Um die Hauptabfrage zu paginieren:

    • Der $found_posts-Eigenschaftswert der Injektorabfrage wird über den found_posts-Filter an den des Hauptabfrageobjekts übergeben.

    • Der Wert des vom Benutzer übergebenen Parameters posts_per_page wird durch pre_get_posts auf die Hauptabfrage gesetzt.

    • $max_num_pages berechnet sich aus der Anzahl der Beiträge in $found_posts und posts_per_page. Da is_singular auf Seiten wahr ist, wird die festgelegte LIMIT-Klausel gesperrt. Das einfache Setzen von is_singular auf false führte zu einigen Problemen. Daher habe ich beschlossen, die LIMIT-Klausel über den post_limits-Filter festzulegen. Ich habe die offset der LIMIT-Klausel auf 0 gesetzt, um 404-Werte auf Seiten mit aktivierter Paginierung zu vermeiden.

Dies kümmert sich um die Paginierung und alle Probleme, die durch die Nachinjektion entstehen können.

DAS SEITENOBJEKT

Das aktuelle Seitenobjekt kann unter Verwendung der Standardschleife auf der Seite, getrennt und über den eingefügten Posts, als Beitrag angezeigt werden. Wenn Sie dies nicht benötigen, können Sie einfach $removePageFromLoop auf true setzen. Dadurch wird der Seiteninhalt nicht angezeigt.

In diesem Stadium verwende ich CSS, um das Seitenobjekt durch die Aktionen loop_start und loop_end auszublenden, da ich keine andere Möglichkeit finde, dies zu tun. Der Nachteil dieser Methode ist, dass alles, was mit dem Aktions-Hook the_post in der Hauptabfrage verknüpft ist, ebenfalls ausgeblendet wird.

DIE KLASSE

Die Klasse PreGetPostsForPages kann verbessert werden und sollte auch einen korrekten Namensraum haben. Sie können dies einfach in der Funktionsdatei Ihres Themas ablegen, es ist jedoch besser, dies in ein benutzerdefiniertes Plugin abzulegen.

Verwenden, ändern und missbrauchen Sie, wie Sie es für richtig halten. Der Code ist gut kommentiert, daher sollte er leicht zu befolgen und anzupassen sein

class PreGetPostsForPages
{
    /**
     * @var string|int $pageID
     * @access protected     
     * @since 1.0.0
     */
    protected $pageID;

    /**
     * @var string $templatePart
     * @access protected     
     * @since 1.0.0
     */
    protected $templatePart;

    /**
     * @var bool $postFormatSupport
     * @access protected     
     * @since 1.0.0
     */
    protected $postFormatSupport;

    /**
     * @var bool $removePageFromLoop
     * @access protected     
     * @since 1.0.0
     */
    protected $removePageFromLoop;

    /**
     * @var array $args
     * @access protected     
     * @since 1.0.0
     */
    protected $args;

    /**
     * @var array $mergedArgs
     * @access protected     
     * @since 1.0.0
     */
    protected $mergedArgs = [];

    /**
     * @var NULL|\stdClass $injectorQuery
     * @access protected     
     * @since 1.0.0
     */
    protected $injectorQuery = NULL;

    /**
     * @var int $validatedPageID
     * @access protected     
     * @since 1.0.0
     */
    protected $validatedPageID = 0;

    /** 
     * Constructor method
     *
     * @param string|int $pageID The ID of the page we would like to target
     * @param string $templatePart The template part which should be used to display posts
     * @param string $postFormatSupport Should get_template_part support post format specific template parts
     * @param bool $removePageFromLoop Should the page content be displayed or not
     * @param array $args An array of valid arguments compatible with WP_Query
     *
     * @since 1.0.0
     */      
    public function __construct( 
        $pageID             = NULL,
        $templatePart       = NULL,
        $postFormatSupport  = false,
        $removePageFromLoop = false,
        $args               = [] 
    ) {
        $this->pageID             = $pageID;
        $this->templatePart       = $templatePart;
        $this->postFormatSupport  = $postFormatSupport;
        $this->removePageFromLoop = $removePageFromLoop;
        $this->args               = $args;
    }

    /**
     * Public method init()
     *
     * The init method will be use to initialize our pre_get_posts action
     *
     * @since 1.0.0
     */
    public function init()
    {
        // Initialise our pre_get_posts action
        add_action( 'pre_get_posts', [$this, 'preGetPosts'] );
    }

    /**
     * Private method validatePageID()
     *
     * Validates the page ID passed
     *
     * @since 1.0.0
     */
    private function validatePageID()
    {
        $validatedPageID = filter_var( $this->pageID, FILTER_VALIDATE_INT );
        $this->validatedPageID = $validatedPageID;
    }

    /**
     * Private method mergedArgs()
     *
     * Merge the default args with the user passed args
     *
     * @since 1.0.0
     */
    private function mergedArgs()
    {
        // Set default arguments
        if ( get_query_var( 'paged' ) ) {
            $currentPage = get_query_var( 'paged' );
        } elseif ( get_query_var( 'page' ) ) {
            $currentPage = get_query_var( 'page' );
        } else {
            $currentPage = 1;
        }
        $default = [
            'suppress_filters'    => true,
            'ignore_sticky_posts' => 1,
            'paged'               => $currentPage,
            'posts_per_page'      => get_option( 'posts_per_page' ), // Set posts per page here to set the LIMIT clause etc
            'nopaging'            => false
        ];    
        $mergedArgs = wp_parse_args( (array) $this->args, $default );
        $this->mergedArgs = $mergedArgs;
    }

    /**
     * Public method preGetPosts()
     *
     * This is the callback method which will be hooked to the 
     * pre_get_posts action hook. This method will be used to alter
     * the main query on the page specified by ID.
     *
     * @param \stdClass WP_Query The query object passed by reference
     * @since 1.0.0
     */
    public function preGetPosts( \WP_Query $q )
    {
        if (    !is_admin() // Only target the front end
             && $q->is_main_query() // Only target the main query
             && $q->is_page( filter_var( $this->validatedPageID, FILTER_VALIDATE_INT ) ) // Only target our specified page
        ) {
            // Remove the pre_get_posts action to avoid unexpected issues
            remove_action( current_action(), [$this, __METHOD__] );

            // METHODS:
            // Initialize our method which will return the validated page ID
            $this->validatePageID();
            // Initiale our mergedArgs() method
            $this->mergedArgs();
            // Initiale our custom query method
            $this->injectorQuery();

            /**
             * We need to alter a couple of things here in order for this to work
             * - Set posts_per_page to the user set value in order for the query to
             *   to properly calculate the $max_num_pages property for pagination
             * - Set the $found_posts property of the main query to the $found_posts
             *   property of our custom query we will be using to inject posts
             * - Set the LIMIT clause to the SQL query. By default, on pages, `is_singular` 
             *   returns true on pages which removes the LIMIT clause from the SQL query.
             *   We need the LIMIT clause because an empty limit clause inhibits the calculation
             *   of the $max_num_pages property which we need for pagination
             */
            if (    $this->mergedArgs['posts_per_page'] 
                 && true !== $this->mergedArgs['nopaging']
            ) {
                $q->set( 'posts_per_page', $this->mergedArgs['posts_per_page'] );
            } elseif ( true === $this->mergedArgs['nopaging'] ) {
                $q->set( 'posts_per_page', -1 );
            }

            // FILTERS:
            add_filter( 'found_posts', [$this, 'foundPosts'], PHP_INT_MAX, 2 );
            add_filter( 'post_limits', [$this, 'postLimits']);

            // ACTIONS:
            /**
             * We can now add all our actions that we will be using to inject our custom
             * posts into the main query. We will not be altering the main query or the 
             * main query's $posts property as we would like to keep full integrity of the 
             * $post, $posts globals as well as $wp_query->post. For this reason we will use
             * post injection
             */     
            add_action( 'loop_start', [$this, 'loopStart'], 1 );
            add_action( 'loop_end',   [$this, 'loopEnd'],   1 );
        }    
    }    

    /**
     * Public method injectorQuery
     *
     * This will be the method which will handle our custom
     * query which will be used to 
     * - return the posts that should be injected into the main
     *   query according to the arguments passed
     * - alter the $found_posts property of the main query to make
     *   pagination work 
     *
     * @link https://codex.wordpress.org/Class_Reference/WP_Query
     * @since 1.0.0
     * @return \stdClass $this->injectorQuery
     */
    public function injectorQuery()
    {
        //Define our custom query
        $injectorQuery = new \WP_Query( $this->mergedArgs );

        // Update the thumbnail cache
        update_post_thumbnail_cache( $injectorQuery );

        $this->injectorQuery = $injectorQuery;

        return $this->injectorQuery;
    }

    /**
     * Public callback method foundPosts()
     * 
     * We need to set found_posts in the main query to the $found_posts
     * property of the custom query in order for the main query to correctly 
     * calculate $max_num_pages for pagination
     *
     * @param string $found_posts Passed by reference by the filter
     * @param stdClass \WP_Query Sq The current query object passed by refence
     * @since 1.0.0
     * @return $found_posts
     */
    public function foundPosts( $found_posts, \WP_Query $q )
    {
        if ( !$q->is_main_query() )
            return $found_posts;

        remove_filter( current_filter(), [$this, __METHOD__] );

        // Make sure that $this->injectorQuery actually have a value and is not NULL
        if (    $this->injectorQuery instanceof \WP_Query 
             && 0 != $this->injectorQuery->found_posts
        )
            return $found_posts = $this->injectorQuery->found_posts;

        return $found_posts;
    }

    /**
     * Public callback method postLimits()
     *
     * We need to set the LIMIT clause as it it is removed on pages due to 
     * is_singular returning true. Witout the limit clause, $max_num_pages stays
     * set 0 which avoids pagination. 
     *
     * We will also leave the offset part of the LIMIT cluase to 0 to avoid paged
     * pages returning 404's
     *
     * @param string $limits Passed by reference in the filter
     * @since 1.0.0
     * @return $limits
     */
    public function postLimits( $limits )
    {
        $posts_per_page = (int) $this->mergedArgs['posts_per_page'];
        if (    $posts_per_page
             && -1   !=  $posts_per_page // Make sure that posts_per_page is not set to return all posts
             && true !== $this->mergedArgs['nopaging'] // Make sure that nopaging is not set to true
        ) {
            $limits = "LIMIT 0, $posts_per_page"; // Leave offset at 0 to avoid 404 on paged pages
        }

        return $limits;
    }

    /**
     * Public callback method loopStart()
     *
     * Callback function which will be hooked to the loop_start action hook
     *
     * @param \stdClass \WP_Query $q Query object passed by reference
     * @since 1.0.0
     */
    public function loopStart( \WP_Query $q )
    {
        /**
         * Although we run this action inside our preGetPosts methods and
         * and inside a main query check, we need to redo the check here aswell
         * because failing to do so sets our div in the custom query output as well
         */

        if ( !$q->is_main_query() )
            return;

        /** 
         * Add inline style to hide the page content from the loop
         * whenever $removePageFromLoop is set to true. You can
         * alternatively alter the page template in a child theme by removing
         * everything inside the loop, but keeping the loop
         * Example of how your loop should look like:
         *     while ( have_posts() ) {
         *     the_post();
         *         // Add nothing here
         *     }
         */
        if ( true === $this->removePageFromLoop )
            echo '<div style="display:none">';
    }   

    /**
     * Public callback method loopEnd()
     *
     * Callback function which will be hooked to the loop_end action hook
     *
     * @param \stdClass \WP_Query $q Query object passed by reference
     * @since 1.0.0
     */
    public function loopEnd( \WP_Query $q )
    {  
        /**
         * Although we run this action inside our preGetPosts methods and
         * and inside a main query check, we need to redo the check here as well
         * because failing to do so sets our custom query into an infinite loop
         */
        if ( !$q->is_main_query() )
            return;

        // See the note in the loopStart method  
        if ( true === $this->removePageFromLoop )
            echo '</div>';

        //Make sure that $this->injectorQuery actually have a value and is not NULL
        if ( !$this->injectorQuery instanceof \WP_Query )
            return; 

        // Setup a counter as wee need to run the custom query only once    
        static $count = 0;    

        /**
         * Only run the custom query on the first run of the loop. Any consecutive
         * runs (like if the user runs the loop again), the custom posts won't show.
         */
        if ( 0 === (int) $count ) {      
            // We will now add our custom posts on loop_end
            $this->injectorQuery->rewind_posts();

            // Create our loop
            if ( $this->injectorQuery->have_posts() ) {

                /**
                 * Fires before the loop to add pagination.
                 *
                 * @since 1.0.0
                 *
                 * @param \stdClass $this->injectorQuery Current object (passed by reference).
                 */
                do_action( 'pregetgostsforgages_before_loop_pagination', $this->injectorQuery );


                // Add a static counter for those who need it
                static $counter = 0;

                while ( $this->injectorQuery->have_posts() ) {
                    $this->injectorQuery->the_post(); 

                    /**
                     * Fires before get_template_part.
                     *
                     * @since 1.0.0
                     *
                     * @param int $counter (passed by reference).
                     */
                    do_action( 'pregetgostsforgages_counter_before_template_part', $counter );

                    /**
                     * Fires before get_template_part.
                     *
                     * @since 1.0.0
                     *
                     * @param \stdClass $this->injectorQuery-post Current post object (passed by reference).
                     * @param \stdClass $this->injectorQuery Current object (passed by reference).
                     */
                    do_action( 'pregetgostsforgages_current_post_and_object', $this->injectorQuery->post, $this->injectorQuery );

                    /** 
                     * Load our custom template part as set by the user
                     * 
                     * We will also add template support for post formats. If $this->postFormatSupport
                     * is set to true, get_post_format() will be automatically added in get_template part
                     *
                     * If you have a template called content-video.php, you only need to pass 'content'
                     * to $template part and then set $this->postFormatSupport to true in order to load
                     * content-video.php for video post format posts
                     */
                    $part = '';
                    if ( true === $this->postFormatSupport )
                        $part = get_post_format( $this->injectorQuery->post->ID ); 

                    get_template_part( 
                        filter_var( $this->templatePart, FILTER_SANITIZE_STRING ), 
                        $part
                    );

                    /**
                     * Fires after get_template_part.
                     *
                     * @since 1.0.0
                     *
                     * @param int $counter (passed by reference).
                     */
                    do_action( 'pregetgostsforgages_counter_after_template_part', $counter );

                    $counter++; //Update the counter
                }

                wp_reset_postdata();

                /**
                 * Fires after the loop to add pagination.
                 *
                 * @since 1.0.0
                 *
                 * @param \stdClass $this->injectorQuery Current object (passed by reference).
                 */
                do_action( 'pregetgostsforgages_after_loop_pagination', $this->injectorQuery );
            }
        }

        // Update our static counter
        $count++;       
    }
}  

VERWENDUNGSZWECK

Sie können jetzt die Klasse (auch in Ihrem Plugin oder Ihrer Funktionsdatei) wie folgt initiieren, um die Seite mit der ID 251 als Ziel festzulegen. Daraufhin werden 2 Beiträge pro Seite vom Typ post Beitrag angezeigt.

$query = new PreGetPostsForPages(
    251,       // Page ID we will target
    'content', //Template part which will be used to display posts, name should be without .php extension 
    true,      // Should get_template_part support post formats
    false,     // Should the page object be excluded from the loop
    [          // Array of valid arguments that will be passed to WP_Query/pre_get_posts
        'post_type'      => 'post', 
        'posts_per_page' => 2
    ] 
);
$query->init(); 

PAGINIERUNG UND CUSTOM-STYLING HINZUFÜGEN

Wie bereits erwähnt, gibt es in der Injektorabfrage einige Aktionen, um Paginierung und/oder benutzerdefiniertes Styling hinzuzufügen.

Im folgenden Beispiel habe ich die Paginierung nach der Schleife mit meiner eigenen Paginierungsfunktion aus der verknüpften Antwort hinzugefügt. Außerdem habe ich mithilfe meines benutzerdefinierten Zählers einen <div> hinzugefügt, um meine Posts in zwei Spalten anzuzeigen.

Hier sind die Aktionen, die ich verwendet habe

add_action( 'pregetgostsforgages_counter_before_template_part', function ( $counter )
{
    $class = $counter%2  ? ' right' : ' left';
    echo '<div class="entry-column' . $class . '">';
});

add_action( 'pregetgostsforgages_counter_after_template_part', function ( $counter )
{
    echo '</div>';
});

add_action( 'pregetgostsforgages_after_loop_pagination', function ( \WP_Query $q )
{
    paginated_numbers();    
});

Beachten Sie, dass die Paginierung von der Hauptabfrage und nicht von der Injektorabfrage festgelegt wird. Daher sollten auch integrierte Funktionen wie the_posts_pagination() funktionieren.

Dies ist das Endergebnis

 enter image description here 

STATISCHE VORDERE SEITEN

Alles funktioniert wie erwartet auf statischen Titelseiten zusammen mit meiner Paginierungsfunktion, ohne dass weitere Änderungen erforderlich sind.

FAZIT

Dies mag wie ein großer Aufwand erscheinen, und es mag sein, aber die Profis überwiegen die große Zeit des Betrügers.

GROSSE PRO'S

  • Sie müssen die Seitenvorlage für die jeweilige Seite in keiner Weise ändern. Dies macht alles dynamisch und kann problemlos zwischen Themen übertragen werden, ohne dass Änderungen am Code vorgenommen werden müssen, solange alles in einem Plugin erledigt wird.

  • Sie müssen höchstens einen content.php-Vorlagenteil in Ihrem Design erstellen, wenn Ihr Design noch keinen hat.

  • Jede Paginierung, die für die Hauptabfrage verwendet wird, funktioniert auf der Seite, ohne dass Änderungen oder zusätzliche Informationen aus der Abfrage an function übergeben werden.

Es gibt noch mehr Profis, an die ich jetzt nicht denken kann, aber das sind die wichtigsten.

13
Pieter Goosen