J'ai fait des recherches assez approfondies sur la façon d'utiliser pre_get_posts
sur les vraies pages et les premières pages statiques, et il semble qu'il n'y ait pas de méthode infaillible.
La meilleure option que j'ai trouvée à ce jour provient d'un article rédigé par @birgire sur Stackoverflow . Je l'ai réécrit dans une classe de démonstration et j'ai rendu le code un peu plus dynamique
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();
Cela fonctionne bien et page comme prévu en utilisant ma propre fonction de pagination .
PROBLÈMES:
En raison de la fonction, je perds l'intégrité de la page qui remplit d'autres fonctions en fonction de l'objet de page stocké dans $post
. $post
avant que la boucle ne soit définie sur le premier post de la boucle et $post
sur le dernier post de la boucle après la boucle, ce qui est attendu. Ce dont j'ai besoin, c'est qu'il $post
soit défini sur l'objet de page actuel, c'est-à-dire l'objet interrogé.
En outre, $wp_the_query->post
et $wp_query->post
détient le premier message de la boucle et non l'objet interrogé comme sur une page normale
J'utilise ce qui suit (en dehors de ma classe ) pour vérifier mes globales avant et après la boucle
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
}
AVANT LA BOUCLE:
Avant la boucle, le problème est partiellement résolu en définissant la valeur $injectPageIntoLoop
true qui injecte l'objet page en tant que première page de la boucle. C'est très utile si vous devez afficher les informations de la page avant les messages demandés, mais si vous ne le souhaitez pas, vous êtes foutu.
Je peux résoudre le problème avant la boucle en piratant directement les globaux, ce que je n'aime pas vraiment. Je connecte la méthode suivante à l' wp
intérieur de ma preGetPosts
méthode
public function wp()
{
$page = get_post( $this->pageID );
$GLOBALS['wp_the_query']->post = $page;
$GLOBALS['wp_query'] = $GLOBALS['wp_the_query'];
$GLOBALS['post'] = $page;
}
et preGetPosts
méthode intérieure
add_action( 'wp', [$this, 'wp'] );
De là, $wp_the_query->post
, $wp_query->post
et $post
toutes les cales de l'objet de la page.
APRÈS LA BOUCLE
C'est là que se situe mon gros problème, après la boucle. Après avoir piraté les globaux à travers le wp
crochet et la méthode,
$wp_the_query->post
et$wp_query->post
est remis au premier poste de la boucle, comme prévu$post
est défini sur le dernier message de la boucle.
Ce dont j'ai besoin, c'est que les trois soient replacés sur l'objet interrogé / l'objet de page actuel.
J'ai essayé d'accrocher la wp
méthode à l' loop_end
action, ce qui ne fonctionne pas. Accrocher la wp
méthode à l' get_sidebar
action fonctionne, mais il est trop tard.
add_action( 'get_sidebar', [$this, 'wp'] );
L'exécution printGlobals()
directe après la boucle dans le modèle confirme que as $wp_the_query->post
et $wp_query->post
sont toujours définis sur le premier message et $post
sur le dernier message.
Je peux ajouter manuellement le code à l'intérieur de la wp
méthode après la boucle à l'intérieur du modèle, mais l'idée n'est pas de modifier directement les fichiers de modèle car la classe devrait être transférable dans un plugin entre les thèmes.
Y at - il bonne façon de résoudre ce problème où une exécution pre_get_posts
sur une vraie page et la page d'accueil statique et toujours garder l'intégrité $wp_the_query->post
, $wp_query->post
et $post
( ayant ceux à l'objet ensemble interrogé ) avant et après la boucle.
ÉDITER
Il semble y avoir confusion sur ce dont j'ai besoin et pourquoi j'en ai besoin
Ce dont j'ai besoin
Je dois conserver les valeurs de $wp_the_query->post
, $wp_query->post
et à $post
travers le modèle quel que soit, et que la valeur doit être l'objet interrogé. À ce stade, avec le code que j'ai publié, les valeurs de ces trois variables ne contiennent pas l'objet de page, mais plutôt les objets de messages dans la boucle. J'espère que c'est assez clair.
J'ai publié du code que vous pouvez utiliser pour tester ces variables
Pourquoi j'en ai besoin
J'ai besoin d'un moyen fiable pour ajouter des publications via des pre_get_posts
modèles de page et des premières pages statiques sans modifier la fonctionnalité de la page complète. À ce stade, en l'état du code en question, il casse ma fonctionnalité de fil d'Ariane et la fonctionnalité de page associée après la boucle en raison de $post
laquelle contient le "mauvais" objet de publication.
Surtout, je ne veux pas modifier directement les modèles de page. Je veux être en mesure d'ajouter des messages à un modèle de page sans AUCUNE modification du modèle
la source
Réponses:
Je l'ai finalement fait fonctionner, mais pas avec le code dans ma question. J'ai totalement abandonné cette idée et j'ai recommencé à prendre une nouvelle direction.
REMARQUE:
Si quelqu'un est capable de régler les problèmes de ma question, n'hésitez pas à poster une réponse. De plus, si vous avez d'autres solutions, n'hésitez pas à poster une réponse.
CLASSE ET SOLUTION RÉUSINÉES:
Ce que j'ai essayé de faire ici était d'utiliser la post-injection, plutôt que de modifier complètement la requête principale et de rester bloqué avec tous les problèmes ci-dessus, y compris (a) modifier directement les globaux, (b) se heurter au problème de la valeur globale, et (c) réattribution des modèles de page.
En utilisant l' injection, je suis en mesure de garder l' intégrité complète de poste, de sorte que
$wp_the_query->post
,$wp_query->post
,$posts
et$post
constante tout au long du séjour modèle. Chacune de ces variables fait référence à l'objet de page actuel (comme c'est le cas avec les vraies pages). De cette façon, des fonctions comme le fil d'Ariane savent que la page actuelle est une vraie page et non une sorte d'archive.J'ai dû modifier légèrement la requête principale ( via des filtres et des actions ) pour ajuster la pagination, mais nous y arriverons.
REQUÊTE POST-INJECTION
Afin d'accomplir la post-injection, j'ai utilisé une requête personnalisée pour renvoyer les publications nécessaires à l'injection. J'ai également utilisé la
$found_pages
propriété de la requête personnalisée pour ajuster celle de la requête principale afin que la pagination fonctionne à partir de la requête principale. Les messages sont injectés dans la requête principale via l'loop_end
action.Afin de rendre la requête personnalisée accessible et utilisable en dehors de la classe, j'ai introduit quelques actions.
Crochets de pagination pour accrocher les fonctions de pagination:
pregetgostsforgages_before_loop_pagination
pregetgostsforgages_after_loop_pagination
Compteur personnalisé qui compte les messages dans la boucle. Ces actions peuvent être utilisées pour modifier la façon dont les publications sont affichées dans la boucle en fonction du numéro de publication.
pregetgostsforgages_counter_before_template_part
pregetgostsforgages_counter_after_template_part
Crochet général pour accéder à l'objet de requête et à l'objet de publication actuel
pregetgostsforgages_current_post_and_object
Ces crochets vous offrent une expérience pratique totale, car vous n'avez rien à changer dans le modèle de page lui-même, ce qui était mon intention initiale depuis le début. Une page peut être complètement modifiée à partir d'un plugin ou d'un fichier de fonction, ce qui rend cette solution très dynamique.
J'ai également utilisé
get_template_part()
afin de charger une partie de modèle, qui sera utilisée pour afficher les messages. La plupart des thèmes utilisent aujourd'hui des parties de modèle, ce qui le rend très utile dans la classe. Si vos utilisations de thèmecontent.php
, vous pouvez tout simplement passercontent
à$templatePart
la chargecontent.php
.Si vous avez besoin d'un soutien après format pour les pièces de modèle, il est facile - vous pouvez simplement passer
content
à$templatePart
et ensemble$postFormatSupport
àtrue
. Par conséquent, la partie du modèlecontent-video.php
sera chargée pour une publication avec un format de publication devideo
.LA REQUÊTE PRINCIPALE
Les modifications suivantes ont été apportées à la requête principale via les filtres et actions respectifs:
Afin de paginer la requête principale:
La
$found_posts
valeur de propriété de la requête de l'injecteur est transmise à celle de l'objet de requête principal via lefound_posts
filtre.La valeur du paramètre transmis par l'utilisateur
posts_per_page
est définie sur la requête principale viapre_get_posts
.$max_num_pages
est calculé en utilisant le nombre de messages dans$found_posts
etposts_per_page
. Parce queis_singular
c'est vrai sur les pages, cela empêche laLIMIT
clause en cours de définition. La simple définitionis_singular
de la valeur false a causé quelques problèmes, j'ai donc décidé de définir laLIMIT
clause via lepost_limits
filtre. J'ai gardé leoffset
de l'LIMIT
ensemble de clause0
pour éviter 404 pages de la pagination sous tension.Cela prend en charge la pagination et tout problème pouvant survenir à la suite de l'injection.
L'OBJET DE PAGE
L'objet de page en cours est disponible pour s'afficher en tant que publication en utilisant la boucle par défaut sur la page, séparée et au-dessus des publications injectées. Si vous n'en avez pas besoin, vous pouvez simplement définir la valeur
$removePageFromLoop
true, ce qui empêchera le contenu de la page d'être affiché.À ce stade, j'utilise CSS pour masquer l'objet de page via les actions
loop_start
etloop_end
car je ne peux pas trouver d'autre moyen de le faire. L'inconvénient de cette méthode est que tout ce qui est accroché au crochet d'the_post
action dans la requête principale sera également masqué.LA CLASSE
La
PreGetPostsForPages
classe peut être améliorée et doit également être correctement espacée. Bien que vous puissiez simplement le déposer dans le fichier de fonctions de votre thème, il serait préférable de le déposer dans un plugin personnalisé.Utilisez, modifiez et abusez comme bon vous semble. Le code est bien commenté, il devrait donc être facile à suivre et à ajuster
USAGE
Vous pouvez maintenant lancer la classe ( également dans votre plugin ou fichier de fonctions ) comme suit pour cibler la page avec l'ID 251, sur laquelle nous afficherons 2 publications par page du
post
type de publication.AJOUT DE PAGINATION ET DE STYLE PERSONNALISÉ
Comme je l'ai mentionné précédemment, il y a quelques actions dans la requête de l'injecteur afin d'ajouter une pagination et / ou un style personnalisé.
Dans l'exemple suivant, j'ai ajouté la pagination après la boucle en utilisant ma propre fonction de pagination à partir de la réponse liée . De plus, en utilisant mon compteur personnalisé, j'ai ajouté un
<div>
à pour afficher mes messages sur deux colonnes.Voici les actions que j'ai utilisées
Notez que la pagination est définie par la requête principale, et non par la requête de l'injecteur, donc les fonctions intégrées comme
the_posts_pagination()
devraient également fonctionner.C'est le résultat final
PAGES AVANT STATIQUES
Tout fonctionne comme prévu sur les premières pages statiques avec ma fonction de pagination sans nécessiter de modification supplémentaire.
CONCLUSION
Cela peut sembler beaucoup de frais généraux, et cela peut l'être, mais les pros l'emportent sur les gros cons.
BIG PRO'S
Vous n'avez pas besoin de modifier le modèle de page pour la page spécifique en aucune façon. Cela rend tout dynamique et peut facilement être transféré entre les thèmes sans apporter de modifications au code, tant que tout se fait dans un plugin.
Tout au plus, vous n'avez besoin de créer une
content.php
partie de modèle dans votre thème que si votre thème n'en a pas encore.Toute pagination qui fonctionne sur la requête principale fonctionnera sur la page sans aucun type de modification ou quoi que ce soit supplémentaire de la requête transmise à la fonction.
Il y a plus de pro auxquels je ne peux pas penser maintenant, mais ce sont les plus importants.
la source