ใช้ pre_get_posts ในหน้าจริงและหน้าคงที่


19

ฉันได้ทำการวิจัยอย่างกว้างขวางเกี่ยวกับวิธีการใช้pre_get_postsบนหน้าจริงและหน้าคงที่และดูเหมือนว่าไม่มีวิธีการพิสูจน์คนโง่

ตัวเลือกที่ดีที่สุดที่ฉันพบว่าวันที่มาจากการโพสต์ทำโดย @birgire ใน 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();

นี้ทำงานได้ดีและหน้าตามที่คาดไว้โดยใช้ฟังก์ชั่นการแบ่งหน้าของตัวเอง

ประเด็น:

เพราะการทำงานของผมหลวมสมบูรณ์หน้าซึ่งสิ่งที่ฟังก์ชั่นอื่น ๆ $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เป็นจริงซึ่งฉีดวัตถุหน้าเป็นหน้าแรกในการวนรอบ สิ่งนี้มีประโยชน์มากหากคุณต้องการแสดงข้อมูลหน้าก่อนโพสต์ที่ขอ แต่ถ้าคุณไม่ต้องการคุณจะถูกทำให้เมา

ฉันสามารถแก้ปัญหาก่อนที่จะวนรอบได้โดยแฮ็กกลมโดยตรงซึ่งฉันไม่ชอบ ฉันขอวิธีต่อไปนี้เพื่อ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การระงับทั้งหมดวัตถุหน้า

หลังจากลูป

นี่คือที่ที่ปัญหาใหญ่ของฉันอยู่หลังลูป หลังจากแฮ็กกลมผ่าน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( มีชุดเหล่านั้นไปยังวัตถุสอบถาม ) ก่อนและหลังห่วง

แก้ไข

ดูเหมือนจะมีความสับสนเกี่ยวกับสิ่งที่ฉันต้องการและเหตุผลที่ฉันต้องการมัน

สิ่งที่ฉันต้องการ

ฉันต้องการที่จะรักษาค่าของ$wp_the_query->post, $wp_query->postและ$postข้ามแม่แบบโดยไม่คำนึงถึงและความคุ้มค่าที่ควรจะเป็นวัตถุสอบถาม ในขั้นตอนนี้ด้วยรหัสที่ฉันโพสต์ค่าของตัวแปรทั้งสามนั้นไม่ได้ถือวัตถุหน้า แต่แทนที่จะโพสต์วัตถุของโพสต์ในวง ฉันหวังว่าชัดเจนเพียงพอ

ฉันได้โพสต์โค้ดที่คุณสามารถใช้เพื่อทดสอบตัวแปรเหล่านี้

ทำไมฉันต้องการมัน

ฉันต้องการวิธีที่เชื่อถือได้ในการเพิ่มการโพสต์ผ่านpre_get_postsเทมเพลตหน้าและสแตติกหน้าคงที่โดยไม่เปลี่ยนฟังก์ชั่นเต็มหน้า ในขั้นตอนนี้เนื่องจากรหัสที่เป็นปัญหาจะทำให้คุณสมบัติ breadcrumb ของฉันและคุณลักษณะหน้าเว็บที่เกี่ยวข้องหลังจากวนซ้ำเนื่องจาก $postที่เก็บวัตถุโพสต์ "ผิด"

ที่สำคัญที่สุดฉันไม่ต้องการแก้ไขเทมเพลตหน้าโดยตรง ฉันต้องการที่จะเพิ่มการโพสต์ลงในเทมเพลทหน้าโดยไม่ต้องแก้ไขใด ๆกับเทมเพลท


คุณพยายามทำอะไรเป้าหมายหรือข้อกำหนดในการทำงานของคุณ คุณไม่ได้บอกทุกที่เท่าที่ฉันจะบอกได้
adelval

คำตอบ:


13

ในที่สุดฉันก็ทำให้มันทำงานได้ แต่ไม่ใช่กับรหัสในคำถามของฉัน ฉันทิ้งความคิดทั้งหมดและเริ่มต้นไปในทิศทางใหม่

บันทึก:

หากใครก็ตามสามารถแก้ไขปัญหาในคำถามของฉันได้โปรดโพสต์คำตอบ นอกจากนี้หากคุณมีวิธีแก้ไขปัญหาอื่น ๆ อย่าลังเลที่จะโพสต์คำตอบ

การทำงานในชั้นเรียนและการแก้ปัญหา:

สิ่งที่ฉันพยายามทำที่นี่คือการใช้การฉีดโพสต์แทนที่จะแก้ไขการสืบค้นหลักอย่างสมบูรณ์และติดอยู่กับปัญหาทั้งหมดข้างต้นรวมถึง (a) การปรับเปลี่ยนโดยตรงโดยตรง (b) วิ่งเข้าไปในปัญหามูลค่าทั่วโลกและ (c) การกำหนดเทมเพลตหน้าใหม่

