Basic Email Form with Honeypot & Captcha

(by Crisses - "Xes")

This is a basic walkthrough that uses PmForm, Captcha and a HoneyPot to set up a relatively simple "email me" form that is hardy against spambots. I've been using these webforms on over 30 websites spanning the last 5 years and the spambots still don't "get it" on the honeypot so I only get "human spam" i.e. a real person spammed me, not a bot, and there's very very few spams that get through these forms (by 2011).

Preparation

Install PmForm and the Captcha recipe.

config.php

As stated on the main PmForm page, I recommend having a separate library directory for any recipes that you use. You will put your Site.PmFormTemplates into this directory. To let PmWiki know to look in this directory for missing pages, modify config.php directly after the <?php if (!defined('PmWiki')) exit(); or "php line" of your config.php:

$WikiLibDirs = array(&$WikiDir,new PageStore('$FarmD/xeslib.d/{$FullName}'),
  new PageStore('$FarmD/wikilib.d/{$FullName}'));

When a page is not found in your wiki.d directory, PmWiki will look in xeslib.d (or whatever you named it) for the page. When a page is edited in the wiki, it will not overwrite the file in your xeslib.d directory, it will be saved in the wiki.d directory.

Next you may put $WikiTitle and any other information that must come before your PmForm recipe. I put $WikiTitle before I call PmForm because I get emails from many websites -- I want to know which website is emailing me. You can add your PmForm recipe before you define $WikiTitle if you aren't using $WikiTitle in your email subject line (see below).

Put your include statements for pmform.php and captcha.php:

include_once('cookbook/pmform.php');
include_once('cookbook/captcha.php');

Define your form in config.php

I use the form reference "mailme" for my email forms. This is the word I will use in the PmWiki directive, directly in the wiki web pages later on to say "Put My Form Here".

In the example the email will be sent TO criss@example.com, and will seemingly come "from" mywiki@example.com. Keep in mind that some services don't like an email that comes "from" the same email address it is going "to" -- it's more likely to be marked as SPAM. PmWiki doesn't care what email address it comes from so you can fake the From address as long as it's a valid email address. Whatever you choose for your "From" address, add it to your email program's address book (or whitelist if you have one).

I need 2 other pieces of information that relate to defining the actual form itself, and defining the format of the email that gets sent to me. I call the form definition #mailmeform and the email format definition #mailmepost.

This is what my PmForm definition statement looks like in config.php:

$PmForm['mailme'] = 'subject="Email from '.$WikiTitle.'" 
                         mailto=criss@example.com 
                         form=#mailmeform 
                         fmt=#mailmepost 
                         from=mywiki@example.com';

If you aren't using $WikiTitle then it would look something like this:

$PmForm['mailme'] = 'subject="Email from my website" 
                         mailto=criss@example.com 
                         form=#mailmeform 
                         fmt=#mailmepost 
                         from=mywiki@example.com';

Review config.php

This is what the top of your config.php file might look like:

<?php if (!defined('PmWiki')) exit();
$WikiLibDirs = array(&$WikiDir,new PageStore('$FarmD/xeslib.d/{$FullName}'),
  new PageStore('$FarmD/wikilib.d/{$FullName}'));

$WikiTitle = 'Your New Website'; //wiki title

/******* PmForm ********/
include_once('cookbook/pmform.php');
include_once('cookbook/captcha.php');
//$EnableCaptchaImage=1;
$PmForm['mailme'] = 'subject="Email from '.$WikiTitle.'" 
                     mailto=criss@example.com 
                     form=#mailmeform 
                     fmt=#mailmepost 
                     from=mywiki@example.com';

Defining Forms

Create or edit the page called Site.LocalTemplates on your wiki. When you're creating your forms, you may want to refer to Site/PageListTemplates and Site.PmFormTemplates for ideas on forms, code examples, and variations.

