Tag Archives: security

Private Posts in WordPress

In WordPress, posts can be set to Public, Private or Password Protected, in the Document menu. Password Protected is out of scope for this article; it’s not a feature I’ve used much. I do however often make Private posts, useful for keeping educational notes and information, for example.

By default, Private posts have a front-end view of the post only logged-in users can see, the post title prefixed with ‘Private:’ . Other than that, the post behaves normally, appearing in the main posts query, and in archives, nestled amongst public posts in whatever order your theme specifies.

Can this feature be improved? In my case, I would prefer not to have my private posts displayed in this way: I’d much rather have my Private posts in their own archive, their own view for users authorised to see them, separate from the public blog. Imagined this way, anyone wanting to keep a private journal alongside their public blog might find this a preferable arrangement.

Sounds like a perfect idea for a custom plugin.

Private Posts Keep

Modifying Private Posts in a custom plugin

I broke down my plugin design with three goals in mind:

  • removing private posts from the main posts view and archives
  • creating a new archive-style view exclusively for private posts
  • creating a Private Posts Widget, for an alternative view and easy front-end access.

I decided to call the plugin Private Posts Keep. In giving Private posts their own area, I was reminded of keep within a castle, hence the perhaps obscure name based on this metaphor. The most straightforward title, to be honest, might be ‘Segregate Private Posts’ since that’s a clear and accurate description of both the intent, and the effect, of the plugin. Hmm, maybe a compromise title would be ‘Private Posts Sanctum’?

Enough of what to call it; let’s get to work making it function. First, let’s make a start on our first two goals, by creating two classes.

Inside our main class, class-pp-keep.php, we’ll create a few functions:

public function rem_pp( $query ) {
  if( current_user_can( 'read_private_posts' ) ){
  /*affects main query on posts page or archives.			 
   *http://codex.wordpress.org/Function_Reference/is_main_query			 
   *http://codex.wordpress.org/Function_Reference/is_home			 
   *http://codex.wordpress.org/Function_Reference/is_archive*/
    if ( $query->is_main_query() &&  $query->is_home() || $query->is_archive() ) {
      $query->set( 'post_status', 'publish' );
    }
  }
}

That takes care of modifying the main query on the posts page or an archive page. However, we’ll find on single post views our navigation links to the next and previous posts won’t reflect this change of emphasis. Let’s fix this with another function to filter these navigation links:

public function get_adjacent_post_mod($where){
  if (is_single()){
    global $wpdb, $post;
    if ( get_post_status ( ) == 'private' ) {
      $where = str_replace( "AND ( p.post_status = 'publish' OR p.post_status = 'private' )", "AND p.post_status = 'private'", $where );
      return $where;	
    } else {
      $where = str_replace( "AND ( p.post_status = 'publish' OR p.post_status = 'private' )", "AND p.post_status = 'publish'", $where );
      return $where;	
    }
  }
}

And we need somewhere to display those now missing Private posts. It’s possible to programmatically insert posts in the database, so that’s what we’ll do. The function first checks a Page with our desired title doesn’t exist, and if not creates it, set to Private, and fills it with the specified content, including a shortcode, for outputting a list of Private posts. we’ll define elsewhere in the plugin.

public function create(){
			$post = '';
			if( get_page_by_title('Private Archive') == NULL )
			// Create post object
			$post = array(
			  'post_title'    => 'Private Archive',
			  'post_status'   => 'private',
			  'post_type'     => 'page',
			  'post_content'   => 'This page is intended for your private posts.
				
				[private-posts]'
			);

			// Insert the post into the database
			
			$insert_post = wp_insert_post( $post, true);//now you can use $post_id within add_post_meta or update_post_meta	
			return $insert_post;	
		}

The shortcode is defined in a function in a separate file. Finally we add our functions to the appropriate WordPress hooks, in our constructor function for the class, like so:

add_action( 'pre_get_posts', array(&$this, 'rem_pp'));
add_action( 'wp_loaded', array(&$this, 'create'));
add_filter( 'get_next_post_where', array(&$this, 'get_adjacent_post_mod'));
add_filter( 'get_previous_post_where', array(&$this, 'get_adjacent_post_mod'));

