Trang tùy chỉnh với plugin


13

Tôi đang phát triển một số plugin nơi tôi muốn kích hoạt các trang tùy chỉnh. Trong trường hợp của tôi, một số trang tùy chỉnh sẽ chứa một biểu mẫu như biểu mẫu liên hệ (không phải theo nghĩa đen). Khi người dùng sẽ điền vào biểu mẫu này và gửi nó, cần có bước tiếp theo sẽ yêu cầu thêm thông tin. Hãy nói rằng trang đầu tiên có biểu mẫu sẽ được đặt tại www.domain.tld/custom-page/và sau khi gửi biểu mẫu thành công, người dùng sẽ được chuyển hướng đến www.domain.tld/custom-page/second. Mẫu có các phần tử HTML và mã PHP cũng nên được tùy chỉnh.

Tôi nghĩ rằng một phần của vấn đề có thể đạt được khi viết lại URL tùy chỉnh, nhưng các phần khác hiện không rõ đối với tôi. Tôi thực sự không biết tôi nên bắt đầu tìm kiếm ở đâu và cách đặt tên chính xác cho vấn đề đó. Bất kỳ trợ giúp sẽ được thực sự đánh giá cao.


Bạn có muốn các trang này được lưu trữ trong WordPress hoặc 'ảo' không?
Welcher

Bạn sẽ phải sử dụng api viết lại. Điều này không nên quá khó khăn. Hãy chắc chắn để gửi dữ liệu lên trang thứ hai và bạn sẽ ổn.
setterGetter

@Welcher: Các trang này không giống như các ưu đãi của WordPress trong bảng điều khiển. Họ chỉ nên lưu dữ liệu vào cơ sở dữ liệu, nhưng đó không phải là vấn đề. @ .setterGetter: Bạn có ví dụ nào về cách chuyển dữ liệu từ trang đầu tiên sang trang thứ hai và ở đâu (hành động?) Để bao gồm tệp PHP hiển thị biểu mẫu không?
dùng1257255

Bạn đã xem xét sử dụng một hình thức trang duy nhất, với nhiều trang chiếu (javascript và / hoặc css) của các trường nhập liệu?
bạch dương

Câu trả lời:


56

Khi bạn truy cập trang frontend, WordPress sẽ truy vấn cơ sở dữ liệu và nếu trang của bạn không tồn tại trong cơ sở dữ liệu, truy vấn đó không cần thiết và chỉ là một sự lãng phí tài nguyên.

May mắn thay, WordPress cung cấp một cách để xử lý các yêu cầu lối vào theo cách tùy chỉnh. Điều đó được thực hiện nhờ vào 'do_parse_request'bộ lọc.

Quay trở lại falsevới cái móc đó, bạn sẽ có thể ngăn WordPress xử lý các yêu cầu và thực hiện nó theo cách tùy chỉnh của riêng bạn.

Điều đó nói rằng, tôi muốn chia sẻ một cách để xây dựng một plugin OOP đơn giản có thể xử lý các trang ảo theo cách dễ sử dụng (và sử dụng lại).

Những gì chúng tôi cần

  • Một lớp cho các đối tượng trang ảo
  • Một lớp trình điều khiển, sẽ xem xét một yêu cầu và nếu nó dành cho một trang ảo, hãy hiển thị nó bằng cách sử dụng mẫu thích hợp
  • Một lớp để tải mẫu
  • Các tập tin plugin chính để thêm các hook sẽ làm cho mọi thứ hoạt động

Giao diện

Trước khi xây dựng các lớp, hãy viết các giao diện cho 3 đối tượng được liệt kê ở trên.

Đầu tiên giao diện trang (tệp 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();
}

Hầu hết các phương pháp chỉ là getters và setters, không cần giải thích. Phương pháp cuối cùng nên được sử dụng để lấy một WP_Postđối tượng từ một trang ảo.

Giao diện điều khiển (tệp 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 ); 
}

và giao diện trình tải mẫu (tệp 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();
}

Nhận xét phpDoc nên khá rõ ràng cho các giao diện này.

Kế hoạch

