14

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.

5
  • 2
    I think there is a bit of confusion here. If someone is not logged in, he/she is not a user, so he/she has not any caps. The subscribers are users, so they have to be registered and make the login to be recognized as subscribers. You cannot give capabilities to someone that has not logged in. If you want anyone not logged view your posts the are simply not different at all from published posts. So, why not pubblish it? Or do you want to show draft to subscribers (logged in as subscribers)?
    – gmazzap
    Sep 3, 2013 at 0:13
  • @G.M.: I'm trying to make unpublished posts accessible to everyone who knows the direct permalink to the post. It may seem like a weird requirement, but I need this for a project. As I've mentioned in my question, if this isn't possible -- can you suggest other work-arounds (if you know one, of course)? Nov 4, 2013 at 14:14
  • @G.M.: Do you know of a plugin which does that? Nov 4, 2013 at 14:26
  • Have you read the answer just below with 3 upvotes? It explain how to do it and in at the end of answer you find a link to download a full working plugin from github.
    – gmazzap
    Nov 4, 2013 at 15:04
  • 2
    For Later readers: There's this plugin by Dominik Schilling available on GitHub.
    – kaiser
    Nov 27, 2013 at 12:32

5 Answers 5

13
+50

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.

1
  • Thank you so much. I finally achieved my goal with some tweaks. Awesome. :D Nov 6, 2013 at 19:07
8

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.
7
  • 2
    This is incredible, thank you so much for sharing this. Absolutely perfect and exactly what I needed.
    – Christian
    Feb 4, 2015 at 6:30
  • Where do I put this code? (which file(s)?)
    – Joelio
    Apr 27, 2015 at 20:21
  • @Joelio Can you be specific about the problem you're solving? As a simple approach, I just added this code to my functions.php, and added a simple definition to my wp-config.php which set the ENV_PRODUCTION constant to true or false depending on the request domain. Apr 28, 2015 at 22:59
  • @goldenapples I added this piece of code to my function.php, what should I add to wp-config ? Thanks for your help Sep 26, 2015 at 10:04
  • @MatthiasGrahamSlick - You just need something that will set the ENV_PRODUCTION constant if you're on production. I was using domain.com for production and staging.domain.com for staging, so my line was define( 'ENV_PRODUCTION', false === stripos( $_SERVER['HTTP_HOST'], 'staging' ) ); Does that help? Sep 28, 2015 at 12:58
0

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.

3
  • No, as mentioned in the question, I am not looking for plugin-based solutions. The usecase is slightly complicated but I'm sure this is the best solution for this specific task I'm trying to accomplish. :-) Jul 19, 2013 at 1:32
  • I'm perplexed about the reason as well. If you want everyone to see the post, why not simply publish the post? You could use custom fields on the post to track any special state you might desire to define.
    – KenB
    Jul 26, 2013 at 0:11
  • I'm letting users see the content, but for some specific reason I don't want the posts to be published. Jul 26, 2013 at 7:20
0

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

-1

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.

1
  • This is not a viable solution to an already published page. You can make edits to a currently live page and save them as a draft. If you were to follow this approach on a live page it would remove the live page from search results over time and potentially cause other problems. Dec 13, 2021 at 15:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.