Tag Archives:

WordPress

Links and Video of the Week (2011/7)

Akismet vs. Antispam Bee

How to fight off comment spammers? With a anti-spam plugin, of course. Akismet is quite popular (it’s included with WordPress, after all), but it’s got its drawbacks: It sends all data to its servers – after all, that’s how it works –, which may be a problem if you’re concerned about privacy; despite quite a goot job of detecting manual comment spam, it may cause false positives, i.e. good comments that falsely get treated as spam; newer versions fill(ed) the database with statistics even for deleted comments (latest version probably fixes this); and there are discussions about Akismet no longer being free (especially if you’re new and need an API key).

So I instead chose Antispam Bee now, which does its job very good without sending all data around, etc. (though it’s got options for IP filtering by country and more), and I’m quite satisfied with it, so it will stay.

I elaborated more on all this in the German version of this post (see link above the headline); major reason for writing an English version at all is one more thing about a plugin conflict that might be interesting to non-German-speakers:

Antispam Bee and Ajax Comment Preview

One of the Bee’s most important methods for combating spam bots is changing the name of the comment entry field – it’s no longer called comment but got a number added. (The ID is the same, so no change in accessing it with CSS.) However, this causes Ajax Comment Preview to malfunction – unless you change a bit in its JavaScript code. My solution may not be the most elegant – especially since it has to be adapted for each blog – but it works:

Replace in ajax-comment-preview.js in function send these three lines:

if ( !t.data.comment || t.oldData == $.param( t.data ) ) {
    return false; // Blank || Last AJAX request was the same, so bail on this one.
}

– they are at line 28 directly before the heavily-indented block that starts with jQuery.post – by these:

if (t.oldData == $.param( t.data )) { return false; } // Last AJAX request was the same, so bail on this one.
if (t.data['comment-12345']) t.data.comment = t.data['comment-12345']; //--ag for Antispam Bee
if ( !t.data.comment ) { return false; } // Blank

In this code, you must replace the highlighted number 12345 in both places with the number used on your blog – you find this by simply looking in the source code (or with FireBug or similar) of a page with a comment field, just search for a textarea with name="comment-, that should do it. (And if it won’t work right away, remember to explicitly reload the page in the browser so it loads the modified JavaScript file.)

Alright, any questions, suggestions, or opinions about my code or the plugins?

Gravatar Hovercards – including Easter Egg

Many probably know Gravatar already – these little images are seen at more and more blog comments, after all. For a few months, Gravatar not only offers these images connected to e-mail addresses, but also user profiles that can be displayed as “hovercards” when hovering the cursor above the Gravatar image. This can look like this:

my Gravatar hovercard

Now Hans found an Easter Egg, i.e. a hidden funny function: When you right-click on the Gravatar icon (the lying G on blue background) in the top right corner of a hovercard, all Gravatars (and any additional user images) start rotating. Nice. :) (Though it looks a bit odd with the frame in some themes like mine…)

As can be seen in the Gravatar source code, instead of right-clicking, you can also left-click while holding the Ctrl, Alt or Meta key (the latter is usually not available on PCs). This easter egg works for me under Windows 7 with Firefox, Chrome, and Safari1, but not with Opera nor IE 8.

Hans’s post today and Chaosweib’s yesterday made me (1) finally add content to my Gravatar profile, and (2) add the hovercard in my blog, too.

Both already linked this plugin, and Hans linked this DIY description (German) which just requires one line of code to include the needed JavaScript file from Gravatar.

Unless you want it a bit more tailored: Since Gravatars are only used on posts and pages (unless you use them e.g. in the sidebar too), you don’t need to include the file for the index and the archives, so I put this code inside a function that’s called from header.php:

//Gravatar Hovercards:
if (is_singular()) { 
    echo '<script type="text/javascript">var description = \'\';</script>'."\n";
    wp_enqueue_script( 'gprofiles', 'http://s.gravatar.com/js/gprofiles.js', array( 'jquery' ), '', true );
}

(Works directly in header.php too; as ususal it must be at an appropriate place inside a <?php?> block.)

