Eigene Seiten mit Plugin

13

Ich entwickle ein Plugin, mit dem ich benutzerdefinierte Seiten aktivieren möchte. In meinem Fall würde eine benutzerdefinierte Seite ein Formular wie ein Kontaktformular enthalten (nicht wörtlich). Wenn der Benutzer dieses Formular ausfüllt und sendet, sollte es den nächsten Schritt geben, der weitere Informationen erfordert. Nehmen wir an, die erste Seite mit Formular befindet sich bei www.domain.tld/custom-page/und nach erfolgreicher Formularübermittlung sollte der Benutzer weitergeleitet werden www.domain.tld/custom-page/second. Vorlage mit HTML-Elementen und PHP-Code sollte ebenfalls benutzerdefiniert sein.

Ich denke, dass ein Teil des Problems mit benutzerdefinierten URL-Umschreibungen erreicht werden kann, aber die anderen Teile sind mir derzeit unbekannt. Ich weiß wirklich nicht, wo ich anfangen soll zu suchen und wie das Problem richtig benannt ist. Jede Hilfe wäre sehr dankbar.

user1257255
quelle
Möchten Sie, dass diese Seiten in WordPress oder "virtuell" gespeichert werden?
Welcher
Sie müssten die Rewrite-API verwenden. Das sollte nicht zu schwierig sein. Stellen Sie sicher, dass Sie die Daten auf der zweiten Seite veröffentlichen und alles in Ordnung ist.
setterGetter
@Welcher: Diese Seiten stimmen nicht mit den Angeboten von WordPress im Dashboard überein. Sie sollten nur Daten in der Datenbank speichern, aber das ist nicht das Problem. @ .setterGetter: Haben Sie ein Beispiel, wie Sie Daten von der ersten Seite an die zweite übergeben und wo (Aktion?) Sie eine PHP-Datei einfügen, die das Formular zeigt?
user1257255
Haben Sie in Betracht gezogen, ein einziges Seitenformular mit mehreren Folien (Javascript und / oder CSS) von Eingabefeldern zu verwenden?
Birgire

Antworten:

56

Wenn Sie eine Frontend-Seite besuchen, fragt WordPress die Datenbank ab. Wenn Ihre Seite nicht in der Datenbank vorhanden ist, wird diese Abfrage nicht benötigt und ist nur eine Verschwendung von Ressourcen.

Glücklicherweise bietet WordPress eine Möglichkeit, Frontend-Anfragen auf eine benutzerdefinierte Weise zu bearbeiten. Dies geschieht dank des 'do_parse_request'Filters.

Wenn Sie falseauf diesen Haken zurückkehren, können Sie WordPress daran hindern, Anforderungen zu verarbeiten, und dies auf Ihre eigene Weise tun.

Trotzdem möchte ich eine Möglichkeit vorstellen, ein einfaches OOP-Plugin zu erstellen, das mit virtuellen Seiten auf einfach zu verwendende (und wiederverwendende) Weise umgehen kann.

Was wir brauchen

  • Eine Klasse für virtuelle Seitenobjekte
  • Eine Controller-Klasse, die eine Anfrage betrachtet und, falls es sich um eine virtuelle Seite handelt, diese mit der richtigen Vorlage anzeigt
  • Eine Klasse zum Laden von Vorlagen
  • Haupt-Plugin-Dateien zum Hinzufügen der Hooks, mit denen alles funktioniert

Schnittstellen

Schreiben Sie vor dem Erstellen von Klassen die Schnittstellen für die drei oben aufgeführten Objekte.

Zuerst das Seiteninterface (Datei 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();
}

Die meisten Methoden sind nur Trickser und Trickser, ohne dass es einer Erklärung bedarf. Die letzte Methode sollte verwendet werden, um ein WP_PostObjekt von einer virtuellen Seite abzurufen.

Die Controller-Schnittstelle (Datei 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 ); 
}

