web-dev-qa-db-fra.com

Pages personnalisées avec plugin

Je développe un plugin où je voudrais activer les pages personnalisées. Dans mon cas, certaines pages personnalisées contiendraient un formulaire tel que le formulaire de contact (pas littéralement). Lorsque l'utilisateur remplira ce formulaire et l'enverra, l'étape suivante nécessitera davantage d'informations. Disons que la première page avec le formulaire se trouverait à www.domain.tld/custom-page/ et qu'une fois le formulaire soumis, l'utilisateur devrait être redirigé vers www.domain.tld/custom-page/second. Les modèles avec des éléments HTML et le code PHP doivent également être personnalisés.

Je pense qu’une partie du problème est possible avec des réécritures d’URL personnalisées, mais les autres parties m’ont actuellement inconnues. Je ne sais vraiment pas par où commencer, ni quelle est la dénomination correcte de ce problème. Toute aide sera grandement appréciée.

12
user1257255

Lorsque vous visitez une page frontale, WordPress interroge la base de données. Si votre page n’existe pas dans la base de données, cette requête n’est pas nécessaire et constitue simplement un gaspillage de ressources.

Heureusement, WordPress offre un moyen de traiter les requêtes frontales de manière personnalisée. Cela se fait grâce au filtre 'do_parse_request' .

En retournant false sur ce crochet, vous pourrez empêcher WordPress de traiter les demandes et le faire à votre façon.

Cela dit, je souhaite partager un moyen de créer un simple plug-in OOP pouvant gérer des pages virtuelles de manière simple à utiliser (et à réutiliser).

Ce dont nous avons besoin

  • Une classe pour les objets de page virtuels
  • Une classe de contrôleur, qui examinera une demande et, s’il s’agit d’une page virtuelle, la montrera à l’aide du modèle approprié.
  • Une classe pour le chargement de template
  • Fichiers du plugin principal pour ajouter les points d'ancrage qui feront que tout fonctionne

Des interfaces

Avant de construire des classes, écrivons les interfaces pour les 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 getter et des setters, pas besoin d'explication. La dernière méthode doit être utilisée pour obtenir un objet WP_Post à 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 de 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:

  • Premièrement, nous instancions une classe Controller (implémentant ControllerInterface) et injectons (probablement dans un constructeur) une instance de TemplateLoader classe (implémentant TemplateLoaderInterface)
  • Sur init hook, nous appelons la méthode ControllerInterface::init() pour configurer le contrôleur et activer le point d'ancrage que le code client utilisera pour ajouter des pages virtuelles.
  • Sur 'do_parse_request' nous appellerons ControllerInterface::dispatch(), nous y vérifierons toutes les pages virtuelles ajoutées et, si l'une d'entre elles possède la même URL de la requête en cours, l'affichera; après avoir défini toutes les variables globales principales ($wp_query, $post). Nous utiliserons également la classe TemplateLoader pour charger le bon modèle.

Au cours de ce flux de travail, nous allons déclencher des points d’accès centraux, tels que wp , template_redirect , template_include ... afin de rendre le plugin plus flexible et d’assurer la compatibilité avec le noyau et les autres plugins, ou du 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 afin d'améliorer la compatibilité avec le code principal et le code tiers.
  • Ajoutez un filtre sur the_permalink pour lui faire renvoyer l’URL de page virtuelle appropriée en cas de besoin.

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 de 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 objet SplObjectStorage où tous les objets de 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.

Si elle est trouvée, la classe fait exactement ce que nous avions prévu: déclencher des points d'ancrage, configurer des variables et charger le modèle via la classe qui étend TemplateLoaderInterface. Après cela, juste exit().

Alors écrivons le dernier cours:

<?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.php et index.php, avant le chargement du modèle 'template_redirect' est déclenché, pour ajouter de la flexibilité et améliorer la compatibilité.

Après cela, le modèle trouvé passe par le 'virtual_page_template' personnalisé et le noyau 'template_include' filters: à nouveau par flexibilité et par compatibilité.

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

Fichier du plugin principal

À ce stade, nous devons écrire le fichier avec les en-têtes de plug-in et l'utiliser pour ajouter les points d'ancrage qui rendront notre flux de travail effectif:

<?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 fichier réel, nous ajouterons probablement d'autres en-têtes, tels que 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 dans le plugin lui-même, dans le thème functions.php, dans un autre plugin, etc.

Ajouter des pages est juste 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, mais n'oubliez pas d'utiliser des URL relatives pour les pages.

Dans le fichier de modèle, vous pouvez utiliser toutes les balises de modèle WordPress et écrire tout le PHP et le HTML dont vous avez besoin.

L'objet global post est rempli de données provenant de notre page virtuelle. La page virtuelle elle-même est accessible via la variable $wp_query->virtual_page.

Pour obtenir l'URL d'une page virtuelle, il suffit de passer à home_url() le 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() renverra le permalien correct vers la page virtuelle.

Notes sur les styles/scripts pour les pages virtuelles

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

C'est très facile, car les pages virtuelles sont facilement reconnues en regardant la variable $wp_query->virtual_page 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 à l'OP

Le transfert de données d'une page à une autre n'est pas lié à ces pages virtuelles, il s'agit simplement d'une tâche générique.

Toutefois, si vous avez un formulaire dans la première page et souhaitez passer des données de cet emplacement à la deuxième page, utilisez simplement l'URL de la deuxième page dans la propriété formulaire action.

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 fichier de modèle de deuxième page:

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

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'étendais la solution de manière à pouvoir enregistrer plus d'une page à la fois (le reste du code est similaire à la solution à laquelle je fais un lien depuis le paragraphe ci-dessus).

La solution nécessite que vous ayez des permaliens Nice 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();
0
david.binda