Tôi đã thực hiện nghiên cứu khá rộng rãi về cách sử dụng pre_get_posts
trên các trang thật và các trang đầu tĩnh và dường như không có phương pháp chứng minh ngu ngốc nào.
Tùy chọn tốt nhất tôi tìm thấy cho đến nay là từ một bài đăng được thực hiện bởi @birgire trên Stackoverflow . Tôi đã viết lại nó thành một lớp demo và làm cho mã động hơn một chút
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();
Điều này hoạt động tốt và trang như mong đợi bằng cách sử dụng chức năng phân trang của riêng tôi .
VẤN ĐỀ:
Do chức năng, tôi mất tính toàn vẹn trang, thứ chứa các chức năng khác dựa vào đối tượng trang được lưu trữ $post
. $post
trước khi vòng lặp được đặt thành bài đầu tiên trong vòng lặp và $post
được đặt thành bài cuối cùng trong vòng lặp sau vòng lặp, dự kiến. Những gì tôi cần là $post
được đặt cho đối tượng trang hiện tại, tức là đối tượng được truy vấn.
Ngoài ra, $wp_the_query->post
và $wp_query->post
giữ bài đăng đầu tiên trong vòng lặp chứ không phải đối tượng được truy vấn như trên một trang bình thường
Tôi sử dụng như sau ( bên ngoài lớp học của tôi ) để kiểm tra toàn cầu của tôi trước và sau vòng lặp
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
}
TRƯỚC KHI LOOP:
Trước vòng lặp, vấn đề được giải quyết một phần bằng cách đặt $injectPageIntoLoop
thành true mà tiêm đối tượng trang làm trang đầu tiên trong vòng lặp. Điều này khá hữu ích nếu bạn cần hiển thị thông tin trang trước các bài đăng được yêu cầu, nhưng nếu bạn không muốn điều đó, bạn sẽ bị lừa.
Tôi có thể giải quyết vấn đề trước vòng lặp bằng cách trực tiếp hack toàn cầu, điều mà tôi không thực sự thích. Tôi móc phương thức sau vào wp
bên trong preGetPosts
phương thức của mình
public function wp()
{
$page = get_post( $this->pageID );
$GLOBALS['wp_the_query']->post = $page;
$GLOBALS['wp_query'] = $GLOBALS['wp_the_query'];
$GLOBALS['post'] = $page;
}
và preGetPosts
phương pháp bên trong
add_action( 'wp', [$this, 'wp'] );
Từ đó, $wp_the_query->post
, $wp_query->post
và $post
tất cả các tổ chức đối tượng trang.
SAU KHI LOOP
Đây là vấn đề lớn của tôi, sau vòng lặp. Sau khi hack toàn cầu thông qua wp
hook và phương thức,
$wp_the_query->post
và$wp_query->post
được đặt trở lại bài đăng đầu tiên trong vòng lặp, như mong đợi$post
được đặt đến bài cuối cùng trong vòng lặp.
Điều tôi cần là cả ba đều được đặt trở lại đối tượng được truy vấn / đối tượng trang hiện tại.
Tôi đã thử nối wp
phương thức thành loop_end
hành động, không hoạt động. Kết nối wp
phương thức để get_sidebar
hành động hoạt động, nhưng đã quá muộn.
add_action( 'get_sidebar', [$this, 'wp'] );
Chạy printGlobals()
trực tiếp sau vòng lặp trong mẫu xác nhận rằng $wp_the_query->post
và $wp_query->post
vẫn được đặt thành bài đầu tiên và $post
bài cuối cùng.
Tôi có thể tự thêm mã bên trong wp
phương thức sau vòng lặp bên trong mẫu, nhưng ý tưởng không phải là thay đổi trực tiếp các tệp mẫu vì lớp có thể được chuyển trong một plugin giữa các chủ đề.
Có cách nào thích hợp để giải quyết vấn đề này, nơi một lần chạy thử pre_get_posts
trên một trang đúng và trang tĩnh và vẫn giữ sự toàn vẹn của $wp_the_query->post
, $wp_query->post
và $post
( có những thiết lập để các đối tượng truy vấn ) trước và sau vòng lặp.
CHỈNH SỬA
Dường như có sự nhầm lẫn về những gì tôi cần và tại sao tôi cần nó
Tôi cân gi
Tôi cần phải giữ lại các giá trị của $wp_the_query->post
, $wp_query->post
và $post
trên mẫu không phân biệt, và giá trị mà nên là đối tượng truy vấn. Ở giai đoạn này, với mã tôi đã đăng, các giá trị của ba biến đó không giữ đối tượng trang, mà là đăng các đối tượng của bài đăng trong vòng lặp. Tôi hy vọng điều đó là đủ rõ ràng.
Tôi đã đăng mã mà bạn có thể sử dụng để kiểm tra các biến này
Tại sao tôi cần nó
Tôi cần một cách đáng tin cậy để thêm bài đăng qua các pre_get_posts
mẫu trang và trang trước tĩnh mà không thay đổi chức năng trang đầy đủ. Ở giai đoạn này, khi mã trong câu hỏi đứng, nó phá vỡ tính năng Breadcrumb của tôi và tính năng trang có liên quan sau vòng lặp do $post
chứa đối tượng bài "sai".
Trên hết, tôi không muốn thay đổi mẫu trang trực tiếp. Tôi muốn có thể thêm bài viết vào một mẫu trang mà không cần sửa đổi bất kỳ mẫu nào