Pagine personalizzate con plugin


13

Sto sviluppando alcuni plugin in cui vorrei abilitare le pagine personalizzate. Nel mio caso una pagina personalizzata conterrebbe un modulo come modulo di contatto (non letteralmente). Quando l'utente compilerà questo modulo e lo invierà, ci dovrebbe essere il passaggio successivo che richiederà ulteriori informazioni. Diciamo che la prima pagina con il modulo si trova in www.domain.tld/custom-page/e dopo l'invio del modulo, l'utente dovrebbe essere reindirizzato a www.domain.tld/custom-page/second. Anche il modello con elementi HTML e codice PHP deve essere personalizzato.

Penso che una parte del problema sia possibile da ottenere con riscritture URL personalizzate, ma le altre parti sono attualmente sconosciute per me. Non so davvero dove dovrei iniziare a cercare e qual è la denominazione corretta per quel problema. Qualsiasi aiuto sarebbe molto apprezzato.


Vuoi che queste pagine siano archiviate in WordPress o "virtuali"?
Welcher,

Dovresti usare l'apri di riscrittura. Questo non dovrebbe essere troppo difficile. Assicurati di pubblicare i dati nella seconda pagina e dovresti stare bene.
setterGetter

@Welcher: queste pagine non sono le stesse delle offerte di WordPress nella dashboard. Dovrebbero semplicemente salvare i dati nel database, ma non è questo il problema. @ .setterGetter: hai qualche esempio su come passare i dati dalla prima alla seconda pagina e dove (azione?) includere il file PHP che mostra il modulo?
user1257255

Hai preso in considerazione l'utilizzo di un singolo modulo di pagina, con più diapositive (javascript e / o css) di campi di input?
birgire,

Risposte:


56

Quando visiti una pagina di frontend, WordPress interrogherà il database e se la tua pagina non esiste nel database, quella query non è necessaria ed è solo uno spreco di risorse.

Fortunatamente, WordPress offre un modo per gestire le richieste di frontend in modo personalizzato. Questo è fatto grazie al 'do_parse_request'filtro.

Tornando falsea quel hook, sarai in grado di fermare WordPress dall'elaborazione delle richieste e farlo nel tuo modo personalizzato.

Detto questo, voglio condividere un modo per creare un semplice plug-in OOP in grado di gestire pagine virtuali in modo facile da usare (e riutilizzare).

Ciò che ci serve

  • Una classe per oggetti pagina virtuali
  • Una classe controller, che esaminerà una richiesta e, se si tratta di una pagina virtuale, la mostrerà utilizzando il modello appropriato
  • Una classe per il caricamento dei modelli
  • File plugin principali per aggiungere gli hook che faranno funzionare tutto

interfacce

Prima di creare classi, scriviamo le interfacce per i 3 oggetti sopra elencati.

Innanzitutto l'interfaccia della pagina (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();
}

La maggior parte dei metodi sono solo getter e setter, senza bisogno di spiegazioni. L'ultimo metodo deve essere utilizzato per ottenere un WP_Postoggetto da una pagina virtuale.

L'interfaccia del controller (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 ); 
}

e l'interfaccia del caricatore di 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();
}

I commenti phpDoc dovrebbero essere abbastanza chiari per queste interfacce.

Il programma

Ora che abbiamo interfacce e prima di scrivere lezioni concrete, rivediamo il nostro flusso di lavoro:

  • Per prima cosa istanziamo una Controllerclasse (implementando ControllerInterface) e iniettiamo (probabilmente in un costruttore) un'istanza di TemplateLoaderclasse (implementando TemplateLoaderInterface)
  • Sul initgancio chiamiamo il ControllerInterface::init()metodo per impostare il controller e per sparare il gancio che codice del consumo utilizzerà per aggiungere pagine virtuali.
  • Su 'do_parse_request' chiameremo ControllerInterface::dispatch()e lì controlleremo tutte le pagine virtuali aggiunte e se una di esse ha lo stesso URL della richiesta corrente, la visualizzeremo; dopo aver impostato tutte le variabili globali principali ( $wp_query, $post). Useremo anche la TemplateLoaderclasse per caricare il modello giusto.

Durante questo flusso di lavoro, attiveremo alcuni hook core, come wp, ... template_redirect, template_includeper rendere il plug-in più flessibile e garantire la compatibilità con core e altri plug-in, o almeno con un buon numero di essi.

Oltre al flusso di lavoro precedente, dovremo anche:

  • Ripulisci gli hook e le variabili globali dopo l'esecuzione del ciclo principale, per migliorare la compatibilità con il core e il codice di terze parti
  • Aggiungi un filtro the_permalinkper farlo restituire l'URL della pagina virtuale giusto quando necessario.

Classi concrete

Ora possiamo codificare le nostre classi concrete. Cominciamo con la classe di pagine (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;
    }
}

Nient'altro che implementare l'interfaccia.

Ora la classe 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();
    }
}

Essenzialmente la classe crea un SplObjectStorageoggetto in cui sono memorizzati tutti gli oggetti delle pagine aggiunte.

Attivato 'do_parse_request', la classe controller esegue il loop di questo archivio per trovare una corrispondenza per l'URL corrente in una delle pagine aggiunte.

