Korzystanie z pre_get_posts na prawdziwych stronach i statycznych stronach głównych

19

Przeprowadziłem dość obszerne badania na temat używania pre_get_posts na prawdziwych stronach i statycznych stronach tytułowych i wydaje się, że nie ma metody głupiego dowodu.

Najlepsza opcja, jaką znalazłem do tej pory, to post opublikowany przez @birgire na Stackoverflow . Przepisałem go na klasę demonstracyjną i uczyniłem kod nieco bardziej dynamicznym

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

Działa to dobrze i strona zgodnie z oczekiwaniami przy użyciu mojej własnej funkcji stronicowania .

PROBLEMY:

Z powodu tej funkcji tracę integralność strony, w której znajdują się inne funkcje zależne od przechowywanego obiektu strony $post. $postprzed ustawieniem pętli na pierwszy post w pętli i $postustawieniem na ostatni post w pętli po pętli, co jest oczekiwane. Potrzebne jest $postustawienie bieżącego obiektu strony, tj. Obiektu, którego dotyczy zapytanie.

Ponadto, $wp_the_query->posti $wp_query->posttrzyma pierwszy post w pętli, a nie obiekt, którego dotyczy zapytanie, jak na normalnej stronie

Korzystam z poniższych ( poza klasą ), aby sprawdzić moje globale przed i po pętli

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
}

PRZED PĘTLĄ:

Przed pętlą problem jest częściowo rozwiązany przez ustawienie wartości $injectPageIntoLooptrue, która wstrzykuje obiekt strony jako pierwszą stronę w pętli. Jest to bardzo przydatne, jeśli chcesz wyświetlić informacje o stronie przed żądanymi postami, ale jeśli tego nie chcesz, to jesteś wkręcony.

Mogę rozwiązać problem przed pętlą bezpośrednio hakując globały, co tak naprawdę nie lubię. Podczepiam następującą metodę do wpmojej preGetPostsmetody

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

i preGetPostsmetoda wewnętrzna

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

Z tego $wp_the_query->post, $wp_query->posta $postwszystko trzyma przedmiot stronę.

PO PĘTLI

To jest mój duży problem, po pętli. Po zhakowaniu globałów za pomocą wphaka i metody,

  • $wp_the_query->posti $wp_query->postjest ustawiany z powrotem na pierwszy post w pętli, zgodnie z oczekiwaniami

  • $post jest ustawiony na ostatni post w pętli.

Potrzebuję tylko, aby wszystkie trzy zostały ustawione z powrotem na obiekt zapytany / bieżący obiekt strony.

Próbowałem podpiąć wpmetodę do loop_enddziałania, co nie działa. Podłączenie wpmetody do get_sidebardziałania działa, ale jest już za późno.

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

Uruchomienie printGlobals()bezpośrednio po pętli w szablonie potwierdza, że ​​jako $wp_the_query->posti $wp_query->postwciąż są ustawione na pierwszy post i $postostatni post.

Mogę ręcznie dodać kod w wpmetodzie po pętli w szablonie, ale pomysł nie polega na bezpośredniej zmianie plików szablonów, ponieważ klasa powinna być przenoszona we wtyczce między motywami.

Czy istnieje jakiś właściwy sposób rozwiązać ten problem, w którym jeden bieg pre_get_postsna prawdziwej stronie i stronie statycznej przedniej i nadal zachować integralność $wp_the_query->post, $wp_query->postoraz $post( posiadające wymienione na odpytywany obiektu ) przed i po pętli.

EDYTOWAĆ

Wydaje się, że istnieje zamieszanie co do tego, czego potrzebuję i dlaczego go potrzebuję

Czego potrzebuję

Muszę zachować wartości $wp_the_query->post, $wp_query->posta $postpo drugiej stronie szablonu niezależnie, a ta wartość powinna być poszukiwana przedmiot. Na tym etapie, z kodem, który opublikowałem, wartości tych trzech zmiennych nie przechowują obiektu strony, ale raczej publikują obiekty postów w pętli. Mam nadzieję, że to dość jasne.

Opublikowałem kod, którego można użyć do przetestowania tych zmiennych

Dlaczego tego potrzebuję