I use a horizontal rule ---- to separate my templates on Site.LocalTemplates. Some people also use [@...@] around PageList definitions to tell the page not to process the definition, but I sometimes preview my forms directly on the Site.LocalTemplates page, so I don't do that for my form definition.

The first line that is mandatory starts the form definition. We defined this in "form=" in our $PmForm statement in config.php. I used #mailmeform so I start my definition with the anchor [[#mailmeform]].

The next line sets the value of "successpage" for the form. If the form is submitted, this is where the website visitor will go next. Whatever the value is, you will want to put a "Thank You" page here. So I use the directive (:template defaults successpage='Email/ThankYou' :) next to tell the form where to send the person after they submit it. Sometimes I will go right away and create that page with a "Your email has been received thank you for your time etc. blah blah" message just so I don't forget to do it later. But you can create the page after testing your form and landing on a "page does not yet exist" message.

If you make a mistake when submitting a form, you don't want to re-enter all your information again. We need a line to tell the form to "remember" the information in case the web visitor has made a mistake. This line tells it to remember the data submitted and pre-fill out the form when there's an error: (:input default request=1:)

Just before creating your form itself, you want the visitor to know when they make a mistake. We have a directive that will print PmForm error messages. I like to "cheat" and make the messages red, because time & time again people don't see these messages unless they stand out. Let's take care of it here, although it might be more elegant to take care of this in css elsewhere:

>>red<<
(:messages:)
>><<

Honeypot

Let the games begin! Here we play a very simple game to "fool" spambots into filling in an extra line on the form that most users cannot even see. The "comment" wikistyle uses css to hide the line. The line is given the ultra-V1ag4a-tempting title "Subject" (your language may vary: the $[Text] encoding is a basic PmWiki language translation feature). Then there's an input text field defining "Subject".

%comment% $[Subject]:      (:input text Subject size=30:)

Later we will tell the PmForm NOT to send an email if the "Subject" is filled in. It will send a message to the browser asking the "person" to leave "Subject" blank. This message is for accessibility purposes -- if the person is a real human using an alternative browser that cannot handle css, they will get a message and be able to remove text from the Subject line and re-submit. Spam-bots do not wait for error messages, and can't understand them.

Email Form

I use a "simple table" to make my form pleasing to the eye, but otherwise this is pretty standard. Again for "Comments" we see the language translation feature. It's a hold-over from me copying the form from PM, but you might want to make the entire form language-sensitive.

||width='' 
|| Name:||(:input text name size=30:) ||
|| Phone:||(:input text phone size=30:) ||
|| Email:||(:input text email size=30:) ||
$[Comments]:\\
(:input textarea comments rows=8 cols=60:)

Ok, that's the form body. Next we have to put the captcha information in before we end the form:

Enter code: {$Captcha} (:input captcha:)

The code {$Captcha} will be replaced either with a random numeric image OR with random numbers, then we have a small text field that will capture the visitor's response.

Then we end the form:

(:input submit name=post value='$[Send]':)
(:input end:)

And last we have to end the PmForm form template definition:

