我已经对如何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
为true可以将页面对象作为循环的第一页注入。如果您需要在请求的帖子之前显示页面信息,这将非常有用,但是如果您不希望这样做,则很麻烦。
我可以通过直接修改全局变量来解决循环前的问题,我并不喜欢这样做。我将以下方法挂接到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()
在模板中的循环之后直接运行将确认as $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
跨过模板不管,并将该值应该是查询对象。在此阶段,使用我发布的代码,这三个变量的值不保存页面对象,而是保存循环中posts的post对象。我希望这很清楚。
我已经发布了可以用来测试这些变量的代码
为什么我需要它
我需要一种可靠的方式来pre_get_posts
在页面模板和静态首页上添加帖子,而无需更改整个页面的功能。在此阶段,如所讨论的代码所示,由于循环$post
持有“错误的”发布对象,因此循环后它破坏了面包屑功能和相关的页面功能 。
最重要的是,我不想直接更改页面模板。我希望能够在不对模板进行任何修改的情况下将帖子添加到页面模板