Potrzebuję niezawodnego sposobu dodawania postów pre_get_postsdo szablonów stron i statycznych stron głównych bez zmiany pełnej funkcjonalności strony. Na tym etapie, gdy kod, o którym mowa, łamie moją funkcję nawigacyjną i powiązaną funkcję strony po pętli, w wyniku $postczego przechowuje „zły” obiekt postu.

Przede wszystkim nie chcę bezpośrednio zmieniać szablonów stron. Chcę mieć możliwość dodawania postów do szablonu strony bez ŻADNEJ modyfikacji tego szablonu

Pieter Goosen
źródło
Co próbujesz zrobić, swoje cele lub wymagania funkcjonalne? Nigdzie o tym nie mówię.
adelval

Odpowiedzi:

13

W końcu udało mi się go uruchomić, ale nie z kodem w moim pytaniu. Całkowicie zrzuciłem cały ten pomysł i zacząłem iść w nowym kierunku.

UWAGA:

Jeśli ktokolwiek jest w stanie rozwiązać problemy w moim pytaniu, prosimy o odpowiedź. Ponadto, jeśli masz jakieś inne rozwiązania, możesz opublikować odpowiedź.

ODNOWIONA KLASA I ROZWIĄZANIE:

To, co próbowałem tutaj zrobić, to użyć post zastrzyku, zamiast całkowicie zmienić główne zapytanie i utknąć we wszystkich powyższych kwestiach, w tym (a) bezpośrednio zmieniając globały, (b) napotykając problem globalnej wartości oraz (c) ponowne przypisywanie szablonów stron.

Za pomocą iniekcji, jestem w stanie utrzymać pełną integralność wpisu, więc $wp_the_query->post, $wp_query->post, $postsi $postpobyt stały w całym szablonie. Każda z tych zmiennych odwołuje się do bieżącego obiektu strony (jak w przypadku prawdziwych stron). W ten sposób funkcje takie jak bułka tarta wiedzą, że bieżąca strona jest prawdziwą stroną, a nie jakimś archiwum.

Musiałem jednak nieco zmienić główne zapytanie ( poprzez filtry i działania ), aby dostosować się do stronicowania, ale do tego dojdziemy.

ZAPYTANIE PO WTRYSKU

Aby wykonać post wstrzyknięcie, użyłem niestandardowego zapytania, aby zwrócić posty potrzebne do wstrzyknięcia. Użyłem również właściwości niestandardowego zapytania, $found_pagesaby dostosować właściwość głównego zapytania, aby podział na strony działał z głównego zapytania. Posty są wstrzykiwane do głównego zapytania poprzezloop_end akcję.

Aby niestandardowe zapytanie było dostępne i użyteczne poza klasą, wprowadziłem kilka akcji.

  • Haczyki stronicowania w celu zaczepienia funkcji stronicowania:

    • pregetgostsforgages_before_loop_pagination

    • pregetgostsforgages_after_loop_pagination

  • Niestandardowy licznik zliczający posty w pętli. Tych działań można użyć do zmiany sposobu wyświetlania postów w pętli zgodnie z numerem postu.

    • pregetgostsforgages_counter_before_template_part

    • pregetgostsforgages_counter_after_template_part

  • Hak ogólny, aby uzyskać dostęp do obiektu zapytania i bieżącego obiektu postu

    • pregetgostsforgages_current_post_and_object

Te zaczepy zapewniają całkowitą swobodę rąk, ponieważ nie trzeba niczego zmieniać w samym szablonie strony, co było moją pierwotną intencją od samego początku. Stronę można całkowicie zmienić z wtyczki lub pliku funkcji, co czyni to rozwiązanie bardzo dynamicznym.

Użyłem również get_template_part()w celu załadowania części szablonu, która będzie używana do wyświetlania postów. Obecnie większość motywów korzysta z części szablonu, co czyni je bardzo przydatnymi w klasie. Jeśli Twoje zastosowań tematycznych content.php, można po prostu przejść contentsię $templatePartdo obciążenia content.php.

Jeśli potrzebujesz obsługi formatu pocztowego dla części szablonu, jest to łatwe - możesz po prostu przejść contentdo $templateParti ustawić $postFormatSupportna true. W rezultacie część szablonu content-video.phpzostanie załadowana dla posta w formacie postu video.

GŁÓWNE ZAPYTANIE

