Quando visiti una pagina di frontend, WordPress interrogherà il database e se la tua pagina non esiste nel database, quella query non è necessaria ed è solo uno spreco di risorse.
Fortunatamente, WordPress offre un modo per gestire le richieste di frontend in modo personalizzato. Questo è fatto grazie al 'do_parse_request'
filtro.
Tornando false
a quel hook, sarai in grado di fermare WordPress dall'elaborazione delle richieste e farlo nel tuo modo personalizzato.
Detto questo, voglio condividere un modo per creare un semplice plug-in OOP in grado di gestire pagine virtuali in modo facile da usare (e riutilizzare).
Ciò che ci serve
- Una classe per oggetti pagina virtuali
- Una classe controller, che esaminerà una richiesta e, se si tratta di una pagina virtuale, la mostrerà utilizzando il modello appropriato
- Una classe per il caricamento dei modelli
- File plugin principali per aggiungere gli hook che faranno funzionare tutto
interfacce
Prima di creare classi, scriviamo le interfacce per i 3 oggetti sopra elencati.
Innanzitutto l'interfaccia della pagina (file 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 maggior parte dei metodi sono solo getter e setter, senza bisogno di spiegazioni. L'ultimo metodo deve essere utilizzato per ottenere un WP_Post
oggetto da una pagina virtuale.
L'interfaccia del controller (file 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 );
}
e l'interfaccia del caricatore di template (file 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();
}
I commenti phpDoc dovrebbero essere abbastanza chiari per queste interfacce.
Il programma
Ora che abbiamo interfacce e prima di scrivere lezioni concrete, rivediamo il nostro flusso di lavoro:
- Per prima cosa istanziamo una
Controller
classe (implementando ControllerInterface
) e iniettiamo (probabilmente in un costruttore) un'istanza di TemplateLoader
classe (implementando TemplateLoaderInterface
)
- Sul
init
gancio chiamiamo il ControllerInterface::init()
metodo per impostare il controller e per sparare il gancio che codice del consumo utilizzerà per aggiungere pagine virtuali.
- Su 'do_parse_request' chiameremo
ControllerInterface::dispatch()
e lì controlleremo tutte le pagine virtuali aggiunte e se una di esse ha lo stesso URL della richiesta corrente, la visualizzeremo; dopo aver impostato tutte le variabili globali principali ( $wp_query
, $post
). Useremo anche la TemplateLoader
classe per caricare il modello giusto.
Durante questo flusso di lavoro, attiveremo alcuni hook core, come wp
, ... template_redirect
, template_include
per rendere il plug-in più flessibile e garantire la compatibilità con core e altri plug-in, o almeno con un buon numero di essi.
Oltre al flusso di lavoro precedente, dovremo anche:
- Ripulisci gli hook e le variabili globali dopo l'esecuzione del ciclo principale, per migliorare la compatibilità con il core e il codice di terze parti
- Aggiungi un filtro
the_permalink
per farlo restituire l'URL della pagina virtuale giusto quando necessario.
Classi concrete
Ora possiamo codificare le nostre classi concrete. Cominciamo con la classe di pagine (file 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;
}
}
Nient'altro che implementare l'interfaccia.
Ora la classe controller (file 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();
}
}
Essenzialmente la classe crea un SplObjectStorage
oggetto in cui sono memorizzati tutti gli oggetti delle pagine aggiunte.
Attivato 'do_parse_request'
, la classe controller esegue il loop di questo archivio per trovare una corrispondenza per l'URL corrente in una delle pagine aggiunte.
Se viene trovato, la classe fa esattamente ciò che abbiamo pianificato: innescare alcuni hook, impostare variabili e caricare il modello tramite l'estensione della classe TemplateLoaderInterface
. Dopo quello, giusto exit()
.
Quindi scriviamo l'ultima 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;
}
}
}
I modelli archiviati nella pagina virtuale vengono uniti in un array con impostazioni predefinite page.php
e index.php
, prima che il caricamento del modello 'template_redirect'
venga attivato, per aggiungere flessibilità e migliorare la compatibilità.
Successivamente, il modello trovato passa attraverso i filtri personalizzati 'virtual_page_template'
e core 'template_include'
: di nuovo per flessibilità e compatibilità.
Finalmente il file modello è appena caricato.
File plugin principale
A questo punto dobbiamo scrivere il file con le intestazioni dei plugin e usarlo per aggiungere gli hook che renderanno possibile il nostro flusso di lavoro:
<?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;
} );
Nel file reale probabilmente aggiungeremo più intestazioni, come collegamenti a plugin e autore, descrizione, licenza, ecc.
Plugin Gist
Ok, abbiamo finito con il nostro plugin. Tutto il codice può essere trovato in un Gist qui .
Aggiunta di pagine
Il plug-in è pronto e funzionante, ma non abbiamo aggiunto alcuna pagina.
Questo può essere fatto all'interno del plugin stesso, all'interno del tema functions.php
, in un altro plugin, ecc.
Aggiungere pagine è solo una questione di:
<?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' );
} );
E così via. Puoi aggiungere tutte le pagine di cui hai bisogno, ricorda di usare gli URL relativi per le pagine.
All'interno del file modello puoi utilizzare tutti i tag modello di WordPress e puoi scrivere tutto il PHP e l'HTML di cui hai bisogno.
L'oggetto post globale è pieno di dati provenienti dalla nostra pagina virtuale. È possibile accedere alla stessa pagina virtuale tramite $wp_query->virtual_page
variabile.
Ottenere l'URL per una pagina virtuale è facile come passare allo home_url()
stesso percorso utilizzato per creare la pagina:
$custom_page_url = home_url( '/custom/page' );
Si noti che nel ciclo principale nel modello caricato, the_permalink()
restituirà il permalink corretto alla pagina virtuale.
Note su stili / script per pagine virtuali
Probabilmente quando vengono aggiunte pagine virtuali, è anche auspicabile avere stili / script personalizzati accodati e quindi utilizzarli solo wp_head()
in modelli personalizzati.
È molto facile, perché le pagine virtuali sono facilmente riconoscibili guardando le $wp_query->virtual_page
variabili e le pagine virtuali possono essere distinte l'una dall'altra guardando i loro URL.
Solo un esempio:
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;
}
}
} );
Note all'OP
Il passaggio di dati da una pagina a un'altra non è correlato a queste pagine virtuali, ma è solo un'attività generica.
Tuttavia, se si dispone di un modulo nella prima pagina e si desidera passare i dati da lì alla seconda pagina, utilizzare semplicemente l'URL della seconda pagina nella action
proprietà del modulo .
Ad esempio nel file modello della prima pagina è possibile:
<form action="<?php echo home_url( '/custom/page/deep' ); ?>" method="POST">
<input type="text" name="testme">
</form>
e quindi nel file modello della seconda pagina:
<?php $testme = filter_input( INPUT_POST, 'testme', FILTER_SANITIZE_STRING ); ?>
<h1>Test-Me value form other page is: <?php echo $testme; ?></h1>