在真实页面和静态首页上使用pre_get_post

时间:2016-01-18 作者:Pieter Goosen

我对如何使用pre_get_posts 在真实页面和静态首页上,似乎没有什么万无一失的方法。

到目前为止,我发现最好的选择是post done by @birgire on Stackoverflow. 我已将其重写为演示类,并使代码更具动态性

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();
通过使用my own pagination function.

问题:

由于该函数,我失去了页面完整性,这会填充依赖于中存储的页面对象的其他函数$post. $post 将循环设置为循环中的第一个立柱之前,以及$post 设置为循环后的循环中的最后一个帖子,这是预期的。我需要的是$post 设置为当前页面对象,即查询的对象。

而且$wp_the_query->post$wp_query->post 保存循环中的第一篇文章,而不是像普通页面上那样保存查询的对象

我使用以下(在我的课堂之外)在循环前后检查我的全局变量

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
}
循环之前:在循环之前,通过设置$injectPageIntoLoop 将页面对象作为循环中的第一个页面注入。如果您需要在请求的帖子之前显示页面信息,这是非常有用的,但是如果您不想这样做,那么您就完蛋了。

我可以通过直接破解globals来解决循环之前的问题,这我真的不喜欢。我将以下方法挂接到wp 在我的内部preGetPosts 方法