[[#mailmeformend]]

Congratulations, you now have a basic form template. :) But we now need the end that processes the data sent in the form.

Review form definition

Here's what I have:

NOTE: The "input pmform" line is necessary but not mentioned above /JB 2018-0328

----
[[#mailmeform]]
(:template defaults successpage='Email/ThankYou' :)
(:input pmform target={$$target} successpage={$$successpage} :) 
(:input default request=1:)
>>red<<
(:messages:)
>><<
%comment% $[Subject]:      (:input text Subject size=30:)
||width='' 
|| Name:||(:input text name size=30:) ||
|| Phone:||(:input text phone size=30:) ||
|| Email:||(:input text email size=30:) ||
$[Comments]:\\
(:input textarea comments rows=8 cols=60:)\\
Enter code: {$Captcha} (:input captcha:)
(:input submit name=post value='$[Send]':)
(:input end:)
[[#mailmeformend]]

Email Format Definition

Now we need to define the format for the email we receive, as well as process information from the form. First tell PmWiki that the definition is starting. This is defined in the fmt= statement in our config.php as #mailmepost. So our first line is an anchor: [[#mailmepost]]

Next we want lines telling PmForm which data is required. Each line will have an associated error statement that will print in the (:messages:) directive in the form definition. In my form, I require a name and comments, and I reject anything other than an empty Subject (for the honeypot):

(:template require name errmsg="$[Please enter your name]" :)
(:template require Subject match="" errmsg="$[Please leave the subject field blank]" :)
(:template require comments errmsg="$[Comments required]" :) 

We also require that the captcha is completed properly:

(:template requires if="captcha" errmsg="Please re-enter the message code" :)

Now if all those tests are passed, a text message will be generated and sent to the TO email we defined in config.php. In the form we define input variables. In the form we print them out by enclosing them in {$$variable}

Name: {$$name}
Phone: {$$phone}
Email: {$$email}

{$$comments}

I like to have a footer that tells me which page they found the form on. This is basically for marketing purposes, because I can put my email form anywhere I want on my website . Were they on my contact page? Were they on a page for a specific service or event? Knowing where they were can put their questions or comments into context for me.

==========
Sent via {$$PageUrl}

And then we end the definition with [[#mailmepostend]].

Review post-form processing & email format

So here's the 2nd template that I've defined in LocalTemplates:

----

[[#mailmepost]]
(:template require name errmsg="$[Please enter your name]" :)
(:template require Subject match="" errmsg="$[Please leave the subject field blank]" :)
(:template require comments errmsg="$[Comments required]" :) 
(:template requires if="captcha" errmsg="Please re-enter the message code" :)
Name: {$$name}
Phone: {$$phone}
Email: {$$email}

{$$comments}

==========
Sent via {$$PageUrl}
[[#mailmepostend]]

Place the form on a page for testing

Now to actually USE your new form. You can put it on Main/ContactUs, or you can put it in Test/TestEmailForm -- it doesn't matter. What you need is a PmForm directive and the reference you gave the form in your config.php file. In this case, I used mailme in the config.php file.

(:pmform mailme:)

I can place this directive anywhere in my wiki and it will pull in the form to email me. So instead of a link to "Email Criss" that sends them to my Contact Us page, I can just drop the whole form in with a little preamble:

For more information and amazing feats by Criss please email me below.

(:pmform mailme:)

Happy emailing! :)


Troubleshooting:

If you test out your form and have any problems, you can email me at https://eclectictech.net/Main/ContactUs and I'll add to this page.

No captcha image

Once you test the form (later), you might require a line $EnableCaptchaImage=1; after you call captcha.php if your captcha is not showing images. This is what the captcha images look like:

Attach:xes_captchasample.png Δ


Best Practices

Security

If you're going to use PmForm you probably want to turn ?action=source off on your website. Using that "action" on someone's wiki can help you learn what they did in the code so you can replicate it, but it also presents possible security issues if you're using data pages or don't want people to see certain information. If I have an email form for people to get on my email list to access special content on my site, the content may be on "SpecialContent/SuccessPage" in my form code. I may not want them to know the landing page they're going to be redirected to unless they fill out my form. Anyway here's how to turn off ?action=source -- add this to config.php after you define your passwords:

$HandleAuth['source'] ='edit';

And now people can't see the wiki code unless they're already able to edit a page anyway.

 0: 00.00 00.00 config start
 1: 00.01 config end
 2: 00.21 MarkupToHTML begin
 3: 00.28 ReadApprovedUrls SiteAdmin.ApprovedUrls begin
 4: 00.28 ReadApprovedUrls SiteAdmin.ApprovedUrls end
 5: 00.30 MarkupToHTML end
 6: 00.30 MarkupToHTML begin
 7: 00.32 MarkupToHTML end
 8: 00.32 MarkupToHTML begin
 9: 00.33 MarkupToHTML end
10: 00.33 now