当您访问前端页面时,WordPress将查询数据库,如果数据库中不存在您的页面,则不需要该查询,这只是浪费资源。
幸运的是,WordPress提供了一种以自定义方式处理前端请求的方法。这要归功于\'do_parse_request\'
滤器
正在返回false
在这个钩子上,您将能够阻止WordPress处理请求,并以您自己的自定义方式进行处理。
也就是说,我想分享一种构建简单OOP插件的方法,该插件可以以易于使用(和重用)的方式处理虚拟页面。
我们需要的是一个用于虚拟页面对象的类一个控制器类,它将查看一个请求,如果是用于虚拟页面,则在构建类之前,使用适当的模板显示它,该类用于加载模板的主插件文件,以添加钩子,使一切都能正常工作,让我们为上面列出的3个对象编写接口。
首先是页面界面(文件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();
}
大多数方法只是getter和setter,不需要解释。最后一个方法应用于获取
WP_Post
对象。
控制器接口(文件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 );
}
和模板加载器接口(文件
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();
}
对于这些接口,phpDoc注释应该非常清楚。
现在我们有了接口,在编写具体类之前,让我们回顾一下我们的工作流程:
首先,我们实例化Controller
类(实现ControllerInterface
) 并(可能在构造函数中)注入TemplateLoader
类(实现TemplateLoaderInterface
)打开init
我们称之为ControllerInterface::init()
方法设置控制器并触发用户代码用于添加虚拟页面的挂钩打开\'do_parse_request\' 我们会打电话的ControllerInterface::dispatch()
, 在那里,我们将检查所有添加的虚拟页面,如果其中一个具有当前请求的相同URL,则显示它;设置所有核心全局变量后($wp_query
, $post
). 我们还将使用TemplateLoader
类以加载正确的模板在此工作流期间,我们将触发一些核心挂钩,如wp
, template_redirect
, template_include
... 使插件更加灵活,并确保与core和其他插件兼容,或者至少与大量插件兼容。
除了以前的工作流程外,我们还需要:
主循环运行后清理挂钩和全局变量,再次提高与核心代码和第三方代码的兼容性the_permalink
使其在需要时返回正确的虚拟页面URL
具体类
现在我们可以编写具体类了。让我们从page类(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;
}
}
只不过是实现接口而已。
现在控制器类(文件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();
}
}
本质上,该类创建
SplObjectStorage
对象,其中存储所有添加的页面对象。
在…上\'do_parse_request\'
, controller类循环此存储以在其中一个添加的页面中查找与当前URL的匹配项。
如果找到了,该类将完全按照我们的计划执行:触发一些挂钩、设置变量并通过类扩展加载模板TemplateLoaderInterface
.在那之后exit()
.
那么让我们写最后一节课:
<?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;
}
}
}
存储在虚拟页面中的模板以默认值合并到一个数组中
page.php
和
index.php
, 加载模板前
\'template_redirect\'
被激发,以增加灵活性和改进兼容性。
之后,找到的模板将通过自定义\'virtual_page_template\'
和核心\'template_include\'
过滤器:同样是为了灵活性和兼容性。
最后加载模板文件。
主插件文件
此时,我们需要编写带有插件头的文件,并使用它添加使我们的工作流发生的挂钩:
<?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;
} );
在真实的文件中,我们可能会添加更多的标题,如插件和作者链接、描述、许可证等。
插件要点好了,我们的插件已经完成了。所有代码都可以在摘要中找到here.
添加页面插件已经准备好并可以运行,但我们还没有添加任何页面。
这可以在插件本身、主题内部完成functions.php
, 在另一个插件中,等等。
添加页面只是一个问题:
<?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\' );
} );
等等。您可以添加所需的所有页面,只需记住对页面使用相对URL即可。
在模板文件中,您可以使用所有WordPress模板标记,并且可以编写所需的所有PHP和HTML。
全局post对象充满了来自虚拟页面的数据。可以通过访问虚拟页面本身$wp_query->virtual_page
变量
获取虚拟页面的URL就像传递到home_url()
用于创建页面的相同路径:
$custom_page_url = home_url( \'/custom/page\' );
请注意,在加载的模板的主循环中,
the_permalink()
将向虚拟页返回正确的永久链接。
关于虚拟页面样式/脚本的说明
可能在添加虚拟页面时,最好将自定义样式/脚本放入队列,然后使用
wp_head()
在自定义模板中。
这很容易,因为查看虚拟页面很容易识别$wp_query->virtual_page
通过查看可变页面和虚拟页面的URL,可以将它们彼此区分开来。
举个例子:
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;
}
}
} );
将数据从一个页面传递到另一个页面与这些虚拟页面无关,而只是一项普通任务。
但是,如果您在第一个页面中有一个表单,并且希望将数据从那里传递到第二个页面,只需在表单中使用第二个页面的URL即可action
所有物
E、 g.在第一页模板文件中,您可以:
<form action="<?php echo home_url( \'/custom/page/deep\' ); ?>" method="POST">
<input type="text" name="testme">
</form>
然后在第二个页面模板文件中:
<?php $testme = filter_input( INPUT_POST, \'testme\', FILTER_SANITIZE_STRING ); ?>
<h1>Test-Me value form other page is: <?php echo $testme; ?></h1>