Se viene trovato, la classe fa esattamente ciò che abbiamo pianificato: innescare alcuni hook, impostare variabili e caricare il modello tramite l'estensione della classe TemplateLoaderInterface. Dopo quello, giusto exit().

Quindi scriviamo l'ultima classe:

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

I modelli archiviati nella pagina virtuale vengono uniti in un array con impostazioni predefinite page.phpe index.php, prima che il caricamento del modello 'template_redirect'venga attivato, per aggiungere flessibilità e migliorare la compatibilità.

Successivamente, il modello trovato passa attraverso i filtri personalizzati 'virtual_page_template'e core 'template_include': di nuovo per flessibilità e compatibilità.

Finalmente il file modello è appena caricato.

File plugin principale

A questo punto dobbiamo scrivere il file con le intestazioni dei plugin e usarlo per aggiungere gli hook che renderanno possibile il nostro flusso di lavoro:

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

Nel file reale probabilmente aggiungeremo più intestazioni, come collegamenti a plugin e autore, descrizione, licenza, ecc.

Plugin Gist

Ok, abbiamo finito con il nostro plugin. Tutto il codice può essere trovato in un Gist qui .

Aggiunta di pagine

Il plug-in è pronto e funzionante, ma non abbiamo aggiunto alcuna pagina.

Questo può essere fatto all'interno del plugin stesso, all'interno del tema functions.php, in un altro plugin, ecc.

Aggiungere pagine è solo una questione di:

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

} );

E così via. Puoi aggiungere tutte le pagine di cui hai bisogno, ricorda di usare gli URL relativi per le pagine.

All'interno del file modello puoi utilizzare tutti i tag modello di WordPress e puoi scrivere tutto il PHP e l'HTML di cui hai bisogno.

L'oggetto post globale è pieno di dati provenienti dalla nostra pagina virtuale. È possibile accedere alla stessa pagina virtuale tramite $wp_query->virtual_pagevariabile.

Ottenere l'URL per una pagina virtuale è facile come passare allo home_url()stesso percorso utilizzato per creare la pagina:

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

Si noti che nel ciclo principale nel modello caricato, the_permalink()restituirà il permalink corretto alla pagina virtuale.

Note su stili / script per pagine virtuali

Probabilmente quando vengono aggiunte pagine virtuali, è anche auspicabile avere stili / script personalizzati accodati e quindi utilizzarli solo wp_head()in modelli personalizzati.

È molto facile, perché le pagine virtuali sono facilmente riconoscibili guardando le $wp_query->virtual_pagevariabili e le pagine virtuali possono essere distinte l'una dall'altra guardando i loro URL.

Solo un esempio:

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

} );

Note all'OP

Il passaggio di dati da una pagina a un'altra non è correlato a queste pagine virtuali, ma è solo un'attività generica.

Tuttavia, se si dispone di un modulo nella prima pagina e si desidera passare i dati da lì alla seconda pagina, utilizzare semplicemente l'URL della seconda pagina nella actionproprietà del modulo .

Ad esempio nel file modello della prima pagina è possibile:

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

e quindi nel file modello della seconda pagina:

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

9
Incredibile risposta completa, non solo sul problema stesso, ma anche sulla creazione di un plug-in stile OOP e altro ancora. Di sicuro hai ottenuto il mio voto, immagina di più, uno per ogni livello coperto dalla risposta.
Nicolai,

2
Soluzione molto liscia e diretta. Aggiornato, twittato.
Kaiser

Il codice in Controller è un po 'sbagliato ... checkRequest () sta ottenendo informazioni sul percorso da home_url () che restituisce localhost / wordpress. Dopo preg_replace e add_query_arg, questo URL diventa / wordpress / virtual-page. E dopo il taglio in checkRequest questo url è wordpress / virtuale. Ciò funzionerebbe se il wordpress fosse installato nella cartella principale del dominio. Potete per favore fornire una soluzione a questo problema perché non riesco a trovare la funzione appropriata che restituisca l'URL corretto. Grazie di tutto! (Accetterò la risposta dopo che diventerà perfetta :)
user1257255

2
Complimenti, bella risposta e ho bisogno di vedere molto lavoro come soluzione gratuita.
bueltge,

@GM: Nel mio caso WordPress è installato in ... / htdocs / wordpress / e il sito è disponibile su localhost / wordpress . home_url () restituisce localhost / wordpress e add_query_arg (array ()) restituisce / wordpress / virtual-page /. Quando stiamo confrontando $ path e abbiamo ritagliato $ this-> pages-> current () -> getUrl () in checkRequest () è un problema perché il $ path è wordpress/virtual-pagee l'URL ritagliato della pagina è virtual-page.
user1257255,

0

Una volta ho usato una soluzione descritta qui: http://scott.sherrillmix.com/blog/blogger/creating-a-better-fake-post-with-a-wordpress-plugin/

In realtà, quando la stavo usando, estendo la soluzione in modo da poter registrare più di una pagina alla volta (il resto di un codice è +/- simile alla soluzione che sto collegando da un paragrafo sopra).

La soluzione richiede di avere permessi simpatici permessi anche se ...

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

Che dire del modello personalizzato in cui posso posizionare il mio modulo?
user1257255

contentnell'array durante la registrazione, la pagina falsa viene visualizzata nel corpo della pagina: può contenere un codice HTML, un testo semplice o persino uno shortcode.
david.binda,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.