Plugin generated virtual pages

I am currently devloping my first WP plugin. This plugin should generate dynamic pages on the fly if a fixed non-existing URL is requested.

This means I want my installation to accept addresses like http://www.mydomain.de/wordpress/mydatatool?page=format&format=mixed. If such an URL is called a PHP script should be executed that generates a page showing an AJAX based JS user interface that allows analysis of database information. That page may not contain static content nor should it be added (automatically or manually) to the WP database. It should be created on demand as a volatile page.

Read More

I suppose I know how I can catch the URL request (action parse_request). So the main problem to me is, how to generate the volatile pages in a way that they look exactly as normal WP pages including usage of theme, menus, header, footer, etc.

add_action('parse_request', array($this, 'custom_url_handler'));

public function custom_url_handler()
{
  $url = filter_input(INPUT_SERVER, 'REQUEST_URI', FILTER_SANITIZE_URL);
  if ($url==='/wordpress/mydatatool')
  {
     echo 'test'; // What to do to embed this into WP theme/menus/etc?
     exit();
  }
}

AFAIK something like this is not directly supported by WP currently.

I did an extensive research and found a lot of code. Most of the code didn’t work with current WP. Also some of the (simple) code snippets were able to deliver volatile pages but they actually did not look like a WP page missing parts of the menus/header/footer.

Finally I found a gist link in a forum thread that provided a working code (see below).

Since I am a beginner and the code isn’t officially maintained I have concerns using it.

Please give me some advice:
– Should I use the code below? Can I consider it safe and reliable? Is there a good chance it will continue working after WP updates?
– Is there an alternative that is maintained in a reliable way?
– Is there a better way to solve my problem?

UPDATE: This is the code that works for me:

<?php
/*
 * Virtual Themed Page class
 *
 * This class implements virtual pages for a plugin.
 *
 * It is designed to be included then called for each part of the plugin
 * that wants virtual pages.
 *
 * It supports multiple virtual pages and content generation functions.
 * The content functions are only called if a page matches.
 *
 * The class uses the theme templates and as far as I know is unique in that.
 * It also uses child theme templates ahead of main theme templates.
 *
 * Example code follows class.
 *
 * August 2013 Brian Coogan
 *
 */

// There are several virtual page classes, we want to avoid a clash!
//
class Virtual_Themed_Pages_BC
{
    public $title = '';
    public $body = '';
    private $vpages = array();  // the main array of virtual pages
    private $mypath = '';
    public $blankcomments = "blank-comments.php";


    function __construct($plugin_path = null, $blankcomments = null)
    {
    if (empty($plugin_path))
        $plugin_path = dirname(__FILE__);
    $this->mypath = $plugin_path;

    if (! empty($blankcomments))
        $this->blankcomments = $blankcomments;

    // Virtual pages are checked in the 'parse_request' filter.
    // This action starts everything off if we are a virtual page
    add_action('parse_request', array(&$this, 'vtp_parse_request'));
    }

    function add($virtual_regexp, $contentfunction)
    {
    $this->vpages[$virtual_regexp] = $contentfunction;
    }


    // Check page requests for Virtual pages
    // If we have one, call the appropriate content generation function
    //
    function vtp_parse_request(&$wp)
    {
    //global $wp;
    if (empty($wp->query_vars['pagename']))
        return; // page isn't permalink

    //$p = $wp->query_vars['pagename'];
    $p = $_SERVER['REQUEST_URI'];

    $matched = 0;
    foreach ($this->vpages as $regexp => $func)
    {
        if (preg_match($regexp, $p))
        {
        $matched = 1;
        break;
        }
    }
    // Do nothing if not matched
    if (! $matched)
        return;

    // setup hooks and filters to generate virtual movie page
    add_action('template_redirect', array(&$this, 'template_redir'));
    add_filter('the_posts', array(&$this, 'vtp_createdummypost'));

    // we also force comments removal; a comments box at the footer of
    // a page is rather meaningless.
    // This requires the blank_comments.php file be provided
    add_filter('comments_template', array(&$this, 'disable_comments'), 11);

    // Call user content generation function
    // Called last so it can remove any filters it doesn't like
    // It should set:
    //    $this->body   -- body of the virtual page
    //    $this->title  -- title of the virtual page
    //    $this->template  -- optional theme-provided template
    //          eg: page
    //    $this->subtemplate -- optional subtemplate (eg movie)
    // Doco is unclear whether call by reference works for call_user_func()
    // so using call_user_func_array() instead, where it's mentioned.
    // See end of file for example code.
    $this->template = $this->subtemplate = null;
    $this->title = null;
    unset($this->body);
    call_user_func_array($func, array(&$this, $p));

    if (! isset($this->body)) //assert
        wp_die("Virtual Themed Pages: must save ->body [VTP07]");

    return($wp);
    }