und das Template Loader Interface (Datei 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-Kommentare sollten für diese Schnittstellen ziemlich klar sein.

Der Plan

Nachdem wir nun Schnittstellen haben, und bevor wir konkrete Klassen schreiben, wollen wir unseren Workflow überprüfen:

  • Zuerst instanziieren wir eine ControllerKlasse (implementierend ControllerInterface) und injizieren (wahrscheinlich in einem Konstruktor) eine Instanz der TemplateLoaderKlasse (implementierend TemplateLoaderInterface)
  • Am initHaken rufen wir die ControllerInterface::init()Methode zum Einrichten der Steuerung und den Haken zu feuern , dass Code Verbraucher virtuelle Seiten hinzufügen verwenden.
  • Bei 'do_parse_request' rufen ControllerInterface::dispatch()wir auf und überprüfen dort alle hinzugefügten virtuellen Seiten. Wenn eine davon dieselbe URL der aktuellen Anfrage hat, zeigen Sie sie an. Nachdem Sie alle globalen Kernvariablen ( $wp_query, $post) gesetzt haben. Wir werden auch TemplateLoaderKlasse verwenden, um die richtige Vorlage zu laden.

Während dieses Workflow werden wir einige Kernhaken auslösen, wie wp, template_redirect, template_include... machen das Plugin flexibler und die Kompatibilität mit Kern und anderen Plugins oder zumindest mit einer guten Anzahl von ihnen.

Abgesehen von den vorherigen Arbeitsabläufen müssen wir auch:

  • Bereinigen Sie Hooks und globale Variablen nach dem Ausführen der Hauptschleife, um die Kompatibilität mit Kerncode und Code von Drittanbietern zu verbessern
  • Fügen Sie einen Filter hinzu the_permalink, damit er bei Bedarf die richtige URL der virtuellen Seite zurückgibt.

Konkrete Klassen

Jetzt können wir unsere konkreten Klassen codieren. Beginnen wir mit der Seitenklasse (Datei 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;
    }
}

Nichts weiter als die Implementierung der Schnittstelle.

Nun die Controller-Klasse (Datei 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();
    }
}

Im Wesentlichen erstellt die Klasse ein SplObjectStorageObjekt, in dem alle hinzugefügten Seitenobjekte gespeichert werden.

Ein 'do_parse_request'Die Controller-Klasse durchläuft diesen Speicher, um auf einer der hinzugefügten Seiten eine Übereinstimmung für die aktuelle URL zu finden.

Wenn es gefunden wird, macht die Klasse genau das, was wir geplant haben: Triggern Sie einige Hooks, richten Sie Variablen ein und laden Sie die Vorlage über die Klassenerweiterung TemplateLoaderInterface. Danach einfach exit().

Schreiben wir also die letzte Klasse:

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

Auf der virtuellen Seite gespeicherte Vorlagen werden mit den Standardeinstellungen in einem Array zusammengeführt, page.phpund index.phpvor dem Laden der Vorlage 'template_redirect'wird die Flexibilität erhöht und die Kompatibilität verbessert.

Danach durchläuft die gefundene Vorlage den benutzerdefinierten Filter 'virtual_page_template'und den Kernfilter. 'template_include'Dies dient wiederum der Flexibilität und Kompatibilität.

Zuletzt wird die Vorlagendatei geladen.

Haupt-Plugin-Datei

An diesem Punkt müssen wir die Datei mit Plugin-Headern schreiben und sie verwenden, um die Hooks hinzuzufügen, die unseren Workflow ermöglichen:

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

In der realen Datei werden wir wahrscheinlich weitere Header wie Plugin- und Autorenlinks, Beschreibung, Lizenz usw. hinzufügen.

Plugin Gist

Ok, wir sind mit unserem Plugin fertig. Den gesamten Code finden Sie in einer Übersicht hier .

Seiten hinzufügen

Das Plugin ist fertig und funktioniert, aber wir haben keine Seiten hinzugefügt.

Dies kann innerhalb des Plugins selbst, innerhalb des Themes functions.php, in einem anderen Plugin usw. erfolgen.

Das Hinzufügen von Seiten ist nur eine Frage von:

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

} );

Und so weiter. Sie können alle benötigten Seiten hinzufügen. Denken Sie jedoch daran, relative URLs für die Seiten zu verwenden.

In der Vorlagendatei können Sie alle WordPress-Vorlagen-Tags verwenden und alle benötigten PHP- und HTML-Dateien schreiben.

Das globale Post-Objekt ist mit Daten gefüllt, die von unserer virtuellen Seite stammen. Auf die virtuelle Seite selbst kann über eine $wp_query->virtual_pageVariable zugegriffen werden .