public function wp()
{
    $page                          = get_post( $this->pageID );
    $GLOBALS[\'wp_the_query\']->post = $page;
    $GLOBALS[\'wp_query\']           = $GLOBALS[\'wp_the_query\'];
    $GLOBALS[\'post\']               = $page;
}
和内部preGetPosts 方法

add_action( \'wp\', [$this, \'wp\'] );
从这里,$wp_the_query->post, $wp_query->post$post all保存page对象。

在循环之后,这就是我的大问题所在,在循环之后。在通过wp 挂钩和方法,

  • $wp_the_query->post$wp_query->post 按预期设置回循环中的第一个帖子

  • $post 设置为循环中的最后一个帖子。

    我需要的是将这三个对象都设置回查询对象/当前页面对象。

    我试过把wp 方法到loop_end 操作,但不起作用。挂钩wp 方法到get_sidebar 行动可行,但为时已晚。

    add_action( \'get_sidebar\', [$this, \'wp\'] );
    
    正在运行printGlobals() 直接在模板中的循环确认为$wp_the_query->post$wp_query->post 仍设置为第一个立柱$post 到最后一个帖子。

    我可以手动将代码添加到wp 方法,但其思想不是直接更改模板文件,因为类应该可以在主题之间的插件中转移。

    有没有合适的方法来解决这个问题pre_get_posts 在真实页面和静态首页上,仍然保持$wp_the_query->post, $wp_query->post, 和$post 在循环之前和之后,将这些设置为查询对象。

    编辑似乎对我需要什么以及为什么需要它感到困惑

    What I need

    我需要保留$wp_the_query->post, $wp_query->post$post 而该值应该是查询的对象。在这个阶段,对于我发布的代码,这三个变量的值并不保存页面对象,而是保存循环中帖子的post对象。我希望这足够清楚。

    我已经发布了可以用来测试这些变量的代码

    Why I need it

    我需要一种可靠的方式通过pre_get_posts 页面模板和静态首页,而不更改完整页面功能。在这个阶段,正如所讨论的代码所示,由于$post 它持有“错误”的post对象。

    最重要的是,我不想直接更改页面模板。我希望能够在没有ANY 修改模板

1 个回复
最合适的回答,由SO网友:Pieter Goosen 整理而成

I finally got it working, but not with the code in my question. I totally scrapped that whole idea and restarted going in a new direction.

NOTE:

If anyone is ever able to sort out the issues in my question, feel free to post an answer. Also, if you have any other solutions, feel free to post an answer.

REWORKED CLASS AND SOLUTION:

What I tried to do here was to use post injection, rather than completely altering the main query and getting stuck with all the above issues, including (a) directly altering globals, (b) running into the global value issue, and (c) reassigning page templates.

By using post injection, I\'m able to keep full post integrity, so $wp_the_query->post, $wp_query->post, $posts and $post stay constant throughout the template. Each of these variables reference the current page object (as is the case with true pages). This way, functions like breadcrumbs know that the current page is a true page and not some kind of archive.

I had to alter the main query slightly (through filters and actions) to adjust for pagination though, but we will come to that.

POST INJECTION QUERY

In order to accomplish post injection, I used a custom query to return the posts needed for injection. I also used the custom query\'s $found_pages property to adjust that of the main query to get pagination working from the main query. Posts are injected into the main query through the loop_end action.

In order to make the custom query accessible and usable outside the class, I introduced a couple of actions.

  • Pagination hooks in order to hook pagination funtions:

    • pregetgostsforgages_before_loop_pagination

    • pregetgostsforgages_after_loop_pagination

  • Custom counter which counts the posts in the loop. These actions can be used to alter how posts are displayed inside the loop according to post number.

    • pregetgostsforgages_counter_before_template_part

    • pregetgostsforgages_counter_after_template_part

  • General hook to access the query object and current post object

    • pregetgostsforgages_current_post_and_object

These hooks gives you a total hands-off experience, as you do not need to change anything in the page template itself, which was my original intention from the start. A page can completely be altered from a plugin or a function file, which makes this solution very dynamic.

I have also used get_template_part() in order to load a template part, which will be used to display the posts. Most themes today make use of template parts, which makes this very useful in the class. If your theme uses content.php, you can simply pass content to $templatePart to load content.php.

If you need post format support for template parts, it is easy – you can simply pass content to $templatePart and set $postFormatSupport to true. As a result, the template part content-video.php will be loaded for a post with a post format of video.

THE MAIN QUERY

The following changes was done to the main query through the respective filters and actions:

  • In order to paginate the main query:

    • The injector query\'s $found_posts property value is passed to that of the main query object through the found_posts filter.

    • The value of the user passed parameter posts_per_page is set to the main query through pre_get_posts.

    • $max_num_pages is calculated using the amount of posts in $found_posts and posts_per_page. Because is_singular is true on pages, it inhibits the LIMIT clause being set. Simply setting is_singular to false caused a few issues, so I decided to set the LIMIT clause through the post_limits filter. I kept the offset of the LIMIT clause set to 0 to avoid 404\'s on pages with pagination turned on.

This takes care of pagination and any issue that might arise from the post injection.

THE PAGE OBJECT

The current page object is available to display as a post by using the default loop on the page, separate and on top of the injected posts. If you do not need this, you can simply set $removePageFromLoop to true, and this will hide the page content from being displayed.

At this stage, I\'m using CSS to hide the page object through the loop_start and loop_end actions as I cannot find another way of doing this. The downside with this method is that anything hooked to the_post action hook inside the main query will also be hidden.

THE CLASS

The PreGetPostsForPages class can be improved and should be properly namespaced as well. While you can simply drop this in your theme\'s functions file, it would be better to drop this into a custom plugin.

Use, modify and abuse as you see fit. The code is well commented, so it should be easy to follow and adjust

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

USAGE

You can now initiate the class (also in your plugin or functions file) as follow to target the page with ID 251, upon which we will show 2 posts per page from the post post type.

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

ADDING PAGINATION AND CUSTOM STYLING

As I mentioned previously, there are a few actions in the injector query in order to add pagination and/or custom styling.

In the following example, I added pagination after the loop using my own pagination function from the linked answer. Also, using my custom counter, I added a <div> to to display my posts in two columns.

Here are the actions I used

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

Note that pagination is set by the main query, not the injector query, so built-in functions like the_posts_pagination() should also work.

This is the end result

enter image description here

STATIC FRONT PAGES

Everything works as expected on static front pages together with my pagination function without requiring any further modification.

CONCLUSION

This might seem like a lot of overhead, and it might be, but the pro\'s outweigh the con\'s big time.

BIG PRO\'S

  • You do not need to alter the page template for the specific page in any way. This makes everything dynamic and can easily be transferred between themes without making modifications to the code whatsoever, so long as everything is done in a plugin.

  • At most, you only need to create a content.php template part in your theme if your theme does not have one yet.

  • Any pagination that works on the main query will work on the page without any type of alteration or anything extra from the query being passed to function.

There are more pro\'s which I cannot think of now, but these are the important ones.

相关推荐

Permalinks for pages

为什么我不能为我的pages? 我有一个4层结构,家用解决方案机械钢所以我希望我的URL结构是www.domein.com/solutions/machines/steel 但我在哪里可以做到这一点??我在4层结构中进行了所有分类。但在permalinks编辑器中,我只能选择帖子或产品。。。我觉得奇怪,我不能对我的页面做同样的事情。。。我希望这是有意义的,有人可以帮助我,谢谢!