Preventing Duplicate Submissions from CF7

Situation: You have users who submit forms but you don’t want them to submit more than once.

Solution: add a custom validation to a form field that uniquely identifies the submission.

This solution is for Contact Form 7. Contact Form 7 has a hook into which you can inject custom validation code. It is somewhat limited, so read carefully.

Let’s assume that essentially what you need to do is to prevent users from submitting a form more than once with the same email address in it. In this example, we will put a validation on one particular form field and check if that email was submitted previously. We will ignore all other fields in this example.

The limitation to the Contact Form 7 field validation is that although we can put in code to validate a particular field, in that code we can’t know what form the submission is coming from. This means that if you set up a validation on a field named “email” then that validation will be run against all form submissions that have an “email” field. If you have more than one form with a field named “email” but you only want this validation run for one particular form, then you have a problem.

The best idea is to give a unique name to the email field in your form. Use this name only on this form and none other. In this example, we use “email_123″.

To create the validation, we add WordPress filter code into Tools -> Add Actions and Filters provided by the “Add Actions and Filters” plugin. This is the same technique used in Changing Form Data Before it is Saved.

The following code is an example. You will need to make some changes to make it work for you. In the my_validate_email function:

  • Change $formName to the name of your form
  • Change $fieldName to the name of your email field
  • Optionally change the error message
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
function is_already_submitted($formName, $fieldName, $fieldValue){
    require_once(ABSPATH . 'wp-content/plugins/contact-form-7-to-database-extension/CFDBFormIterator.php');
    $exp = new CFDBFormIterator();
    $atts = array();
    $atts['show'] = $fieldName;
    $atts['filter'] = "$fieldName=$fieldValue";
    $exp->export($formName, $atts);
    $found = false;
    while ($row = $exp->nextRow()) {
        $found = true;
    }
    return $found;
}
 
function my_validate_email($result, $tag) {
    $formName = 'email_form'; // Name of the form containing this field
    $fieldName = 'email_123'; // Set to your form's unique field name
    $name = $tag['name'];
    if($name == $fieldName){
        $valueToValidate = $_POST[$name];
        if (is_already_submitted($formName, $fieldName, $valueToValidate)) {
            $result['valid'] = false;
            $result['reason'][$name] = 'Email has already been submitted'; // error message
        }
    }
    return $result;
}
 
add_filter('wpcf7_validate_email*', 'my_validate_email', 10, 2);

 

If you field is not an email field, use ‘wpcf7_validate_text‘ instead of ‘wpcf7_validate_email‘, e.g.:

29
add_filter('wpcf7_validate_text*', 'my_validate_email', 10, 2);

