How do I make a draft post accessible to everyone?

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:

Read More
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.

Related posts

5 comments

  1. 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.

    screen shot

    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.

  2. 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.
  3. I think the “User Role Editor” plugin available at WordPress.org’s website might be what you’re looking for. By the way, why do you want to give access to your drafts to everybody? I personally can’t think of an instance where this would be required.

  4. I think G.M.’s comment is the best one here.
    I assume you are trying to do the following:

    1. Write a post
    2. Save as draft
    3. Allow an external (not logged-in) user view the draft for approval
    4. Publish

    Is that correct?

    Unfortunately, I can’t think of any simple way to do this. You could post it as a private post so that they need to enter a password to view it but you need to be logged in for this.
    You could also password protect it, but then it will still appear in your feed and list of recent posts, etc.
    Could you not create a guest user account and give them the username/password when you give them the URL?

    Read here for more info: http://codex.wordpress.org/Content_Visibility

    Alternatively, there is a plugin that might suit your needs: http://wordpress.org/extend/plugins/shareadraft/
    I had a quick look at the code and it appears the developer is modifying the value returned by get_post_status so you might be able to play around with that:

    http://codex.wordpress.org/Function_Reference/get_post_status

    HTH

  5. You could just change the visibility on the page/post to “Private” which is only visible to Editors and Administrators and not public visitors, search engines, rss feeds, etc.

Comments are closed.