WordPress MU and AJAX comments
November 3rd, 2007 |Discontinued
This plugin has been discontinued. There are better, more compatible ones available by now so there’s really no reason to further support this one!
How complicated could it be to adopt a WordPress plugin to work with WordPress MU? Pretty complicated, if you’re the purist and perfectionist I am. From adoption to rewrite, up to a weird WordPress issue i am currently fighting.
Starting
I already mentioned a while ago that I am looking for an elegant solution of combining multiple blogs. Well, turns out that WordPress can do just this for me, with the superior free thing that WordPress MU is. During installation I stumbled up a PlugIn that is called AJAX Comments, and this is something I always wanted to have in WordPress. So i thought I’ll just grab it, bring it to WordPress MU and enjoy the sexyness of AJAXified commenting.
I wished.
Reality turned out to be much more cruel. First of all the plugin didn’t work. Ok, probably I’m just missing out on a few adoptions.
Probably that would have done the trick, but as I started digging into the code I got enthusastic to bring this thing to a more sophisticated stage of operating. What was bothering me about that PlugIn?
- It’s all mixed in one file
- The whole comment processing happens independent from WordPress core functionality
- Therefore it’s interfering with other plugins
- It needs in-depth adoption for different themes, which especially for use with WordPress MU seemed to be a problem
Please note that, by now, there’s still a problem with fetching the WordPress error message, so the PlugIn will just use a generic, non-localized, uninformative standard error message!
Organize, baby!
So I started rewriting (as I thought it might be interesting to others I added precious documentation to everything, so if if you’re not interested in my rambling and just want to get to the code have a look at the sources). First of all WordPress MU seems to not like PlugIns inside folders (at least not in the folder /mu-plugins), so I put the main file to /mu-plugins and added the folder for the additional stuff. Then I started separating functionality. The JavaScript was output through that very file, due it needed a few little parameters. I took the whole JS-functionality and put it into one clean, static .js – file and then hooked in a few variable definitions that would solve the parameter problem. So let’s have a look how the JavaScript-sections turn out after the little rework.
-
global $ajax_comments_count;
-
$ajax_comments_count = 0;
-
add_action('wp_footer', 'ajax_comments_getcount');
-
add_action('comment_text', 'ajax_comments_countdisplay');
-
add_action('wp_head', 'ajax_comments_js');
What for?
43, 44 and 45 handle with a simple but annoying issue. The problem is that when a page is viewed by anyone who is logged in chances are that there are more comments actually being displayed than are approved. For example if you’re an admin you’ll see all unapproved comments, but the variables available in WordPress core functions don’t tell us anything about how many comments there actually are on the page. So what I did was to hook a function to comment_text, abusing a filter, to increment the global variable $ajax_comments_count by 1 for every actually displayed commment. Neat:
-
@/wpmu/mu-plugins/ajax-comments/functions.php
-
function ajax_comments_countdisplay($comment = '') {
-
global $ajax_comments_count;
-
$ajax_comments_count++;
-
return $comment;
-
}
Line 44 hooks in just before the closing body-tag and hardcodes the JavaScript variable telling the scripting-magic-pixie-stuff if there’s an even or odd number of comments:
-
@/wpmu/mu-plugins/ajax-comments/functions.php
-
function ajax_comments_getcount() {
-
global $ajax_comments_count;
-
echo '<script type="text/javascript">ajax_comments_odd = '. ($ajax_comments_count % 2 == 0 ? 'true' : 'false') .';</script>' . "\n";
-
}
Multiple Theme Support
Line 46 deals with everything else regarding JavaScript, including theme-specific settings. Yep, if you install the original PlugIn it soon turns out that you’ll have to modify it for various themes. That’s disturbing, but ok in general. However, when using this for WordPress MU you’d run into problems if the available themes use different markup. So I added a few settings to make this more flexible:
-
function ajax_comments_js() {
-
require_once('themes.php');
-
if (!$ajax_comments_themes['hardcode']) {
-
$curTempDir = (substr(TEMPLATEPATH, strlen(dirname(TEMPLATEPATH))));
-
$allThemes = array_keys($ajax_comments_themes);
-
for ($i=0; !is_array($theme), $i<count($allThemes); $i++) {
-
if (strpos($curTempDir, $allThemes[$i])) {
-
$theme = $ajax_comments_themes[$allThemes[$i]];
-
}
-
}
-
if (!is_array($theme)) $theme = $ajax_comments_themes['default'];
-
} else {
-
$theme = $ajax_comments_themes[$ajax_comments_themes['hardcode']];
-
}
Okay, so let’s have a look at this. Line 2 includes the theme-settings script themes.php. In this script you can specify theme-specific settings, like so (this also is the default setting the PlugIn falls back to if it can’t figure out anything else):
-
@/wpmu/mu-plugins/ajax-comments/functions.php
-
$ajax_comments_themes['default'] = array(
-
'commentform', // [0] = id or classname form
-
'commentlist', // [1] = id or classname 04: comment-<ol>
-
'commentform', // [2] = id or classname element comments are inserted before
-
'' // [3] = comma-separated list of element-id's to hide
-
);
Line 3 is easy: If there’s a hardcoded theme-setting the PlugIn will obey, use it and just go on. The more interesting part happens if there’s no hardcoded setting. Then the current template path is used to find a matching setting (hint: if you add settings, name them according to the directory they are located at and all the magic will do just fine). If there’s no matching setting available, the default settings will be used, matching the default kubrick-theme.
-
@/wpmu/mu-plugins/ajax-comments/functions.php
-
$hideMore = $theme[3];
-
if (strpos($hideMore, ',') !== false) {
-
$all = explode(',', $hideMore);
-
for ($i=0; $i<count($all); $i++) {
-
$all[$i] = trim($all[$i]);
-
}
-
$hideMore = join("','", $all);
-
}
-
-
if (!empty($hideMore)) $hideMore = "'". trim($hideMore) . "'";
$hideMore can be one or many Id’s, so the PlugIn has to act accordingly, creating a nice string that can be used as output to feed the new Array() that soon will follow:
-
@/wpmu/mu-plugins/ajax-comments/functions.php
-
echo '<script type="text/javascript" src="'. PLUGIN_AJAXCOMMENTS_ROOT.PLUGIN_AJAXCOMMENTS_PATH .'ajax-comments/scriptaculous/prototype.js"></script>' . "\n";
-
echo '<script type="text/javascript" src="'. PLUGIN_AJAXCOMMENTS_ROOT.PLUGIN_AJAXCOMMENTS_PATH .'ajax-comments/scriptaculous/scriptaculous.js"></script>' . "\n";
-
echo '<script type="text/javascript" src="'. PLUGIN_AJAXCOMMENTS_ROOT.PLUGIN_AJAXCOMMENTS_PATH .'ajax-comments/ajax-comments.js"></script>' . "\n";
-
echo '<script type="text/javascript">' . "\n"
-
.' ajax_comments_path = "'. PLUGIN_AJAXCOMMENTS_ROOT.PLUGIN_AJAXCOMMENTS_PATH .'";' . "\n"
-
." ajax_comments_form = '${theme[0]}';\n"
-
." ajax_comments_list = '${theme[1]}';\n"
-
." ajax_comments_here = '${theme[2]}';\n"
-
." ajax_comments_hide = new Array($hideMore);\n"
-
."</script>\n";
-
}
On to sophisticated AJAX-Magic
There you go. All JavaScript-stuff done, theme-related settings included. So we can head on to the next part our PlugIn will have to do server-sided: Handling the AJAX-request. Here is where to biggest changes happened, because I totally changed the way this worked. The original PlugIn was called instead of WordPress, then imported WordPress-functionality and finally handled the comment-process itself, hardcoded and separated from core WordPress functionality. This is a method I just can’t get along with. Not only does it intefere with all other PlugIns by just stripping them out, but it also makes the PlugIn very unstable due it totally relies on the current state of WordPress.
Integrating
PlugIns should be integrated into, and therefore benefiting of, the core application’s functionality. WordPress offers a very nice way of doing so: Hooks, that is. I already handled the JavaScript-stuff with hooks, so doing the same with the AJAX-functionality seemed like a good and clean idea. After a little research, however, it turned out that action hooks are a kind of unconsistent implemented thing. There are a lot of hooks, granted, but there are also a lot of hooks missing. Especially if it comes to comments, things get kind of weird. When WordPress encounters an error there’s a good chance it will simply call wp_die();, thus killing the app immediately, without any notice to PlugIns. That’s no good. But let’s get back to this later. First the core AJAX-section:
-
@/wpmu/mu-plugins/ajax-comments.php
-
if(isset($_POST['ajax-comments-submit'])) {
-
add_action('comment_post', 'ajax_comments_send');
-
}
Update: The PlugIn now takes the whole error-page as a response and then strips out the error-message from there. This way no files need to be modified whatsoever.
The first hook is a native WordPress-hook and calls ajax_comments_send() after a comment is saved to the database. That’s exactly when we want to jump in to cut off the regular application, fetch the last comment and send it back as AJAX-response. Nice. However when I started looking for the hook when a comment-error is encountered I soon realized how loose WordPress’ error-handling is. To be honest I was disappointed, that’s really bad implementation there. Let’s have a look at wp-comments-post.php and see what’s happening if there’s an error:
-
@/wpmu/wp-comments-post.php
-
// If the user is logged in
-
$user = wp_get_current_user();
-
if ( $user->ID ) {
-
@/wpmu/wp-comments-post.php
-
} else {
-
if ( get_option('comment_registration') )
-
wp_die( __('Sorry, you must be logged in to post a comment.') );
-
}
Uhm … great. No error is being generated, no action is being called, no handling of this issue whatsoever occurs. The developer is being left with a silent application kill, that’s just a no-go. If wp_die() would call do_action() at least we could react to this in some way, however we can never be sure what happened, because there’s no error code or anything like this. All we would know is: The application has been terminated. Have fun!
However, that seems to be the most we can do without going in for some really crucial changes on WordPress’ core. So I added one line of code that should keep me busy for about 10 hours or so:
Update: The PlugIn now takes the whole error-page as a response and then strips out the error-message from there. This way no files need to be modified whatsoever.
AJAX-Response
Looks easy and makes sense, due the function ajax_comments_send() is hooked in to wp_abort and the $message is the most we can get out of WordPress at the time. I’ll explain the problem with this line at the end, due it’s still unresolved. Let’s have a look at the last function:
-
@/wpmu/mu-plugins/ajax-comments/functions.php
-
function ajax_comments_send () {
-
$passed = func_get_arg();
-
$comment = @get_comment($passed);
So far, so easy. We fetch the argument, check if it was a valid comment-id, and die() with whatever error-message has been sent by do_action(‘wp_abort’) being identified with a status code 406 header if something went wrong. get us the saved comment’s data and go on:
-
@/wpmu/mu-plugins/ajax-comments/functions.php
-
header('Content-type: text/html; charset=utf-8');
-
ob_start();
-
$comments = array($comment);
-
global $comment;
-
include(TEMPLATEPATH.'/comments.php');
-
$commentout = ob_get_clean();
-
-
preg_match('|(<li.*</li>)|ims', $commentout, $matches);
-
-
die($matches[1]);
-
}
-
}
This part is hardly modified from the original. This idea is great and I love the simplicity how the comment can be integrated in the current theme without any problems. What we do is we call the template with a fake list of comments only including the one generated by the AJAX-request. Then we fetch out this one comment from the generated HTML and return it. Lovely! And works perfect. Basically I only changed the regexp to fetch the whole list-item, not to loose any attributes. The alternation will be dealt with via JavaScript.
Update: Due the function is only being called if a comment has successfully been saved we don’t need to implement any kind of error handling here. How useful would the message ‘unknown error’ be anyways?!
Finally: DHTML
This part already was very nice, so I only made a few minor changes. I didn’t like the alerts so I added a function that would display the message inside a div with the id ajax-comments-message, nested in another div and getting class error attached if status code 500 is being returned (wp’s header status for wp_die()). Also, if the result is an error, the actual message has to be fetched from the whole page, which is pretty easy due to very simple markup. A lot of words, let’s just have a look of how this turns out:
-
<div id="ajax-comments-message" class="error">
-
<div>
-
the message
-
</div>
-
</div>
The outer div is for easy CSS access and may has the class error, the inner div is for DHTML compatibility (guaranteeing a firstChild-node) and contains whatever WordPress returns in $message (at the time either p or ul). Furthermore in the original plugin there was no check if the form is there. So I added an onload-block:
-
@/wpmu/mu-plugins/ajax-comments/ajax-comments-js
-
ac_oldLoad = window.onload;
-
window.onload = function () {
-
ac_oldLoad;
-
f = ajax_comments_find_element(ajax_comments_form, 'form');
-
if (f) {
-
f.onsubmit = ajax_comments_submit;
-
new Insertion.Bottom(f, '<input id="ajax-comments-submit" name="ajax-comments-submit" type="hidden" value="1" />'); // toggle ajax-catch
-
}
-
};
This ensures that the AJAX-functionality on the server is only activated when the script can find the comment-form, otherwise the behaviour will fall back to default. The rest of the JavaScript is pretty straight forward, so just browse through the file if you want to know more.
A pain in the Source
If you’re still reading you’re probably interested in my problem with the do_action() in wp_die(). This is an interesting story and I’d be glad if you have a look at it in the WordPress Support Forum Thread I opened, hoping for help.
Update: The PlugIn now takes the whole error-page as a response and then strips out the error-message from there. This way I don’t need the do_action() in wp_die() anymore, however it’s interesting why it didn’t work …
Nice work!
One small bug, though. When I submit a comment, I see “1 Response to ””. Title is blank until I hit refresh. Comment count is also wrong.
I’ve WP-Cache plug-in. A conflict maybe?
Thanks :)
Not sure how you see the “1 Response to”-thing, due the plugin shouldn’t fiddle around with that line at all (it would require quite a lot of coding for a very minor mathematical correctness).
However I’ll look into it, probably there’s a lightweight solution I’ve overlooked.
Thanks for reporting!
[...] Plugin-Entwicklungs-Doku: KnoDocBlog [...]
[...] Update And this rework is well documented. [...]
Thank you for this documentation also :)
Error message remains when a successful submission follows a failed submission. Also error message does not change when a failed submission follows another different type of failed submission. I think you should clear the error message each time the submit button clicked.
Another issue, is it possible to make the newly posted comment style different to the last comment when alt comment style is used for comments, though it’s not a big deal to be the same.
And thanks for your good job.
Hi,
nice plug-in, good on new wp themes, but I have sodelicious 1.0 black (I’ve already make it widget ready and change comments.php) so now it works in Opera and FF but fall in IE6.
when I hit ‘submit’ instead of run script inside of page it open wp-comments-post.php with new post.
What can be wrong with it?
thank you
Hi Logan,
thanks for the hint, you’re right and I guess that’s a minor thing to fix. I’ll get into it as soon as my system is back up and running again :)
I’m not sure what you mean by alt-comments. The script usually applies the “alt”-css class to every other comment automatically. If you talk about a whole different styling you could set up a new template and hardcode the file’s name in ajax-comments/functions.php, around line 75.
Hope that helps!
Hi oxyk,
I tried to check the problem on your page, however it doesn’t seem to be working at all. If I try to access a post’s page I’m redirected back to the main page again.
I guess if wp-comments-post.php is opened directly there’s an issue with the paths, due the page should never open separately – but as said above I can’t seem to check it.
Hi Christian,
Thank you for the quick reply, it helps me to solve the problem :)
It was submit button that was as image so IE have a hard times to submit it properly.
btw, I use external (not wp) ajax script named “lightbox” to make pics look better. it loads prototype.js and scriptaculous.js?load=effects – so when I enable your plugin pics’s effects do not work properly.
Now I load ajax-comment plugin without those 2 libraries and it works fine for me.
Thank you fr the great plugin ;)
so I don’t need to make form validation now.
Glad I could help :) Happy blogging!
Thanks for this, I’m hoping to use it in a project I’m working on, but I’ll also be hacking it to support threaded comments.
Cheers,
Beau
[...] Support [...]
Free gay porn trailers
Valentine card s
does this plugins compatible with ajax edit comments?
testing
test~~
[...] Plugin-Entwicklungs-Doku: KnoDocBlog [...]