Pages personnalisées avec plugin

13

Je développe un plugin où j'aimerais activer des pages personnalisées. Dans mon cas, une page personnalisée contiendrait un formulaire comme un formulaire de contact (pas littéralement). Lorsque l'utilisateur remplira ce formulaire et l'enverra, il devrait y avoir l'étape suivante qui nécessitera plus d'informations. Supposons que la première page avec le formulaire se trouve au niveau www.domain.tld/custom-page/et après la soumission réussie du formulaire, l'utilisateur doit être redirigé vers www.domain.tld/custom-page/second. Le modèle avec des éléments HTML et du code PHP doit également être personnalisé.

Je pense qu'une partie du problème est possible à réaliser avec des réécritures d'URL personnalisées, mais les autres parties me sont actuellement inconnues. Je ne sais vraiment pas par où commencer et quel est le nom correct pour ce problème. Toute aide sera grandement appréciée.

user1257255
la source
Voulez-vous que ces pages soient stockées dans WordPress ou «virtuelles»?
Welcher
Vous devez utiliser l'api de réécriture. Cela ne devrait pas être trop difficile. Assurez-vous de publier les données sur la deuxième page et tout ira bien.
setterGetter
@Welcher: Ces pages ne sont pas les mêmes que celles proposées par WordPress dans le tableau de bord. Ils devraient simplement enregistrer les données dans la base de données, mais ce n'est pas le problème. @ .setterGetter: Avez-vous un exemple sur la façon de passer des données de la première page à la seconde et où (action?) inclure le fichier PHP qui montre le formulaire?
user1257255
Avez-vous envisagé d'utiliser un formulaire d'une seule page, avec plusieurs diapositives (javascript et / ou css) de champs de saisie?
birgire

Réponses:

56

Lorsque vous visitez une page frontend, WordPress interroge la base de données et si votre page n'existe pas dans la base de données, cette requête n'est pas nécessaire et n'est qu'un gaspillage de ressources.

Heureusement, WordPress offre un moyen de gérer les demandes frontales de manière personnalisée. Cela se fait grâce au 'do_parse_request'filtre.

En revenant falsesur ce crochet, vous pourrez empêcher WordPress de traiter les demandes et le faire à votre manière.

Cela dit, je veux partager un moyen de créer un plugin OOP simple qui peut gérer des pages virtuelles de manière facile à utiliser (et à réutiliser).

Ce dont nous avons besoin

  • Une classe pour les objets de page virtuelle
  • Une classe de contrôleur, qui examinera une demande et si c'est pour une page virtuelle, montrez-la en utilisant le modèle approprié
  • Une classe pour le chargement de modèles
  • Fichiers plugin principaux pour ajouter les crochets qui feront que tout fonctionne

Interfaces

Avant de construire des classes, écrivons les interfaces des 3 objets listés ci-dessus.

D'abord l'interface de la page (fichier 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 plupart des méthodes ne sont que des getters et setters, pas besoin d'explication. La dernière méthode doit être utilisée pour obtenir un WP_Postobjet à partir d'une page virtuelle.

L'interface du contrôleur (fichier 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 ); 
}

et l'interface du chargeur de modèles (fichier 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();
}

Les commentaires phpDoc devraient être assez clairs pour ces interfaces.

Le plan

Maintenant que nous avons des interfaces, et avant d'écrire des classes concrètes, passons en revue notre flux de travail:

  • D'abord, nous instancions une Controllerclasse (implémentant ControllerInterface) et injectons (probablement dans un constructeur) une instance de TemplateLoaderclasse (implémentant TemplateLoaderInterface)
  • En mode initraccroché, nous appelons la ControllerInterface::init()méthode pour configurer le contrôleur et pour déclencher le crochet que le code consommateur utilisera pour ajouter des pages virtuelles.
  • Sur 'do_parse_request' nous appellerons ControllerInterface::dispatch(), et là nous vérifierons toutes les pages virtuelles ajoutées et si l'une d'elles a la même URL de la requête en cours, l'afficher; après avoir défini toutes les variables globales de base ( $wp_query, $post). Nous utiliserons également la TemplateLoaderclasse pour charger le bon modèle.

Au cours de ce flux de travail , nous allons déclencher des crochets de base, comme wp, template_redirect, template_include... pour faire le plugin plus souple et d' assurer la compatibilité avec le noyau et d' autres plug - ins, ou tout au moins avec un bon nombre d'entre eux.

Outre le flux de travail précédent, nous devrons également:

  • Nettoyez les crochets et les variables globales après l'exécution de la boucle principale, encore une fois pour améliorer la compatibilité avec le code principal et le code tiers
  • Ajoutez un filtre the_permalinkpour lui faire renvoyer la bonne URL de page virtuelle lorsque cela est nécessaire.

Classes de béton

Nous pouvons maintenant coder nos classes concrètes. Commençons par la classe de page (fichier 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;
    }
}

Rien de plus que la mise en œuvre de l'interface.

Maintenant, la classe du contrôleur (fichier 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();
    }
}

Essentiellement, la classe crée un SplObjectStorageobjet dans lequel tous les objets pages ajoutés sont stockés.

Sur 'do_parse_request', la classe de contrôleur boucle ce stockage pour trouver une correspondance pour l'URL actuelle dans l'une des pages ajoutées.

S'il est trouvé, la classe fait exactement ce que nous avions prévu: déclencher des hooks, configurer des variables et charger le modèle via l'extension de classe TemplateLoaderInterface. Après ça, juste exit().

