Halaman khusus dengan plugin

13

Saya sedang mengembangkan beberapa plugin di mana saya ingin mengaktifkan halaman khusus. Dalam kasus saya, beberapa halaman khusus akan berisi formulir seperti formulir kontak (tidak secara harfiah). Ketika pengguna akan mengisi formulir ini dan mengirimkannya, harus ada langkah berikutnya yang akan membutuhkan lebih banyak informasi. Katakanlah bahwa halaman pertama dengan formulir akan terletak di www.domain.tld/custom-page/dan setelah pengiriman formulir berhasil, pengguna harus diarahkan ke www.domain.tld/custom-page/second. Template dengan elemen HTML dan kode PHP juga harus dibuat khusus.

Saya pikir sebagian masalah mungkin terjadi dengan penulisan ulang URL khusus, tetapi bagian lain saat ini tidak saya kenal. Saya benar-benar tidak tahu di mana saya harus mulai mencari dan apa nama yang tepat untuk masalah itu. Bantuan apa pun akan sangat dihargai.

pengguna1257255
sumber
Apakah Anda ingin halaman ini disimpan di WordPress atau 'virtual'?
Welcher
Anda harus menggunakan api menulis ulang. Ini seharusnya tidak terlalu sulit. Pastikan untuk mengirim data ke halaman kedua dan Anda harus baik-baik saja.
setterGetter
@Welcher: Halaman-halaman ini tidak sama dengan penawaran WordPress di dashboard. Mereka seharusnya hanya menyimpan data ke dalam basis data, tetapi bukan itu masalahnya. @ .setterGetter: Apakah Anda memiliki contoh cara mengirim data dari halaman pertama ke halaman kedua dan di mana (tindakan?) untuk menyertakan file PHP yang memperlihatkan formulir?
user1257255
Apakah Anda mempertimbangkan untuk menggunakan formulir halaman tunggal, dengan beberapa slide (javascript dan / atau css) bidang input?
birgire

Jawaban:

56

Ketika Anda mengunjungi halaman frontend, WordPress akan menanyakan database dan jika halaman Anda tidak ada di database, permintaan itu tidak diperlukan dan hanya membuang-buang sumber daya.

Untungnya, WordPress menawarkan cara untuk menangani permintaan frontend dengan cara kustom. Itu dilakukan berkat 'do_parse_request'filter.

Kembali falsepada kait itu, Anda akan dapat menghentikan WordPress dari memproses permintaan dan melakukannya dengan cara Anda sendiri.

Yang mengatakan, saya ingin berbagi cara untuk membangun plugin OOP sederhana yang dapat menangani halaman virtual dengan cara yang mudah digunakan (dan digunakan kembali).

Apa yang kita butuhkan

  • Kelas untuk objek halaman virtual
  • Kelas controller, yang akan melihat permintaan dan jika itu untuk halaman virtual, perlihatkan itu menggunakan templat yang tepat
  • Kelas untuk memuat template
  • File plugin utama untuk menambahkan kait yang akan membuat semuanya berfungsi

Antarmuka

Sebelum membangun kelas, mari kita menulis antarmuka untuk 3 objek yang tercantum di atas.

Pertama antarmuka halaman (file 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();
}

Sebagian besar metode hanya getter dan setter, tidak perlu penjelasan. Metode terakhir harus digunakan untuk mendapatkan WP_Postobjek dari halaman virtual.

Antarmuka pengontrol (file 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 ); 
}

dan antarmuka pemuat template (file 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();
}

Komentar phpDoc harus cukup jelas untuk antarmuka ini.

Rencana

