Simple contact form with field validation

I’m looking to create a very simple contact for that I can output with a shortcode. I have the following code, but it doesn’t seem to validate properly and even lets you submit the form without inputting any details. Any chance someone could explain how to validate the form please. I would also like to add a honey pot trap as these are better than captures imo but I’m pretty new to PHP. Any help would be great thanks.

    function html_form_code() {
    echo '<form action="' . esc_url( $_SERVER['REQUEST_URI'] ) . '" method="post">';
    echo '<p>';
    echo 'Your Name (required) <br />';
    echo '<input type="text" name="cf-name" pattern="[a-zA-Z0-9 ]+" value="' . ( isset( $_POST["cf-name"] ) ? esc_attr( $_POST["cf-name"] ) : '' ) . '" size="40" />';
    echo '</p>';
    echo '<p>';
    echo 'Your Email (required) <br />';
    echo '<input type="email" name="cf-email" value="' . ( isset( $_POST["cf-email"] ) ? esc_attr( $_POST["cf-email"] ) : '' ) . '" size="40" />';
    echo '</p>';
    echo '<p>';
    echo 'Your Message (required) <br />';
    echo '<textarea rows="10" cols="35" name="cf-message">' . ( isset( $_POST["cf-message"] ) ? esc_attr( $_POST["cf-message"] ) : '' ) . '</textarea>';
    echo '</p>';
    echo '<input type="text" name="content" id="content" value="" class="hpot" />';
    echo '<p><input type="submit" name="cf-submitted" value="Send"/></p>';
    echo '</form>';
}

function deliver_mail() {

  $errors = new WP_Error();
if ( isset( $_POST[ 'content' ] ) && $_POST[ 'content' ] !== '' ) {
  $errors->add( 'cheater', 'Sorry, this field should not be filled. Are you trying to cheat?' );
}
if ( isset( $_POST[ 'cf-name' ] ) && $_POST[ 'cf-name' ] == '' ) {
  $errors->add('error', 'Please fill in a valid name.' );
}

if ( isset( $_POST[ 'cf-email' ] ) && $_POST[ 'cf-email' ] == '' ) {
  $errors->add('error', 'Please fill in a valid email.' );
}

if ( isset( $_POST[ 'cf-message' ] ) && $_POST[ 'cf-message' ] == '' ) {
  $errors->add('error', 'Please fill in a valid message.' );
}

if ( empty( $errors->errors ) ){
  deliver_mail();
}
else {
  echo 'Please fill the required fields';
}


    // if the submit button is clicked, send the email
    if ( isset( $_POST['cf-submitted'] ) ) {

        // sanitize form values
        $name    = sanitize_text_field( $_POST["cf-name"] );
        $email   = sanitize_email( $_POST["cf-email"] );
        $message = esc_textarea( $_POST["cf-message"] );

        // get the administrator's email address
        $to = get_option( 'admin_email' );

        $headers = "From: $name <$email>" . "\r\n";

        // If email has been process for sending, display a success message
        if ( wp_mail( $to, $message, $headers ) ) {
            echo '<div class=cf-success>';
            echo '<p>Thank you for contacting us '. $name .', a member of our team will be in touch with you shortly.</p>';
            echo '</div>';
        } else {
            echo '<div class=cf-error>';
            echo '<p>An unexpected error occurred</p>';
            echo '</div>';
        }
    }
}

function cf_contact_form() {
    ob_start();
    deliver_mail();
    html_form_code();

    return ob_get_clean();
}

add_shortcode( 'contact_form', 'cf_contact_form' );

So would it be like the above? Thanks

I also receive this error when trying to run the field checks.

Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 262144 bytes) in /Applications/MAMP/htdocs/centenary-framework/wp-content/themes/cent_framework/assets/inc/core/contact-form.php on line 147

Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 262144 bytes) in /Applications/MAMP/htdocs/centenary-framework/wp-includes/load.php on line 671

