How do I show errors after validation with a custom form frontend?

I’m struggling to understand how I should achieve a decent error-handler at frontend (serverside). The code is written in functions.php in a specific theme (if that matters). Down below a custom post type named pupil is created upon submit.

$pupil = new Pupil();
add_shortcode('wppb-register', array($pupil, 'frontEndRegistration'));

//Check if visitor is trying to save a new pupil
$pupil->checkFrontEndRegistration();

In the pupil-class I show (return) this content (form):

Read More
//Show public register-form
public function frontEndRegistration($atts, $content="", $errors = null) {   
     $class_errors = array();

    if (is_array($errors) && !empty($errors)) {

        foreach($errors as $key=>$err) {
            if ($err === true) {
                $class_errors[$key] = ' error';
            }
        }           
    }
    $content .= '
    <form id="adduser" name="adduser" method="post" action="">' .
            '<table class="form-table"><tbody><tr><td colspan="2">'.$this->getRelatedCoursesPupil(null, array('args' => array('fetchContent' => 'returncontent')))
    .  '</td></tr>
    <tr>
        <th><label for="email">Förnamn*</label></th>
        <td><input name="first_name" id="first_name" value="" class="regular-text' . $class_errors['first_name'] . '" type="text"></td>
    </tr>
    <tr>
        <th><label for="email">Efternamn*</label></th>
        <td><input name="last_name" id="last_name" value="" class="regular-text' . $class_errors['last_name'] . '" type="text"></td>
    </tr>        
    <tr>
        <th><label for="age">Ålder*</label></th>
        <td><input name="age" id="age" value="" class="regular-text" type="text"></td>
    </tr>          <tr>
        <th><label for="email">E-post</label></th>
        <td><input name="email" id="email" value="" class="regular-text" type="text"></td>
    </tr>
    <tr>
        <th><label for="homephone">Telefon hem*</label></th>
        <td><input name="homephone" id="homephone" value="" class="regular-text" type="text"></td>
    </tr>

    <tr>
        <th><label for="cellphone">Mobiltelefon*</label></th>
        <td><input name="cellphone" id="cellphone" value="" class="regular-text" type="text"></td>
    </tr>
    <tr>
        <th><label for="adress">Adress</label></th>
        <td><input name="adress" id="adress" value="" class="regular-text" type="text"></td>
    </tr>
    <tr>
        <th><label for="postnr">Postnr</label></th>
        <td><input name="postnr" id="postnr" value="" class="regular-text" type="text"></td>
    </tr>
    <tr>
        <th><label for="ort">Ort</label></th>
        <td><input name="ort" id="ort" value="" class="regular-text" type="text"></td>
    </tr>
    <tr>
        <th><label for="description">Övrigt</label></th>
        <td><textarea name="description" id="description" class="description"></textarea></td>
    </tr>    
    <tr><td>
    <input type="hidden" name="action" value="newpupil-frontend" />
    ' . wp_nonce_field( 'newpupil-frontend', 'newpupil-frontend') . 

    '</td></tr></tbody>
    </table>
    <input type="submit" value="OK" name="addusersub" id="addusersub" />
    </form>
    ';

    return $content;
}

Here I’m doing the actual checking before saving it to database.

//Save pupil
public function checkFrontEndRegistration() {

        if( 'POST' == $_SERVER['REQUEST_METHOD'] && !empty( $_POST['action'] ) &&  $_POST['action'] == "newpupil-frontend") {

            //Required fields
            $errors = array();
            if (!isset($_POST['first_name']) || strlen($_POST['first_name']) == 0) {
                $errors['first_name'] = true;
            }
            if (!isset($_POST['last_name']) || strlen($_POST['last_name']) == 0) {
                $errors['last_name'] = true;
            }
            if (!isset($_POST['age']) || strlen($_POST['age'] == 0)) {
                $errors['age'] = true;
            }         
            if (!isset($_POST['homephone']) || strlen($_POST['homephone']) == 0) {
                $errors['homephone'] = true;
            } 
            if (!isset($_POST['cellphone']) || strlen($_POST['cellphone']) == 0) {
                 $errors['cellphone'] = true;
            }           

            //Add new pupil-cpt into post-table when there are no errors
            if (empty($errors)) {
                //Remove hook temporarily
                remove_action( 'save_post' , 'postsubmitted');

                $pupil = array(
                    'post_title'    => 'unregistered',
                    'post_status'   => 'private',           // Choose: publish, preview, future, draft, etc.
                    'post_type' => 'pupil',
                    'post_author' => 5500                   
                );
                //save the new post and return its ID
                $post_id = wp_insert_post($pupil); 

                //Save everything relevant for this new pupil
                saveform_db($post_id, $_POST, true);        //True says that it's frontend-saving

                //Rehook temporarily removed hook
                add_action( 'save_post' , 'postsubmitted');
            }
            else {
                throw new Exception('Pupils must be entered');
                //$this->frontEndRegistration('', '', $errors);
            }

        }

    }

It works fine, but I can’t figure out how to show the errors when user hasn’t typed in required fields.

Instead of throw new Exception(Pupils must be entered); I want to show the same frontendRegistrationform() but with the errors that are set when there are no value in the required fields.

I guess I oould use sessions, but is there any better way?

Related posts

Leave a Reply

1 comment

  1. This is a pretty basic solution. First I am assuming that you are not saving the data/form, and just returning to the same page.. In your $formContent you would do something like this.

    <?php
    $formContent = '
        ....
        <tr>
            <th><label for="email">Förnamn*</label></th>
            <td><input name="first_name" id="first_name" value="" class="regular-text' . $class_errors['first_name'] . '" type="text">'
        . ( array_key_exists( 'first_name', $class_errors ) ? "<br/>You must enter your first name." : "" ) . '</td>
        </tr>";
    

    If you wanted to access the errors on the form itself, I would personally recommend doing the error checking through JavaScript. This way, you can actually prevent the page from submitting, rather than submitting, cancelling, and redirecting back. With jQuery, it would be something like:

    $('form').submit(function(event){
        var $form = $(this);
    
        // if any empty fields with the required class are found
        if ($form.find('input.required:empty').length) {
            event.preventDefault(); // cancel form submission
            $form.find('input.required:empty').after('<p class="error">This field is required');
        }
    });
    

    Obviously you could customize each field’s message by reading the label attached to that input, or putting individual checks on each field. You can also work in some custom validation here, like Phone number validation, email, etc.

    That’s my preferred method.

    For the PHP side, you should be able to store your error classes either on a class variable, or a global variable. Since the post.php page points back to itself, there is no redirection between your form submission checking, and re-rendering the page. You should be able to store the errors in a class variable and use it within both functions.

    Very high level:

    class myFormStuff {
        protected $error_classes;
    
        // happens first, on init most likely
        function checkFrontEndRegistration(){
            $this->error_classes['first_name'] = "Dude, where's your name at?";
        }
    
        // Happens way later in the page reload, when rendering the form
        function frontEndRegistration() {
    
            if ( array_key_exists( 'first_name', $this->error_classes ) ) {
                echo '<p class="error">' . $this->error_classes['first_name'] . '</p>';
            }
        }
    }