Sekarang kita memiliki antarmuka, dan sebelum menulis kelas konkret, mari kita tinjau alur kerja kami:

  • Pertama kita instantiate sebuah Controllerkelas (implementasi ControllerInterface) dan menyuntikkan (mungkin dalam konstruktor) sebuah instance dari TemplateLoaderkelas (menerapkan TemplateLoaderInterface)
  • Pada inithook kami memanggil ControllerInterface::init()metode untuk mengatur controller dan mengaktifkan hook yang akan digunakan kode konsumen untuk menambahkan halaman virtual.
  • Pada 'do_parse_request' kami akan menelepon ControllerInterface::dispatch(), dan di sana kami akan memeriksa semua halaman virtual yang ditambahkan dan jika salah satu dari mereka memiliki URL yang sama dengan permintaan saat ini, tampilkan; setelah menetapkan semua variabel global inti ( $wp_query, $post). Kami juga akan menggunakan TemplateLoaderkelas untuk memuat template yang tepat.

Selama ini alur kerja kita akan memicu beberapa kait inti, seperti wp, template_redirect, template_include... untuk membuat plugin lebih fleksibel dan memastikan kompatibilitas dengan inti dan plugin lainnya, atau setidaknya dengan baik jumlah mereka.

Selain dari alur kerja sebelumnya, kita juga perlu:

  • Bersihkan kait dan variabel global setelah loop utama berjalan, sekali lagi untuk meningkatkan kompatibilitas dengan kode inti dan pihak ketiga
  • Tambahkan filter the_permalinkuntuk membuatnya mengembalikan URL halaman virtual yang tepat ketika dibutuhkan.

Kelas Beton

Sekarang kita bisa mengkodekan kelas konkret kita. Mari kita mulai dengan kelas halaman (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;
    }
}

Tidak lebih dari mengimplementasikan antarmuka.

Sekarang kelas controller (file 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();
    }
}

Pada dasarnya kelas membuat SplObjectStorageobjek tempat semua objek halaman yang ditambahkan disimpan.

Aktif 'do_parse_request', kelas pengontrol loop penyimpanan ini untuk menemukan kecocokan untuk URL saat ini di salah satu halaman yang ditambahkan.

Jika ditemukan, kelas melakukan persis apa yang kita rencanakan: memicu beberapa kait, mengatur variabel dan memuat templat melalui perluasan kelas TemplateLoaderInterface. Setelah itu baru saja exit().

Jadi mari kita tulis kelas terakhir:

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

Template yang disimpan di halaman virtual digabungkan dalam sebuah array dengan default page.phpdan index.php, sebelum memuat template 'template_redirect'dipecat, untuk menambah fleksibilitas dan meningkatkan kompatibilitas.

Setelah itu, templat yang ditemukan melewati filter kustom 'virtual_page_template'dan inti 'template_include': lagi untuk fleksibilitas dan kompatibilitas.

Akhirnya file template baru saja dimuat.

File plugin utama

Pada titik ini kita perlu menulis file dengan header plugin dan menggunakannya untuk menambahkan kait yang akan membuat alur kerja kita terjadi:

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

Dalam file asli kita mungkin akan menambahkan lebih banyak header, seperti plugin dan tautan penulis, deskripsi, lisensi, dll.

Pengaya Plugin

Ok, kita selesai dengan plugin kita. Semua kode dapat ditemukan di Intisari di sini .

Menambahkan Halaman

Plugin sudah siap dan berfungsi, tetapi kami belum menambahkan halaman apa pun.

Itu bisa dilakukan di dalam plugin itu sendiri, di dalam tema functions.php, di plugin lain, dll.

Tambahkan halaman hanya masalah:

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

} );

Dan seterusnya. Anda dapat menambahkan semua halaman yang Anda butuhkan, hanya ingat untuk menggunakan URL relatif untuk halaman tersebut.

Di dalam file template Anda dapat menggunakan semua tag template WordPress, dan Anda dapat menulis semua PHP dan HTML yang Anda butuhkan.

Objek posting global diisi dengan data yang berasal dari halaman virtual kami. Halaman virtual itu sendiri dapat diakses melalui $wp_query->virtual_pagevariabel.

Untuk mendapatkan URL untuk halaman virtual semudah melewati ke home_url()jalur yang sama yang digunakan untuk membuat halaman:

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

