Your Hate Feeds Me
I don’t usually share specific answers to the Webcomic Survey’s, but this answer to How did you first hear about Webcomic? is too funny not to share (minus authorial credit; I’m not about to share who said what in the survey):
ComicPress guy hating on you in a post, so I checked out Webcomic and realized it was much better!
I cracked up when I read this, mostly because the whole “feud” (if you can even call it that) between myself and the ComicPress guy(s?) is both silly and, I’d thought, long since past. To summarize:
I’m not a fan of ComicPress. I believe (does this need an IMHO?) that it’s poorly designed. Older versions were ugly (the newer, cleaner version isn’t so bad). That’s my opinion, and I encourage users to ignore me and try out all the options available to them, ComicPress included when they’re looking for ways to publish their comic on the web.
In the past I’ve been sporadically vocal about my dislike of ComicPress, mostly on my own site and mostly because it’s so popular people tend to not even know there are (does this also need an IMHO?) better alternatives out there. Someone working on CP apparently took this all a little too personally and has since stated his undying hatred for me. He’s also not a fan of Webcomic, last I heard.
And I say “last I heard” because this was all over a year ago. Things came to a head in June 2010 when ComicPress guy posted a “comparison” of Webcomic & Inkblot and ComicPress (a post which no longer appears to be accessible on his site), to which I replied. My reply also explains in greater detail the history (such as it is) between myself and ComicPress guy.
I never paid a lot of attention to ComicPress or the guy who’s hatred I incurred before this, and I’ve paid even less attention to both since then (basically none until I was recently asked to help a CP user switch her site to Webcomic). It’s pretty amazing, though, that either:
- A post hating on me from so long ago is getting people to try (and switch) to Webcomic, or (possibly more likely since the only post I’m aware of that hates on me is now password protected)
- that guy is still hating on me after all this time.
Anyway, I got a good laugh out of this. I still have no idea how to react except a “Stay classy!” to that fellow. Now let’s all get back to making webcomics, shall we?
Faking a Forum with WordPress
The most recent incarnation of the official Webcomic site has been running for over a year now, and during that time I’ve gotten’ a number of questions asking how, exactly, it works. It’s a good question that deserves a detailed answer, so I’ve finally decided to explain how the Webcomic site runs wiki-style documentation, a forum, and an issue tracker with nothing but stock WordPress features.
The question that finally pushed me to write this tutorial was specifically asking how the forum on webcomicms.net works, so I’m going to focus specifically on that. The examples presented here are adapted (not straight copied) from the code used at webcomicms.net and should be perfectly functional.
Leyline
Usually when people ask how the wiki-docs, forum, or issue tracker on webcomicms.net work I respond with “smoke and mirrors”, which is an accurate but unhelpful explanation. A better explanation would be Leyline. “Leyline” is a name I’ve used for a series of custom-built, unreleased plugins used on various, personal WordPress sites.
The version of Leyline used on webcomicms.net handles all of the sticky bits needed to, say, run a forum. This isn’t entirely accurate though: what Leyline really does is setup the various WordPress features that allow us to run something like a forum.
Custom Post Types
WordPress 3’s custom post types are what really make everything possible, so the first thing Leyline does is register the post types we need:
function hook_init() {
global $wp_rewrite;
$forum_topic_slug = ( is_object( $wp_rewrite ) and $wp_rewrite->using_permalinks() ) ? 'forum/topic' : false;
$forum_slug = ( is_object( $wp_rewrite ) and $wp_rewrite->using_permalinks() ) ? 'forum' : false;
register_post_type( 'forum_topic', array( 'labels' => array( 'name' => 'Forum Topics', 'singular_name' => 'Forum Topic' ), 'public' => true, 'rewrite' =>$forum_topic_slug, 'supports' => array( 'title', 'editor', 'author', 'thumbnail', 'excerpt', 'trackbacks', 'custom-fields', 'comments', 'revisions', 'post-formats' ) ) );
register_taxonomy( 'forum', array( 'forum_topic' ), array( 'labels' => array( 'name' => 'Forums', 'singular_name' => 'Forum' ), 'hierarchical' => true, 'public' => true, 'rewrite' => $rforum_slug, 'update_count_callback' => '_update_post_term_count' ) );
} add_action( 'init', 'hook_init' );
Here we’ve hooked into WordPress’ init process and registered our new post type, “forum_topic”, as well as a new taxonomy, “forum”. This simple function takes care of a lot of the heavy lifting. Once this is running you should see an entire new section in the Admin dashboard for Forum Topics that let’s you manage them just like any other post. A new Forums item should also appear under Forum Topics, just like normal post tags or categories.
The register_post_type and register_taxonomy functions are, thankfully, pretty well documented and have many options that have been left out of this example for brevity.
That’s It!
Ok, not quite, but WordPress does take care of most of the hard work for us. Now that the custom post type and taxonomy for a forum are registered we can create theme template files for them like we would for a typical WordPress post or taxonomy term (tags, categories, etc.). We don’t even have to worry about users being able to converse in a forum topic because WordPress’ comment system handles this.
Building post templates and styling comments and the like is well covered elsewhere on the web so I won’t go into that here. There are a few more things we need to do to really make our “forum” work like a forum, however.
Sorting by Most Recent Comment
There’s no built-in way (that I’m aware of) to sort WordPress posts by the most recently posted comment, for example, which is a pretty standard forum feature. We can take care of that with a custom sorting function and a hook:
function hook_the_posts( $posts ) {
if ( 'forum_topic' == $posts[ 0 ]->post_type )
usort( $posts, 'usort_posts_by_last_comment_date' );
return $posts;
} add_action( 'the_posts', 'hook_the_posts' );
function usort_posts_by_last_comment_date( $a, $b ) {
$c = current( get_comments( array( 'post_id' => $a->ID, 'status' => 'approve', 'number' => 1 ) ) );
$d = current( get_comments( array( 'post_id' => $b->ID, 'status' => 'approve', 'number' => 1 ) ) );
$e = ( $c ) ? mysql2date( 'U', $c->comment_date ) : mysql2date( 'U', $a->post_date );
$f = ( $d ) ? mysql2date( 'U', $d->comment_date ) : mysql2date( 'U', $b->post_date );
if ( $e < $f )
return 1;
elseif ( $e > $f )
return -1;
else
return 0;
}
Here we’ve hooked into WordPress’ the_posts hook, which the documentaiton explains is:
applied to the list of posts queried from the database after minimal processing for permissions and draft status on single-post pages.
So it’s firing basically anytime we grab posts from the database. We don’t want to sort every post type by the most recent comment, though, so we check to see if the first post (and presumably all other posts) are a “forum_topic” post.
If they are, we call the usort_posts_by_last_comment_date() function, which uses the get_comments() function to grab the most recently published comment for two posts, compares them, and orders them the way we want.
Posting a Topic
We’ve actually taken care of just about everything we need to run a forum with this tiny handful of functions, but there’s one more thing we’re missing: how do we allow any user to actually post a forum topic?
We probaby don’t want every user being an Author, since this would give them access to all of our other posting options. We could go through the process of setting up custom capabilities specifically for our new forum_topic post type, but WordPress doesn’t make it overly simple to work with roles and capabilities out of the box. Personally, I don’t want users poking around my admin dashboard anyway; I’d prefer a method that exists outside of the WordPress admin area.
The Form
The first thing we’ll need is a form. Something like this should work, and we can drop it into any theme template file:
<?php global $current_user; get_currentuserinfo(); ?>
<form action="" method="post">
<?php wp_nonce_field( 'unique_action_name' ); ?>
<input type="text" name="new_post_title" class="title"<?php if ( !is_user_logged_in() ) echo ' disabled'; ?>>
<textarea name="new_post_content" cols="45" rows="8"<?php if ( !is_user_logged_in() ) echo ' disabled'; ?>></textarea>
<input type="hidden" name="new_post_author" value="<?php echo ( !empty( $current_user ) ) ? $current_user->ID : 2; ?>">
<input type="hidden" name="forum_board" value="45">
<input type="hidden" name="action" value="new_forum_topic">
<input type="submit" name="submit" value="Post" id="submit">
</form>
There are a few things going on here:
- First, we call get_currentuserinfo(); and populate the global $current_user variable. This gives ous lots of useful information about the user looking at our site.
- Next we have the form, which uses wp_nonce_field for added security.
- Then we have some standard fields for the topic title and body content.
- Last, we have some hidden fields which store the users ID, the taxonomy ID of the forum this topic will be added to, and an “action” that should be unique enough to allow us to identify that this is the form a user submitted.
Note that if a user isn’t logged in we don’t leave new_post_author blank. We specifically assign it a user ID (2, in this case) which acts as an anonymous user. We’ll deal with this in the next step.
The Handler
Good old init is great for a lot of things, including form handling. We’re going to go back to it now and add a new hook that will handle this form when a user submits it:
function handle_new_forum_topic() {
if ( isset( $_POST[ 'action' ] ) and 'new_forum_topic' == $_POST[ 'action' ] ) {
check_admin_referer( 'unique_action_name' );
if ( empty( $_POST[ 'new_post_title' ] ) )
wp_die( 'Error: a title is required' );
elseif ( empty( $_POST[ 'new_post_content' ] ) )
wp_die( 'Error: please write a post' );
elseif ( empty( $_POST[ 'new_post_author' ] ) or 2 == $_POST[ 'new_post_author' ] )
wp_die( 'Error: you must login to post' );
elseif ( !is_wp_error( $new_post = wp_insert_post( array( 'post_type' => 'forum_topic', 'post_status' => 'publish', 'post_title' => $_POST[ 'new_post_title' ], 'post_content' => $_POST[ 'new_post_content' ], 'post_author' => $_POST[ 'new_post_author' ] ) ) ) ) {
wp_set_object_terms( $new_post, $_POST[ 'forum_board' ], 'forum_board' );
$_POST[ 'new_forum_topic' ] = get_permalink( $new_post );
} else
wp_die( 'Error: your post could not be saved, please try again' );
}
} add_aciton( 'init', 'handle_new_forum_topic' );
Here we run through a series of checks to make sure the form submission is legit:
- First we check the “action” to make sure the “new_forum_topic” form was actually submitted.
- Immediately after this we use check_admin_referer to verify the form nonce.
- Assuming check_admin_referer() didn’t stop things we now verify that:
- A post title was provided.
- Some kind of post content was provided.
- And that the user was, in fact, logged in. If new_post_author is blank (a spam bot may have unset our default value) or the default “dummy” user ID we ignore the submission.
Finally, if everything appears to be ok, we send our information to wp_insert_post. This function helpfully returns the new-created post’s ID, which we can then give to wp_set_object_terms to assign it to the correct forum.
One More Thing
So what happens after our new forum topic is posted? Ideally it’d work similar to an actual forum, where the user is shown their new topic in all it’s glory. This is why, after creating the forum topic and assigning it to a forum, we set $_POST[ ‘new_forum_topic’ ] to permalink of our newly created post. We can snatch this value in one last function that will redirect our user to their new forum topic:
function hook_template_redirect() {
if ( isset( $_POST[ 'new_forum_topic' ] ) )
wp_redirect( $_POST[ 'new_forum_topic' ] );
} add_action( 'template_redirect', 'hook_template_redirect' );
That’s It! Really!
Assuming I haven’t made any errors in adapting Leyline’s code to this tutorial you should now have all the code you need (minus actual theme templates, which you can build like any other template file) to get a forum running. Just drop it in a plugin and have fun.