Contact reCAPTCHA (updated 08/05/2010)
Most websites need a way to allow users to contact the site’s owners. It’s no longer advisable to place an email on a website, as emails can be harvested and their owners become targets for spam.
Contact Form: To avoid the email spam problem we can build a contact form for users to contact our clients. On the form we typically could include a textbox for the user’s name, a textbox for their email, and a textarea for user’s comments. PHP can use the mail() function to send email. Using a form instead of placing an email on a website is both safer and more convenient.
Form Spam: Once forms became commonplace on the web, the ways to exploit the use of forms to promote form spam became commonplace as well. Form spam refers to machine driven spam that is inserted in any and all forms available via the web, for example in the comments area of a blog.
CAPTCHA is a type of challenge-response test used on web pages to ensure that a form response is not generated by a machine. A typical CAPTCHA requires a user type letters or digits presented via a distorted image. CAPTCHA applications and solutions exist in many server side languages, but we currently recommend using reCAPTCHA (covered next).
reCAPTCHA is a free CAPTCHA web service that helps to digitize books, newspapers and old time radio shows. reCAPTCHA requires that the web site owners that wish to use the service sign up, and receive public and private codes that are sent each and every time a request is made to the service. The website owner must download an API file that will sit on the website and contact the reCAPTCHA server that will both serve up the image and provide feedback to insure a human is entering data in our contact form. Here are the details of the reCAPTCHA API
Add Your Own Elements/Requirements: Another nice thing about this package is the contact page can accommodate any number of form elements of many types including radio buttons, checkboxes & selects. All form elements you add will be sent as name/value pairs of email data thus:
ITC280 message from: Bill Newman, February 4, 2010 2:04 am
From Website: ITC280
Name: Bill Newman
Email: bill@newmanic.com
How Did You Hear About Us?: Magazine
Interested In: New Website,Lollipops
Join Mailing List?: Yes
Comments: Great website!
Package Contents: The contents of this zip file is the reCAPTCHA API file required to contact the reCAPTCHA server and a couple of related files that will be our contact page, plus a JavaScript utility file. Here is an overview of the involved files:
File | Purpose | Related Files | Notes |
recaptchalib.php | This reCAPTCHA library file includes all code necessary to contact the reCAPTCHA server. We reference this file from our forms to use their system. | contact.php uses this file to provide all web service contact with reCAPTCHA | recaptchalib.php is provided by reCAPTCHA and goes into the include folder |
contact.php | This is our contact form which allows a name, email and comment field and emails the data to our website owner. | Uses recaptchalib.php to connect to the reCAPTCHA web service. Uses util.js to guarantee a valid email is entered. | CSS file goes in an include folder |
util.js | A JavaScript utility file to be sure users enter proper data before submitting | JavaScript code in contact.php references functions like empty() and only_email() found in this file | While reCAPTCHA makes sure no machine enters data, we should make sure our users enter at least a valid email. |
contact_multiple.php | A version of contact.php that demonstrates multiple form elements | contact.php | You can add as many form elements as you wish, and this page shows an example |
no_captcha.php | This version of contact.php has no CAPTCHA |
| For those who don’t need or can’t use reCAPTCHA |
Overview: The file named contact.php is a postback application designed to provide a contact form for users to email our clients. contact.php references recaptchalib.php as an include file which provides all the web service plumbing to connect and serve up the CAPTCHA image and verify we have a human entering data.
contact.php also references a JavaScript include file named util.js which contains utility functions like empty() which requires a user to enter data into a form element, and isEmail() which checks the email as entered by the user to be sure it fits a general definition of a valid email before the form is submitted.
Once the user passes both hurdles by entering valid data per the JavaScript requirements, and enters valid data for reCAPTCHA, the form data is sent via email to our website client.
Installation: Here are the basic steps for installation. The contact pages as they stand will not work until reCAPTCHA license keys are applied. The keys for Zephir are shown below. To work on another domain you must get a reCAPTCHA license for that domain. If you are now installing these scripts on a different server, go to Private & Public Keys From reCAPTCHA and insert your keys into contact.php and/or contact_multiple.php before proceeding.
When first installed your script will not yet send email. Once you have tested your script, proceed to Sending Email (below)
1) Edit & Upload contact.php. Open contact.php and view approximately line 65, and 136:
#include 'include/header_inc.php'; #PLACE REFERENCE TO YOUR HEADER FILE HERE
These lines are references to where your header and footer include files can be placed. Make sure the name and path match your include files. These lines are commented out, so be sure to remove the comment to test!
reCAPTCHA Keys: If you are now installing these scripts on a different server, go to Private & Public Keys From reCAPTCHA and insert your keys into contact.php before proceeding. If you are using Zephir, on about line 50-51, enter the following API keys:
# zephir ONLY reCAPTCHA keys are below:
$publickey = "6Ld11wYAAAAAAMqyo-I6NvENfuD3VJOXRBLG_8cE";
$privatekey = "6Ld11wYAAAAAAEbf282RKWoikILiUBE7U1QJzfmO";
Once you’ve made sure your include file paths & names match, upload this file to the root of your web application space.
2) Upload recaptchalib.php and util.js: Inside this folder you’ll find a folder named include. Inside this folder you’ll find the files recaptchalib.php and util.js. These files need no editing, but are required to be uploaded into an include folder inside your web application space.
Testing reCAPTCHA: Now we can point our browser to the web address for contact.php and test our form. If we do not see the reCAPTCHA icon show up, it is likely you have a licensing issue. Remember that the contact.php form is only licensed to Central. Be sure to troubleshoot any PHP errors before you proceed. IMPORTANT: Spell zephir with a “i” not a “y” when testing via a browser. Otherwise reCAPTCHA won’t recognize the domain.
Sending Email: Now that you have tested your form, you may notice you get a message no email was sent. Download contact.php and look for the setting at about line 58 that says:
$sendEmail = FALSE; //if true, will send an email, for real! Otherwise just show user data.
Change this to TRUE.
$toAddress is the email address where you would like to send the email. $fromAddress is a fallback in case of problems and should be filled out as the ‘noreply’ user from the same domain. Edit these at about lines 55-56:
$fromAddress = "noreply@example.com"; #replace example.com with the website's domain - leave set as 'noreply'
$toAddress = "fake@example.com"; //place your/your customer's email address here - ZEPHIR WILL ONLY EMAIL seattlecentral.edu ADDRESSES!
$Website = "My Customer's Website"; //place name of your customer's website here
$sendEmail = FALSE; //if true, will send an email, for real! Otherwise just show user data.
These lines must all match your client’s domain, website name and sendEmail must be set to true.
Using Zephir? Zephir will only send email to a Seattle Central Email Address. View the code at approximately line 55-56:
$fromAddress = "noreply@example.com"; #replace example.com with the website's domain - leave set as 'noreply'
$toAddress = "fake@example.com"; //place your/your customer's email address here - ZEPHIR WILL ONLY EMAIL seattlecentral.edu ADDRESSES!
example.com is the only known domain that will NOT be forwarded by ANY server. We need to carefully change the from and to addresses above. Example.com will be changed for the domain where your script will reside. The toAddress will be your Central Email address. If my Central login was horsey01, these lines would look like this after editing:
$fromAddress = "noreply@seattlecentral.edu"; #replace example.com with the website's domain - leave set as 'noreply'
$toAddress = "horsey01@seattlecentral.edu"; //place your/your customer's email address here - ZEPHIR WILL ONLY EMAIL seattlecentral.edu ADDRESSES!
DON’T MAKE A MISTAKE HERE! Ask Bill about when the ITC280 class got banned in South America! We need to make sure we don’t start sending spam, while trying to prevent it!
Not using your Central Email address? Here is a small Jing video Sara has done to show you how to setup your Central Email and Forward It To Your Preferred Email
Additional Configuration
Private & Public Keys From reCAPTCHA: By default our contact.php script only works from a Seattle Central server, in our case Zephir. reCAPTCHA is free, however it is subject to licensing, and will only work with private/public keys that are licensed to each domain. To be able to use reCAPTCHA on a different domain, you need to get your private and public keys from http://recaptcha.net/api/getkey
Once you have your private public keys, be sure to store them in a safe place, and edit contact.php at approximately lines 50-51:
# zephir ONLY reCAPTCHA keys are below:
$publickey = "xxxxxx";
$privatekey = "xxxxxx";
Replace these keys with the keys given to you by reCAPTCHA.
contact_multiple.php: To see how a form can be setup to allow several different types of form elements, test with the contact_multiple.php version included in the zip file. Remember it must be edited in the same way that contact.php was edited, although the line numbers will vary.
Adding/Changing Form Elements: Feel free to change the form to add any elements you wish. There are only 2 form elements that should be left alone the Name & Email elements that figure in the Subject Line and return email address. Any other form elements added, with any nearly name or type (radio, checkbox, select, etc.) will be delivered via email with user entered data. Form elements named with underscores like:
<input type="radio" name="How_Did_You_Hear_About_Us?" value="Internet Search" /> Internet Search
Will be replaced with spaces to allow for a better formatting in the email created:
How Did You Hear About Us?: Internet Search
Form element names must not contain spaces and should start with a letter, and be limited to mainly letters of the alphabet. Note we used the question mark above, and since we’re not entering data in the DB, we should get away with this.
Adding Checkboxes: In PHP checkboxes need to have the characters "[]" (empty square brackets) placed at the end of each checkbox name. This allows PHP to send an array of items:
<input type="checkbox" name="Interested_In[]" value="New Website" /> New Website <br />
<input type="checkbox" name="Interested_In[]" value="Website Redesign" /> Website Redesign <br />
<input type="checkbox" name="Interested_In[]" value="Lollipops" /> Complimentary Lollipops <br />
The square brackets will not be delivered to the email. Without these brackets PHP delivers only the last item checked.
Required Form Elements: You can also add as many radio buttons, checkboxes and selects as you wish. If you add form elements that you wish to make mandatory, consider adding to the JavaScript that requires form elements via the empty() function (approx. line 75):
<script type="text/javascript">
//here we make sure the user has entered valid data
function checkForm(thisForm)
{//check form data for valid info
if(empty(thisForm.Name,"Please Enter Your Name")){return false;}
if(!isEmail(thisForm.Email,"Please enter a valid Email Address")){return false;}
if(empty(thisForm.Comments,"Please Enter A Comment")){return false;}
return true;//if all is passed, submit!
}
</script>
If I added a <select> form element, for example, named “Customer_State” to identify which state the user comes from:
<select name="Customer_State">
<option value="">Please pick a state</option>
<option value="CA">California</option>
<option value="OR">Oregon</option>
<option value="WA">Washington</option>
</select>
Note in a <select> the first option must not be one of the valid choices, but instead include instructions for making a selection (‘Please pick a state’, highlighted above).
We also need to add a reference to the JavaScript to require the form element be selected by the user:
<script type="text/javascript">
//here we make sure the user has entered valid data
function checkForm(thisForm)
{//check form data for valid info
if(empty(thisForm.Name,"Please Enter Your Name")){return false;}
if(empty(thisForm.Customer_State,"Please Enter Your State")){return false;}
if(!isEmail(thisForm.Email,"Please enter a valid Email Address")){return false;}
if(empty(thisForm.Comments,"Please Enter A Comment")){return false;}
return true;//if all is passed, submit!
}
</script>
Also be sure to place a ‘marker’ (red asterisk?) next to the required elements, so users know what is expected. Also place the checks for empty() in the order that the form elements appear in, so a user is not confused by the feedback when they do not enter required data.
APPENDIX
POST Embedded as JavaScript Array: You may notice that no PHP code is required to be placed in the ‘form area’ (starting approx line 122). This is highly unusual (to date) because the POST data needs to be placed back into the proper form fields when the user enters an incorrect reCAPTCHA. All manner of PHP plumbing could do this job, but PHP code would be wired directly into the form elements, defeating the purpose of our ‘easy’ to use form.
The way around this problem (in this case) is that the POST data is printed (embedded) to the page as a JavaScript array on Postback (when the data is returned to the page with POST data) via a PHP function sendPOSTtoJS():
function send_POSTtoJS($skipFields)
{#sends PHP POST data to a JS array, where it will be picked up and matched to form elements, then elements will be reloaded
$aSkip = explode(",",$skipFields); #split form elements to skip into array
echo '<script type="text/javascript">';
echo 'var POST = new Array();'; #JS Array is named POST
foreach($_POST as $varName=> $value)
{#loop POST vars to create JS array on the current page
if(!in_array($varName,$aSkip)|| $varName == 'Email')
{#skip skipField elements - all except Email!
if(is_array($_POST[$varName]))
{#checkboxes are arrays, and we need to loop through each checked item to insert
echo 'POST["' . $varName . '"] = new Array();';
foreach($_POST[$varName] as $key=>$val)
{#here we have an array as an element of an array
echo 'POST["' . $varName . '"][' . $key . ']="' . $val . '";';
}
}else{//not an array, so likely text, radio or select
echo 'POST["' . $varName . '"] = "' . nl_2br2($value) . '";'; #nl_2br2() changes c/r to <br /> on the fly, helps JS array!
}
}
}
echo 'addOnload(loadElements);'; #loadElements in util.js will match form objects to POST array
echo '</scr';
echo 'ipt>';
}#end send_POSTtoJS()
The JS array is then read by JS during the window.onload phase via a function named loadElements() which compares the POST array to the objects, and loads data to match what the user had entered originally!
function loadElements()
{//function called onLoad to reload form elements passed to JS via PHP
for (var f = 0; f<document.forms.length;f++)
{//loop through array of forms - will try each one since onload allows no parameters
thisForm = document.forms[f]; //the current form
for(x=0; x<thisForm.elements.length; x++)
{//loop array of form elements
fObj = thisForm.elements[x]; //the current element
//accommodate 'brackets' on the element name which means an array in PHP
var fCheck = "";
var brackets = fObj.name.indexOf("[]");
if(brackets != -1)
{//found one! remove brackets from form element name
fCheck = fObj.name.substring(0,brackets);
}
//identify the element type, match the POST array & load data
switch(fObj.type)
{//handle radio & checkbox elements first
case "radio":
case "checkbox":
for(var i in POST)
{//loop POST array transferred from PHP
if(fObj.name == i || fCheck == i )
{//radio or checkbox with same name, with or without "[]"
if(fObj.value == POST[i]){fObj.checked = true;}//radios, checkboxes w/o "[]"
for(var z=0;z<i.length;z++)
{//radios, checkboxes with [], z is the internal array of checkbox, etc.
if(fObj.value == POST[i][z]){fObj.checked = true;}
}
}
}
break;
//handle text, textarea & password elements here
//I was surprised this works for single item selects, too!
default:
for(var i in POST)
{//loop POST array transferred from PHP
if(fObj.name == i)
{//radio or checkbox with same name, with or without []
var postStr = POST[i];
var postStr = postStr.replace(/<br \/>/g, "\n"); //replace <br /> tag fObj.value = postStr;
}
}
}//end switch
}//end form elements loop
}//end forms loop
}//end loadElements()
Carriage Returns & Break Tags: the <textarea> elements presented a special challenge as the newline characters created when a user hit a carriage return in a <textarea> breaks the JS POST array as described above.
To get around this problem, we replaced all carriage returns, line feeds and new line characters with <br /> tags via a PHP function named nl_2br2() (newline to break):
function nl_2br2($text)
{
$text = str_replace(array("\r\n", "\r", "\n"), "<br />", $text);
return $text;
}#end nl_2br2()
We would do this as a matter of course if we wanted to store this data in a database. When the data to be viewed was printed on a page, the <br /> tags would place breaks in the text, just as we would expect. However, if we want to allow a user to edit this data, we would convert the <br /> tags back to newline characters via a related function:
function br_2nl($text)
{
$nl = "\n"; //new line character
$text = str_replace("<br />",$nl,$text); //XHTML <br />
$text = str_replace("<br>",$nl,$text); //HTML <br>
$text = str_replace("<br/>",$nl,$text); //bad break!
return $text;
}#end br_2nl()
String Sanitization: (optional function) While we’re sending data to our client’s email, we may wish to remove any potential ‘hacker bits’ that might have been sent along as well. However, sanitizing the string of the ‘bad stuff’ can sometimes remove more than we wish.
If we were inserting data into a database we could use a built in PHP function like mysql_real_escape_string() to do this work for us. However, not every site uses a database, so we’ll need to use something else.
If we were using PHP 5.2 or higher (and could guarantee our customers were at least to that version) we could use data filtering. However since this is not always available, here’s a homegrown function, sanitize_it() to do some minimal cleanup for us.
NOTE: sanitize_it() has been commented out of both contact.php & contact_multiple.php on about lines 201 & 233 respectively. Uncomment and test if you wish to use it. Here’s a breakdown of the function on about line 258 of contact.php:
function sanitize_it($str)
{#We would like to trust the user, and aren't using a DB, but we'll limit input to alphanumerics & punctuation
$str = strip_tags($str); #remove HTML & script tags
$str = preg_replace("/[^[:alnum:][:punct:]]/"," ",$str); #allow alphanumerics & punctuation - convert the rest to single spaces
return $str;
}
We use the built in PHP function strip_tags() to remove HTML and JS script references. Then we use a Perl Compatible regular expression function preg_replace() to remove anything that is not an alphanumeric (letter or number) or common punctuation (see highlights above).
The last highlight is the single space that is the REPLACEMENT for any supposedly ‘bad data’. Since we have encountered several false positives, we’re using the single space instead of an empty string to replace the data instead of remove it.
This function was removed since it was creating oddly formatted emails on some servers. It’s been left in place if you wish to work with it!