Perhatikan bahwa dalam loop utama pada template yang dimuat, the_permalink()akan mengembalikan permalink yang benar ke halaman virtual.

Catatan tentang gaya / skrip untuk halaman virtual

Mungkin ketika halaman virtual ditambahkan, itu juga diinginkan untuk memiliki gaya khusus / skrip enqueued dan kemudian hanya digunakan wp_head()dalam templat kustom.

Itu sangat mudah, karena halaman virtual mudah dikenali melihat $wp_query->virtual_pagevariabel dan halaman virtual dapat dibedakan satu dengan yang lain melihat URL mereka.

Contoh saja:

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

} );

Catatan untuk OP

Melewati data dari satu halaman ke halaman lain tidak terkait dengan halaman virtual ini, tetapi hanya tugas umum.

Namun, jika Anda memiliki formulir di halaman pertama, dan ingin meneruskan data dari sana ke halaman kedua, cukup gunakan URL halaman kedua di actionproperti form .

Misalnya dalam file templat halaman pertama Anda dapat:

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

dan kemudian di file templat halaman kedua:

<?php $testme = filter_input( INPUT_POST, 'testme', FILTER_SANITIZE_STRING ); ?>
<h1>Test-Me value form other page is: <?php echo $testme; ?></h1>
gmazzap
sumber
9
Jawaban komprehensif yang luar biasa, tidak hanya pada masalah itu sendiri, tetapi juga pada membuat gaya plug-in OOP dan banyak lagi. Anda mendapatkan upvote saya pasti, bayangkan lebih banyak, satu untuk setiap level yang dijawab oleh jawabannya.
Nicolai
2
Solusi yang sangat apik dan lurus ke depan. Pembaruan, tweeted.
kaiser
Kode di Controller sedikit salah ... checkRequest () mendapatkan info path dari home_url () yang mengembalikan localhost / wordpress. Setelah preg_replace dan add_query_arg, url ini menjadi / wordpress / virtual-page. Dan setelah trim di checkRequest, url ini adalah wordpress / virtual. Ini akan berfungsi jika wordpress akan dipasang di folder root domain. Bisakah Anda memberikan perbaikan untuk masalah itu karena saya tidak dapat menemukan fungsi yang sesuai yang akan mengembalikan url kanan. Terimakasih untuk semuanya! (Saya akan menerima jawaban setelah itu akan menjadi sempurna :)
user1257255
2
Selamat, jawaban yang bagus dan saya perlu melihat banyak pekerjaan ini sebagai solusi gratis.
bueltge
@ GM: Dalam kasus saya WordPress diinstal di ... / htdocs / wordpress / dan situs ini tersedia di localhost / wordpress . home_url () mengembalikan localhost / wordpress dan add_query_arg (array ()) mengembalikan / wordpress / virtual-page /. Ketika kita membandingkan $ path dan memangkas $ this-> halaman-> current () -> getUrl () di checkRequest () adalah masalah karena $ path adalah wordpress/virtual-pagedan url yang dipangkas halaman adalah virtual-page.
user1257255
0

Saya pernah menggunakan solusi yang dijelaskan di sini: http://scott.sherrillmix.com/blog/blogger/creating-a-better-fake-post-with-a-wordpress-plugin/

Sebenarnya, ketika saya menggunakannya, saya memperluas solusi dengan cara saya bisa mendaftarkan lebih dari satu halaman dalam satu waktu (sisa kode adalah +/- mirip dengan solusi yang saya tautkan dari paragraf di atas).

Solusinya mengharuskan Anda untuk memiliki permalink bagus diizinkan ...

<?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();
david.binda
sumber
Bagaimana dengan template khusus tempat saya dapat meletakkan formulir saya?
user1257255
contentdalam array ketika Anda mendaftarkan halaman palsu sedang ditampilkan di badan halaman - itu dapat berisi HTML serta teks sederhana atau bahkan kode pendek.
david.binda