Wordpress plugin generating virtual pages and using theme template Wordpress plugin generating virtual pages and using theme template wordpress wordpress

Wordpress plugin generating virtual pages and using theme template


(Update at foot of article including link to improved working code gist)

I wanted to post an answer to this as it seems that NONE of the queries about WordPress virtual pages on here had answers! And there was a lot of blood involved in getting the answer to this one, testing it and ensuring it worked well. Hopefully this will save a few others the pain I went through ...

It turns out that in 2013 with WordPress 3.5.2+ (now 3.6 as of a week ago) the solution from Xavi Esteve mentioned above no longer works, as WordPress has evolved, dangit.

Using the template_redirect method above by itself, the problem is that as far as WordPress is concerned, there is no page/post content and thus many themes will not call the_content(), thus my code on the the_content filter never gets called.

The best solution nowadays seems to be to hook into the 'the_posts' filter and return a pseudo-page, however that in itself has no themeing attached to it.

The solution to the lack of theme was to hybridize this with part of Xavi Esteve's approach to allow me to change the template being used to generate the page.

This approach should work immediately for most, if not all, WordPress themes which was my goal and it's working really well with the themes I have tested so far.

I used the approach documented by Dave Jesch on this page (there are other versions of this, but Dave is the only one who explained it carefully, thanks Dave!): http://davejesch.com/wordpress/wordpress-tech/creating-virtual-pages-in-wordpress/

I also went through lots of pain here with the Wordpress comments section appearing at the bottom of the page in some themes. The solution for this will appear in the file linked above and is probably out of scope for this specific solution.

Also, to prevent a warning with WordPress 3.5.2+, I had to also add a post member:

 $post->ancestors = array();

This solution is used in the wp-cinema WordPress plugin (file views.php if you want to grab some working code, should be checked in in the next few weeks). If there are problems with the approach, I'll be keeping that file current as it's part of a larger project.

The full working solution is below. This is excerpted from a much longer piece of code which also prevents comments from appearing etc (see link provided above). The code:

add_action('parse_request', 'vm_parse_request');// Check page requests for Virtual movie pages// If we have one, generate 'movie details' Virtual page.// ...//function vm_parse_request(&$wp){    if (empty($wp->query_vars['pagename']))       return; // page isn't permalink    $p = $wp->query_vars['pagename'];    if (! preg_match("#wp-cinema/movie/([^/]+)#", $p, $m))       return;    // setup hooks and filters to generate virtual movie page    add_action('template_redirect', 'vm_template_redir');    $this->vm_body = "page body text";    add_filter('the_posts', 'vm_createdummypost');    // now that we know it's my page,    // prevent shortcode content from having spurious <p> and <br> added    remove_filter('the_content', 'wpautop');}// Setup a dummy post/page // From the WP view, a post == a page//function vm_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->vm_body;    $p->post_title = $this->vm_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 page.php// template if it exists (it normally will).//// We use the theme page.php if we possibly can; if not, we do our best.// The get_template_part() call will use child theme template if it exists.// This gets called before any output to browser//function vm_template_redir(){    // Display movie template using WordPress' internal precedence    //  ie: child > parent; page-movie.php > page.php    //  this call includes the template which outputs the content    get_template_part('page', 'movie');    exit;}

By the way, it's important to say that I feel this is pretty much a hack and would love to know how it could be done better. Also, I'd love to see WordPress step up to the mark and provide an API for generating fake pages. (I suspect they have ideological reasons why they won't, but it would be nice to see their solutions to this, even if alternative, explained in depth); I personally feel there are cases where I don't want to go meddling with a user's site just to generate pages.

UPDATE Feb 2014: I've abstracted this into a class which should provide sufficient flexibility for most applications: https://gist.github.com/brianoz/9105004