As WordPress organically evolves based on the needs of the community — from blogging engine, to content management system, and toward an app platform — it’s become ever more important that its default behavior can be modified to suit a wide range of demands. The primary means of making customizations is through the use of hooks, which consist of actions for executing functionality at a specific point in time, and filters for modifying data passed through them. Plugins and themes take advantage of this system to extend WordPress in an unlimited number of ways.
One of my very first plugins simply limited the length of post titles so they didn’t break the design if they were too long. We’ve all come a long way since then!
According to the reference at adambrown.info, the number of hooks available in WordPress has grown from 175 in version 1.5 to 671 in version 2.5, all the way up 1,588 in version 3.4!
Even with the ever-expanding growth of opportunities to modify the way WordPress behaves, sometimes the existing hooks just aren’t enough — or at least they don’t appear to be at first glance or based on the available documentation. That’s when it becomes beneficial to dive into the core code and understand how things operate under the hood.
Working with hooks is pretty straight-forward and there are a number of resources for using them, so I won’t cover that here except to walk through ways to discover and implement them that may not be immediately obvious.
The Problem
Previewing comments before publishing can be immensely helpful, both to verify formatting (Markdown, HTML, etc) and for proofreading purposes. I’ve written plugins in the past to allow for comment previews and really wanted to include them in our recent redesign, but wasn’t satisfied with my efforts from a couple of years ago (who hasn’t been there before?). Time for a new approach.
The problem is that WordPress runs comments through a few filters before saving them and any number of plugins can modify the data along the way. So how do I go about making sure comment previews are filtered exactly the same way as regular comments?
Copying the code from core that processes comment data before it’s saved to the database and modifying it to fit my needs is one approach, but that means any future changes would need to be manually synchronized and there would be a ton of redundant code (copying code from core and tweaking it can be a decent solution in other scenarios when done in moderation).
Combing Core for a Solution
What I really wanted to do was take advantage of the existing code and do as little work as possible. Looking at where the comment form was pointing, I noticed it submitted to wp-comments-post.php
in the root directory and taking a look at that file revealed a bunch of sanitization and validation functionality, among other things, that I definitely didn’t want to mess with.
Unfortunately, it didn’t look like all the filters were actually applied in this file or that there was a way to short-circuit the request before the comment was saved using wp_new_comment()
towards the end of that file. Bummer.
But I decided to search core for “function wp_new_comment” just to see what that function does (a good IDE will help find function declarations much quicker).
As it turns out, that’s where the filters are applied and the data is finally passed off to wp_insert_comment()
for actual saving in the database. Still, it didn’t look like there was a way to access the data and return it for display before it was saved. It is being passed through a couple of functions just before being saved, however, so I decided to check those out.
The first, wp_filter_comment()
, didn’t provide any help, but wp_allow_comment()
provides a filter at the last possible moment, and it has access to all of the filtered data. Nice!
Limiting Impact
Now that I have a hook that will provide the filtered data at a point before it’s saved, how do I short-circuit the request without affecting normal comment submissions or allowing the preview to be saved? My hook callback needed to be aware of the request context and limit its impact to comment previews only. Since this request is being submitted via AJAX, I simply added an argument called “mode” to the request and set its value to “preview” so that when my callback function that’s hooked into pre_comment_approved
is fired, it can check the super global $_POST array for the current “mode” and only take action during a comment preview request.
Additional processing needed to be done to mimic the HTML output of comments and to handle whether or not users are logged in, but the basic hook callback boiled down to this little bit of code:
<?php | |
/** | |
* Filter requests for comment previews and return JSON. | |
* | |
* @param mixed $approved Approval status (0|1|’spam’). | |
* @param array $commentdata Comment information. | |
* @return mixed Prints comment data if context is preview, otherwise returns approval status. | |
*/ | |
function blazersix_comment_preview( $approve, $commentdata ) { | |
// Check to make sure the context is ‘preview’. | |
if ( isset( $_POST[‘mode’] ) && ‘preview’ == $_POST[‘mode’] ) { | |
// Retrieve custom HTML here. | |
$commentdata[‘html’] = blazersix_get_comment_html( $commentdata ); | |
// Short-circuit the request and return comment data. | |
echo json_encode( $commentdata ); | |
wp_die(); | |
} | |
return $approve; | |
} | |
add_filter( ‘pre_comment_approved’, ‘blazersix_comment_preview’, 10, 2 ); |
The takeaway is that learning WordPress core and especially how to read it can have huge benefits in discovering the best way to do something, even if it’s not obvious. In essence, core code can serve as the ultimate documentation.
Dig in, there’s plenty to learn.
Taking It Further (Wishlist)
I really, really, really wanted to run our comments through GitHub’s Markdown Rendering API for some killer code formatting (it wouldn’t be hard). While I doubt we’d have any problems, I’m just a little leery of running into their API limits or being cut off with little recourse in the event it did happen. Seriously, though, that’s a service they should consider adding as a paid feature in addition to their excellent version control offerings.
I did build a plugin for embedding GitHub Gists in posts via oEmbed, which is what’s being used for the code in this post. Oh, and it also caches them locally in case GitHub is unreachable, like it was this past week. Check it out if you’re interested in embedding code in your own WordPress blog.
Leave a comment or share this post if you enjoyed it and would like to see more. We won’t mind. Honestly.
Leave a Reply