After hours seeking through the WordPress sources it I was unable to find ANY resonable method of creating a front end page from a plugin. My goal was simple, I wanted http://mysite.com/friends to show a page generated by my plugin. This is a multi-site installation and I do not want blog owners to be able to eliminate this page. My final implementation doesn't involve users blogs but it could.
wp-includes/theme.php: I added this function
function get_extension_page_template() {
$page = get_queried_object();
$templates = array($page->ID.'.php');
return get_query_template($page->ID,$templates);
}
wp-includes/template-loader.php: I added this just ofter the if(is_404() line(near line 8)
elseif ( is_extension_page() && $template = get_extension_page_template()) :
wp-includes/query.php: I added two functions
function set_extension_page($page,$object) {
global $wp_query;
return $wp_query->set_extension_page($page,$object);
}
function is_extension_page() {
global $wp_query;
return $wp_query->is_extension_page();
}
wp-includes/query.php: I added two properties to the WP_Query class
var $extension_page = null;
var $extension_page_object = null;
wp-includes/query.php: I added two methods to the WP_Query class
function set_extension_page($page,$object) {
$this->extension_page_object = $object;
$this->extension_page = $page;
}
function is_extension_page() {
return isset($this->extension_page);
}
wp-includes/query.php: I changed the get_queried_object method in the WP_Query class
function get_queried_object() {
if ( isset($this->queried_object) )
return $this->queried_object;
if (isset($this->extension_page_object)) {
return $this->extension_page_object;
}
$this->queried_object = NULL;
$this->queried_object_id = 0;
if ( $this->is_category || $this->is_tag || $this->is_tax ) {
$tax_query_in_and = wp_list_filter( $this->tax_query->queries, array( 'operator' => 'NOT IN' ), 'NOT' );
$query = reset( $tax_query_in_and );
if ( 'term_id' == $query['field'] )
$term = get_term( reset( $query['terms'] ), $query['taxonomy'] );
else
$term = get_term_by( $query['field'], reset( $query['terms'] ), $query['taxonomy'] );
if ( $term && ! is_wp_error($term) ) {
$this->queried_object = $term;
$this->queried_object_id = (int) $term->term_id;
if ( $this->is_category )
_make_cat_compat( $this->queried_object );
}
} elseif ( $this->is_post_type_archive ) {
$this->queried_object = get_post_type_object( $this->get('post_type') );
} elseif ( $this->is_posts_page ) {
$page_for_posts = get_option('page_for_posts');
$this->queried_object = & get_page( $page_for_posts );
$this->queried_object_id = (int) $this->queried_object->ID;
} elseif ( $this->is_singular && !is_null($this->post) ) {
$this->queried_object = $this->post;
$this->queried_object_id = (int) $this->post->ID;
} elseif ( $this->is_author ) {
$this->queried_object_id = (int) $this->get('author');
$this->queried_object = get_userdata( $this->queried_object_id );
}
return $this->queried_object;
}
wp-includes/class-wp.php: I changed the handle_404 method to the following
function handle_404() {
global $wp_query;
if ( !is_admin() && ( 0 == count( $wp_query->posts ) ) && !is_404() && !is_robots() && !is_search() && !is_home() ) {
// Don't 404 for these queries if they matched an object.
if ( ( is_extension_page() || is_tag() || is_category() || is_tax() || is_author() || is_post_type_archive() ) && $wp_query->get_queried_object() && !is_paged() ) {
if ( !is_404() )
status_header( 200 );
return;
}
$wp_query->set_404();
status_header( 404 );
nocache_headers();
} elseif ( !is_404() ) {
status_header( 200 );
}
}
The final result is that I can create an object in plugins on patched sites and test for certain conditions, such as $blogpage = $this->ID and then call set_extension_page($this->ID,$this); to register the page to be displayed. The theme must also have a template setup for that plugin which is named (first parameter of set_extension_page).php but its better than nothing.
I admit this is just an ugly hack, but it works. It would be nice to see a feature like this added to wordpress in the future.