2

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

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 no-reply@example.com) 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.

  • Sorry to be an idiot, where do I run my checks? Thanks – user53340 Aug 23 '16 at 15:12
  • If you are new to PHP, you are not an idiot, you just have to learn this stuff :P - As for where yo do your check, you need to modify your deliver_mail() function. You don't listen for your $_POST there. Actually, your function should do only one thing (or have only one logic) so if your function is used to send emails, then you don't check for content there (makes sense?) So you could check for your $_POST just before you validate your form. I'll update my answer with some code. – bynicolas Aug 23 '16 at 15:21
  • I've updated my code to how I'm assuming it should be but I guess not because its still not working. lol I also got a memory error when I submitted the form and then refreshed the page again so I'm guessing I'm not exiting the code correctly or something? – user53340 Aug 23 '16 at 15:25
  • Also when the page refreshes after form submission, the fields are still filled with the information, is that correct? My understanding was that sanitizing the form should remove the user inputs in the fields. Or am I understanding the use of sanitize incorrectly? – user53340 Aug 23 '16 at 15:30
  • Ok, this ended up being an lengthier edit then anticipated, but I think it can be a good reference for you. Note that it is untested, so if you find bug, let me know and we'll try to sort it out. Check it out and let me know. For your fields being still filled after send. It's because you don't clean your $_POST variable and you use it to populate your form. My code should take care of that – bynicolas Aug 23 '16 at 16:51
0

Since you are somewhat new to PHP, here's a nice example of a simple server side honeypot.

What you'll do is setup a hidden text field in your form and check if it's not empty to catch the spam.

Form HTML

<label for="honeypot" class="bot">Leave blank if you are human</label>
<input type="text" id="honeypot" name="honeypot" class="bot" />

CSS

.bot { display:none }

PHP (simplified)

 if ( $_POST() ) {
    $name = strip_tags(urldecode(trim($_POST['name'])));
    // OR
    $name = test_input($_POST['name']; 
    $honeypot = $_POST('honeypot');
    // Only checks IF something is in it.
    if ($honeypot){
      $msg = "You are a bot";
    } else {
      $msg = "Success";
      // Additional processing
      if ($name){
          mail(...)
      }
    }
    echo $msg;
 }

After a brief search I also found this function that you could use.

function test_input($data) {
  $data = trim($data);
  $data = stripslashes($data);
  $data = htmlspecialchars($data);
  return $data;
}
  • Thank you for your message. That is definitely helpful, my main problem is the lack of form validation. It currently just lets me submit the form regardless of whether I fill anything in or not. And I'm concerned it's not checking for string injections etc. Thats the main thing I'm after. Would it be a case of following the same principle as for the honey pot? Thanks again. – user53340 Aug 23 '16 at 14:44
  • I'll update the PHP with additional validation tests, but you should never trust user input and should sanitize/validate in any way you can. – Greg McMullen Aug 23 '16 at 14:47
  • Thanks. I'm am looking to make it as secure as possible, but as I say, I'm pretty new to PHP so I'm aware of this stuff, just I don't have a clue how to implement the safety measures. I know you can query input fields to stop someone trying to hack the form etc but I don't know how you would do that. Thanks again for your help. – user53340 Aug 23 '16 at 14:50
  • Are you pushing this into a database, or just getting it emailed to you? By the original post, it appears you are only doing the latter. – Greg McMullen Aug 23 '16 at 14:54
  • Yes I just want it emailed. It shouldn't go into the database, but I don't want it to make my site vulnerable either. Had that with Gravity forms a while back. Thanks for the update! – user53340 Aug 23 '16 at 14:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.