Das Abrufen der URL für eine virtuelle Seite ist so einfach wie das Übergeben home_url()des Pfads, der zum Erstellen der Seite verwendet wurde:

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

Beachten Sie, dass in der Hauptschleife in der geladenen Vorlage der the_permalink()richtige Permalink zur virtuellen Seite zurückgegeben wird.

Hinweise zu Stilen / Skripten für virtuelle Seiten

Wahrscheinlich ist es beim Hinzufügen virtueller Seiten auch wünschenswert, benutzerdefinierte Stile / Skripte in die Warteschlange zu stellen und dann nur wp_head()in benutzerdefinierten Vorlagen zu verwenden.

Das ist sehr einfach, da virtuelle Seiten anhand von $wp_query->virtual_pageVariablen leicht erkannt werden und virtuelle Seiten anhand ihrer URLs voneinander unterschieden werden können.

Nur ein Beispiel:

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

} );

Hinweise zum OP

Das Übergeben von Daten von einer Seite zu einer anderen bezieht sich nicht auf diese virtuellen Seiten, sondern ist nur eine allgemeine Aufgabe.

Wenn Sie jedoch ein Formular auf der ersten Seite haben und Daten von dort an die zweite Seite übergeben möchten, verwenden Sie einfach die URL der zweiten Seite im Formular action .

ZB in der ersten Seitenvorlagendatei können Sie:

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

und dann in der zweiten Seitenvorlagendatei:

<?php $testme = filter_input( INPUT_POST, 'testme', FILTER_SANITIZE_STRING ); ?>
<h1>Test-Me value form other page is: <?php echo $testme; ?></h1>
gmazzap
quelle
9
Erstaunlich umfassende Antwort, nicht nur auf das Problem selbst, sondern auch auf die Erstellung eines Plug-Ins im OOP-Stil und mehr. Sie haben mit Sicherheit meine Zustimmung, stellen Sie sich mehr vor, eine für jedes Level, das die Antwort abdeckt.
Nicolai
2
Sehr schnelle und unkomplizierte Lösung. Updvoted, getwittert.
Kaiser
Der Code in Controller ist ein bisschen falsch ... checkRequest () ruft Pfadinformationen von home_url () ab, die localhost / wordpress zurückgeben. Nach preg_replace und add_query_arg wird diese URL zu / wordpress / virtual-page. Und nach dem Abschneiden in checkRequest lautet diese URL wordpress / virtual. Dies würde funktionieren, wenn WordPress im Stammordner der Domäne installiert würde. Können Sie bitte eine Lösung für dieses Problem bereitstellen, da ich keine geeignete Funktion finde, die die richtige URL zurückgibt? Danke für alles! (Ich akzeptiere die Antwort,
wenn
2
Herzlichen Glückwunsch, nette Antwort und ich muss diese Menge Arbeit als kostenlose Lösung sehen.
Bueltge
@GM: In meinem Fall ist WordPress in ... / htdocs / wordpress / installiert und die Site ist auf localhost / wordpress verfügbar . home_url () gibt localhost / wordpress und add_query_arg (array ()) gibt / wordpress / virtual-page / zurück. Beim Vergleich von $ path und abgeschnittenem $ this-> pages-> current () -> getUrl () in checkRequest () tritt ein Problem auf, da der $ path wordpress/virtual-pageund die abgeschnittene URL der Seite gleich sind virtual-page.
user1257255
0

Ich habe einmal eine hier beschriebene Lösung verwendet: http://scott.sherrillmix.com/blog/blogger/creating-a-better-fake-post-with-a-wordpress-plugin/

Tatsächlich erweitere ich die Lösung, als ich sie verwendete, so, dass ich mehr als eine Seite pro Seite registrieren kann (der Rest eines Codes ist +/- ähnlich der Lösung, die ich aus einem obigen Absatz verknüpfe).

Die Lösung erfordert, dass Sie nette Permalinks haben, ...

<?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
quelle
Was ist mit einer benutzerdefinierten Vorlage, in die ich mein Formular einfügen kann?
user1257255
contentWenn Sie die gefälschte Seite registrieren, wird sie im Array im Hauptteil der Seite angezeigt. Sie kann sowohl HTML als auch einfachen Text oder sogar einen Shortcode enthalten.
David.Binda