โดยใช้การฉีดโพสต์ผมสามารถที่จะให้ความสมบูรณ์ของการโพสต์เต็มรูปแบบเพื่อ $wp_the_query->post, $wp_query->post, $postsและ$postเข้าพักอย่างต่อเนื่องตลอดทั้งแม่แบบ แต่ละตัวแปรเหล่านี้อ้างอิงถึงวัตถุหน้าปัจจุบัน (เช่นเดียวกับกรณีที่มีหน้าจริง) ด้วยวิธีนี้ฟังก์ชั่นเช่น breadcrumbs รู้ว่าหน้าปัจจุบันเป็นหน้าจริงและไม่ใช่ไฟล์เก็บถาวรบางประเภท

ฉันต้องเปลี่ยนข้อความค้นหาหลักเล็กน้อย ( ผ่านตัวกรองและการกระทำ ) เพื่อปรับสำหรับการแบ่งหน้า แต่เราจะทำเช่นนั้น

โพสต์แบบสอบถามการฉีด

เพื่อให้การฉีดสำเร็จฉันใช้คิวรีที่กำหนดเองเพื่อส่งคืนโพสต์ที่จำเป็นสำหรับการฉีด ฉันยังใช้$found_pagesคุณสมบัติของคิวรีแบบกำหนดเองเพื่อปรับคิวรีหลักเพื่อให้การแบ่งหน้าทำงานจากคิวรีหลัก โพสต์จะถูกแทรกลงในคิวรีหลักผ่านการloop_endดำเนินการ

เพื่อที่จะทำให้แบบสอบถามที่กำหนดเองสามารถเข้าถึงได้และใช้งานได้นอกชั้นเรียนฉันได้แนะนำการกระทำสองสามอย่าง

  • การแบ่งหน้า hooksเพื่อขอให้แบ่งหน้า funtions:

    • pregetgostsforgages_before_loop_pagination

    • pregetgostsforgages_after_loop_pagination

  • ตัวนับที่กำหนดเองซึ่งนับการโพสต์ในลูป การกระทำเหล่านี้สามารถนำมาใช้เพื่อแก้ไขวิธีการโพสต์แสดงภายในวงตามหมายเลขโพสต์

    • pregetgostsforgages_counter_before_template_part

    • pregetgostsforgages_counter_after_template_part

  • ตะขอทั่วไปในการเข้าถึงวัตถุแบบสอบถามและวัตถุที่โพสต์ในปัจจุบัน

    • pregetgostsforgages_current_post_and_object

hooks เหล่านี้มอบประสบการณ์การใช้จริงโดยรวมแก่คุณเนื่องจากคุณไม่จำเป็นต้องเปลี่ยนแปลงอะไรในเทมเพลตหน้าซึ่งเป็นความตั้งใจดั้งเดิมของฉันตั้งแต่เริ่มต้น หน้าสามารถเปลี่ยนแปลงได้อย่างสมบูรณ์จากปลั๊กอินหรือไฟล์ฟังก์ชั่นซึ่งทำให้การแก้ปัญหานี้เป็นแบบไดนามิกมาก

ฉันยังใช้get_template_part()เพื่อโหลดส่วนเทมเพลตซึ่งจะใช้ในการแสดงโพสต์ ธีมส่วนใหญ่ในปัจจุบันใช้ประโยชน์จากส่วนเทมเพลตซึ่งทำให้สิ่งนี้มีประโยชน์มากในชั้นเรียน หากธีมของคุณใช้content.phpคุณสามารถส่งcontentต่อไปได้$templatePartcontent.phpโหลด

หากคุณจำเป็นต้องโพสต์สนับสนุนรูปแบบสำหรับชิ้นส่วนแม่มันเป็นเรื่องง่าย - คุณก็สามารถผ่านcontentไป$templatePartและชุดที่จะ$postFormatSupport trueเป็นผลให้ส่วนแม่แบบcontent-video.phpvideoจะถูกโหลดสำหรับโพสต์ที่มีรูปแบบของการโพสต์

คำถามหลัก

ทำการเปลี่ยนแปลงต่อไปนี้กับคิวรีหลักผ่านตัวกรองและการดำเนินการที่เกี่ยวข้อง:

  • เพื่อแบ่งหน้าแบบสอบถามหลัก:

    • $found_postsค่าคุณสมบัติของคิวรี injector ถูกส่งผ่านไปยังของวัตถุแบบสอบถามหลักผ่านfound_postsตัวกรอง

    • ค่าของผู้ใช้พารามิเตอร์ผ่านการตั้งค่าเป็นหลักผ่านแบบสอบถามposts_per_pagepre_get_posts

    • $max_num_pagesคำนวณโดยใช้จำนวนโพสต์ในและ$found_posts posts_per_pageเนื่องจากis_singularเป็นความจริงบนหน้าเว็บมันยับยั้ง LIMITข้อที่ถูกตั้งค่า เพียงแค่ตั้งค่าis_singularเป็นเท็จทำให้เกิดปัญหาเล็กน้อยฉันจึงตัดสินใจตั้งLIMITส่วนผ่านpost_limitsตัวกรอง ฉันเก็บoffsetของLIMITชุดข้อเพื่อ0ที่จะหลีกเลี่ยง 404 บนหน้าเว็บที่มีการแบ่งหน้าเปิด

