Adding field to form & fixing Akismet privacy

In a previous post I asked how to do this:

I got around to figuring out the programming to do it purely in functions.php today. As I expected it’s very simple, certainly not the kind of thing you need a separate plugin for. There is an important privacy surprise, so please to read to the end. First we need to add the Akismet field to the comment form:

function akismet_comment_consent( $fields ) {
  $fields['akismet']='<p class="comment-form-akismet-consent"><input id="wp-comment-akismet-consent" name="wp-comment-akismet-consent" type="checkbox" value="yes" required="required" /><label for="wp-comment-akismet-consent">I agree to my comment being scanned for spam by Akismet.</label></p>';
  return $fields;
}
add_filter( 'comment_form_default_fields', 'akismet_comment_consent' );

This is just a straightforward use of the comment_form_default_fields hook where we are adding to the $fields array. The HTML is structured in exactly the same way as the cookies consent field, with the only difference that I’ve added the required="required" attribute/value which instructs the client’s browser to require the checkbox is checked before submission.

Next we need to prevent comments being submitted without accepting the checkbox.

function akismet_consent( $commentdata ) {
  if ( (!is_user_logged_in())&&('post'===get_post_type($_POST['comment_post_ID']))&&(!isset($_POST['wp-comment-akismet-consent'])) ) {
    wp_die( '<strong>' . __( 'ERROR: ' ) . '</strong>' . __( 'Please accept the Akismet checkbox.' ) . '<p><a href="javascript:history.back()">' . __( '&laquo; Back' ) . '</a></p>');
  }
  return $commentdata;
}
add_filter( 'preprocess_comment', 'akismet_consent', -1 );

Here we’re using the preprocess_comment hook. This hook runs before a comment is set, and it’s the same one that Akismet attaches to. The conditions therefore for returning an error is whether this is a comment that has been submitted to a post, from a user that is not logged-in, and who did not accept the Akismet checkbox. Obviously the conditional check for the post type would need to be modified if you’re allowing comments on pages. If the Akismet checkbox value wasn’t set then return an error.

Now the part that surprised me. Even if your comment never gets submitted to your database because of checks you’ve run sever-side, there’s a good chance it is being sent to Akismet first.This is because Akismet sets itself to high priority. To be safe with the consent checkbox, we’ve set our function to an even higher priority. However given this behaviour we’ll also reduce the Akismet priority to below normal so it will trigger after any other functions attached to the preprocess_comment hook, and in the process also disable Akismet for logged-in users:

function lower_akismet_priority() {
  $akismetfilt=remove_filter( 'preprocess_comment', array( 'Akismet', 'auto_check_comment' ), 1 );
  if ( ($akismetfilt) && (!is_user_logged_in()) ){
    add_filter( 'preprocess_comment', array( 'Akismet', 'auto_check_comment' ), 20 );
  }
}
add_action( 'init', 'lower_akismet_priority', 99 );

I’ve just edited this last function, because it dawned on me that PHP isn’t really a low-level programming language and I can’t be sure the IF statement isn’t rewritten when compiled.

There’s no “update_filter()” function, so the filter must be removed first as described here. Of course there’s a chance that lowering the priority will break something, but I can’t imagine what that is so unless I encounter a problem with this approach I will leave it like this.

And we’re done!

2 Likes

While I let others scrutinise my code, let me point out this isn’t really suitable as a general purpose solution as it is. If you have open registrations on your website this will allow anyone who registers to avoid Akismet by signing-in, something you probably don’t want. In that scenario you’ll want to use the comment_form_logged_in_after hook and then exclude the trusted classes (probably everyone except subscribers, I believe one acceptable way to do that would be first check user is logged in then check permissions: (!current_user_can('edit_posts'))).

1 Like