Ketika Anda mengunjungi halaman frontend, WordPress akan menanyakan database dan jika halaman Anda tidak ada di database, permintaan itu tidak diperlukan dan hanya membuang-buang sumber daya.
Untungnya, WordPress menawarkan cara untuk menangani permintaan frontend dengan cara kustom. Itu dilakukan berkat 'do_parse_request'
filter.
Kembali false
pada kait itu, Anda akan dapat menghentikan WordPress dari memproses permintaan dan melakukannya dengan cara Anda sendiri.
Yang mengatakan, saya ingin berbagi cara untuk membangun plugin OOP sederhana yang dapat menangani halaman virtual dengan cara yang mudah digunakan (dan digunakan kembali).
Apa yang kita butuhkan
- Kelas untuk objek halaman virtual
- Kelas controller, yang akan melihat permintaan dan jika itu untuk halaman virtual, perlihatkan itu menggunakan templat yang tepat
- Kelas untuk memuat template
- File plugin utama untuk menambahkan kait yang akan membuat semuanya berfungsi
Antarmuka
Sebelum membangun kelas, mari kita menulis antarmuka untuk 3 objek yang tercantum di atas.
Pertama antarmuka halaman (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();
}
Sebagian besar metode hanya getter dan setter, tidak perlu penjelasan. Metode terakhir harus digunakan untuk mendapatkan WP_Post
objek dari halaman virtual.
Antarmuka pengontrol (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 );
}
dan antarmuka pemuat 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();
}
Komentar phpDoc harus cukup jelas untuk antarmuka ini.
Rencana
Sekarang kita memiliki antarmuka, dan sebelum menulis kelas konkret, mari kita tinjau alur kerja kami:
- Pertama kita instantiate sebuah
Controller
kelas (implementasi ControllerInterface
) dan menyuntikkan (mungkin dalam konstruktor) sebuah instance dari TemplateLoader
kelas (menerapkan TemplateLoaderInterface
)
- Pada
init
hook kami memanggil ControllerInterface::init()
metode untuk mengatur controller dan mengaktifkan hook yang akan digunakan kode konsumen untuk menambahkan halaman virtual.
- Pada 'do_parse_request' kami akan menelepon
ControllerInterface::dispatch()
, dan di sana kami akan memeriksa semua halaman virtual yang ditambahkan dan jika salah satu dari mereka memiliki URL yang sama dengan permintaan saat ini, tampilkan; setelah menetapkan semua variabel global inti ( $wp_query
, $post
). Kami juga akan menggunakan TemplateLoader
kelas untuk memuat template yang tepat.
Selama ini alur kerja kita akan memicu beberapa kait inti, seperti wp
, template_redirect
, template_include
... untuk membuat plugin lebih fleksibel dan memastikan kompatibilitas dengan inti dan plugin lainnya, atau setidaknya dengan baik jumlah mereka.
Selain dari alur kerja sebelumnya, kita juga perlu:
- Bersihkan kait dan variabel global setelah loop utama berjalan, sekali lagi untuk meningkatkan kompatibilitas dengan kode inti dan pihak ketiga
- Tambahkan filter
the_permalink
untuk membuatnya mengembalikan URL halaman virtual yang tepat ketika dibutuhkan.
Kelas Beton
Sekarang kita bisa mengkodekan kelas konkret kita. Mari kita mulai dengan kelas halaman (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;
}
}
Tidak lebih dari mengimplementasikan antarmuka.
Sekarang kelas 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();
}
}
Pada dasarnya kelas membuat SplObjectStorage
objek tempat semua objek halaman yang ditambahkan disimpan.
Aktif 'do_parse_request'
, kelas pengontrol loop penyimpanan ini untuk menemukan kecocokan untuk URL saat ini di salah satu halaman yang ditambahkan.
Jika ditemukan, kelas melakukan persis apa yang kita rencanakan: memicu beberapa kait, mengatur variabel dan memuat templat melalui perluasan kelas TemplateLoaderInterface
. Setelah itu baru saja exit()
.
Jadi mari kita tulis kelas terakhir:
<?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;
}
}
}
Template yang disimpan di halaman virtual digabungkan dalam sebuah array dengan default page.php
dan index.php
, sebelum memuat template 'template_redirect'
dipecat, untuk menambah fleksibilitas dan meningkatkan kompatibilitas.
Setelah itu, templat yang ditemukan melewati filter kustom 'virtual_page_template'
dan inti 'template_include'
: lagi untuk fleksibilitas dan kompatibilitas.
Akhirnya file template baru saja dimuat.
File plugin utama
Pada titik ini kita perlu menulis file dengan header plugin dan menggunakannya untuk menambahkan kait yang akan membuat alur kerja kita terjadi:
<?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;
} );
Dalam file asli kita mungkin akan menambahkan lebih banyak header, seperti plugin dan tautan penulis, deskripsi, lisensi, dll.
Pengaya Plugin
Ok, kita selesai dengan plugin kita. Semua kode dapat ditemukan di Intisari di sini .
Menambahkan Halaman
Plugin sudah siap dan berfungsi, tetapi kami belum menambahkan halaman apa pun.
Itu bisa dilakukan di dalam plugin itu sendiri, di dalam tema functions.php
, di plugin lain, dll.
Tambahkan halaman hanya masalah:
<?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' );
} );
Dan seterusnya. Anda dapat menambahkan semua halaman yang Anda butuhkan, hanya ingat untuk menggunakan URL relatif untuk halaman tersebut.
Di dalam file template Anda dapat menggunakan semua tag template WordPress, dan Anda dapat menulis semua PHP dan HTML yang Anda butuhkan.
Objek posting global diisi dengan data yang berasal dari halaman virtual kami. Halaman virtual itu sendiri dapat diakses melalui $wp_query->virtual_page
variabel.
Untuk mendapatkan URL untuk halaman virtual semudah melewati ke home_url()
jalur yang sama yang digunakan untuk membuat halaman:
$custom_page_url = home_url( '/custom/page' );
Perhatikan bahwa dalam loop utama pada template yang dimuat, the_permalink()
akan mengembalikan permalink yang benar ke halaman virtual.
Catatan tentang gaya / skrip untuk halaman virtual
Mungkin ketika halaman virtual ditambahkan, itu juga diinginkan untuk memiliki gaya khusus / skrip enqueued dan kemudian hanya digunakan wp_head()
dalam templat kustom.
Itu sangat mudah, karena halaman virtual mudah dikenali melihat $wp_query->virtual_page
variabel dan halaman virtual dapat dibedakan satu dengan yang lain melihat URL mereka.
Contoh saja:
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;
}
}
} );
Catatan untuk OP
Melewati data dari satu halaman ke halaman lain tidak terkait dengan halaman virtual ini, tetapi hanya tugas umum.
Namun, jika Anda memiliki formulir di halaman pertama, dan ingin meneruskan data dari sana ke halaman kedua, cukup gunakan URL halaman kedua di action
properti form .
Misalnya dalam file templat halaman pertama Anda dapat:
<form action="<?php echo home_url( '/custom/page/deep' ); ?>" method="POST">
<input type="text" name="testme">
</form>
dan kemudian di file templat halaman kedua:
<?php $testme = filter_input( INPUT_POST, 'testme', FILTER_SANITIZE_STRING ); ?>
<h1>Test-Me value form other page is: <?php echo $testme; ?></h1>