Since is_singular() – which limits the output to posts, pages, and attachments – only works after WordPress is properly initialized, this code won’t work directly (i.e. outside of a function) in functions.php.

The echo line makes the hovercard (not the egg) work in Internet Explorer, eliminating a problem in some themes (including mine) – this is described in this forum thread at the end.

Alright, what do you think about the hovercard – useful? nice? annoying? waste of resources?

  1. on the iPad, the keys are missing; tapping and holding the icon doesn’t work []

WordPress: Hint for False-Positive Spam

Paper spam ?! Another little code for the blog solution of mine that may not be perfect yet helpful…

Many encountered this problem already: A comment was erroneously marked as spam by the spam filter – the well-known Akismet (like here), another plugin or just too strict a blacklist. Such false positives happen on my blog about once every three weeks on average.

The experienced blogger or blog commenter knows that (s)he just has to wait until the blog owner (hopefully) manually approves the comment. The inexperienced commenter, however, may be at a complete loss when confronted with the top of the page that even after scrolling down does not reveal his/her comment; some may try to comment again, some may never return.

A hintful message may help – but (unlike for comments in moderation) WordPress doesn’t offer such a message. The normal loop that outputs the comments actually doesn’t know which current comment is from the caller, since the part of the URL that specifies this – #comment-123, which in particular causes the correct positioning in the browser1 – is not part of the request that’s visible to the server, but remains inside the browser.

Now one might add the comment number during the redirect after the comment posting as an & parameter to the URL (and query this in the output loop), but I somehow don’t like this method – who knows if this doesn’t cause several such URLs floating around the search engines. I found two other solutions I’d like to show you here:

Solution 1: Spam comment from the same IP address?

The first idea: Check during comment output if there’s a spammed comment from the past few minutes from the same IP address that the current request is coming from. In a function for the theme’s functions.php, this looks like this:

