Lorsque vous visitez une page frontend, WordPress interroge la base de données et si votre page n'existe pas dans la base de données, cette requête n'est pas nécessaire et n'est qu'un gaspillage de ressources.
Heureusement, WordPress offre un moyen de gérer les demandes frontales de manière personnalisée. Cela se fait grâce au 'do_parse_request'
filtre.
En revenant false
sur ce crochet, vous pourrez empêcher WordPress de traiter les demandes et le faire à votre manière.
Cela dit, je veux partager un moyen de créer un plugin OOP simple qui peut gérer des pages virtuelles de manière facile à utiliser (et à réutiliser).
Ce dont nous avons besoin
- Une classe pour les objets de page virtuelle
- Une classe de contrôleur, qui examinera une demande et si c'est pour une page virtuelle, montrez-la en utilisant le modèle approprié
- Une classe pour le chargement de modèles
- Fichiers plugin principaux pour ajouter les crochets qui feront que tout fonctionne
Interfaces
Avant de construire des classes, écrivons les interfaces des 3 objets listés ci-dessus.
D'abord l'interface de la page (fichier PageInterface.php
):
<?php
namespace GM\VirtualPages;
interface PageInterface {
function getUrl();
function getTemplate();
function getTitle();
function setTitle( $title );
function setContent( $content );
function setTemplate( $template );
/**
* Get a WP_Post build using virtual Page object
*
* @return \WP_Post
*/
function asWpPost();
}
La plupart des méthodes ne sont que des getters et setters, pas besoin d'explication. La dernière méthode doit être utilisée pour obtenir un WP_Post
objet à partir d'une page virtuelle.
L'interface du contrôleur (fichier ControllerInterface.php
):
<?php
namespace GM\VirtualPages;
interface ControllerInterface {
/**
* Init the controller, fires the hook that allows consumer to add pages
*/
function init();
/**
* Register a page object in the controller
*
* @param \GM\VirtualPages\Page $page
* @return \GM\VirtualPages\Page
*/
function addPage( PageInterface $page );
/**
* Run on 'do_parse_request' and if the request is for one of the registered pages
* setup global variables, fire core hooks, requires page template and exit.
*
* @param boolean $bool The boolean flag value passed by 'do_parse_request'
* @param \WP $wp The global wp object passed by 'do_parse_request'
*/
function dispatch( $bool, \WP $wp );
}
et l'interface du chargeur de modèles (fichier TemplateLoaderInterface.php
):
<?php
namespace GM\VirtualPages;
interface TemplateLoaderInterface {
/**
* Setup loader for a page objects
*
* @param \GM\VirtualPagesPageInterface $page matched virtual page
*/
public function init( PageInterface $page );
/**
* Trigger core and custom hooks to filter templates,
* then load the found template.
*/
public function load();
}
Les commentaires phpDoc devraient être assez clairs pour ces interfaces.
Le plan
Maintenant que nous avons des interfaces, et avant d'écrire des classes concrètes, passons en revue notre flux de travail:
- D'abord, nous instancions une
Controller
classe (implémentant ControllerInterface
) et injectons (probablement dans un constructeur) une instance de TemplateLoader
classe (implémentant TemplateLoaderInterface
)
- En mode
init
raccroché, nous appelons la ControllerInterface::init()
méthode pour configurer le contrôleur et pour déclencher le crochet que le code consommateur utilisera pour ajouter des pages virtuelles.
- Sur 'do_parse_request' nous appellerons
ControllerInterface::dispatch()
, et là nous vérifierons toutes les pages virtuelles ajoutées et si l'une d'elles a la même URL de la requête en cours, l'afficher; après avoir défini toutes les variables globales de base ( $wp_query
, $post
). Nous utiliserons également la TemplateLoader
classe pour charger le bon modèle.
Au cours de ce flux de travail , nous allons déclencher des crochets de base, comme wp
, template_redirect
, template_include
... pour faire le plugin plus souple et d' assurer la compatibilité avec le noyau et d' autres plug - ins, ou tout au moins avec un bon nombre d'entre eux.
Outre le flux de travail précédent, nous devrons également:
- Nettoyez les crochets et les variables globales après l'exécution de la boucle principale, encore une fois pour améliorer la compatibilité avec le code principal et le code tiers
- Ajoutez un filtre
the_permalink
pour lui faire renvoyer la bonne URL de page virtuelle lorsque cela est nécessaire.
Classes de béton
Nous pouvons maintenant coder nos classes concrètes. Commençons par la classe de page (fichier Page.php
):
<?php
namespace GM\VirtualPages;
class Page implements PageInterface {
private $url;
private $title;
private $content;
private $template;
private $wp_post;
function __construct( $url, $title = 'Untitled', $template = 'page.php' ) {
$this->url = filter_var( $url, FILTER_SANITIZE_URL );
$this->setTitle( $title );
$this->setTemplate( $template);
}
function getUrl() {
return $this->url;
}
function getTemplate() {
return $this->template;
}
function getTitle() {
return $this->title;
}
function setTitle( $title ) {
$this->title = filter_var( $title, FILTER_SANITIZE_STRING );
return $this;
}
function setContent( $content ) {
$this->content = $content;
return $this;
}
function setTemplate( $template ) {
$this->template = $template;
return $this;
}
function asWpPost() {
if ( is_null( $this->wp_post ) ) {
$post = array(
'ID' => 0,
'post_title' => $this->title,
'post_name' => sanitize_title( $this->title ),
'post_content' => $this->content ? : '',
'post_excerpt' => '',
'post_parent' => 0,
'menu_order' => 0,
'post_type' => 'page',
'post_status' => 'publish',
'comment_status' => 'closed',
'ping_status' => 'closed',
'comment_count' => 0,
'post_password' => '',
'to_ping' => '',
'pinged' => '',
'guid' => home_url( $this->getUrl() ),
'post_date' => current_time( 'mysql' ),
'post_date_gmt' => current_time( 'mysql', 1 ),
'post_author' => is_user_logged_in() ? get_current_user_id() : 0,
'is_virtual' => TRUE,
'filter' => 'raw'
);
$this->wp_post = new \WP_Post( (object) $post );
}
return $this->wp_post;
}
}
Rien de plus que la mise en œuvre de l'interface.
Maintenant, la classe du contrôleur (fichier Controller.php
):
<?php
namespace GM\VirtualPages;
class Controller implements ControllerInterface {
private $pages;
private $loader;
private $matched;
function __construct( TemplateLoaderInterface $loader ) {
$this->pages = new \SplObjectStorage;
$this->loader = $loader;
}
function init() {
do_action( 'gm_virtual_pages', $this );
}
function addPage( PageInterface $page ) {
$this->pages->attach( $page );
return $page;
}
function dispatch( $bool, \WP $wp ) {
if ( $this->checkRequest() && $this->matched instanceof Page ) {
$this->loader->init( $this->matched );
$wp->virtual_page = $this->matched;
do_action( 'parse_request', $wp );
$this->setupQuery();
do_action( 'wp', $wp );
$this->loader->load();
$this->handleExit();
}
return $bool;
}
private function checkRequest() {
$this->pages->rewind();
$path = trim( $this->getPathInfo(), '/' );
while( $this->pages->valid() ) {
if ( trim( $this->pages->current()->getUrl(), '/' ) === $path ) {
$this->matched = $this->pages->current();
return TRUE;
}
$this->pages->next();
}
}
private function getPathInfo() {
$home_path = parse_url( home_url(), PHP_URL_PATH );
return preg_replace( "#^/?{$home_path}/#", '/', esc_url( add_query_arg(array()) ) );
}
private function setupQuery() {
global $wp_query;
$wp_query->init();
$wp_query->is_page = TRUE;
$wp_query->is_singular = TRUE;
$wp_query->is_home = FALSE;
$wp_query->found_posts = 1;
$wp_query->post_count = 1;
$wp_query->max_num_pages = 1;
$posts = (array) apply_filters(
'the_posts', array( $this->matched->asWpPost() ), $wp_query
);
$post = $posts[0];
$wp_query->posts = $posts;
$wp_query->post = $post;
$wp_query->queried_object = $post;
$GLOBALS['post'] = $post;
$wp_query->virtual_page = $post instanceof \WP_Post && isset( $post->is_virtual )
? $this->matched
: NULL;
}
public function handleExit() {
exit();
}
}
Essentiellement, la classe crée un SplObjectStorage
objet dans lequel tous les objets pages ajoutés sont stockés.
Sur 'do_parse_request'
, la classe de contrôleur boucle ce stockage pour trouver une correspondance pour l'URL actuelle dans l'une des pages ajoutées.
S'il est trouvé, la classe fait exactement ce que nous avions prévu: déclencher des hooks, configurer des variables et charger le modèle via l'extension de classe TemplateLoaderInterface
. Après ça, juste exit()
.
Écrivons donc la dernière classe:
<?php
namespace GM\VirtualPages;
class TemplateLoader implements TemplateLoaderInterface {
public function init( PageInterface $page ) {
$this->templates = wp_parse_args(
array( 'page.php', 'index.php' ), (array) $page->getTemplate()
);
}
public function load() {
do_action( 'template_redirect' );
$template = locate_template( array_filter( $this->templates ) );
$filtered = apply_filters( 'template_include',
apply_filters( 'virtual_page_template', $template )
);
if ( empty( $filtered ) || file_exists( $filtered ) ) {
$template = $filtered;
}
if ( ! empty( $template ) && file_exists( $template ) ) {
require_once $template;
}
}
}
Les modèles stockés dans la page virtuelle sont fusionnés dans un tableau avec les valeurs par défaut page.php
et index.php
, avant le chargement du modèle 'template_redirect'
, pour ajouter de la flexibilité et améliorer la compatibilité.
Après cela, le modèle trouvé passe par les filtres personnalisés 'virtual_page_template'
et les 'template_include'
filtres principaux : encore une fois pour plus de flexibilité et de compatibilité.
Enfin, le fichier modèle vient d'être chargé.
Fichier plugin principal
À ce stade, nous devons écrire le fichier avec des en-têtes de plug-in et l'utiliser pour ajouter les crochets qui permettront à notre flux de travail de se produire:
<?php namespace GM\VirtualPages;
/*
Plugin Name: GM Virtual Pages
*/
require_once 'PageInterface.php';
require_once 'ControllerInterface.php';
require_once 'TemplateLoaderInterface.php';
require_once 'Page.php';
require_once 'Controller.php';
require_once 'TemplateLoader.php';
$controller = new Controller ( new TemplateLoader );
add_action( 'init', array( $controller, 'init' ) );
add_filter( 'do_parse_request', array( $controller, 'dispatch' ), PHP_INT_MAX, 2 );
add_action( 'loop_end', function( \WP_Query $query ) {
if ( isset( $query->virtual_page ) && ! empty( $query->virtual_page ) ) {
$query->virtual_page = NULL;
}
} );
add_filter( 'the_permalink', function( $plink ) {
global $post, $wp_query;
if (
$wp_query->is_page && isset( $wp_query->virtual_page )
&& $wp_query->virtual_page instanceof Page
&& isset( $post->is_virtual ) && $post->is_virtual
) {
$plink = home_url( $wp_query->virtual_page->getUrl() );
}
return $plink;
} );
Dans le vrai fichier, nous ajouterons probablement plus d'en-têtes, comme des liens de plugin et d'auteur, une description, une licence, etc.
Plugin Gist
Ok, nous avons fini avec notre plugin. Tout le code peut être trouvé dans un Gist ici .
Ajout de pages
Le plugin est prêt et fonctionne, mais nous n'avons ajouté aucune page.
Cela peut être fait à l'intérieur du plugin lui-même, à l'intérieur du thème functions.php
, dans un autre plugin, etc.
Ajouter des pages n'est qu'une question de:
<?php
add_action( 'gm_virtual_pages', function( $controller ) {
// first page
$controller->addPage( new \GM\VirtualPages\Page( '/custom/page' ) )
->setTitle( 'My First Custom Page' )
->setTemplate( 'custom-page-form.php' );
// second page
$controller->addPage( new \GM\VirtualPages\Page( '/custom/page/deep' ) )
->setTitle( 'My Second Custom Page' )
->setTemplate( 'custom-page-deep.php' );
} );
Etc. Vous pouvez ajouter toutes les pages dont vous avez besoin, n'oubliez pas d'utiliser des URL relatives pour les pages.
À l'intérieur du fichier de modèle, vous pouvez utiliser toutes les balises de modèle WordPress et vous pouvez écrire tout le PHP et HTML dont vous avez besoin.
L'objet de publication global est rempli de données provenant de notre page virtuelle. La page virtuelle elle-même est accessible via une $wp_query->virtual_page
variable.
Pour obtenir l'URL d'une page virtuelle, il suffit de passer au home_url()
même chemin que celui utilisé pour créer la page:
$custom_page_url = home_url( '/custom/page' );
Notez que dans la boucle principale du modèle chargé, the_permalink()
retournera le permalien correct à la page virtuelle.
Notes sur les styles / scripts pour les pages virtuelles
Probablement lorsque des pages virtuelles sont ajoutées, il est également souhaitable d'avoir des styles / scripts personnalisés mis en file d'attente, puis de les utiliser uniquement wp_head()
dans des modèles personnalisés.
C'est très facile, car les pages virtuelles sont facilement reconnaissables en regardant les $wp_query->virtual_page
variables et les pages virtuelles peuvent être distinguées les unes des autres en regardant leurs URL.
Juste un exemple:
add_action( 'wp_enqueue_scripts', function() {
global $wp_query;
if (
is_page()
&& isset( $wp_query->virtual_page )
&& $wp_query->virtual_page instanceof \GM\VirtualPages\PageInterface
) {
$url = $wp_query->virtual_page->getUrl();
switch ( $url ) {
case '/custom/page' :
wp_enqueue_script( 'a_script', $a_script_url );
wp_enqueue_style( 'a_style', $a_style_url );
break;
case '/custom/page/deep' :
wp_enqueue_script( 'another_script', $another_script_url );
wp_enqueue_style( 'another_style', $another_style_url );
break;
}
}
} );
Notes à OP
La transmission de données d'une page à une autre n'est pas liée à ces pages virtuelles, mais est juste une tâche générique.
Cependant, si vous avez un formulaire dans la première page et que vous souhaitez transmettre des données de celui-ci à la deuxième page, utilisez simplement l'URL de la deuxième page dans la action
propriété de formulaire .
Par exemple, dans le fichier de modèle de première page, vous pouvez:
<form action="<?php echo home_url( '/custom/page/deep' ); ?>" method="POST">
<input type="text" name="testme">
</form>
puis dans le deuxième fichier de modèle de page:
<?php $testme = filter_input( INPUT_POST, 'testme', FILTER_SANITIZE_STRING ); ?>
<h1>Test-Me value form other page is: <?php echo $testme; ?></h1>