플러그인이있는 사용자 정의 페이지


13

사용자 정의 페이지를 활성화하려는 플러그인을 개발 중입니다. 필자의 경우 일부 사용자 정의 페이지에는 연락처 양식과 같은 양식이 포함되어 있습니다. 사용자가이 양식을 작성하여 보내면 추가 정보가 필요한 다음 단계가 있어야합니다. 양식이있는 첫 번째 페이지는 www.domain.tld/custom-page/양식 제출이 완료된 이후 에 위치 한다고 사용자가 리디렉션되어야한다고 가정합니다 www.domain.tld/custom-page/second. HTML 요소 및 PHP 코드가있는 템플릿도 사용자 정의해야합니다.

맞춤 URL 다시 쓰기로 문제의 일부를 달성 할 수 있다고 생각하지만 다른 부분은 현재 알려지지 않았습니다. 나는 어디서부터 시작해야하는지, 그리고 그 문제에 대한 올바른 이름이 무엇인지 모른다. 어떤 도움이라도 정말로 감사하겠습니다.


이 페이지를 WordPress 또는 '가상'에 저장 하시겠습니까?
Welcher

다시 쓰기 API를 사용해야합니다. 너무 어렵지 않아야합니다. 데이터를 두 번째 페이지에 게시해야합니다.
setterGetter

@Welcher :이 페이지는 대시 보드의 WordPress 오퍼와 동일하지 않습니다. 데이터를 데이터베이스에 저장해야하지만 문제는 아닙니다. @ .setterGetter : 첫 페이지에서 두 번째 페이지로 데이터를 전달하는 방법과 양식을 보여주는 PHP 파일을 포함 할 위치 (동작?)에 대한 예가 있습니까?
user1257255

입력 필드의 여러 슬라이드 (자바 스크립트 및 CSS)와 함께 단일 페이지 양식을 사용하는 것을 고려 했습니까?
birgire

답변:


57

프런트 엔드 페이지를 방문하면 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 ); 
}

템플릿 로더 인터페이스 (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();
}

phpDoc 주석은 이러한 인터페이스에 대해 분명해야합니다.

계획

이제 인터페이스가 있고 구체적인 클래스를 작성하기 전에 워크 플로를 검토하겠습니다.

  • 먼저 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을 반환하도록 필터를 추가하십시오 .

구체적인 수업

이제 구체적인 클래스를 코딩 할 수 있습니다. 페이지 클래스 (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;
    }
}

인터페이스를 구현하는 것 외에는 아무것도 없습니다.

이제 컨트롤러 클래스 (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();
    }
}

기본적으로 클래스 SplObjectStorage는 추가 된 모든 페이지 개체가 저장 되는 개체를 만듭니다 .

'do_parse_request', 컨트롤러 클래스가 추가 된 페이지 중 하나에서 현재 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;
} );

실제 파일에는 플러그인 및 작성자 링크, 설명, 라이센스 등과 같은 헤더를 더 추가 할 것입니다.

플러그인 요지

자, 플러그인이 끝났습니다. 모든 코드는 여기 Gist 에서 찾을 수 있습니다 .

페이지 추가

플러그인이 준비되어 작동하지만 페이지를 추가하지 않았습니다.

플러그인 자체, 테마 내부 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을 작성할 수 있습니다.

글로벌 포스트 오브젝트는 가상 페이지에서 온 데이터로 채워집니다. 가상 페이지 자체는 $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;
        }
    }

} );

OP에 대한 참고 사항

페이지에서 다른 페이지로 데이터를 전달하는 것은 이러한 가상 페이지와 관련이 없지만 일반적인 작업입니다.

그러나 첫 페이지에 양식이 있고 거기에서 두 번째 페이지로 데이터를 전달하려는 경우 양식 action속성 에서 두 번째 페이지의 URL을 사용하십시오 .

예를 들어 첫 페이지 템플릿 파일에서 다음을 수행 할 수 있습니다.

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

9
문제 자체뿐만 아니라 OOP 스타일 플러그인 작성 등에 대한 포괄적 인 답변. 답을 다루는 모든 수준에 대해 하나씩, 더 많은 것을 상상해보십시오.
Nicolai

2
매우 매끄럽고 직선적 인 솔루션. Updvoted, 트윗
카이저

Controller의 코드가 약간 잘못되었습니다 ... checkRequest ()가 localhost / wordpress를 반환하는 home_url ()에서 경로 정보를 가져옵니다. preg_replace 및 add_query_arg 후에이 URL은 / wordpress / virtual-page가됩니다. 그리고 checkRequest에서 트림 후이 URL은 워드 프레스 / 가상입니다. 이것은 워드 프레스가 도메인의 루트 폴더에 설치 될 경우 작동합니다. 올바른 URL을 반환하는 적절한 기능을 찾을 수 없으므로 해당 문제에 대한 수정 사항을 제공 할 수 있습니까? 모든 것에 감사드립니다! (완벽 해지면 답변을받습니다.)
user1257255

2
축하합니다. 좋은 답변이며이 많은 작업을 무료 솔루션으로 봐야합니다.
bueltge

@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이고 page의 잘린 URL은 이므로 문제 가 virtual-page됩니다.
user1257255

0

한 번 여기에 설명 된 솔루션을 사용했습니다 : 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

content등록시 가짜 페이지가 페이지 본문에 표시되고 있습니다. HTML뿐만 아니라 간단한 텍스트 나 짧은 코드도 포함 할 수 있습니다.
david.binda
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.