DEV Community

Chuck Watson
Chuck Watson

Posted on

Contact Form with Recapcha v3 and PHP

I recently decided to update the old contact form on my website as it was an old wordpress plugin with a fairly non-functional verification code system. I figured I would DIY and use Google's Recapcha V3. It is basic html, vanilla javascript and PHP. Since I am using javascript's forEach function it is not supported in IE.

the HTML

<div id="contact-form-box">
    <form method="post" id="contactform" action="your-file-to-post-to.php">
        <div>
            <label for="name">Name <span class="req">*</span></label>
            <input id="name" type="text" class="einput" name="name" required="required">
        </div>
        <div>
            <label for="email">Email <span class="req">*</span></label>
            <input id="email" type="email" class="einput" name="email" required="required">
        </div>
        <div>
            <label for="message">Message <span class="req">*</span></label>
            <textarea id="message" class="einput" name="message" required="required"></textarea>
        </div>
        <div id="errorlog"></div>
        <div>
            <button type="submit">Submit</button>
        </div>
    </form>
</div>
Enter fullscreen mode Exit fullscreen mode

the javascript
We get the recapcha token and submit the form. Any errors thrown put a user friendly message in the form's #errorlog div.
There are 2 functions, one to deal with any errors thrown, and one to submit the form. First I set it so the submit button is disabled while the form is submitting. Then I check for empty fields (.einput), to avoid any incomplete submissions. Finally I use the Google provided script to get a recapcha token and submit the form using javascript's fetch api. If there are no errors the form is cleared out and a submitted message is put inside of it.

<script src="https://www.google.com/recaptcha/api.js?render=YOUR RECAPCHA KEY"></script>
<script>

function doError(msg){
    button.disabled = false;
    button.textContent = "Submit";
    log.className = "error message";
    log.textContent = msg;
}

function formSubmit(event){
    event.preventDefault();
    button.disabled = true;
    button.textContent = "Sending...";
    log.className = "";
    log.textContent = "";
    try{
        form.querySelectorAll(".einput").forEach(einput => {
            console.log(einput.value);
            if(einput.value == ""){
                throw "Please fill out all fields.";
            }
        });
        const formdata = new FormData(form);
        grecaptcha.ready(function() {
            grecaptcha.execute('YOUR RECAPCHA KEY', {action: 'submit'}).then(function(token) {
            formdata.append('g-recaptcha-response', token);
                fetch(form.action, {
                    method: 'POST',
                    body: formdata
                })
                .then((res) => res.json())
                .then((data) => {
                    if(data.status == 200){
                        form.innerHTML = "";
                        form.textContent = "Submitted, thanks!";
                        form.classList.add("submitted");
                    }
                    else{
                        throw data.message;
                    }
                })
                .catch((err) => {
                    doError("Sorry there was a problem submitting the form, please try again.");
                });
            });
        });
    }
    catch(e){
        doError(e);
    }
}
const form = document.getElementById("contactform");
const log = document.getElementById("errorlog");
const button = form.querySelector("button");
form.addEventListener("submit", formSubmit);

</script>
Enter fullscreen mode Exit fullscreen mode

the PHP
Finally we have the PHP file (your-file-to-post-to.php) with the script to deal with the form's posted data.
The checkPostAndSendEmail function does one last check for an empty message, and also checks for recapcha secret, and recapcha token, and validates the email address structure. It then uses cURL to post the secret and token to googles recapcha verify url, checks the response and if no issues it moves on to send the email. My needs are very simple so there's not lots of processing to send the email, just stick the posted fields together with implode() and send it out. The end result is a json string sent back to the javascript with status (200 or 400) and a message.

function checkPostAndSendEmail($post=[],$secret=''){
  if(empty($secret)){
    throw new \Exception('fail');
  }
  if(empty($post['g-recaptcha-response'])){
    throw new \Exception('Recapcha empty');
  }
  if (!filter_var($post['email'], FILTER_VALIDATE_EMAIL)) {
    throw new \Exception('Bad Email');
  }
  if(empty($post['message'])){
    throw new \Exception('No Message');  
  }
  $recapchapost['secret'] = $secret;
  $recapchapost['response'] = $post['g-recaptcha-response'];
  $url = 'https://www.google.com/recaptcha/api/siteverify';
  $ch = curl_init($url);
  curl_setopt($ch, CURLOPT_POSTFIELDS, $recapchapost);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); 
  curl_setopt($ch, CURLOPT_TIMEOUT, 10);
  $response = curl_exec($ch);
  if(curl_errno($ch)){
    throw new \Exception(curl_error($ch));
  }
  curl_close($ch);
  $capcharesponse = json_decode($response,true);
  if(!$capcharesponse['success'] || $capcharesponse['score']<.5){
    throw new \Exception('Recapcha failed '.json_encode($capcharesponse['error-codes']));
  }
  unset($post['g-recaptcha-response']);
  $message = implode('<br>',$post);
  $to = 'ADDRESS WHERE THE EMAIL IS SENT';
  $subject = 'Contact Form Submit';
  $headers = "MIME-Version: 1.0\r\n";
  $headers .= "Content-type: text/html; charset=iso-8859-1\r\n";
  $headers .= "X-Priority: 3\r\n";
  $headers .= "X-Mailer: PHP". phpversion() ."\r\n";

  if(!mail($to, $subject, $message, $headers)){
    throw new \Exception('Unable to send email');
  }
}
Enter fullscreen mode Exit fullscreen mode
if(isset($_POST['name'])){
  try{
    checkPostAndSendEmail($_POST,'RECAPCHA SECRET');
    $response = ['status'=>200, 'message'=>'message sent'];
  }
  catch(\Exception $e){
    $errormsg = $e->getMessage();
    $response = ['status'=>400, 'message'=>$errormsg];
  }
  header('Content-type: application/json');
  exit(json_encode($response));
}
Enter fullscreen mode Exit fullscreen mode

You can read more about the Recapcha process and details on Google's site.

Discussion (0)