Eu fiz uma pesquisa bastante extensa sobre como usar pre_get_posts
em páginas verdadeiras e nas primeiras páginas estáticas, e parece que não existe um método à prova de idiotas.
A melhor opção que encontrei até hoje foi de um post feito por @birgire no Stackoverflow . Eu o reescrevi em uma classe demo e tornei o código um pouco mais dinâmico
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();
Isso funciona bem e pagina conforme o esperado, usando minha própria função de paginação .
PROBLEMAS:
Por causa da função, perco a integridade da página, em que outras funções dependem do objeto de página armazenado $post
. $post
antes que o loop seja definido como a primeira postagem no loop e $post
definido como a última postagem no loop após o loop, o que é esperado. O que eu preciso é que $post
seja definido como o objeto de página atual, ou seja, o objeto consultado.
Além disso, $wp_the_query->post
e $wp_query->post
ocupa o primeiro posto no circuito e não o objeto consultado como em uma página normal
Eu uso o seguinte ( fora da minha classe ) para verificar minhas globais antes e depois do loop
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
}
ANTES DO LAÇO:
Antes do loop, o problema é parcialmente resolvido configurando- $injectPageIntoLoop
se true, que injeta o objeto de página como primeira página no loop. Isso é bastante útil se você precisar mostrar as informações da página antes das postagens solicitadas, mas se você não quiser, está ferrado.
Eu posso resolver o problema antes do loop invadindo diretamente os globais, o que realmente não gosto. Eu conecto o método a seguir wp
dentro do meu preGetPosts
método
public function wp()
{
$page = get_post( $this->pageID );
$GLOBALS['wp_the_query']->post = $page;
$GLOBALS['wp_query'] = $GLOBALS['wp_the_query'];
$GLOBALS['post'] = $page;
}
e preGetPosts
método interno
add_action( 'wp', [$this, 'wp'] );
A partir daí, $wp_the_query->post
, $wp_query->post
e $post
tudo mantém o objeto página.
DEPOIS DO LAÇO
É aqui que está o meu grande problema, depois do loop. Depois de hackear os globais através do wp
gancho e método,
$wp_the_query->post
e$wp_query->post
volta à primeira postagem do loop, conforme o esperado$post
é definido como a última postagem no loop.
O que eu preciso é que todos os três sejam retornados ao objeto consultado / objeto de página atual.
Eu tentei ligar o wp
método à loop_end
ação, o que não funciona. Ligar o wp
método à get_sidebar
ação funciona, mas é tarde demais.
add_action( 'get_sidebar', [$this, 'wp'] );
A execução printGlobals()
direta após o loop no modelo confirma que as $wp_the_query->post
e $wp_query->post
ainda estão definidas na primeira postagem e $post
na última postagem.
Posso adicionar manualmente o código dentro do wp
método após o loop dentro do modelo, mas a idéia não é alterar os arquivos do modelo diretamente, pois a classe deve ser transferida em um plug-in entre temas.
Existe alguma maneira adequada para resolver este problema em uma corrida pre_get_posts
em uma verdadeira página e página estática e ainda manter a integridade $wp_the_query->post
, $wp_query->post
e $post
( tendo os set para o objeto consultado ) antes e depois do loop.
EDITAR
Parece haver confusão sobre o que eu preciso e por que eu preciso
O que eu preciso
Eu preciso manter os valores de $wp_the_query->post
, $wp_query->post
e $post
através do modelo, independentemente, e esse valor deve ser o objeto consultado. Nesta fase, com o código que eu publiquei, os valores dessas três variáveis não mantêm o objeto de página, mas publicam objetos de postagens no loop. Espero que esteja claro o suficiente.
Eu publiquei um código que você pode usar para testar essas variáveis
Por que eu preciso disso
Preciso de uma maneira confiável de adicionar postagens pre_get_posts
nos modelos de página e nas primeiras páginas estáticas sem alterar a funcionalidade da página inteira. Nesse estágio, como está o código em questão, ele interrompe meu recurso de trilha de navegação e o recurso de página relacionado após o loop, devido ao $post
qual contém o objeto de postagem "errado".
Acima de tudo, não quero alterar os modelos de página diretamente. Quero poder adicionar postagens a um modelo de página sem QUALQUER modificação no modelo