-
-
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 |
nirajkvinit -
The first parameter of $vp->add('#/mypattern/unique#i', 'mytest_contentfunc'); function is a regular expression. Simply adjust that to be a regular expression with a wildcard on the end so that it will pick up all urls with paths starting at /mypattern/. Something like this should do the trick (I didn't test the regex - the wildcard group may need tweaked):
$vp->add('#/mypattern/?(.+)?#i', 'mytest_contentfunc');
WindowsAndLinux -
You need to add an init action that is included on every page load. Ex: I built a plugin that includes the virtual page class here and then does the following (this is an abbreviated version, it may not work perfectly if you just copy and paste it). The important piece is that the add_action() is on every page load so that the virtual page class can take over the urls you put in the $v->add() method
add_action('init', 'geo_seo_pageNew');
function geo_seo_pageNew() {
$vp = new geoseo_Virtual_Themed_Pages();
$vp->add('#/local/#i', 'geo_seoMagic');
}
function geo_seoMagic($v, $url) {
$v->body = 'My special page content here';
$v->template = 'page';
$v->slug = $url;
}
Loving this! Thank you. I'm getting a (non-specified) Wordpress error when trying to share virtual page on facebook (via Add to Any plugin). Will report back.
A couple of small changes so that entire URL can be shared:
Around line 32 add :
public $itemID = '';
Then around line 140 update:
$p->post_name = 'my_path/'.$this->itemID;
...but it's adding a second instance of the itemID to the URL string as well.
I had the same problem with sharing, because WP is not returning a correct permalink.
To solve this problem, I added this line of Code:
add_filter('page_link', array(&$this, 'vtp_createpagelink'), 1, 2);
just after the addition of the filter 'the_post'
The new method vtp_createpagelink is just as follows:
function vtp_createpagelink($link, $postID) { if ($postID == -1) { $link = home_url($_SERVER['REQUEST_URI']); } }
Thanks everyone, I have been trying to figure out how to get a virtual post to get all of the meta data added by SEO plugins like Yoast WP-SEO and others. Anyone have any suggestions? Virtual posts working flawlessy except for this for me.
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.
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']='';
Hi, I was wondering if anyone could help me set this up, how do I actually use this ? I've set up a page template the has a require line to the class file, placed in wp-includes., but how do you actually call it ? I can't figure it out... how do I actually instantiate a virtual page with this ?