我正在开发一些想要启用自定义页面的插件。在我的情况下,某些自定义页面将包含诸如联系表单之类的表单(并非字面意思)。当用户填写并发送此表格时,应该有需要更多信息的下一步。假设具有表单的第一页位于www.domain.tld/custom-page/,并且成功提交表单后,应将用户重定向到www.domain.tld/custom-page/second。带有HTML元素和PHP代码的模板也应该是自定义的。我真的不知道该从哪里开始寻找该问题的正确命名。任何帮助将不胜感激。

评论

您是否要将这些页面存储在WordPress或“虚拟”中?

您必须使用重写api。这应该不太困难。请确保将数据发布到第二页,并且应该没问题。

@Welcher:这些页面与WordPress在仪表板中提供的页面不同。他们应该只将数据保存到数据库中,但这不是问题。 @ .setterGetter:您有任何示例如何将数据从第一页传递到第二页,以及在何处(动作?)包括显示表单的PHP文件?

您是否考虑过使用具有多个输入字段的幻灯片(javascript和/或css)的单页表单?

#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类加载正确的模板。

在此工作流程中,我们将触发一些核心挂钩,例如wptemplate_redirecttemplate_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