He realizado una investigación bastante extensa sobre cómo usar pre_get_posts
en páginas verdaderas y en las portadas estáticas, y parece que no hay un método infalible.
La mejor opción que encontré hasta la fecha fue una publicación realizada por @birgire en Stackoverflow . Lo reescribí en una clase de demostración e hice el código un poco más 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();
Esto funciona bien y la página como se esperaba al usar mi propia función de paginación .
CUESTIONES:
Debido a la función, pierdo la integridad de la página, que rellena otras funciones que dependen del objeto de la página almacenado $post
. $post
antes de que el bucle se establezca en la primera publicación del bucle y $post
se establezca en la última publicación del bucle después del bucle, que se espera. Lo que necesito es que $post
esté configurado para el objeto de página actual, es decir, el objeto consultado.
Además, $wp_the_query->post
y $wp_query->post
contiene la primera publicación en el bucle y no el objeto consultado como en una página normal
Utilizo lo siguiente ( fuera de mi clase ) para revisar mis globales antes y después del ciclo
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 DEL BUCLE:
Antes del ciclo, el problema se resuelve en parte al establecer $injectPageIntoLoop
en verdadero, que inyecta el objeto de página como la primera página del ciclo. Esto es bastante útil si necesita mostrar la información de la página antes de las publicaciones solicitadas, pero si no quiere eso, está jodido.
Puedo resolver el problema antes del bucle pirateando directamente los globales, lo que realmente no me gusta. Engancho el siguiente método wp
dentro de mi 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;
}
y dentro del preGetPosts
método
add_action( 'wp', [$this, 'wp'] );
A partir de esto, $wp_the_query->post
, $wp_query->post
y $post
todas las bodegas objeto de página.
DESPUÉS DEL BUCLE
Aquí es donde está mi gran problema, después del ciclo. Después de hackear los globos a través del wp
gancho y el método,
$wp_the_query->post
y$wp_query->post
vuelve a la primera publicación del bucle, como se esperaba$post
se establece en la última publicación del bucle.
Lo que necesito es que los tres se vuelvan a establecer en el objeto consultado / objeto de página actual.
He intentado conectar el wp
método a la loop_end
acción, que no funciona. Enganchar el wp
método a la get_sidebar
acción funciona, pero es demasiado tarde.
add_action( 'get_sidebar', [$this, 'wp'] );
Correr printGlobals()
directamente después del bucle en lo confirma la plantilla que a medida $wp_the_query->post
y $wp_query->post
están siendo ajustado en el primer puesto y $post
el último mensaje.
Puedo agregar manualmente el código dentro del wp
método después del ciclo dentro de la plantilla, pero la idea no es alterar los archivos de la plantilla directamente ya que la clase debería ser transferible en un complemento entre los temas.
¿Hay alguna forma adecuada de resolver este problema por el que una carrera pre_get_posts
en una página verdadera y primera página estática y aún así mantener la integridad de $wp_the_query->post
, $wp_query->post
y $post
( tener los fijados al objeto consultada ) antes y después del bucle.
EDITAR
Parece haber confusión sobre lo que necesito y por qué lo necesito
Lo que necesito
Necesito retener los valores de $wp_the_query->post
, $wp_query->post
y a $post
través de la plantilla independientemente, y ese valor debe ser el objeto consultado. En esta etapa, con el código que he publicado, los valores de esas tres variables no contienen el objeto de página, sino que publican objetos de publicaciones en el bucle. Espero que sea lo suficientemente claro.
He publicado un código que puedes usar para probar estas variables
Porque lo necesito
Necesito una forma confiable de agregar publicaciones a través pre_get_posts
de plantillas de página y portadas estáticas sin cambiar la funcionalidad de la página completa. En esta etapa, tal como está el código en cuestión, rompe mi función de ruta de navegación y la función de página relacionada después del bucle debido al $post
cual contiene el objeto de publicación "incorrecto".
Sobre todo, no quiero alterar las plantillas de página directamente. Quiero poder agregar publicaciones a una plantilla de página sin NINGUNA modificación a la plantilla