-
-
Save brianoz/9105004 to your computer and use it in GitHub Desktop.
<?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 $id\n"; | |
$v->template = 'page'; // optional | |
$v->subtemplate = 'billing'; // optional | |
} | |
} | |
// end |
Update: Looks like this person figured it out: https://stackoverflow.com/questions/74613027/wordpress-virtual-page-trying-to-get-property-post-type-of-non-object
Also, here is another plugin that could be helpful to the next person!
https://gist.github.com/gmazzap/1efe17a8cb573e19c086
This is such a handy bit of code, I've been really pleased with how it works, but naturally at the eleventh hour I've realised I have a problem.
If the path has multiple slashes in it, the page works fine, but it's returned with a 404 status. So /catalogue/?product_id=123
is fine as is /catalogue/?search=foo
however where I have pages like /catalogue/widgets/my_super_widget/
while displaying correctly, they are returned as a 404 error page.
Has anyone else got any pointers on how to sort this out, please?
This is such a handy bit of code, I've been really pleased with how it works, but naturally at the eleventh hour I've realised I have a problem.
If the path has multiple slashes in it, the page works fine, but it's returned with a 404 status. So
/catalogue/?product_id=123
is fine as is/catalogue/?search=foo
however where I have pages like/catalogue/widgets/my_super_widget/
while displaying correctly, they are returned as a 404 error page.Has anyone else got any pointers on how to sort this out, please?
@tobestool Sorry for the late reply. I just discovered this issue myself. I believe it was caused by a wordpress update. Anyway, if you add the following line to dummypost, it should rid the 404
$wp->query_vars['error']='';
Has anyone been able to get this to work on Wordpress 6.1.1 (or newer once that happens)? I get a couple of errors that look like "Trying to get property 'post_type' of non-object in .../wp-includes/link-template.php on line 4066." If I downgrade to Wordpress 6.0.3 then I can my code working.