I’m not to sure what this means. =(

EDIT

These issues have now been fixed. I’m pasting the working code below in the hope it will help someone in the future. Many thanks to everyone that helped!!

// Form markup
 function html_form_code()
 {
     ?>

 <form action="<?php esc_url($_SERVER['REQUEST_URI']);
     ?>" method="post">
   <p>Your Name (required)<br />
     <input type="text" name="cf-name" pattern="[a-zA-Z0-9 ]+" value="<?php isset($_POST['cf-name']) ? esc_attr($_POST['cf-name']) : '';
     ?>" size="40" />
   </p>
   <p>Your Email (required)<br />
     <input type="email" name="cf-email" value="<?php isset($_POST['cf-email']) ? esc_attr($_POST['cf-email']) : '';
     ?>" size="40" />
   </p>
   <p>Your Message (required)<br />
     <textarea rows="10" cols="35" name="cf-message"><?php isset($_POST['cf-message']) ? esc_attr($_POST['cf-message']) : '';
     ?></textarea>
   </p>
   <p><input type="submit" name="cf-submitted" value="Send"/></p>
 </form>

 <?php

 }

// Form validation
 function my_validate_form()
 {
     $errors = new WP_Error();

     if (isset($_POST[ 'content' ]) && $_POST[ 'content' ] !== '') {
         $errors->add('cheater', 'Sorry, this field should not be filled. Are you trying to cheat?');
     }

     if (isset($_POST[ 'cf-name' ]) && $_POST[ 'cf-name' ] == '') {
         $errors->add('name_error', 'Please fill in a valid name.');
     }

     if (isset($_POST[ 'cf-email' ]) && $_POST[ 'cf-email' ] == '') {
         $errors->add('email_error', 'Please fill in a valid email.');
     }

     if (isset($_POST[ 'cf-message' ]) && $_POST[ 'cf-message' ] == '') {
         $errors->add('message_error', 'Please fill in a valid message.');
     }

     return $errors;
 }

// Form delivery
 function deliver_mail($args = array())
 {

  // This $default array is a way to initialize some default values that will be overwritten by our $args array.
  // We could add more keys as we see fit and it's a nice way to see what parameter we are using in our function.
  // It will only be overwritten with the values of our $args array if the keys are present in $args.
  // This uses WP wp_parse_args() function.
  $defaults = array(
    'name' => '',
    'email' => '',
    'message' => '',
    'to' => get_option('admin_email'), // get the administrator's email address
  );

     $args = wp_parse_args($args, $defaults);

     $headers = "From: {$args['name']}  <{$args['email']}>"."\r\n";

  // Send email returns true on success, false otherwise
  if (wp_mail($args['to'], $args['message'], $headers)) {
      return;
  } else {
      return false;
  }
 }

// Form sanitize
function my_sanitize_field($input)
{
    return trim(stripslashes(sanitize_text_field($input)));
}

// Form succsess message
function my_form_message()
{
    global $errors;
    if (is_wp_error($errors) && empty($errors->errors)) {
        echo '<div class="cf-success">';
        echo '<p>Thank you for contacting us '.$_POST['cf-name'].', a member of our team will be in touch with you shortly.</p>';
        echo '</div>';

    //Empty $_POST because we already sent email
    $_POST = '';
    } else {
        if (is_wp_error($errors) && !empty($errors->errors)) {
            $error_messages = $errors->get_error_messages();
            foreach ($error_messages as $k => $message) {
                echo '<div class="cf-error '.$k.'">';
                echo '<p>'.$message.'</p>';
                echo '</div>';
            }
        }
    }
}

// Form shortcode
add_shortcode('contact_form', 'cf_contact_form');
function cf_contact_form()
{
    ob_start();

    my_form_message();
    html_form_code();

    return ob_get_clean();
}

// Error validation
add_action('init', 'my_cf_form');
function my_cf_form()
{
    if (isset($_POST['cf-submitted'])) {
        global $errors;
        $errors = my_validate_form();
        if (empty($errors->errors)) {
            $args = array(
         'name' => my_sanitize_field($_POST['cf-name']),
         'email' => my_sanitize_field($_POST['cf-email']),
         'message' => my_sanitize_field($_POST['cf-message']),
       );
            deliver_mail($args);
        } else {
            return $errors;
        }
    }
}

2 Answers
2

You don’t have any validation mechanism.

Your logic should be somewhat along those lines

  • Submit form
  • Check submitted fields ($_POST) against expected values
  • If all looks good send
  • If something is not as expected, log error ( you can use WP_Error() ) and rebuild form showing error message (and maybe repopulating fields with previous “good” values).

All I see here is you sanitize the inputs, but you don’t actually validate if your inputs have the values you expect (i.e. valid email, phone, length of name, etc.).

You send your email regardless if your fields have the expected values. Your else will output an error ONLY if wp_mail() fails, not if your actual fields have validated values or not.

To add an honey pot, you simply need to add an hidden field in your form that you expect to be empty.

For instance, in your HTML

<input type="text" name="content" id="content" value="" class="hpot" />

Then when you validate your form input, you expect that field to be empty.

using WP_Error class you can add errors to the object for later using them to inform the user or whatnot.

$errors = new WP_Error();
if ( isset( $_POST[ 'content' ] ) && $_POST[ 'content' ] !== '' ) {
  $errors->add( 'cheater', 'Sorry, this field should not be filled. Are you trying to cheat?' );
}

So the PHP check above is a way that you could use to validate your form. You simply add some if statements with expected values of your form (of course this can be expanded to function validating your input). Then, if you use WP_Error class, by adding to the object if errors are found, you just have to do a final check before sending.

if ( empty( $errors->errors ) ){
  deliver_mail();
}
else {
  // Here you can use your $_POST variable to repopulate the form with accepted 
  // form value (you would have to update your html_form_code() to accept an argument) 
  // or just reload the contact page displaying an error message.
}

EDIT

Ok so here’s a more complete example

CSS

add this to your css so the field is actually not displayed in the browser

.hpot {
  display: none;
}

PHP

Here’s another way of writing your html function, it’s just easier to read

function html_form_code() { ?>

<form action="<?php esc_url( $_SERVER['REQUEST_URI'] ); ?>" method="post">
  <p>Your Name (required)<br />
    <input type="text" name="cf-name" pattern="[a-zA-Z0-9 ]+" value="<?php isset( $_POST["cf-name"] ) ? esc_attr( $_POST["cf-name"] ) : ''; ?>" size="40" />
  </p>
  <p>Your Email (required)<br />
    <input type="email" name="cf-email" value="<?php isset( $_POST["cf-email"] ) ? esc_attr( $_POST["cf-email"] ) : ''; ?>" size="40" />
  </p>
  <p>Your Message (required)<br />
    <textarea rows="10" cols="35" name="cf-message"><?php isset( $_POST["cf-message"] ) ? esc_attr( $_POST["cf-message"] ) : ''; ?></textarea>
  </p>
  <p><input type="submit" name="cf-submitted" value="Send"/></p>
</form>

<?php } 

Your deliver_mail function should not listen for $_POST and should not sanitize. On a side note, using the forms user email as a from header could cause some issues with some ISP, because the email is sent from your domain and now an email is sent from your domain but with non matching domain in the from address (could be seen as spam). Use an address from your domain here (like [email protected]) and set the user’s email in the body of the email (in the message). You could also set it as a reply-to field for convenience of use.

function deliver_mail( $args = array() ) {

  // This $default array is a way to initialize some default values that will be overwritten by our $args array.
  // We could add more keys as we see fit and it's a nice way to see what parameter we are using in our function.
  // It will only be overwritten with the values of our $args array if the keys are present in $args.
  // This uses WP wp_parse_args() function.
  $defaults = array(
    'name'    => '',
    'email'   => '',
    'message' => '',
    'to'      => get_option( 'admin_email' ), // get the administrator's email address
  );

  $args = wp_parse_args( $args, $defaults );

  $headers = "From: {$args['name']} <{$args['email']}>" . "\r\n";

  // Send email returns true on success, false otherwise
  if( wp_mail( $args['to'], $args['message'], $headers ) ) {
    return;
  }
  else {
    return false;
  }
}

Your validation function

function my_validate_form() {

  $errors = new WP_Error();

  if ( isset( $_POST[ 'content' ] ) && $_POST[ 'content' ] !== '' ) {
    $errors->add( 'cheater', 'Sorry, this field should not be filled. Are you trying to cheat?' );
  }

  if ( isset( $_POST[ 'cf-name' ] ) && $_POST[ 'cf-name' ] == '' ) {
    $errors->add('name_error', 'Please fill in a valid name.' );
  }

  if ( isset( $_POST[ 'cf-email' ] ) && $_POST[ 'cf-email' ] == '' ) {
    $errors->add('email_error', 'Please fill in a valid email.' );
  }

  if ( isset( $_POST[ 'cf-message' ] ) && $_POST[ 'cf-message' ] == '' ) {
    $errors->add('message_error', 'Please fill in a valid message.' );
  }

  return $errors;
}

Your sanitization function. Here is a general sanitization function trimming white spaces and escaping html, but this could be more complex depending on the input fields you have. But I think for your purpose it’s enough

function my_sanitize_field( $input ){

  return trim( stripslashes( sanitize_text_field ( $input ) ) );

}

Displaying your success/error message, you could use this to retrieve the WP_Error object

function my_form_message(){

  global $errors;
  if( is_wp_errors( $errors ) && empty( $errors->errors ) ){

    echo '<div class="cf-success">';
    echo '<p>Thank you for contacting us '. $_POST['cf-name'] .', a member of our team will be in touch with you shortly.</p>';
    echo '</div>';

    //Empty $_POST because we already sent email
    $_POST = '';

  }
  else {

  if( is_wp_errors( $errors ) && ! empty( $errors->errors ) ){

    $error_messages = $errors->get_error_messages(); 
    foreach( $error_messages as $k => $message ){
        echo '<div class="cf-error ' . $k . '">';
        echo '<p>' . $message . '</p>';
        echo '</div>';

    }

  }

}

Finally your shortcode function

add_shortcode( 'contact_form', 'cf_contact_form' );
function cf_contact_form() {

  ob_start();

  my_form_message();
  html_form_code();

  return ob_get_clean();
}

And hooking to init to listen for $_POST before we render our form and output the $errors if we find some or send if all is ok.

add_action( 'init', 'my_cf_form');
function my_cf_form(){

  if( isset( $_POST['cf-submitted'] ) ) {

    global $errors;
    $errors = my_validate_form(); 
    if( empty( $errors->errors ) ){

       $args = array(
         'name'    => my_sanitize_field( $_POST['cf-name'] ),
         'email'   => my_sanitize_field( $_POST['cf-email'] ),
         'message' => my_sanitize_field( $_POST['cf-message'] ),
       );
       deliver_mail( $args );
    }
    else {
      return $errors;
    } 
  }
}

Remember to always prefix your functions so you don’t conflict with other plugins function names.

Leave a Comment