Recently we had an interesting client support issue: the client was getting an unruly number of identical entries on their Gravity Forms (a popular WordPress form plugin) -based surveys. Typically a duplicate here or there doesn’t really matter, but this particular client was using the surveys in classrooms full of impatient, click-hungry kids. Our client was seeing exact duplicate submissions from the same classroom at the same time, sometimes inflating the number of submissions from a class well over the number of survey respondents.
We were able to purge the existing duplicate survey results but the client needed a way to prevent this problem from cropping up again. Google searches for “Gravity Forms duplicate prevention” yielded nothing useful (the surveys don’t collect things like email addresses that we could use to simply prevent duplicates based on a given field), so we had to think outside the box. The team, full of people who use and love open source software, also decided that we would package up and release our solution back to the WordPress community. As a result, Buckeye Interactive developed its first (public) WordPress plugin, Gravity Forms Duplicate Prevention.
We had three major goals with the plugin:
- Prevent duplicate submissions
- Make it flexible enough to work without modification to existing forms
There’s a concept within User Experience (UX) design called “Visual Feedback” which, at a high level, is simply responding to user interaction. This could be a progress bar, a hover state on a link, or a depressed state on a button after a user clicks it. Visual feedback is a cornerstone of good interface design and in this instance we found that the survey generated by Gravity Forms was not effective enough in its feedback: the user clicked a submit button and nothing seemed to happen so the user clicked it again.
Enter the Honeypot
A honeypot is a sort of trap that programmers can set to catch unauthorized users of a system. Gravity Forms has a built-in honeypot mechanism that will randomly add an extra field to a form with instructions for the user to leave that input empty. If the field is not empty upon submission, Gravity Forms will assume the form was filled out by an automated spam script (which likely doesn’t know how to read instructions) and will silently discard the entry (the last part is important and will come into play shortly).
WordPress has nice hooks and filters API that allows plugin developers to hook into different points throughout the execution of a page. Gravity Forms makes excellent use of these hooks to allow third-party developers to tie into the lifecycle of a form submission. In the case of our plugin we used the
gform_validation filter, which allows Gravity Forms developers to create custom validation logic.
When a form is submitted, we generate a cryptographic hash of the user data and and store it in a short-lived PHP user session. If the next submission matches the hash we can be almost certain the submission is a duplicate (the chances of an md5 hash collision – two different strings producing the exact same hash – are 1 in 340 undecillion or 340 trillion trillion trillion). If the submitted form’s hash matches our stored value we simply manipulate the form data so that it falls prey to the honeypot, thus tricking Gravity Forms into quietly ignoring the duplicate entry.
The advantage of this approach lies in the fact that we’re not comparing form data to the database (in the case of anonymous surveys it’s perfectly acceptable for two users to have the same answers) but instead to the individual users’ most recent submission. We’re also not penalizing a user for double-clicking (not everyone realizes that it’s not only unnecessary, but problematic, on web forms) nor are we causing site owners to modify their forms.
Gravity Forms Duplicate Prevention is available now in the WordPress.org plugin repository and its source is available on Github. If you have any questions, run into any issues, or have any features you’d like to see please get in touch with us or send us a pull request on Github!