WordPress Howto: Making Posts Scheduled in the future show up

This is a quirky little issue based on shoehorning WordPress into matching a "Calendar of Events" style page, but working within the WordPress system. Honestly, this system is fine, but it's probably a little messier than it could be. A co-worker made the decision to go this route and had spent many days developing the site before finding a problem and needing this function.

We needed an event calendar and my co-worker found this: http://www.keithmillington.co.uk/wordpress/?p=22

It's a pretty good idea. Co-worker took the idea to another level, though, and wanted to be able to click into the post (which is a good idea, because it's then bookmarkable and easily sent in email or linked on twitter. Keith's post just shows a category page, basically, with no ability to go into the individual post. That worked great for his client's needs, but ours needed the landing page, too).

So co-worker had it all working, it was pretty slick, but then the go-live day arrives and suddenly all the events are showing up as 404 Not Found pages. They asked me to investigate and it turns out that most of the Event days were in the future -- makes sense because if it's an event calendar, people want to know before, right?. Co-worker was able to see them during testing because he was always logged in as a WordPress Admin and had preview rights. Right before go-live he was not logged in as an admin and it was showing 404 Not Founds for all the individual event pages because they were basically "scheduled" posts.

I investigated and the only problem was that the post was labelled "future". This is important because I was worried that there would be 2 or more places in the code that prevented the post from showing. But, I tracked it down to one line in the WordPress core, temporarily over-wrote the variable and found it worked. Now it was time to extend WordPress (you don't want to edit core files, the next upgrade will over-write them). We looked around for awhile and found a few plugins, but nothing that screamed "stable!" to me. All were very hack-y -- one was doing an update query on the entire posts database table for every page load. So, it took a few hours, but I came up with the following:

 
// this adds a few more queries, unforunately, but it
// necessary because of the how the events system was created
// If the post we are looking at is an Event, as defined by a category ID,
// we will show it no matter it's date
// by altering it's post_status via the SQL
 
function future_events_can_be_shown ($w='')
{
	global $wp, $wpdb;
 
	// 'name' will be set only if this is an attempted view of a single post
	// no need to take up resources if this isn't a single post
	if ( !isset ( $wp->query_vars['name'] ) or empty ( $wp->query_vars['name'] ) )
	{
		return $w;
	}
 
	$post_id = $wpdb->get_var("SELECT ID FROM $wpdb->posts WHERE post_name = '" .
               mysql_escape_string ( $wp->query_vars['name'] ) . "'");
	$cats    = get_the_category( $post_id );
 
	$event_categories = array ( 90, 122, 123 );
        // these are the hard-coded event IDs that identify this as an event
 
	// we have loaded the post's category list, now we'll loop
        // through them to see if any of them match one of our
        //event IDs (which are category IDs)
	$is_event_category = false;
	foreach ( $cats as $event_obj )
	{
		if ( in_array ( $event_obj->term_id, $event_categories ) )
		{
			$is_event_category = true; // yup, we've a match,
			break;				// no need to keep looking...
		}
	}
 
	if ( !$is_event_category ) // NSFY!  No match, buh, bye
	{
		return $w;
	}
 
	$w .= " , 'publish' as post_status "; // just a way to override the post status
	return $w;
}
 
add_filter('posts_fields','future_events_can_be_shown');
 

Basically, I just determine the post ID from the post url (like /2010/08/post-name-here/) using the "name" query_var ("post-name-here" in this example). Then I queried for all the categories that post is in. If any are the Event categories (as hardcoded) then we over-write the post_status. I hook into "posts_fields" because that allows me to change which database table columns/fields are pulled in the query.

We could have used a custom field for the date and not scheduled the post for the future but we wanted to sort them easily (you can sort on meta_values using query_posts but they are stored as strings, so you can't really sort dates very well). Plus, the post authors would be computer users, not computer experts and it had to make sense and be easy. Having a "post date" that was not shown and was in the past with a custom Event Date field they had to remember to scroll down and add in would confuse the hell out of them.

So, to add an "Event" the admin goes in, adds the text (Name, Location, Directions, a Description, etc) using the post content window and several custom fields. Then for the Event Date, s/he just "schedules" the post for the date of the event (yes, in the future). Using Keith's method (linked above), the category page lists all the events. When they click on one, my plugin over-writes one field -- post_status -- with the 'publish' value (it is stored as 'future' for scheduled posts), but only for events in the listed categories.

To make this better, you could have a WordPress Admin Option page where they could select which categories should be treated this way, but I have a million things to do, so we hardcoded the 3 category id's that should be handled like this ($event_categories). In the rare future case someone will have to edit this theme file if we change/add/remove an Event category. Yes, I know. Not optimal. You understand how the world works, though, right? If not, then it's because the word isn't perfect.

A quick note: it seems like you should be able to just use query_posts('p=post_id') in a theme page to get it, right? Well, the problem with posts like this ("scheduled") is that it's determined to be a 404 Not Found well before WordPress goes looking for your theme. And, when it goes looking for the theme, it's not looking for the index.php page or single.php, it's looking for "404.php" etc. In addition, WordPress Core unsets the post array once it finds that post_status is "future". In other words, it loads the post, THEN checks that the post_status is "publish" and if not, it then unsets the post variable. That's what tripped me up. If they would have left the post variable in place but set a flag "404" it could be over-ridden in the theme.

Of course, there are a million decisions made like this is an app like WordPress, and it's not a bad decision; there are always many ways to tackle an issue, and WordPress decided that unsetting that variable was the best way to ensure a scheduled post didn't leak out accidentally (and believe me, WordPress, you succeeded!). If you are curious, the code is here:

 
// file wp-includes/query.php line 2281 in WP 2.9.1
$status = get_post_status($this->posts[0]);
//$type = get_post_type($this->posts[0]);
if ( ('publish' != $status) ) {
    if ( ! is_user_logged_in() ) {
    // User must be logged in to view unpublished posts.
    $this->posts = array();
 
 [[... if statement closed, etc ...]]
 

Another note: It's hard to figure out what the queries are in a beast like WordPress, so I turned on mysql query logging and tailed it in realtime to see what queries there where (there were a LOT for every page load; a SHOCKING number). I determined it was working using that. Turn it off when you are done, though, or you'll soon be out of hard-drive space.

If you have any questions, I'm "ha17" on stackoverflow.com