(Thanks to a user for pointing this out)

  1. March 1st, 2013 at 12:51 | #1

    I am trying to check 2 fields: email and licenseplate. I there a way to do that?

  2. Michael Simpson
    March 1st, 2013 at 15:20 | #2

    You can check them individually but not as a a set.

    The CF7 validation is done independently for each individual field. This is “field level validation” and there is no available “form level validation.” I encourage you to post on the CF7 forum to request this feature.

  3. March 1st, 2013 at 16:06 | #3

    Would you be able to give me a quick lesson on how to do this? I can get one or the other to work but not both.

    Thanks for you time.

    I will post this as a feature in the cf7 forum

  4. Michael Simpson
    March 1st, 2013 at 17:58 | #4

    To check both fields (independently)
    1. make two copies of the my_validate_email function, giving each a different name.

    In each:
    1.1. Give each one a different $fieldName.
    1.2. Update the error message

    2. Provide 2 add_filter function calls, one to to register each of those functions (change the 2nd argument to function names in (1))

  5. March 1st, 2013 at 18:11 | #5

    Thanks for the response. I tried it and it didn’t work, I think number 2 I am confused about.

    Here is what I have:

    [code]function is_already_submitted($formName, $fieldName, $fieldValue){
    require_once(ABSPATH . 'wp-content/plugins/contact-form-7-to-database-extension/CFDBFormIterator.php');
    $exp = new CFDBFormIterator();
    $atts = array();
    $atts['show'] = $fieldName;
    $atts['filter'] = "$fieldName=$fieldValue";
    $exp->export($formName, $atts);
    $found = false;
    while ($row = $exp->nextRow()) {
    $found = true;
    }
    return $found;
    }

    function my_validate_email($result, $tag) {
    $formName = 'Free Wash'; // Name of the form containing this field
    $fieldName = 'email'; // Set to your form's unique field name
    $name = $tag['name'];
    if($name == $fieldName){
    $valueToValidate = $_POST[$name];
    if (is_already_submitted($formName, $fieldName, $valueToValidate)) {
    $result['valid'] = false;
    $result['reason'][$name] = 'Email has already been submitted'; // error message
    }
    }
    return $result;
    }

    function my_validate_license($result, $tag) {
    $formName = 'Free Wash'; // Name of the form containing this field
    $fieldName = 'licenseplate'; // Set to your form's unique field name
    $name = $tag['name'];
    if($name == $fieldName){
    $valueToValidate = $_POST[$name];
    if (is_already_submitted($formName, $fieldName, $valueToValidate)) {
    $result['valid'] = false;
    $result['reason'][$name] = 'License Plate has already been submitted'; // error message
    }
    }
    return $result;
    }

    add_filter('wpcf7_validate_email', 'my_validate_email', 10, 2);
    add_filter('wpcf7_validate_email', 'my_validate_license', 10, 2);
    [/code]

  6. March 1st, 2013 at 19:19 | #6

    Figured it out, the above code is right except this line
    add_filter(‘wpcf7_validate_email’, ‘my_validate_license’, 10, 2);
    should be
    add_filter(‘wpcf7_validate_text’, ‘my_validate_license’, 10, 2);

    Thanks for the help

  7. March 18th, 2013 at 18:45 | #7

    Another Question on this script.
    Is there away to ignore the case when comparing values in this script?

    Thanks

    Jeff

  8. Michael Simpson
    March 18th, 2013 at 21:39 | #8

    @Jeff Frey
    What case? I don’t follow.

  9. March 19th, 2013 at 10:12 | #9

    One of the fields we are checking is a license plate number. We need when checked it doesn’t look at upper letter as different form lower case letters.

    Sorry could have explained that better the first time.

    Thanks for your help.

    • Michael Simpson
      March 19th, 2013 at 12:56 | #10

      Try using regex in the filter:

      6
      
      $atts['filter'] = "$fieldName~~/$fieldValue/i";
  10. March 19th, 2013 at 13:24 | #11

    Thanks Michael,
    It works perfect.

    I appreciate all your help!

    Hope this helps others as well.

    Thanks again.

  11. Alberto
    April 1st, 2013 at 18:29 | #12

    Hi;

    I want to validate the submission of only one national document number in the form.

    I try to submit same document but the functions do not detect it.

    I try with the default WP theme.

    It is a required field, and I try with the following code too:
    add_filter(‘wpcf7_validate_text*’, ‘my_validate_email’, 10, 2);

    Anything else to try?

    Thanks!!!

    • Michael Simpson
      April 5th, 2013 at 20:07 | #13

      A thought: the “wpcf7_validate_text” is hooked into CF7 text fields. If you are trying to validate a different kind of field, then you might need a different hook name. Use the debug technique to print out something to see if your function is even called.

  12. jasnon
    September 13th, 2013 at 12:19 | #14

    Hi Michael,

    I’m glad I came across this post, this is exactly what I was looking for. I’m trying to take it a step further though and validate previous submissions for specific users only (preferrably the currently logged in user). I’m guessing this is something I need to add to the filter but I can’t figure out exactly what to add. I tried using the normal syntax from shortcodes but it didn’t seem to work. This is what I have currently:

    $atts['filter'] = “$fieldName=$fieldValue&&Username=$user_login”;

    Thanks as always for the help.

    • Michael Simpson
      September 14th, 2013 at 15:12 | #15

      Try this:

      if (is_user_logged_in()) {
      $current_user = wp_get_current_user(); // WP_User object
      $user = $current_user->user_login;
      $atts['filter'] = "$fieldName=$fieldValue&&Submitted Login=$user";
      }

  13. jasnon
    September 16th, 2013 at 19:45 | #16

    Worked perfect!

    Thanks again for the help, your support is always appreciated.

  14. kevinharter
    December 4th, 2013 at 12:47 | #17

    Thanks for the post, this helps tremendously! I have it working on my site, but I need to only limit the email being used once DURING THE CURRENT MONTH. Currently, it won’t let that email address to be used again EVER.

    I’m trying to integrate a contest into CFDB. Essentially, I want to restrict entries to once a month, with the “reset” occurring at midnight on the first day of the month.

    Any help is appreciated!

    • Michael Simpson
      December 4th, 2013 at 14:32 | #18

      Try using something like this for the filter in is_already_submitted:
      $atts['filter'] = "$fieldName=$fieldValue&&submit_time>last month";

      That “submit_time>last month” identifies submissions from the last month (but not just in the current month) to exclude. I don’t think that is exactly what you want but should point you in the direction. See also: Filtering Submit Time and PHP strtotime which interprets English text like “last month”

  15. kevinharter
    December 4th, 2013 at 14:58 | #19

    Thank you for the “Holy Quick!” reply, Michael! This indeed gets me close to my goal. I appreciate your time.

  16. londonxn
    February 25th, 2014 at 12:34 | #20

    Hello Michael, I am having issue with duplicate entries in Contact Form DB database. I am using this extension along side “Contact Form 7 email verification” which it is to allow for email addresses to be verified.

    Each time when a form submitted, it writes to the database and then it duplicate it after person verified her/his email. Basically the submit button is triggered twice.

    I have contacted with the developer of “Contact Form 7 email verification” but he confirmed that this is something that can be done from your side.

    Would you please kindly help me with this and if there is a function to stop this double entry.

    thanks

  17. Isidoros
    April 4th, 2014 at 06:15 | #21

    @Michael Simpson
    Hello Michael, is it possible to check two or more fields as a set?
    Thks for this great plugin

    • Michael Simpson
      April 4th, 2014 at 08:18 | #22

      No because the CF7 validation hook is called independently for each individual field. This is called “field level validation”. As far as I know, CF7 lacks a “form level validation” hook that we could use to check more than one field together. For example, if you had “City”, “Zip” fields you can use field level validation to check that the zip was valid and the city was valid but not that the city and zip matched.

      I posted this on the CF7 forum some time ago but it has not be addressed to my knowledge.

      That being said, you can try editing the CF7 code to try to make this happen.

  18. Isidoros
    April 4th, 2014 at 11:07 | #23

    Accept my apologizes for not identifying the answer but your proposed link gave me some ideas. Thanks for the immediate response!

  19. andymaciel
    August 4th, 2014 at 22:41 | #24

    Hello Michael!

    For long time ago I´ve been using CFDB making my programming easier, with this post I could add the functionality that my client wanted, and because of that, I´ve already donate to you and want to thank you for all the hours you´ve been working so hard, I just came here to say THANK YOU Michael, thanks not only for the time you spent programming but also for the time you take to sit and answer each message for helping all of us.

    Warm regards from Mexico!

  20. reddo
    August 14th, 2014 at 16:49 | #25

    @Michael Simpson Ny chance to get the full code to use here? I tried replacing line 6 from your code with this, but it doesn’t seem to work. What should I use for `$fieldname` on line 17?

    • Michael Simpson
      August 14th, 2014 at 17:03 | #26

      Lines 16 & 17 are the only one you should need to change.

  21. reddo
    August 14th, 2014 at 17:12 | #27

    I meant to use the filter on the logged in user and I’ve used the code

    if (is_user_logged_in()) {
    $current_user = wp_get_current_user(); // WP_User object
    $user = $current_user->user_login;
    $atts['filter'] = “$fieldName=$fieldValue&&Submitted Login=$user”;
    }

    from a previous comment of yours.

  22. Michael Simpson
    August 14th, 2014 at 17:20 | #28

    @reddo
    If you have any further difficulty, please post on the support forum: http://wordpress.org/support/plugin/contact-form-7-to-database-extension

  23. reddo
    August 14th, 2014 at 17:28 | #29

    I think we misunderstood each other :(. I still need help with using the above code. I tried placing it in the place of row 6 of your original code, but I still can submit the same info twice from the form. I changed $formName to my form’s name, but I’m not sure what should I set for $fieldName. If you would be so kind to point me in the right direction, I would be very grateful.

  24. reddo
    August 15th, 2014 at 03:46 | #30

    Hi again. First of all, sorry for the amount of comment. I just wanted to say that I got it to work, it was my mistake, I used wpcf7_validate_text when I actually had a select field. switched to wpcf7_validate_select and all is well. Thank you for this great plugin. Rating it 5* right now!

  1. No trackbacks yet.
You must be logged in to post a comment.