I have several unpublished posts in my WordPress website and I am trying to make it accessible for normal users (who are not logged in) using the normal post slugs (site.com/post-here
). I understand it may not be the best practice but for my special purpose, this needs to be done.
I have tried adding the following code snippet into my functions.php
file:
function enable_view_drafts() {
$role = get_role( 'subscriber' );
$role->add_cap( 'read_private_posts' );
$role->add_cap( 'edit_posts' );
}
add_action( 'after_setup_theme', 'enable_view_drafts');
I’ve also tried init
hook instead of after_setup_theme
. No luck.
My understanding is that changes to roles are saved to the database so only need to be done once. That is why I’m using after_setup_theme
hook to call the function.
But when I try to access the page as a normal user, I’m being shown a 404 page instead of showing the post content. I’ve also tried loading the preview URL (site.com/?p=212&preview=true
) but that didn’t work either.
These are my guesses:
- the normal user doesn’t have enough
caps
to read the drafts post. - testing and viewing draft posts on the front-end is not possible for any users (including administrators).
What changes do I have to make in order to accomplish what I’m trying to do? If it’s not possible, what alternative solutions do you suggest?
Note: I’m not looking for plugin-based solutions.
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.