Contact Form with Recapcha v3 and PHP
Tags: recapcha, php, javascript
Published: 2022-05-11 15:07:10

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](https://developers.google.com/recaptcha/docs/v3). It is basic html, vanilla javascript and PHP.

**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>
```

**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](https://www.w3schools.com/jsref/prop_pushbutton_disabled.asp) while the form is submitting.  Then I check for valid fields [checkValidity()](https://developer.mozilla.org/en-US/docs/Web/API/HTMLSelectElement/checkValidity), 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](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch). 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 CODE"></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 = "";
    if(!form.checkValidity()){
        doError("Please fill out all fields.");
    }
    else{
        const formdata = new FormData(form);
        grecaptcha.ready(function() {
            grecaptcha.execute('YOUR RECAPCHA CODE', {action: 'submit'}).then(function(token) {
            formdata.append('g-recaptcha-response', token);
                fetch(form.action, {
                    method: 'POST',
                    body: formdata
                })
                .then((response) => {
                    if(response.ok){
                        form.innerHTML = "";
                        form.textContent = "Submitted, thanks!";
                        form.classList.add("submitted");
                    }
                    else{
                        response.json().then((data) => {
                            doError(data.message);
                        })
                    }
                })
                .catch(err => {
                    doError("Sorry there was a problem submitting the form, please try again.");
                    console.log(err);
                });
            });
        });
    }
}
const form = document.getElementById("contactform");
const log = document.getElementById("errorlog");
const button = form.querySelector("button");
form.addEventListener("submit", formSubmit);

</script>
```

**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](https://www.php.net/manual/en/ref.curl.php) 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 key value pairs together with [implode()](https://www.php.net/manual/en/function.implode.php), array_map, array_keys, sprintf, 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>', array_map(function ($v, $k) { return sprintf("%s: %s", $k, $v); }, $post, array_keys($post)));
  $to = 'EMAIL';
  $subject = 'SUBJECT';
  $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');
  }
}

if(isset($_POST['name'])){
  try{
    checkPostAndSendEmail($_POST,'RECAPCHA SECRET');
    http_response_code(200);
    $response_message = 'message sent';
  }
  catch(\Exception $e){
    http_response_code(400);
    $response_message = $e->getMessage();
  }
}
else{
  http_response_code(400);
  $response_message = 'sorry, fail.';
}
header('Content-type: application/json');
exit(json_encode(['message'=>$response_message]));
```

You can read more about the Recapcha process and details on [Google's site](https://developers.google.com/recaptcha/docs/v3).