Bây giờ chúng ta có các giao diện và trước khi viết các lớp cụ thể, hãy xem lại quy trình làm việc của chúng tôi:

  • Đầu tiên chúng ta khởi tạo một Controllerlớp (thực hiện ControllerInterface) và tiêm (có thể trong một hàm tạo) một thể hiện của TemplateLoaderlớp (triển khai TemplateLoaderInterface)
  • Khi inithook, chúng ta gọi ControllerInterface::init()phương thức để thiết lập bộ điều khiển và kích hoạt hook mà mã người tiêu dùng sẽ sử dụng để thêm các trang ảo.
  • Trên 'do_parse_Vquest', chúng tôi sẽ gọi ControllerInterface::dispatch()và ở đó chúng tôi sẽ kiểm tra tất cả các trang ảo được thêm vào và nếu một trong số chúng có cùng URL của yêu cầu hiện tại, hãy hiển thị nó; sau khi đã đặt tất cả các biến toàn cục cốt lõi ( $wp_query, $post). Chúng tôi cũng sẽ sử dụng TemplateLoaderlớp để tải đúng mẫu.

Trong thời gian này công việc chúng tôi sẽ kích hoạt một số móc cốt lõi, như wp, template_redirect, template_include... để làm cho các plugin linh hoạt hơn và đảm bảo khả năng tương thích với lõi và các plugin khác, hoặc ít nhất là với một số lượng tốt của họ.

Ngoài quy trình làm việc trước đó, chúng tôi cũng sẽ cần:

  • Dọn dẹp móc và biến toàn cục sau khi chạy vòng lặp chính, một lần nữa để cải thiện khả năng tương thích với mã bên thứ ba và bên thứ ba
  • Thêm bộ lọc vào the_permalinkđể làm cho nó trả về đúng URL trang ảo khi cần.

Lớp bê tông

Bây giờ chúng ta có thể mã các lớp cụ thể của chúng tôi. Hãy bắt đầu với lớp trang (tệp 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;
    }
}

Không có gì hơn là thực hiện giao diện.

Bây giờ lớp trình điều khiển (tệp 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();
    }
}

Về cơ bản lớp tạo một SplObjectStorageđối tượng trong đó tất cả các đối tượng trang được thêm vào được lưu trữ.

Bật 'do_parse_request', lớp trình điều khiển lặp vòng lưu trữ này để tìm kết quả khớp cho URL hiện tại trong một trong các trang được thêm.

Nếu nó được tìm thấy, lớp thực hiện chính xác những gì chúng ta đã lên kế hoạch: kích hoạt một số hook, thiết lập các biến và tải mẫu thông qua việc mở rộng lớp TemplateLoaderInterface. Sau đó, chỉ exit().

Vì vậy, hãy viết lớp cuối cùng:

<?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;
        }
    }
}

Các mẫu được lưu trữ trong trang ảo được hợp nhất trong một mảng với các giá trị mặc định page.phpindex.phptrước khi tải mẫu 'template_redirect'được kích hoạt, để thêm tính linh hoạt và cải thiện khả năng tương thích.

Sau đó, mẫu tìm thấy sẽ chuyển qua các bộ lọc tùy chỉnh 'virtual_page_template'và lõi 'template_include': một lần nữa để linh hoạt và tương thích.

Cuối cùng, tập tin mẫu vừa được tải.

Tệp plugin chính

Tại thời điểm này, chúng ta cần viết tệp với các tiêu đề plugin và sử dụng nó để thêm các hook sẽ làm cho quy trình công việc của chúng ta xảy ra:

<?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;
} );

Trong tệp thực, chúng tôi có thể sẽ thêm nhiều tiêu đề hơn, như liên kết plugin và tác giả, mô tả, giấy phép, v.v.

Plugin Gist

Ok, chúng tôi đã hoàn thành với plugin của chúng tôi. Tất cả các mã có thể được tìm thấy trong một Gist ở đây .

Thêm trang

Plugin đã sẵn sàng và hoạt động, nhưng chúng tôi chưa thêm bất kỳ trang nào.

Điều đó có thể được thực hiện bên trong chính plugin, bên trong chủ đề functions.php, trong một plugin khác, v.v.

Thêm trang chỉ là vấn đề:

<?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' );

} );

Và như thế. Bạn có thể thêm tất cả các trang bạn cần, chỉ cần nhớ sử dụng URL tương đối cho các trang.

Bên trong tệp mẫu, bạn có thể sử dụng tất cả các thẻ mẫu WordPress và bạn có thể viết tất cả PHP và HTML bạn cần.

Đối tượng bài đăng toàn cầu chứa đầy dữ liệu đến từ trang ảo của chúng tôi. Các trang ảo có thể được truy cập thông qua $wp_query->virtual_pagebiến.

Để có được URL cho một trang ảo dễ dàng như chuyển đến home_url()cùng một đường dẫn được sử dụng để tạo trang:

