your child theme<\/a>, or build it into a plugin.<\/p>\n\n\n\nLet's break that down, so you can customize it to your own usage.<\/p>\n\n\n\n
Basic Overview of Spam Filter<\/h2>\n\n\n\n We start by overriding any of the spam filters marking an entry as spam. Remember we want this to kill the spam silently.<\/p>\n\n\n\n
That's the purpose of iw_dont_mark_spam()<\/em> and setting it to priority 20. If you're finding this doesn't work, increase the priority. As both the default spam filters and the Honeypot for Contact Form 7 filters are set to priority 10, I didn't need any higher.<\/p>\n\n\n\nNotice that this spam filter is hooked into wpcf7_skip_mail<\/em>? That allows us to do our spam checks and fail the mail silently, while appearing to succeed.<\/p>\n\n\n\nWe run our Honeypot check inside the iw_skip_spam()<\/em> function, because we're skipping it in its default place. If you don't use Honeypot, you don't need to add that.<\/p>\n\n\n\nWe automatically fail any form entries that contain Russian characters. I'm finding a lot of spam has them.<\/p>\n\n\n\n
We then pull in the Disallowed Comment Keys, run them against the words in the form entry and fail the entry if we find one.<\/p>\n\n\n\n
This checks against whole words, stripping out punctuation and capitalization for the checks. It won't affect the formatting of the form entry.<\/p>\n\n\n\n
Detailed Overview of Each Section<\/h2>\n\n\n\n Let's break the code down even further, in case any of the sections don't make sense, or you want to understand exactly which parts you need to customize for your individual use.<\/p>\n\n\n\n
Skip Spam Check<\/h2>\n\n\n\nadd_filter( 'wpcf7_spam', 'iw_dont_mark_spam', 20 );\nfunction iw_dont_mark_spam() {\n return false;\n}<\/code><\/pre>\n\n\n\nThis function tells Contact Form 7 that whatever the results of any other spam checks, you don't want any form entries to be marked as spam.<\/p>\n\n\n\n
If you want it to be marked as spam, and build on any other checks, you might put all of your code in this function (called something else) instead of hooking it into the wpcf7_skip_mail<\/em> filter.<\/p>\n\n\n\nIf you did so, you would need to do it as:<\/p>\n\n\n\n
add_filter( 'wpcf7_spam', 'iw_spam_filter', 20, 2 );\nfunction iw_spam_filter( $spam, $form ) {\n return false;\n}<\/code><\/pre>\n\n\n\nIn that case, $spam<\/em> is essentially an unused variable, with $form<\/em> being the one you might want to work on.<\/p>\n\n\n\nOtherwise, you won't need to touch anything in this function.<\/p>\n\n\n\n
Setup Spam Filter<\/h2>\n\n\n\nadd_filter( 'wpcf7_skip_mail', 'iw_skip_spam' );\nfunction iw_skip_spam() {\n $submission = WPCF7_Submission::get_instance();\n}<\/code><\/pre>\n\n\n\nWe're hooking our spam filtering function into wpcf7_skip_mail<\/em> so that we can fail it silently if we return true;<\/em> or allow it to send the mail with return false;<\/em>.<\/p>\n\n\n\n$submission<\/em> gets the current form. There are other ways of doing it, like passing it into the function, but I found this way to be the most reliable.<\/p>\n\n\n\nRun Honeypot Check<\/h2>\n\n\n\nif ( defined( 'HONEYPOT4CF7_PLUGIN' ) ) {\n if ( true == honeypot4cf7_spam_check( false, $submission ) ) {\n return true;\n }\n}<\/code><\/pre>\n\n\n\nThe Honeypot for Contact Form 7 works by hooking into wpcf7_spam<\/em>. We're overriding that filter, so we have to bring it in here.<\/p>\n\n\n\nTo make sure we're only running this if the honeypot plugin is installed and activated, we wrap it in<\/p>\n\n\n\n
if ( defined( 'HONEYPOT4CF7_PLUGIN' ) ) {}<\/code><\/pre>\n\n\n\nIf honeypot4cf7_spam_check<\/em> finds spam, we want to automatically fail this spam check with return true<\/em>. Otherwise, we continue with the checks.<\/p>\n\n\n\nTurn the whole form entry into a string<\/h2>\n\n\n\n$form_data = implode(\n ' ',\n wpcf7_array_flatten( $submission->get_posted_data() )\n);<\/code><\/pre>\n\n\n\nWe're doing a few things in this line. I've spread it out over a few for the formatting of this blog, but in my code, it's all on one line.<\/p>\n\n\n\n
We start by getting the form data with $submission->get_posted_data()<\/em>.<\/p>\n\n\n\nUsing wpcf7_array_flatten<\/em>, we turn it into a simple array, if necessary. Unless you have a complicated form, it will probably be a simple array at this point anyway.<\/p>\n\n\n\nFinally, we turn the whole form into a single string, with each field separated by a space, with implode()<\/em>.<\/p>\n\n\n\nAutomatically fail if it contains Russian<\/h2>\n\n\n\nif ( preg_match( '\/[\u0411\u0431\u0413\u0433\u0414\u0434\u0401\u0451\u0416\u0436\u0417\u0437\u0418\u0438\u0419\u0439\u041a\u043a\u041b\u043b\u041f\u043f\u0424\u0444\u0426\u0446\u0427\u0447\u0428\u0448\u0429\u0449\u042a\u044a\u042b\u044b\u042c\u044c\u042d\u044d\u042e\u044e\u042f\u044f]\/', $form_data ) ) {\n return true;\n}<\/code><\/pre>\n\n\n\nI included all the upper and lower case Russian letters that aren't similar to the Latin alphabet. If you have other special characters that you want to automatically fail, add them between the square brackets.<\/p>\n\n\n\n
Using preg_match<\/em>, we look through the form for anything that matches a character in the list and return true<\/em> to skip the mail if they are found.<\/p>\n\n\n\nStrip punctuation and turn the form into an array of words<\/h2>\n\n\n\n$form_data = preg_replace( '\/[^a-z0-9]+\/i', ' ', strtolower( $form_data ) );\n$form_data = preg_replace( '\/\\s+\/', ' ', $form_data );\n$form_data = explode( ' ', $form_data );<\/code><\/pre>\n\n\n\nHere we are stripping all punctuation. If you wanted to run a check against email, you would do it before this step.<\/p>\n\n\n\n
We start by turning any uppercase letters to lowercase with strtolower( $form_data )<\/em>.<\/p>\n\n\n\nUsing preg_replace<\/em>, we turn anything that's not a letter or a number into a space. You could probably leave out the 0-9<\/em>, but I wanted to keep my options open.<\/p>\n\n\n\nWith preg_replace( '\/\\s+\/', ' ', $form_data );<\/em> we're turning any double+ spaces created by previous steps into single spaces. This is so we get a relatively clean array in the next step.<\/p>\n\n\n\nUsing explode()<\/em>, we're splitting the form into an array of individual words, wherever there is a space.<\/p>\n\n\n\nThe reason we're turning it into an array and not just operating on the string is so that we can easily get exact match, rather than partial matching.<\/p>\n\n\n\n
Get the Disallowed Words into an array<\/h2>\n\n\n\n$bad_words = get_option( 'disallowed_keys' );\n\nif ( empty( $bad_words ) ) {\n return false;\n}\n\n$bad_words = explode( \"\\n\", trim( $bad_words ) );<\/code><\/pre>\n\n\n\nWe get the list of bad words from Settings -> Discussion -> Disallowed Comment Keys<\/em>. If there aren't any, we're finished with the checks.<\/p>\n\n\n\nThe words in this list should be whole words. Not phrases or partial words. We're checking for whole words later in the list. If you want to do some fuzzy partial matching, you would do it to the string earlier, rather than after you've turned it into an array.<\/p>\n\n\n\n
With explode()<\/em>, we split the bad words into an array. If you look in the database under disallowed_keys<\/em> (in wp_options<\/em>), you will see the list of bad words as a single string. Don't let that fool you: they're invisibly separated by the line ending.<\/p>\n\n\n\nIf you prefer to hard code the bad words, rather than use the Disallowed Comments function of WordPress, add them as an array to $bad_words<\/em>. This can be a good idea if you're the admin of the site for a client or others and you don't want them to have to deal with or edit the bad words.<\/p>\n\n\n\nYou might also hard code your bad words here if you want some words to fail the contact form checks that you're fine with allowing in the WordPress comments.<\/p>\n\n\n\n
If you want to do both, you might add below the last line:<\/p>\n\n\n\n
$more_bad_words = array( 'bad', 'word' );\n$bad_words = array_merge( $bad_words, $more_bad_words );<\/code><\/pre>\n\n\n\nJust replace 'bad', 'word'<\/em> with an array of the words you want to fail the spam test.<\/p>\n\n\n\nDue to the way the spam check works in the next step, you want to have the words with the most frequency at the top if you have a long list.<\/p>\n\n\n\n
Check for spam<\/h2>\n\n\n\nforeach ( $bad_words as $word ) {\n $word = trim( strtolower( $word ) );\n if ( strlen( $word ) < 3 ) {\n continue;\n }\n if ( in_array( $word, $form_data ) ) {\n return true;\n }\n}\n\nreturn false;<\/code><\/pre>\n\n\n\nIn this step, we iterate over the list of bad words individually.<\/p>\n\n\n\n
With the first few lines, we clean up any potential spaces or uppercase letters, skipping the word if it's less than three letters.<\/p>\n\n\n\n
If the word is found in the list of form words, we've identified spam and fail the form without the need for further processing.<\/p>\n\n\n\n
If we get to the end of all these checks, then the form is allowed to be sent. So we finish with return false<\/em>.<\/p>\n","protected":false},"excerpt":{"rendered":"I like Contact Form 7. It’s been around forever, it’s lightweight and simple to setup. It’s perfect when you just need to throw up a contact form onto your site. However, it’s not so good in the spam filtering. I usually use Honeypot for Contact Form 7, which helps, but a fair few bots still<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[14],"tags":[],"yoast_head":"\n
Custom Spam Filter For Contact Form 7<\/title>\n \n \n \n \n \n \n \n \n \n \n \n \n \n\t \n\t \n\t \n