www.domain.tld/custom-page/
,并且成功提交表单后,应将用户重定向到www.domain.tld/custom-page/second
。带有HTML元素和PHP代码的模板也应该是自定义的。我真的不知道该从哪里开始寻找该问题的正确命名。任何帮助将不胜感激。#1 楼
当您访问前端页面时,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();
}
大多数方法只是获取器和设置器,无需解释。应该使用最后一种方法从虚拟页面获取
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注释对于这些接口应该很清楚。 br />现在我们有了接口,并且在编写具体的类之前,让我们回顾一下我们的工作流程:首先,我们实例化一个
Controller
类(实现ControllerInterface
)并注入(可能在构造函数中) TemplateLoader
类的实例(实现TemplateLoaderInterface
)在
init
钩子上,我们调用ControllerInterface::init()
方法来设置控制器并触发钩子,消费者代码将使用该钩子来添加虚拟页面。在'do_parse_request'上,我们将调用
ControllerInterface::dispatch()
,并在此检查所有添加的虚拟页面,如果其中一个具有与当前请求相同的URL,则显示它;设置所有核心全局变量($wp_query
,$post
)之后。我们还将使用TemplateLoader
类加载正确的模板。在此工作流程中,我们将触发一些核心挂钩,例如
wp
,template_redirect
,template_include
...,以使插件更加灵活并确保与核心的兼容性和其他插件,或者至少有很多插件。除了以前的工作流程之外,我们还需要:
清理钩子和全局主循环运行后的变量,再次以提高与核心代码和第三方代码的兼容性
在
the_permalink
上添加过滤器,以使其在需要时返回正确的虚拟页面URL。 > 现在我们可以编写具体的类了。让我们从页面类(文件
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;
}
}
只不过是实现接口。 :
<?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();
}
}
基本上,该类创建一个
Controller.php
对象,所有添加的页面对象都存储在该对象中。在
SplObjectStorage
上,控制器类对此进行循环存储,以在添加的页面之一中找到当前URL的匹配项。如果找到,则该类完全按照我们的计划进行:触发一些挂钩,设置变量并通过该类加载模板扩展
'do_parse_request'
。之后,只需TemplateLoaderInterface
即可。所以让我们写最后一个类:
<?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;
}
}
}
虚拟存储的模板页被合并为默认值
exit()
和page.php
的数组,然后触发加载模板index.php
,以增加灵活性并提高兼容性。 e核心'template_redirect'
过滤器:再次具有灵活性和兼容性。最后,模板文件刚刚加载完毕。工作流发生:
<?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;
} );
在真实文件中,我们可能会添加更多标头,例如插件和作者链接,描述,许可证等。
插件Gist
好,我们完成了插件。所有代码都可以在此处的摘要中找到。
添加页面
插件已经准备就绪并且可以正常工作,但是我们没有添加任何页面。
这可以在插件本身内部,主题
'virtual_page_template'
中,在另一个插件中进行,等等。 等等。您可以添加所需的所有页面,只记得使用页面的相对URL。
在模板文件中,您可以使用所有WordPress模板标签,并且可以编写所需的所有PHP和HTML 。
全局post对象中填充了来自我们虚拟页面的数据。可以通过
'template_include'
变量访问虚拟页面本身。获取虚拟页面的URL就像传递给
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' );
} );
请注意,在已加载模板的主循环中,
$wp_query->virtual_page
将返回正确的永久链接到虚拟页面。 />可能添加虚拟页面时,也希望先排入自定义样式/脚本,然后在自定义模板中使用home_url()
。和虚拟页面可以通过查看URL来区分。仅举一个例子:
将数据从一个页面传递到另一个页面与这些虚拟页面无关,而只是一个常规任务。
但是,如果您在第一页中有一个表单,并且想要将数据从那里传递到第二页,只需在表单
the_permalink()
属性中使用第二页的URL。在第一页模板文件中,您可以:$custom_page_url = home_url( '/custom/page' );
,然后在第二页模板文件中:
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;
}
}
} );
评论
令人惊奇的综合答案,不仅涉及问题本身,而且涉及创建OOP风格的插件等等。您肯定会得到我的支持,想像更多,答案涵盖的每个级别都有一个。
–尼古拉
2014-09-25 10:50
非常光滑和直接的解决方案。更新,发推文。
– kaiser
2014-09-25 13:23
Controller中的代码有点错误... checkRequest()从home_url()获取路径信息,该信息返回localhost / wordpress。在preg_replace和add_query_arg之后,此网址变为/ wordpress / virtual-page。在checkRequest中进行修剪后,此网址为wordpress / virtual。如果将wordpress安装在domain的根文件夹中,则此方法有效。您能否提供解决该问题的方法,因为我找不到合适的函数来返回正确的url。谢谢你为我做的一切! (在它变得完美之后,我将接受答案:)
–user1257255
2014-09-25 18:20
恭喜,答案不错,我需要将大量工作视为免费的解决方案。
– Bueltge
2014-09-25 19:11
@GM:就我而言,WordPress安装在... / htdocs / wordpress /中,并且该站点位于localhost / wordpress上。 home_url()返回localhost / wordpress,而add_query_arg(array())返回/ wordpress / virtual-page /。当我们比较$ path并在checkRequest()中修剪$ this-> pages-> current()-> getUrl()时,会出现问题,因为$ path是wordpress / virtual-page,页面的修剪url是virtual-page。
–user1257255
2014年9月26日下午16:13
#2 楼
我曾经使用这里描述的解决方案:http://scott.sherrillmix.com/blog/blogger/creating-a-better-fake-post-with-a-wordpress-plugin/实际上,当我使用它时,我以一种可以一次注册多个页面的方式扩展了解决方案(代码的其余部分+/-与我从上一段链接的解决方案类似)。
该解决方案要求您拥有允许使用的漂亮的永久链接。
<?php
class FakePages {
public function __construct() {
add_filter( 'the_posts', array( $this, 'fake_pages' ) );
}
/**
* Internally registers pages we want to fake. Array key is the slug under which it is being available from the frontend
* @return mixed
*/
private static function get_fake_pages() {
//http://example.com/fakepage1
$fake_pages['fakepage1'] = array(
'title' => 'Fake Page 1',
'content' => 'This is a content of fake page 1'
);
//http://example.com/fakepage2
$fake_pages['fakepage2'] = array(
'title' => 'Fake Page 2',
'content' => 'This is a content of fake page 2'
);
return $fake_pages;
}
/**
* Fakes get posts result
*
* @param $posts
*
* @return array|null
*/
public function fake_pages( $posts ) {
global $wp, $wp_query;
$fake_pages = self::get_fake_pages();
$fake_pages_slugs = array();
foreach ( $fake_pages as $slug => $fp ) {
$fake_pages_slugs[] = $slug;
}
if ( true === in_array( strtolower( $wp->request ), $fake_pages_slugs )
|| ( true === isset( $wp->query_vars['page_id'] )
&& true === in_array( strtolower( $wp->query_vars['page_id'] ), $fake_pages_slugs )
)
) {
if ( true === in_array( strtolower( $wp->request ), $fake_pages_slugs ) ) {
$fake_page = strtolower( $wp->request );
} else {
$fake_page = strtolower( $wp->query_vars['page_id'] );
}
$posts = null;
$posts[] = self::create_fake_page( $fake_page, $fake_pages[ $fake_page ] );
$wp_query->is_page = true;
$wp_query->is_singular = true;
$wp_query->is_home = false;
$wp_query->is_archive = false;
$wp_query->is_category = false;
$wp_query->is_fake_page = true;
$wp_query->fake_page = $wp->request;
//Longer permalink structures may not match the fake post slug and cause a 404 error so we catch the error here
unset( $wp_query->query["error"] );
$wp_query->query_vars["error"] = "";
$wp_query->is_404 = false;
}
return $posts;
}
/**
* Creates virtual fake page
*
* @param $pagename
* @param $page
*
* @return stdClass
*/
private static function create_fake_page( $pagename, $page ) {
$post = new stdClass;
$post->post_author = 1;
$post->post_name = $pagename;
$post->guid = get_bloginfo( 'wpurl' ) . '/' . $pagename;
$post->post_title = $page['title'];
$post->post_content = $page['content'];
$post->ID = - 1;
$post->post_status = 'static';
$post->comment_status = 'closed';
$post->ping_status = 'closed';
$post->comment_count = 0;
$post->post_date = current_time( 'mysql' );
$post->post_date_gmt = current_time( 'mysql', 1 );
return $post;
}
}
new FakePages();
评论
可以在其中放置表单的自定义模板呢?
–user1257255
2014-09-24 17:51
注册假页面时数组中的内容会显示在页面的主体中-它可以包含HTML以及简单文本甚至是简码。
– david.binda
2014-09-24 17:53
评论
您是否要将这些页面存储在WordPress或“虚拟”中?您必须使用重写api。这应该不太困难。请确保将数据发布到第二页,并且应该没问题。
@Welcher:这些页面与WordPress在仪表板中提供的页面不同。他们应该只将数据保存到数据库中,但这不是问题。 @ .setterGetter:您有任何示例如何将数据从第一页传递到第二页,以及在何处(动作?)包括显示表单的PHP文件?
您是否考虑过使用具有多个输入字段的幻灯片(javascript和/或css)的单页表单?