$custom_page_url = home_url( '/custom/page' );

Lưu ý rằng trong vòng lặp chính trong mẫu đã tải, the_permalink()sẽ trả lại permalink chính xác cho trang ảo.

Ghi chú về kiểu / tập lệnh cho trang ảo

Có lẽ khi các trang ảo được thêm vào, chúng ta cũng mong muốn có các kiểu / tập lệnh tùy chỉnh được yêu thích và sau đó chỉ sử dụng wp_head()trong các mẫu tùy chỉnh.

Điều đó rất dễ dàng, bởi vì các trang ảo dễ dàng được nhận ra khi nhìn vào $wp_query->virtual_pagecác trang biến và các trang ảo có thể được phân biệt giữa các trang này với các URL khác.

Chỉ là một ví dụ:

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;
        }
    }

} );

Ghi chú cho OP

Truyền dữ liệu từ trang này sang trang khác không liên quan đến các trang ảo này, mà chỉ là một nhiệm vụ chung.

Tuy nhiên, nếu bạn có một biểu mẫu trong trang đầu tiên và muốn chuyển dữ liệu từ đó sang trang thứ hai, chỉ cần sử dụng URL của trang thứ hai trong thuộc tính biểu mẫu action.

Ví dụ: trong tệp mẫu trang đầu tiên bạn có thể:

<form action="<?php echo home_url( '/custom/page/deep' ); ?>" method="POST">
    <input type="text" name="testme">
</form>

và sau đó trong tệp mẫu trang thứ hai:

<?php $testme = filter_input( INPUT_POST, 'testme', FILTER_SANITIZE_STRING ); ?>
<h1>Test-Me value form other page is: <?php echo $testme; ?></h1>

9
Câu trả lời toàn diện tuyệt vời, không chỉ về vấn đề mà còn về việc tạo ra một trình cắm thêm kiểu OOP và hơn thế nữa. Bạn có upvote của tôi chắc chắn, tưởng tượng nhiều hơn, một cho mỗi cấp độ câu trả lời bao gồm.
Nicolai

2
Giải pháp rất trơn tru và thẳng về phía trước. Nâng cao, tweet.
kaiser

Mã trong Trình điều khiển hơi sai một chút ... checkRequest () đang nhận thông tin đường dẫn từ home_url () trả về localhost / wordpress. Sau preg numplace và add_query_arg, url này trở thành / wordpress / trang ảo. Và sau khi cắt trong checkRequest, url này là wordpress / virtual. Điều này sẽ hoạt động nếu wordpress sẽ được cài đặt trong thư mục gốc của tên miền. Bạn có thể vui lòng cung cấp bản sửa lỗi cho vấn đề đó không vì tôi không thể tìm thấy chức năng phù hợp sẽ trả về đúng url. Cảm ơn bạn cho tất cả mọi thứ! (Tôi sẽ chấp nhận câu trả lời sau khi nó trở nên hoàn hảo :)
user1257255

2
Xin chúc mừng, câu trả lời tốt đẹp và tôi cần phải xem rất nhiều công việc này là giải pháp miễn phí.
bueltge

@GM: Trong trường hợp của tôi, WordPress được cài đặt trong ... / htdocs / wordpress / và trang web có sẵn trên localhost / wordpress . home_url () trả về localhost / wordpress và add_query_arg (mảng ()) trả về / wordpress / virtual-page /. Khi chúng tôi so sánh $ path và cắt $ this-> page-> current () -> getUrl () trong checkRequest () là vấn đề vì đường dẫn $ là wordpress/virtual-pagevà url được cắt của trang là virtual-page.
dùng1257255

0

Tôi đã từng sử dụng một giải pháp được mô tả ở đây: http://scott.sherrillmix.com/blog/blogger/creating-a-better-fake-post-with-a-wordpress-plugin/

Trên thực tế, khi tôi đang sử dụng nó, tôi mở rộng giải pháp theo cách tôi có thể đăng ký nhiều hơn một trang một lần (phần còn lại của mã là +/- tương tự như giải pháp tôi liên kết từ một đoạn trên).

Giải pháp yêu cầu bạn phải có permalinks đẹp cho phép ...

<?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();

Điều gì về mẫu tùy chỉnh nơi tôi có thể đặt biểu mẫu của mình?
dùng1257255

contenttrong mảng khi bạn đăng ký trang giả mạo đang được hiển thị trong phần thân của trang - nó có thể chứa HTML cũng như văn bản đơn giản hoặc thậm chí là một mã ngắn.
david.binda
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.