Wordpress - How do I make a draft post accessible to everyone?
You cannot assign capabilities to unknown users. If you want to make a post visible for everyone, create a separate URL for these posts and add a control element to the post editor to enable the preview on selected posts only.
When such an URL is called, check if a preview is allowed for the post and if the post hasn’t been published already. Also make sure search engines ignore this URL.
For the URL I would use an endpoint:
add_rewrite_endpoint( 'post-preview', EP_ROOT );
Now you can create URLs like …
http://example.com/post-preview/123
… where 123
ist the post ID.
Then use a callback handler to inspect the post ID, check if it is valid and overwrite the main query. This is probably the only acceptable use case for query_posts()
. :)
Let’s say the endpoint is a class T5_Endpoint
(a model), and the output handler is a class T5_Render_Endpoint
(a view) which gets the model passed earlier. Then there is probably a method render()
called on template_redirect
:
public function render()
{
$post_id = $this->endpoint->get_value();
if ( ! $post_id )
return;
if ( 1 !== $this->meta->get_value( $post_id )
or 'publish' === get_post_status( $post_id )
)
{
wp_redirect( get_permalink( $post_id ) );
exit;
}
$query = array (
'suppress_filters' => TRUE,
'p' => $post_id,
'post_type' => 'any'
);
query_posts( $query );
add_action( 'wp_head', 'wp_no_robots' );
}
$this->meta
is another model (class T5_Post_Meta
) for the post meta value that controls if a preview is allowed. The control is set into the Publish box (action post_submitbox_misc_actions
), rendered by another view that gets the same meta class.
So T5_Post_Meta
knows where and when to store the meta value, the views do something with it.
Also, hook into transition_post_status
to delete the post meta field when the post is published. We don’t want to waste resources, right?
This is just an outline. There are many details to cover … I have written a small plugin that shows how to implement this: T5 Public Preview.
I solved this problem in what I thought was a simpler way than @toscho's answer above.
My use case is I'm using the same database for an internal intranet staging site and a public-facing site, and the workflow is that authors write drafts and share it with other users who view those drafts on the intranet site, before publishing. I specifically didn't want to require reviewers to log in to see drafts, so I'm just depending on a constant, ENV_PRODUCTION
which is set in the wp-config file based on the hostname in $_SERVER['SERVER_NAME']
. Thats what the checks for ENV_PRODUCTION
here are doing; just shorting out all of these filters if the production site is being viewed.
This is a little weird, because you have to hook in after WP_Query removes all of the posts from the $wp_query->posts array, but it seems stable and secure to me.
/*
* On staging site home and archives, drafts should be visible.
*/
function show_drafts_in_staging_archives( $query ) {
if ( ENV_PRODUCTION )
return;
if ( is_admin() || is_feed() )
return;
$query->set( 'post_status', array( 'publish', 'draft' ) );
}
add_action( 'pre_get_posts', 'show_drafts_in_staging_archives' );
/*
* Make drafts visible on staging site single views.
*
* (Because on single views, WP_Query goes through logic to make sure the
* current user can edit the post before displaying a draft.)
*/
function show_single_drafts_on_staging( $posts, $wp_query ) {
if ( ENV_PRODUCTION )
return $posts;
//making sure the post is a preview to avoid showing published private posts
if ( ! is_preview() )
return $posts;
if ( count( $posts ) )
return $posts;
if ( !empty( $wp_query->query['p'] ) ) {
return array ( get_post( $wp_query->query['p'] ) );
}
}
add_filter( 'the_posts', 'show_single_drafts_on_staging', 10, 2 );
There's two separate parts to the filters.
- A filter on the "pre_get_posts" hook sets the default post_status to 'publish,draft' on the staging site. This will return the draft posts in archive listings.
- A separate filter is needed for single views, because there's some nasty logic in the WP_Query class to remove draft posts from the query results unless the current cuser can edit them. I got around this by filtering 'the_posts', and adding the post I wanted right back to the results.