นี้จะดูแลเลขหน้าและปัญหาใด ๆ ที่อาจเกิดขึ้นจากการฉีดโพสต์

วัตถุประสงค์ของหน้า

วัตถุหน้าปัจจุบันพร้อมที่จะแสดงเป็นโพสต์โดยใช้การวนรอบเริ่มต้นบนหน้าแยกและด้านบนของโพสต์ฉีด หากคุณไม่ต้องการสิ่งนี้คุณสามารถตั้งค่า$removePageFromLoopเป็นจริงและจะซ่อนเนื้อหาของหน้าจากการแสดง

ในขั้นตอนนี้ฉันใช้ CSS เพื่อซ่อนออบเจ็กต์หน้าผ่านทางloop_startและloop_endการทำงานเนื่องจากฉันไม่สามารถหาวิธีอื่นในการทำเช่นนี้ ข้อเสียของวิธีนี้คือทุกสิ่งที่เชื่อมต่อกับthe_postตะขอการกระทำภายในแบบสอบถามหลักจะถูกซ่อนไว้ด้วย

ห้องเรียน

PreGetPostsForPagesระดับได้ดีขึ้นและควรจะ namespaced ถูกต้องเช่นกัน ในขณะที่คุณสามารถวางสิ่งนี้ลงในไฟล์ฟังก์ชั่นของธีมของคุณได้มันจะดีกว่าที่จะวางสิ่งนี้ลงในปลั๊กอินที่กำหนดเอง

ใช้ปรับเปลี่ยนและใช้ตามที่เห็นสมควร รหัสมีความเห็นดีดังนั้นควรง่ายต่อการติดตามและปรับ

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

การใช้งาน

ตอนนี้คุณสามารถเริ่มชั้นเรียน ( รวมถึงในปลั๊กอินหรือไฟล์ฟังก์ชั่นของคุณ ) ดังต่อไปนี้เพื่อกำหนดเป้าหมายหน้าด้วย ID 251 ซึ่งเราจะแสดง 2 โพสต์ต่อหน้าจากpostประเภทโพสต์

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

การเพิ่มการกำหนดและรูปแบบที่กำหนดเอง

ดังที่ฉันได้กล่าวไปก่อนหน้านี้มีการกระทำบางอย่างในข้อความค้นหาหัวฉีดเพื่อเพิ่มการแบ่งหน้าและ / หรือกำหนดสไตล์เอง

ในตัวอย่างต่อไปนี้ฉันจะเพิ่มเลขหน้าหลังจากวนโดยใช้ฟังก์ชั่นการแบ่งหน้าของตัวเองจากคำตอบที่เชื่อมโยง นอกจากนี้เมื่อใช้ตัวนับที่กำหนดเองของฉันฉันได้เพิ่ม<div>ไปยังเพื่อแสดงโพสต์ของฉันในสองคอลัมน์

นี่คือการกระทำที่ฉันใช้

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

โปรดทราบว่าการแบ่งหน้าถูกตั้งค่าโดยเคียวรีหลักไม่ใช่เคียวรีหัวฉีดดังนั้นฟังก์ชันในตัวเช่นthe_posts_pagination()ควรทำงานได้เช่นกัน

นี่คือผลลัพธ์สุดท้าย

ป้อนคำอธิบายรูปภาพที่นี่

หน้าต่อหน้า

ทุกอย่างทำงานได้ตามที่คาดหวังในหน้าคงที่พร้อมกับฟังก์ชั่นการแบ่งหน้าของฉันโดยไม่ต้องแก้ไขใด ๆ เพิ่มเติม

สรุปผลการศึกษา

นี่อาจดูเหมือนเป็นค่าใช้จ่ายจำนวนมากและมันอาจเป็นไปได้ แต่มือโปรนั้นมีค่ามากกว่าเวลาที่มาก

บิ๊กโปร

  • คุณไม่จำเป็นต้องแก้ไขเทมเพลตหน้าสำหรับหน้าใด ๆ โดยเฉพาะ สิ่งนี้ทำให้ทุกอย่างเป็นแบบไดนามิกและสามารถถ่ายโอนระหว่างธีมได้อย่างง่ายดายโดยไม่ต้องทำการแก้ไขโค้ดใด ๆ ตราบใดที่ทุกอย่างทำในปลั๊กอิน

  • อย่างมากคุณจะต้องสร้างcontent.phpส่วนเทมเพลตในธีมของคุณหากธีมของคุณยังไม่มี

  • การแบ่งหน้าใด ๆ ที่ทำงานกับแบบสอบถามหลักจะทำงานบนหน้าเว็บโดยไม่มีการเปลี่ยนแปลงใด ๆ หรืออะไรเพิ่มเติมจากแบบสอบถามที่ถูกส่งผ่านไปยังฟังก์ชั่น

มีโปรมากกว่าที่ฉันคิดไม่ถึงตอนนี้ แต่สิ่งเหล่านี้เป็นสิ่งสำคัญ


โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.