Custom pages with plugin

时间:2014-09-22 作者:user1257255

我正在开发一些插件,我想在其中启用自定义页面。在我的情况下,一些自定义页面将包含一个类似于联系人表单的表单(不是字面意义上的)。当用户填写并发送此表单时,应该有下一步需要更多信息。假设包含表单的第一个页面位于www.domain.tld/custom-page/ 成功提交表单后,应将用户重定向到www.domain.tld/custom-page/second. 带有HTML元素和PHP代码的模板也应该是自定义的。

我认为部分问题可以通过自定义URL重写来实现,但其他部分目前我还不知道。我真的不知道应该从哪里开始寻找,这个问题的正确名称是什么。任何帮助都将不胜感激。

2 个回复
最合适的回答,由SO网友:gmazzap 整理而成

当您访问前端页面时,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.phpindex.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>

SO网友:david.binda

我曾经使用过下面描述的解决方案: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();

结束