Our rem_pp() function is added to the pre_get_posts action hook. Note we need to add our navigation links filter to two separate hooks, get_next_post_where & get_previous_post_where

Our widget is created in a separate class, class-pp-keep-widget.php.

In our main plugin file, pp-keep.php, which runs the plugin, we’ll tie all this together:

/*
Plugin Name: Private Posts Keep
Description: Segregates Private Posts
License: GPL2
*/
/* exit if directly accessed */
defined( 'ABSPATH' ) || exit;
define( 'PPKEEP_PATH', plugin_dir_path( __FILE__ ) );

require_once( __DIR__ . '/pp-keep-shortcode.php' );
require_once( __DIR__ . '/class-pp-keep.php' );
require_once( __DIR__ . '/class-pp-keep-widget.php' );
if( class_exists( 'PP_Keep' ) ) {
	$PPKeep = new PP_Keep();// instantiate the plugin class
}

if ( class_exists( 'PP_Keep_Widget' ) ) {
	// Register and load the widget
	function pp_load_widget() {
		register_widget( 'PP_Keep_Widget' );
	}
	add_action( 'widgets_init', 'pp_load_widget' );
}

/**
 * Activate the plugin
 */
function ppkeep_activate()
{
	// Do nothing
} // END public static function activate
/**
 * Deactivate the plugin
 */
function ppkeep_deactivate()
{
	$page = get_page_by_title('Private Archive');
	if (isset($page)){
		$page_id= $page->ID;
	}
	wp_delete_post($page_id, true);
} // END public static function deactivate

function ppkeep_uninstall () {
	if ( ! current_user_can( 'activate_plugins' ) )
	    return;
} // END public static function uninstall
register_activation_hook(__FILE__, 'ppkeep_activate');
register_deactivation_hook(__FILE__, 'ppkeep_deactivate');
register_uninstall_hook(__FILE__, 'ppkeep_uninstall');

The file runs a basic security check, defined( 'ABSPATH' ) || exit; and loads up our shortcode and classes . The activation, deactivation, and uninstall hooks are features available to plugin authors so code can be triggered when the user performs these actions. Here they are standard, except for the deactivate hook, which I’ve set to delete the private Page the plugin creates.

Try it out for yourself

The plugin works and the full plugin source code is freely available on my github. To install in your WordPress site, you’ll have to download the .zip archive:

Then go to your WordPress Admin>Plugins, and hit ‘Add New’ then ‘Upload Plugin’. Uploading the .zip file will install the plugin for you to activate.

After activation, you will find private posts are now gone from your main posts page and archives, but are listed on a Private Posts Archive page the plugin has created for you, which should show up on your Dashboard under ‘Pages’. You can edit this page as normal, but ensure it keeps the shortcode which outputs a simple list of Private Posts by title, with their permalinks. Alternatively you can add the shortcode anywhere you like.

You will find a Widget available which you can add to your sidebar. The widget will only show up for users authorised to read private posts, and displays a short list of the latest, along with a link to the Private Posts Archive page the plugin creates.

How can the plugin be improved?

The code which delete the Archive page on deactivation, and the link to the page in the widget, use the title and url of the page to function. This depends on the user neither changing the title nor deleting the page, which cannot be guaranteed (see github issue for more details).

Moreover, there aren’t many options available to the user. The widget has no options other than the widget title, and there are no options to modify how the Archive page displays the private posts.

Finally, I have a suspicion the plugin wouldn’t be accepted into the Plugin directory without a good code review and some refactoring. The code works, and does what it says on the tin, but it isn’t necessarily elegantly written and designed.

I wrote it back during the turn of 2015/16, but life rather got in the way of me developing it further. Nevertheless it’s a useful plugin that adds value to the native private posts feature of WordPress, and well worth revisiting.

You can follow along with development of the plugin, or even help out yourself, at the following url:

https://github.com/KorvinM/pp-keep