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
fuente
Respuestas:
Finalmente lo hice funcionar, pero no con el código en mi pregunta. Eliminé totalmente esa idea y comencé a ir en una nueva dirección.
NOTA:
Si alguien puede resolver los problemas de mi pregunta, no dude en publicar una respuesta. Además, si tiene otras soluciones, no dude en publicar una respuesta.
CLASE Y SOLUCIÓN REFABRICADAS:
Lo que traté de hacer aquí fue usar la inyección posterior, en lugar de alterar por completo la consulta principal y atascarme con todos los problemas anteriores, incluidos (a) alterar directamente los globales, (b) encontrarse con el problema del valor global y (c) reasignación de plantillas de página.
Mediante el uso de la inyección, que soy capaz de mantener la integridad post completo, por lo que
$wp_the_query->post
,$wp_query->post
,$posts
y la$post
estancia constante durante toda la plantilla. Cada una de estas variables hace referencia al objeto de la página actual (como es el caso de las páginas verdaderas). De esta manera, las funciones como migas de pan saben que la página actual es una página verdadera y no una especie de archivo.Sin embargo, tuve que modificar ligeramente la consulta principal (a través de filtros y acciones ) para ajustar la paginación, pero llegaremos a eso.
CONSULTA POSTERIOR A LA INYECCIÓN
Para lograr la inyección posterior, utilicé una consulta personalizada para devolver las publicaciones necesarias para la inyección. También utilicé la
$found_pages
propiedad de la consulta personalizada para ajustar la de la consulta principal para que la paginación funcione desde la consulta principal. Las publicaciones se inyectan en la consulta principal a través de laloop_end
acción.Para hacer que la consulta personalizada sea accesible y utilizable fuera de la clase, introduje un par de acciones.
Ganchos de paginación para enganchar las funciones de paginación:
pregetgostsforgages_before_loop_pagination
pregetgostsforgages_after_loop_pagination
Contador personalizado que cuenta las publicaciones en el bucle. Estas acciones se pueden usar para alterar cómo se muestran las publicaciones dentro del bucle según el número de publicación.
pregetgostsforgages_counter_before_template_part
pregetgostsforgages_counter_after_template_part
Enlace general para acceder al objeto de consulta y al objeto de publicación actual
pregetgostsforgages_current_post_and_object
Estos ganchos le brindan una experiencia total, ya que no necesita cambiar nada en la plantilla de la página, que era mi intención original desde el principio. Una página puede modificarse completamente desde un complemento o un archivo de función, lo que hace que esta solución sea muy dinámica.
También lo he usado
get_template_part()
para cargar una parte de la plantilla, que se usará para mostrar las publicaciones. La mayoría de los temas actuales utilizan partes de plantillas, lo que hace que esto sea muy útil en la clase. Si sus usos temáticoscontent.php
, sólo tiene que pasarcontent
a$templatePart
la cargacontent.php
.Si necesita ayuda del formato de mensaje para las piezas de la plantilla, es fácil - simplemente puede pasar
content
a$templatePart
y conjunto$postFormatSupport
atrue
. Como resultado, la parte de la plantillacontent-video.php
se cargará para una publicación con un formato de publicación devideo
.LA CONSULTA PRINCIPAL
Los siguientes cambios se realizaron en la consulta principal a través de los respectivos filtros y acciones:
Para paginar la consulta principal:
El
$found_posts
valor de propiedad de la consulta del inyector se pasa al del objeto de consulta principal a través delfound_posts
filtro.El valor del parámetro pasado por el usuario
posts_per_page
se establece a través de la consulta principalpre_get_posts
.$max_num_pages
se calcula utilizando la cantidad de publicaciones en$found_posts
yposts_per_page
. Comois_singular
es cierto en las páginas, inhibe laLIMIT
cláusula que se establece. Simplemente estableceris_singular
en falso causó algunos problemas, así que decidí establecer laLIMIT
cláusula a través delpost_limits
filtro. Me quedé con laoffset
delLIMIT
conjunto cláusula para0
evitar 404 de las páginas con paginación activado.Esto se encarga de la paginación y de cualquier problema que pueda surgir de la inyección posterior.
EL OBJETO DE PÁGINA
El objeto de la página actual está disponible para mostrarse como una publicación utilizando el bucle predeterminado en la página, separado y encima de las publicaciones inyectadas. Si no necesita esto, simplemente puede establecerlo
$removePageFromLoop
en verdadero, y esto ocultará el contenido de la página para que no se muestre.En esta etapa, estoy usando CSS para ocultar el objeto de la página a través de las acciones
loop_start
yloop_end
ya que no puedo encontrar otra forma de hacerlo. La desventaja de este método es que todo lo que esté conectado althe_post
gancho de acción dentro de la consulta principal también estará oculto.LA CLASE
La
PreGetPostsForPages
clase se puede mejorar y también debe tener un espacio de nombres adecuado. Si bien simplemente puede colocar esto en el archivo de funciones de su tema, sería mejor colocarlo en un complemento personalizado.Usa, modifica y abusa como mejor te parezca. El código está bien comentado, por lo que debería ser fácil de seguir y ajustar
USO
Ahora puede iniciar la clase ( también en su plugin o archivo de funciones ) de la siguiente manera para orientar la página con ID 251, sobre la cual mostraremos 2 publicaciones por página del
post
tipo de publicación.AÑADIR PAGINACIÓN Y ESTILO PERSONALIZADO
Como mencioné anteriormente, hay algunas acciones en la consulta del inyector para agregar paginación y / o estilo personalizado.
En el siguiente ejemplo, agregué paginación después del ciclo usando mi propia función de paginación de la respuesta vinculada . Además, usando mi contador personalizado, agregué un
<div>
para mostrar mis publicaciones en dos columnas.Aquí están las acciones que usé
Tenga en cuenta que la consulta principal establece la paginación, no la consulta del inyector, por lo que las funciones integradas como
the_posts_pagination()
también deberían funcionar.este es el resultado final
PÁGINAS FRONTALES ESTÁTICAS
Todo funciona como se esperaba en las portadas estáticas junto con mi función de paginación sin requerir ninguna modificación adicional.
CONCLUSIÓN
Esto puede parecer un montón de gastos generales, y puede ser, pero los profesionales superan el gran momento de la estafa.
GRANDES PRO
No necesita modificar la plantilla de página para la página específica de ninguna manera. Esto hace que todo sea dinámico y se puede transferir fácilmente entre temas sin realizar modificaciones en el código, siempre y cuando todo se realice en un complemento.
A lo sumo, solo necesita crear una
content.php
parte de plantilla en su tema si su tema aún no tiene uno.Cualquier paginación que funcione en la consulta principal funcionará en la página sin ningún tipo de alteración ni nada extra de la consulta que se pasa a funcionar.
Hay más profesionales en los que no puedo pensar ahora, pero estos son los importantes.
fuente