Making a Custom Post type only visible to non-users via a specific link

I am working on creating a small web-app with wordpress as the framework. It allows you to input a customer and in turn sends an email to the customer with a link back to the website in order to complete a short survey.

I have a “Customer” Custom Post Type which includes the survey questions as custom fields. I’m locking down the content and admin areas etc with s2member which so far has worked great.
However, I am after a way in which I can display a separate page which has the survey form on it and only display it for those which receive the link in an email. Possibly something with cookies and htaccess?

I’ve tried to find an answer for the last few hours but it’s a bit over my head. Any help even if just a push in the right direction would be greatly appreciated!

Solutions Collecting From Web of "Making a Custom Post type only visible to non-users via a specific link"

I think what you want is a custom rewrite rule — specifically, a custom endpoint.

This would have to live outside s2member.

To start, wrap everything in a class:

<?php
class WPSE71804
{
    // post type key, whatever this happens to be.
    const TYPE = 'customer';

    // endpoint mask, 2 ^ 18
    const EP = 262144;

    // key prefix, used for options
    const PREFIX = 'wpse71804_key_';

    // container for the instance of this class
    private static $ins = null;

    public static function instance()
    {
        is_null(self::$ins) && self::$ins = new self;
        return self::$ins;
    }

    public static function init()
    {
        add_action('plugins_loaded', array(self::instance(), '_setup'));
    }

    // add actions and such.
    public function _setup()
    {
       // we'll add actions here later.
    }
}

There are some constants there that we’ll use later.

You’ll need to modify your post type registration to include a custom endpoint mask in the rewrite argument.

<?php
class WPSE71804
{
    // snip snip

    // add actions and such.
    public function _setup()
    {
        add_action('init', array($this, 'register'));
    }

    // register the post type
    public function register()
    {
        // rewrite is the args to pay attention to we need 
        // to set a custom endpoint mask
        register_post_type(self::TYPE, array(
            'label'     => __('Customers', 'wpse'),
            'public'    => true,
            'rewrite'   => array(
                'slug'          => 'customer',
                'ep_mask'       => self::EP,
                'with_front'    => false,
            ),
        ));
    }
}

From there, we can hook into init and call add_rewrite_endpoint.

This sets up a rewrite so we can go to yoursite.com/customers/the-post/key/some_key_here.

<?php
class WPSE71804
{
    // snip snip

    // add actions and such.
    public function _setup()
    {
        add_action('init', array($this, 'register'));
        add_action('init', array($this, 'endpoint'), 11);
    }

    // snip snip

    public function endpoint()
    {
        add_rewrite_endpoint('key', self::EP);
    }
}

Now it’s just a matter of hooking into template_redirect and validating the key.

<?php
class WPSE71804
{
    // snip snip

    public static function init()
    {
        add_action('plugins_loaded', array(self::instance(), '_setup'));
        register_activation_hook(__FILE__, array(__CLASS__, 'activate'));
    }

    // add actions and such.
    public function _setup()
    {
        add_action('init', array($this, 'register'));
        add_action('init', array($this, 'endpoint'), 11);
        add_action('template_redirect', array($this, 'validate_key'));
    }

    // snip snip

    public function validate_key()
    {
        // not a a singular customer page? Or have an admin user? bail.
        if(!is_singular(self::TYPE) || current_user_can('manage_options'))
            return;

        if(!($_key = get_query_var('key')) || !($key = self::get_key($_key)))
        {
            global $wp_query;
            $wp_query->set_404();
        }

        // if we're here, the key is okay, let the request go through
    }
}

It might also be helpful to create a nice API to use (the above bit of code uses one of those methods).

<?php
class WPSE71804
{
    // snip snip

    /********** API **********/

    // create a new key
    public static function create_key()
    {
        $k = wp_generate_password(24, false);
        self::update_key($k, 'notdone');
        return $k;
    }

    // update a key
    public static function update_key($key, $val='done')
    {
        return update_option(self::PREFIX . $key, $val);
    }

    // delete a key
    public static function delete_key($key)
    {
        return delete_option(self::PREFIX . $key);
    }

    public static function get_key($key)
    {
        return get_option(self::PREFIX . $key);
    }
}

Now you can use the above something like…

<?php
// create a key
$k = WPSE71804::create_key();

// send mail with key here!

// after they submit the survey, you might want to make a note of that.
WPSE71804::update_key($k, 'done');

// or maybe just delete it and revoke access to the page
WPSE71804::delete_key($k);

Not sure how well that will play along with s2member, but essentially this will block all access to the pages without a key on the front end. You may not need to restrict access with s2member at all. Here is all that as a plugin.

My (simple) solution would be put everything you need into

if ( ! is_user_logged_in() ){    
//stuff    
}

and everything in this if will be shown only for those who aren’t logged in.

Posts can have password protected posts post_password column in the wp_posts table. Or you can use http://wordpress.org/extend/plugins/post-password-plugin/ to generate unique tokens.