Écrivons donc la dernière 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;
        }
    }
}

Les modèles stockés dans la page virtuelle sont fusionnés dans un tableau avec les valeurs par défaut page.phpet index.php, avant le chargement du modèle 'template_redirect', pour ajouter de la flexibilité et améliorer la compatibilité.

Après cela, le modèle trouvé passe par les filtres personnalisés 'virtual_page_template'et les 'template_include'filtres principaux : encore une fois pour plus de flexibilité et de compatibilité.

Enfin, le fichier modèle vient d'être chargé.

Fichier plugin principal

À ce stade, nous devons écrire le fichier avec des en-têtes de plug-in et l'utiliser pour ajouter les crochets qui permettront à notre flux de travail de se produire:

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

Dans le vrai fichier, nous ajouterons probablement plus d'en-têtes, comme des liens de plugin et d'auteur, une description, une licence, etc.

Plugin Gist

Ok, nous avons fini avec notre plugin. Tout le code peut être trouvé dans un Gist ici .

Ajout de pages

Le plugin est prêt et fonctionne, mais nous n'avons ajouté aucune page.

Cela peut être fait à l'intérieur du plugin lui-même, à l'intérieur du thème functions.php, dans un autre plugin, etc.

Ajouter des pages n'est qu'une question de:

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

} );

Etc. Vous pouvez ajouter toutes les pages dont vous avez besoin, n'oubliez pas d'utiliser des URL relatives pour les pages.

À l'intérieur du fichier de modèle, vous pouvez utiliser toutes les balises de modèle WordPress et vous pouvez écrire tout le PHP et HTML dont vous avez besoin.

L'objet de publication global est rempli de données provenant de notre page virtuelle. La page virtuelle elle-même est accessible via une $wp_query->virtual_pagevariable.

Pour obtenir l'URL d'une page virtuelle, il suffit de passer au home_url()même chemin que celui utilisé pour créer la page:

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

Notez que dans la boucle principale du modèle chargé, the_permalink()retournera le permalien correct à la page virtuelle.

Notes sur les styles / scripts pour les pages virtuelles

Probablement lorsque des pages virtuelles sont ajoutées, il est également souhaitable d'avoir des styles / scripts personnalisés mis en file d'attente, puis de les utiliser uniquement wp_head()dans des modèles personnalisés.

C'est très facile, car les pages virtuelles sont facilement reconnaissables en regardant les $wp_query->virtual_pagevariables et les pages virtuelles peuvent être distinguées les unes des autres en regardant leurs URL.

Juste un exemple:

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

} );

Notes à OP

La transmission de données d'une page à une autre n'est pas liée à ces pages virtuelles, mais est juste une tâche générique.

Cependant, si vous avez un formulaire dans la première page et que vous souhaitez transmettre des données de celui-ci à la deuxième page, utilisez simplement l'URL de la deuxième page dans la actionpropriété de formulaire .

Par exemple, dans le fichier de modèle de première page, vous pouvez:

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

puis dans le deuxième fichier de modèle de page:

<?php $testme = filter_input( INPUT_POST, 'testme', FILTER_SANITIZE_STRING ); ?>
<h1>Test-Me value form other page is: <?php echo $testme; ?></h1>
gmazzap
la source
9
Une réponse complète incroyable, non seulement sur le problème lui-même, mais aussi sur la création d'un plug-in de style OOP et plus encore. Vous avez obtenu mon vote positif, imaginez plus, un pour chaque niveau couvert par la réponse.
Nicolai
2
Solution très simple et simple. Updvoted, tweeté.
kaiser
Le code dans Controller est un peu faux ... checkRequest () obtient les informations de chemin de home_url () qui retourne localhost / wordpress. Après preg_replace et add_query_arg, cette URL devient / wordpress / virtual-page. Et après le trim de checkRequest, cette URL est wordpress / virtual. Cela fonctionnerait si le wordpress était installé dans le dossier racine du domaine. Pouvez-vous s'il vous plaît fournir une solution à ce problème, car je ne trouve pas la fonction appropriée qui retournerait l'URL correcte. Merci pour tout! (J'accepterai la réponse une fois qu'elle sera parfaite :)
user1257255
2
Félicitations, belle réponse et j'ai besoin de voir ce travail comme une solution gratuite.
bueltge
@GM: Dans mon cas, WordPress est installé dans ... / htdocs / wordpress / et le site est disponible sur localhost / wordpress . home_url () renvoie localhost / wordpress et add_query_arg (array ()) renvoie / wordpress / virtual-page /. Lorsque nous comparons $ path et trimmed $ this-> pages-> current () -> getUrl () dans checkRequest () est un problème parce que $ path est wordpress/virtual-pageet l'url tronquée de la page l'est virtual-page.
user1257255
0

J'ai utilisé une fois une solution décrite ici: http://scott.sherrillmix.com/blog/blogger/creating-a-better-fake-post-with-a-wordpress-plugin/

En fait, lorsque je l'utilisais, j'étend la solution de manière à pouvoir enregistrer plus d'une page à la fois (le reste d'un code est +/- similaire à la solution que je relie à partir d'un paragraphe ci-dessus).

La solution nécessite que vous ayez de bons permaliens autorisés ...

<?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
la source
Qu'en est-il du modèle personnalisé où je peux placer mon formulaire?
user1257255
contentdans le tableau lorsque vous vous inscrivez, la fausse page est affichée dans le corps de la page - elle peut contenir du HTML ainsi que du texte simple ou même un shortcode.
david.binda