Prevent duplicate comment submissions in Drupal 7

Posted on 02/03/2016

Whenever you use a general purpose CMS and compare it to the systems built for one particular purpose, you will always miss a lot of details. This stands true when you are building an ecommerce website, a blog, a forum, and so on. However, the great thing about Drupal is that you can usually find a contrib module that provides such functionality, you can build it yourself using something like Rules or Views, or ultimately, write that in your custom module.

WordPress is primarily a blogging platform; there are a lot of installations that push the boundaries of what the system can do, but that doesn't change the main purpose of the CMS. As blogging software, WordPress has pre-built functionality when it comes to comments, workflows and editing. The example I will talk about in this article, is posting duplicate comments. When you try to do that in WordPress, you will receive the following message:

Duplicate comment detected; it looks as though you’ve already said that!

This is very handy for multiple reasons; sometimes the internet connection of the visitor can take time to resolve, your website can be slower, or simply the visitor is impatient. Drupal is missing this functionality, but it allows you to provide custom validation to any form on the site. And that's exactly what we will do.

Our goal is to display an error message whenever a user tries to create a comment that he or she has already posted. Error message in a form means that we need to add our custom validation callback to the comment form, and perform the necessary checks there.

This can be done in both - a custom module, and custom theme. This decision is completely up to you, though my preference is to put this in a custom module, because it alters the workflow, and has nothing to do with theming of the website.

Grab an empty Drupal 7 module, use your existing module, or add this to your theme's template.php file:

  1. /**
  2.  * Implements hook_form_alter().
  3.  */
  4. function YOUR_MODULE_OR_THEME_form_alter(&$form, &$form_state, $form_id) {
  5.   // This will filter comment forms for ANY content type.
  6.   if ($form['#id'] == 'comment-form') {
  7.     // Add our custom validation callback.
  8.     $form['#validate'][] = 'YOUR_MODULE_OR_THEME_duplicate_comments_validate';
  9.   }
  10. }

In the code above we just added the name of our function that will be added to the list of validation callbacks executed when the form is submitted. Sometimes you will have multiple validation callbacks that you can attach to forms; whenever possible, split them into different functions (you can always add multiple callbacks at once) and re-use on different forms.

The next step is to write our function. The exact validation depends on the nature of message, but often times users will repeat something they said (e.g. "I agree, please proceed." on a consultation website). What I recommend doing, is to prevent duplicate submissions within the past 5 minutes. This rules out longer page loads or impatient users. Here's what you'd need to do in that case:

  1. /**
  2.  * Constants.
  3.  */
  4. // Number of minutes for which we will check if the user posted a duplicate comment.
  5. define('YOUR_MODULE_OR_THEME_DUPLICATE_WINDOW', 5);
  6.  
  7. /**
  8.  * Comment form validation callback.
  9.  */
  10. function YOUR_MODULE_OR_THEME_duplicate_comments_validate(&$form, &$form_state) {
  11.   global $user;
  12.   // Check if there were similar comments posted for this node.
  13.   $query = db_select('comment', 'c')
  14.     ->fields('c', array('cid'))
  15.     // Make sure that we are searching for duplicates posted for this particular node.
  16.     ->condition('c.nid', $form['#node']->nid)
  17.     // Make sure that we are searching for duplicates of current user.
  18.     ->condition('c.uid', $user->uid)
  19.     // Make sure that the duplicate is not posted in the past 5 minutes.
  20.     ->condition('c.created', REQUEST_TIME - YOUR_MODULE_OR_THEME_DUPLICATE_WINDOW * 60, '>=');
  21.   $query->leftJoin('field_data_comment_body', 'b', 'c.cid = b.entity_id');
  22.   $query->condition('b.comment_body_value', $form_state['values']['comment_body'][LANGUAGE_NONE][0]['value']);
  23.   $duplicate = $query
  24.     ->execute()
  25.     ->fetchAll();
  26.   if (count($duplicate)) {
  27.     // Show the error message.
  28.     form_set_error('comment_body', t('You have already submitted this reply less than @minutes minutes ago.', array('@minutes' => YOUR_MODULE_OR_THEME_DUPLICATE_WINDOW)));
  29.   }
  30. }

Adding a constant at the beginning will allow us to inform the user when did he post the last duplicate. This way, if they for whatever reason want to really post another post with the same content, will understand how the system works.

That's it - the validation callback will be executed for every content type on the site.

I'll create another article explaining how to do the same for Drupal 8.