function ag_spammed_comment ($gotcomments) {
    global $wpdb, $post;
    $spamcom = $wpdb->get_results ("
        SELECT * FROM $wpdb->comments
        WHERE comment_post_ID = '$post->ID'
          AND comment_author_IP = '".$_SERVER['REMOTE_ADDR']."'
          AND comment_approved = 'spam'
          AND comment_type = ''
          AND TIME_TO_SEC(TIMEDIFF(NOW(),comment_date))<120");
    if ($spamcom) {
        if (!$gotcomments) echo '<ol class="commentlist">';
        foreach ($spamcom as $sc) {
            echo '<li id="comment-'.$sc->comment_ID.'" class="comment caughtasspam">'.
            '<strong>Apparently, the automatic spam filter marked your comment as spam.</strong><br/>'.
            'If this was a mistake, please be patient until the comment is approved manually.'.
            '</li>'."\n";
        }
        if (!$gotcomments) echo '</ol>';
    }
}

Call this from comments.php with ag_spammed_comment (true); (wrapped in <?php ?>) after the output of existing comments and with false instead of true in the branch for a yet uncommented post – of course you only need this differentiation if you want to include (and style) the message in the ol/ul comment list; if you want to use a separate div block, you can do without it.

Then of course style the .caughtasspam class in your style.css accordingly (e.g. with a red frame).

However, there’s a little…

Problem: The cache

If you’re using a cache plugin such as WP Super Cache that temporarily stores generated pages, there’s the problem that such a plugin – originally appropriately – doesn’t invalidate the affected page’s cache, i.e. still delivers the same old page without the code above having a chance to print its hint.

One solution: Modify the plugin such that spam comments (not spam trackbacks) do delete the cached page. For WP Super Cache, this can be done in wp-cache-phase2.php in function wp_cache_get_postid_from_comment where after

} elseif ( $comment['comment_approved'] == 'spam' ) { 

you replace these two lines:

if ( isset( $GLOBALS[ 'wp_super_cache_debug' ] ) && $GLOBALS[ 'wp_super_cache_debug' ] ) wp_cache_debug( "Spam comment. Don't delete any cache files.", 4 );
return $postid;

with these:

//--ag: for false-positive message
if ( $comment['comment_type'] == '' ) {
    if ( isset( $GLOBALS[ 'wp_super_cache_debug' ] ) && $GLOBALS[ 'wp_super_cache_debug' ] ) wp_cache_debug( "Spam comment. But update cache for post $postid to allow for false-positive message.", 4 );
    return wp_cache_post_change($postid);
} else {
    if ( isset( $GLOBALS[ 'wp_super_cache_debug' ] ) && $GLOBALS[ 'wp_super_cache_debug' ] ) wp_cache_debug( "Spam trackback. Don't delete any cache files.", 4 );
    return $postid;
}

Then the hint works. However at the expense of some performance if real spammers happen to try to flood a page with spam comments that is also often viewed by visitors – which, though, won’t happen that often on most blogs, I guess.

The bigger disadvantage in my opinion, however, is that you thus have (another?) plugin where you have to be careful to copy the changes to the new version during an update. Since I’d like to skip such tasks, I’m using another way:

Solution 2: JavaScript (with jQuery)

Of course this solution won’t work if the commenter has disabled JavaScript in his browser – a disadvantage I’m willing to accept, hoping this combination will be rare enough. Moreover, this solution also works if someone is using proxy servers that change on every request.

There are even two variations of this solution, the first of which I’ll only outline briefly: You use the comment_post_redirect filter (which is called in wp-comments-post.php) to modify the redirection’s target URL in case of a spam comment such that #comment-123 is replaced with something like #spammed and then use JavaScript to display (or fill) a block that’s prepared in the theme but originally set to display: none (or empty) if #spammed is part of the URL – which is something that JavaScript, unlike the server, does have access to.

Variation 2 which I’m using here works without such a filter and just looks if there’s an element named #comment-123 on the (complete loaded) page at all. If not, the message is inserted (via JavaScript to avoid it being indexed by search engines):

<div id="spammedhint" class="comment caughtasspam" style="display:none;"></div>
<script type="text/javascript">
<!--
var theUrl = document.location.toString();
if (theUrl.match("#comment-")) {
    var theHash = theUrl.substr(theUrl.indexOf("#"));
    if (jQuery(theHash).length==0) {
        jQuery(document).ready(function() {
            jQuery("#spammedhint").html("<strong>Apparently, the automatic spam filter marked your comment as spam.</strong><br/>"+
            "If this was a mistake, please be patient until the comment is approved manually.").fadeIn();
            var targetOfs = jQuery("#spammedhint").offset().top;
            jQuery("html,body").animate({scrollTop: targetOfs-20}, 500);
        });
    }
}
//-->
</script>

The presence check is done with if (jQuery(theHash).length==0), since jQuery() always returns an object, which means that if (jQuery(theHash)), which one might think of first, is always true. The last jQuery line then scrolls to the message blog (rather, a little above it) within 500 ms; I had sometimes problems with very short times, maybe because the document wasn’t all readyafter all and the browser jumped back to another position. (Maybe that only happens on a reload, though, which is often used during testing.)

I added this HTML/JS code directly (and of course not wrapped in <?php ?>) to comments.php directly after <?php if ('open' == $post->comment_status) : ?> thus directly before the output of the input fields.

Now this method has a side effect which, on one hand, unfortunately (but probably very rarely) will occur when someone got a link from somewhere with a wrong or nonsensical comment number, but on the other hand fortunately also occurs when someone bookmarked his comment that went through at first but is spammed later, since the message is shown then too.

Which also allows you to test this function easily – I prepared such a link here. :) You can also write a new comment and include “diesisteinspamtest” (German for “thisisaspamtest”) in it since I added this “word” to the blacklist. But don’t overdo it since I got to approve these comments…

So if you don’t use a cache plugin, you can easily use the first solution, otherwise you got to weigh the pros and cons; as I said I chose solution 2.

Any opinions, criticism, ideas, problems, questions…?

  1. if you got a flawed theme that doesn’t add this ID to the comments, thus causing all commenters staring at the top of the page after commenting, now’s finally the time to fix this… []