    // Setup a dummy post/page 
    // From the WP view, a post == a page
    //
    function vtp_createdummypost($posts)
    {

    // have to create a dummy post as otherwise many templates
    // don't call the_content filter
    global $wp, $wp_query;

    //create a fake post intance
    $p = new stdClass;
    // fill $p with everything a page in the database would have
    $p->ID = -1;
    $p->post_author = 1;
    $p->post_date = current_time('mysql');
    $p->post_date_gmt =  current_time('mysql', $gmt = 1);
    $p->post_content = $this->body;
    $p->post_title = $this->title;
    $p->post_excerpt = '';
    $p->post_status = 'publish';
    $p->ping_status = 'closed';
    $p->post_password = '';
    $p->post_name = 'movie_details'; // slug
    $p->to_ping = '';
    $p->pinged = '';
    $p->modified = $p->post_date;
    $p->modified_gmt = $p->post_date_gmt;
    $p->post_content_filtered = '';
    $p->post_parent = 0;
    $p->guid = get_home_url('/' . $p->post_name); // use url instead?
    $p->menu_order = 0;
    $p->post_type = 'page';
    $p->post_mime_type = '';
    $p->comment_status = 'closed';
    $p->comment_count = 0;
    $p->filter = 'raw';
    $p->ancestors = array(); // 3.6

    // reset wp_query properties to simulate a found 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;
    unset($wp_query->query['error']);
    $wp->query = array();
    $wp_query->query_vars['error'] = '';
    $wp_query->is_404 = FALSE;

    $wp_query->current_post = $p->ID;
    $wp_query->found_posts = 1;
    $wp_query->post_count = 1;
    $wp_query->comment_count = 0;
    // -1 for current_comment displays comment if not logged in!
    $wp_query->current_comment = null;
    $wp_query->is_singular = 1;

    $wp_query->post = $p;
    $wp_query->posts = array($p);
    $wp_query->queried_object = $p;
    $wp_query->queried_object_id = $p->ID;
    $wp_query->current_post = $p->ID;
    $wp_query->post_count = 1;

    return array($p);
    }


    // Virtual Movie page - tell wordpress we are using the given
    // template if it exists; otherwise we fall back to page.php.
    //
    // This func gets called before any output to browser
    // and exits at completion.
    //
    function template_redir()
    {
    //    $this->body   -- body of the virtual page
    //    $this->title  -- title of the virtual page
    //    $this->template  -- optional theme-provided template eg: 'page'
    //    $this->subtemplate -- optional subtemplate (eg movie)
    //

    if (! empty($this->template) && ! empty($this->subtemplate))
    {
        // looks for in child first, then master:
        //    template-subtemplate.php, template.php
        get_template_part($this->template, $this->subtemplate);
    }
    elseif (! empty($this->template))
    {
        // looks for in child, then master:
        //    template.php
        get_template_part($this->template);
    }
    elseif (! empty($this->subtemplate))
    {
        // looks for in child, then master:
        //    template.php
        get_template_part($this->subtemplate);
    }
    else
    {
        get_template_part('page');
    }

    // It would be possible to add a filter for the 'the_content' filter
    // to detect that the body had been correctly output, and then to
    // die if not -- this would help a lot with error diagnosis.

        exit;
    }


    // Some templates always include comments regardless, sigh.
    // This replaces the path of the original comments template with a
    // empty template file which returns nothing, thus eliminating
    // comments reliably.
    function disable_comments($file)
    {
    if (file_exists($this->blankcomments))
       return($this->mypath.'/'.$blankcomments);
    return($file);
    }


} // class


// Example code - you'd use something very like this in a plugin
//
if (0)
{
    // require 'BC_Virtual_Themed_pages.php';
    // this code segment requires the WordPress environment

    $vp =  new Virtual_Themed_Pages_BC();
    $vp->add('#/mypattern/unique#i', 'mytest_contentfunc');

    // Example of content generating function
    // Must set $this->body even if empty string
    function mytest_contentfunc($v, $url)
    {
    // extract an id from the URL
    $id = 'none';
    if (preg_match('#unique/(d+)#', $url, $m))
        $id = $m[1];
    // could wp_die() if id not extracted successfully...

    $v->title = "My Virtual Page Title";
    $v->body = "Some body content for my virtual page test - id $idn";
    $v->template = 'page'; // optional
    $v->subtemplate = 'billing'; // optional
    }
}

// end

Related posts

Leave a Reply