W głównym zapytaniu wprowadzono następujące zmiany poprzez odpowiednie filtry i akcje:

  • Aby paginować główne zapytanie:

    • Wartość $found_postswłaściwości zapytania iniektora jest przekazywana do właściwości głównego obiektu zapytania przez found_postsfiltr.

    • Wartość parametru przekazanego przez użytkownika posts_per_pagejest ustawiana na zapytanie główne poprzez pre_get_posts.

    • $max_num_pagesjest obliczany na podstawie liczby postów w $found_posts i posts_per_page. Ponieważ is_singularjest to prawda na stronach, hamuje LIMITustawianie klauzuli. Samo ustawienie wartości is_singularfalse spowodowało kilka problemów, więc postanowiłem ustawić LIMITklauzulę przez post_limitsfiltr. Ciągle offsetz LIMITzestawem klauzula 0uniknąć 404 jest na stronach z paginacji włączone.

To zajmuje się paginacją i wszelkimi problemami, które mogą wyniknąć z wstrzyknięcia.

OBIEKT STRONY

Bieżący obiekt strony jest dostępny do wyświetlania jako post za pomocą domyślnej pętli na stronie, osobnej i na górze wstrzykniętych postów. Jeśli nie jest to potrzebne, możesz po prostu ustawić wartość $removePageFromLooptrue, a to ukryje zawartość strony przed wyświetlaniem.

Na tym etapie używam CSS do ukrywania obiektu strony poprzez akcje loop_starti, loop_endponieważ nie mogę znaleźć innego sposobu na zrobienie tego. Minusem tej metody jest to, że wszystko, co podpięte jest do the_posthaka akcji w głównym zapytaniu, również zostanie ukryte.

KLASA

PreGetPostsForPagesKlasa może zostać ulepszone i powinny być prawidłowo przestrzeni nazw, jak również. Chociaż możesz po prostu upuścić to w pliku funkcji motywu, lepiej byłoby upuścić to w niestandardowej wtyczce.

Używaj, modyfikuj i nadużywaj według własnego uznania. Kod jest dobrze skomentowany, więc powinno być łatwe do naśladowania i dostosowania

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

STOSOWANIE

Możesz teraz zainicjować klasę ( także w pliku wtyczki lub pliku funkcji ) w następujący sposób, aby kierować reklamy na stronę o identyfikatorze 251, na której pokażemy 2 posty na stronę z danego posttypu.

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

DODAWANIE PAGINACJI I STYLIZACJI NIESTANDARDOWEJ

Jak wspomniałem wcześniej, w zapytaniu wtryskiwacza jest kilka akcji w celu dodania stronicowania i / lub niestandardowego stylu.

W poniższym przykładzie dodałem paginację po pętli, używając własnej funkcji paginacji z połączonej odpowiedzi . Ponadto, używając mojego niestandardowego licznika, dodałem<div> aby wyświetlić moje posty w dwóch kolumnach.

Oto działania, których użyłem

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

Zauważ, że podział na strony jest ustawiany przez zapytanie główne, a nie zapytanie iniektora, więc wbudowane funkcje, takie jak the_posts_pagination()powinny również działać.

To jest wynik końcowy

wprowadź opis zdjęcia tutaj

STATYCZNE STRONY PRZEDNIE

Wszystko działa zgodnie z oczekiwaniami na statycznych stronach głównych wraz z moją funkcją paginacji, bez konieczności jakiejkolwiek dalszej modyfikacji.

WNIOSEK

Może się to wydawać dużym obciążeniem i może być, ale profesjonaliści przeważają nad wielkim czasem oszustwa.

BIG PRO'S

  • Nie musisz w żaden sposób zmieniać szablonu strony dla konkretnej strony. To sprawia, że ​​wszystko jest dynamiczne i może być łatwo przenoszone między motywami bez modyfikacji kodu, o ile wszystko odbywa się we wtyczce.

  • Co najwyżej musisz utworzyć content.phpczęść szablonu w motywie, tylko jeśli Twój motyw jeszcze go nie ma.

  • Każda podział na strony, który działa na głównym zapytaniu, będzie działał na stronie bez żadnych zmian ani żadnych dodatkowych informacji z zapytania przekazywanego do funkcji.

Jest więcej pro, o których nie mogę teraz myśleć, ale to są te ważne.

Pieter Goosen
źródło
3
i.imgur.com/LTfLK.gif
TheDeadMedic