How to create a simple form plugin with server validation in Wordpress

Julian Hansen
CERTIFIED EXPERT
Published:
Edited by: Andrew Leniart
This article demonstrates a very simple pattern for creating a WordPress plugin that displays and processes a form.

The form is activated by a shortcode and includes server side validation of the data .

Introduction
What we want to achieve with this article is a basic understanding of the various moving parts in WordPress with respect to displaying and processing a custom form.


The objectives will be to create a working wire frame of functionality that covers all the bases of form processing.

Getting Started
First step is to create somewhere for your plugin to live. To do this browse to your site's Plugins folder and create a new folder called sample-form. In this folder create a file called sample-form.php.


You should have a folder structure that now looks like this


/public_html (this may differ)
   /wp-content
      /plugins
         /sample-form
            sample-form.php

The next step is to decorate the PHP file to tell WordPress about this plugin. So open the file in your favourite PHP code editor and enter the following into it. 


<?php
/**
 * Plugin Name:     Sample Form
 * Plugin URI:      http://your.site.name.here
 * Description:     A simple custom form plugin
 * Author:          Julian Hansen
 * Author URI:      http://your.site.name.here
 * Text Domain:     sample-form
 * Domain Path:     /languages
 * Version:         0.1.0
 *
 * @package         Sample_Form
 */


 Theoretically we have just created a plugin - it will show up in the WordPress Plugins where it can be activated.

Of course it is doing nothing useful at the moment apart from changing the magnetic state on a section of the server's disk.

The Form Short Code
At some point we are going to want to use this form on our site.

One way to do this is to link it to a Short Code


To add a short code we need two things

1. A function the short code will link to.
2. A name for the short code which we pass to the add_shortcode() function in WordPress.

Let's add that to our sample-form.php file

<?php
/* plugin header ommitted for brevity, but it would go here exactly as you see it above */

function sampleForm()
{
}

add_shortcode( 'sc_sample_form', 'sampleForm' );


A bit more code but our plugin still doesn't do much to impress. If we use the short code sc_sample_form it will result in the sampleForm() function being called but without any significant outcome.


Displaying The Form

The purpose of this plugin is to display a form. A form needs a few things so lets discuss those first 


1. A target to aim at. In this instance we will simply aim it at the page the form is hosted on by using the $_SERVER['REQUEST_URI'] variable. Note we could also use the $_SERVER['PHP_SELF'] - it is up to you which you choose. 

2. A method - we will use POST as that is standard for forms 

3. fields to capture data. We will use one to capture a person's name 

4. A submit button to submit the form 


To ensure that our plugin is nicely structured we are going to use templates that we include to display the various output that we require. 

To this end we need to create an additional folder in our plugins folder - let's live a little and call it templates.


In this folder create two files:

sample-form.html

sample-form-complete.html


We should now have a structure that looks like this 

/public_html (this may differ)

    /wp-content

       /plugins

          /sample-form

             sample-form.php

             /templates

                sample-form.html

                sample-form-complete.html


It's time to create our form so go ahead and edit sample-form.html (don't worry about the other file for now)

<?php
$url = esc_url( $_SERVER['REQUEST_URI'];

echo <<< FORM <form action="{$url}" method="post">
   Name <input type="text" name="data[name]" value="">
   <input type="submit" /> </form>

FORM;

You will notice a few things:


1. That I have chosen to use HEREDOC for my output. The reason I did this is that it makes the code a little easier to read and work with. With HEREDOC I can embed variables directly in the output without having to wrap them in messy <?php echo ... ?> tags. I can write my HTML as I would normally while still using it as a template.


2. That I have chosen to use an array format (data[name]) for my naming. The reasons for this will be made clearer later on


3. That $url is defined outside of the HEREDOC, this is because it requires the use of the esc_url() function. You cannot call functions inside a HEREDOC so you have to save the result of a function call into variables and then use the variables in the output.


Only one thing left to do and that is to load the form in our short code function. We will do that by using a PHP include() to include the template into our main PHP file.  

<?php

/* plugin header ommitted for brevity, but it would go here exactly as you see it above */

function showForm() {
    include('templates/show-form.html'); 
}

function sampleForm() {
    showForm(); 
}

A point to note here:

We could have placed the include() directly into the sampleForm() function. Instead we call a showForm() function and do it there. The reason for this will become apparent later on. 


First Test

We are now ready to do our first test. Go ahead and add the short code [sc_sample_form] to a page on your site and then browse to that page. You should see your form displayed. Our plugin now officially does something useful. However, we are not done yet. For a form to be properly useful it needs more than a pretty face - it needs a good brain as well. 


We need something that will take the form data and 

a) Check that it is good data, and report back if it is not 

b) Do something useful with the data if it passes muster.


Routing 

As we are using the same plugin code to process the form as we use to display it, we will need to put some logic into our plugin code to route the request to the correct part of the plugin. 


Specifically, we need to check 

1. if the plugin is being activated for the first time - which indicates we should display the form 

2. if the form is being submitted so we can process the data. 


There is an easy check we can do to determine if the form is being posted to or called for the first time and that is by checking the state of the $_POST global variable to see if it is empty.


NOTE: We could use the WordPress do_action() call back - I am doing it this way for two reasons

1. It is important to understand how the relationship between forms and POSTED data work at the base level
2. For this simple form, it is unnecessary to run through additional hook code to do what we can accomplish in two lines.
The WordPress functions for advanced form processing will be dealt with in a later article.

[Sidebar: This is where our form naming convention comes into play. If you look at the form definition above you will see that our input control has the name data[name]. The reasons for this are many but the main one is that it allows us to extract the form data easily from the $_POST while ignoring any other control data that might have been submitted with the form. In this case, there is only form data.]


It is always good to do robust checking of POST data to make sure it exists before you try to use it. My preferred method of doing this is as follows 

$data = isset($_POST['data']) ? $_POST['data'] : false; 


This one line of code ensures that $data will have a value that we can then test. If it is false it means no data was posted so this is a first-time request. 


We can now do a simple test on $data to see if it is truthy or falsy and from that decide how to route the process. 


So let's update the code to do exactly this

<?php
/* plugin header omitted for brevity, but it would go here exactly as you see it above */

/* function showForm() omitted for brevity - imagine it is still here though */

function sampleForm()
{
  // SAFELY EXTRACT OUR DATA
  $data = isset($_POST['data']) ? $_POST['data'] : false;

  // CHECK IF WE MUST DISPLAY OR PROCESS THE FORM
  if ($data) {
    processForm($data);
  }
  else {
    showForm();
  }
}

Not too much different from what we had but you should take note that


1. We have our extraction process for getting data out of the $_POST 

2. We have an if statement that tests the value of $data to see if we must process or show the form

3. We have a new function called processForm()


Processing the Form Data

Let us assume that our data is good (we will deal with the case where it isn't later on). We need to do something useful with it. 


For this sample, we are simply going to load a success page and show a thank you message, and at the same time include the data that was posted to the form. 


This is where we get to edit the other template you created above. So go ahead and open sample-form-complete.html and add the following code.


<p>Thank you <?php echo $data['name'];?></p>


That's a wrap on the processing. Naturally, in a real-world example, we would expand on this but this serves as an adequate placeholder until then. All that remains is for us to load this content and we do that in the processForm() function which for now can be defined like this


<?php
/* plugin header omitted for brevity, but it would go here exactly as you see it above */

/* function showForm() omitted for brevity - imagine it is still here though */

function processForm($data) {
    include('templates/sample-form-complete.html');
}

/* function sampleForm() omitted for brevity - image it too is still here */


That's all we need for the processing. The only thing to note here is the $data parameter. This is passed in from our sampleForm() controller function. By doing it this way $data becomes a local variable to the included template which gives us easy access to the validated data submitted by the form.


We now have a complete process - our form accepts data, submits it to the server and the server responds by showing a completion page. But we are only halfway there. We are assuming that what the browser sent us is what we were expecting to receive and in the real world we simply cannot do that


Validation and Sanitation

The first rule of web applications is: assume that anything sent to our application from outside is malicious. 

The second rule is that users will make mistakes. 


To combat these two demons we need to ensure that our form does two things before it consumes any data sent to it.


It must check that the data

a) Exists and is valid.

b) Does not contain any contaminants.


We will not be covering Sanitation as part of this exercise as it requires its own dedicated discussion. It is a very important part of the process so take some time to familiarise yourself with what it is and how to implement it.  


We will need to include some sort of form validation though.


There are essentially two types of validation we do when validating form data.


Client Side validation

This is done in the browser usually using a combination of JavaScript and HTML5 features. This is really for convenience.


Server side validation

Irrespective of what validation we do on the client we must still validate the data on the server before we process it. It is a relatively simple task for those with the skills to bypass any browser validation processes - so it is vital we perform a check on the server in addition to any client side processing.


For this discussion, we will focus only on server side validation. This document is primarily focused on a WordPress implementation and client side JavaScript falls outside this scope.


For this example, the validation we will perform is to ensure that the name field contains at least 10 characters.


To do this we need to make some changes to our code. We need to add a validation function and we need to call this function from our sampleForm() controller so that we can make a decision on how to proceed if there are errors. 


/* other functions are still here - imagine*/ 

function validateForm($data)
{
  return strlen($data['name']) >= 10;
}

function sampleForm()
{
  $data = isset($_POST['data']) ? $_POST['data'] : false;
  if ($data) {
    if (!validateForm($data)) {
      showForm();
    }
    else {
      processForm($data);
    }
  }
  else {
    showForm();
  }
} 

Not too much has changed: 

We have replaced the processForm() function call inside sampleForm() with an if statement that checks the return from validateForm() and only proceeds to call processForm() if the form data is valid - otherwise it sends us straight back to the showForm()


This will work but there is still something missing. The user is going to be mighty upset if (s)he just filled in a large number of fields and one of them was wrong and all that happens is an empty form is shown with no error messages. 


To better manage our user's blood pressure we will need to accomplish two things if we encounter an error


a) Preserve the inputs the user has already captured to the form

b) Send back meaningful error messages so that the user is informed of what went wrong and can make the necessary corrections.


To do this we are going to first make some changes to our validation and our controller. The validation function now needs to return some sort of error message. It makes sense to use the presence (or absence) of errors as an indicator of whether data is valid or not. 


The other change we need to make is that now we need to pass data to the showForm() function (in addition to the error messages) so that when the form is rendered to be sent back to the user it includes the data that has already been captured. So let's start with the controller and validation function first.


/* We are still here */ 

function showForm($data = [])
{
 include('templates/sample-form.html');
}

function validateForm($data)
{
  $data['errors'] = [];
  if (strlen($data['name']) < 10) {
    $data['errors']['name'] = 'Name is too short';
  }
  return $data;
}

function sampleForm()
{
  $data = isset($_POST['data']) ? $_POST['data'] : false;
  if ($data) {
    $data = validateForm($data);
    if (!empty($data['errors'])) {
      showForm($data);
    }
    else {
      processForm($data);
    }
  }
  else {
    showForm(['name' => '']);
  }
}

Quite a bit has happened, so let's go through them each in turn:

1. showForm() now takes an optional parameter. We will see shortly how this is to be used.


2. The validation function has changed to include returning errors. We are adding the errors to the $data parameter passed to the validation function so they are available to the showForm() function later on. Errors are set by field name. This way we can return multiple errors each linked to their respective fields.


3. In the controller (sampleForm()), we do the validation check and if it fails we route back to the form.


4. In the default (first view) action we are passing an array with name initialized to an empty string. You will see when we update the template this is not strictly necessary, we do it because, later,  we may want to initialize our form with certain values - this provides a means to do so.


All that remains is for us to update our sample-form.html template to accommodate the above changes and we are done.

<?php
$url = esc_url( $_SERVER['REQUEST_URI']);

$error = !empty($data['errors']['name']) 
   ? '<div class="error"> ' . $data['errors']['name'] . '</div>' 
   : '';

$name = isset($data['name']) ? $data['name'] : '';

echo <<< FORM
<form action="{$url}" method="post">
  Name <input type="text" name="data[name]" value="{$name}">
  {$error}
  <input type="submit" />
</form>

FORM;

A few changes here:


1. The optional $data parameter to showForm() is now local to our template so we can use it to extract field and error information


2. We are setting $error and $name values outside of the HEREDOC form. This is because we need to check that they actually exist. When the form is first loaded there may not be any values - we don't want to assume $data['name'] holds a value nor do we want to force showForm() to have to send values to the form. Therefore we do it this way to provide the greatest flexibility.


3. Notice that the $error value is set to the full HTML code for the error. This is because we are going to insert it into the form only if there is an error.


4. Finally, we are setting the value attribute on the <input> to the value of data['name'] if it exists or the empty string.


That's all there is to it - we now have a complete working WordPress plugin with all the moving parts needed to build a custom form.


Here is the full listing

sample-form.php

<?php
/**
 * Plugin Name:     Sample Form
 * Plugin URI:      http://your.site.name.here
 * Description:     A simple custom form plugin
 * Author:          Julian Hansen
 * Author URI:      http://your.site.name.here 
 * Text Domain:     sample-form
 * Domain Path:     /languages
 * Version:         0.1.0
 *
 * @package         Sample_Form
*/

function showForm($data = [])
{
  include('templates/sample-form.html');
}

function processForm($data)
{
  include('templates/sample-form-complete.html');
}

function validateForm($data)
{
  $data['errors'] = array();
  if (strlen($data['name']) < 10) {
    $data['errors']['name'] = 'Name is too short';
  }

  return $data;
}

function sampleForm()
{
  $data = isset($_POST['data']) ? $_POST['data'] : false;
  if ($data) {
    $data = validateForm($data);
    if (!empty($data['errors'])) {
      showForm($data);
    }
    else {
      processForm($data);
    }
  }
  else {
    showForm(array('name' => ''));
  }

}
add_shortcode( 'sc_sample_form', 'sampleForm' );

sample-form.html

<?php
$url = esc_url( $_SERVER['REQUEST_URI']);

$error = !empty($data['errors']['name'])
   ? '<div class="error"> ' . $data['errors']['name'] . '</div>' 
   : '';

$name = isset($data['name']) ? $data['name'] : '';

echo <<< FORM
<form action="{$url}" method="post">
  Name <input type="text" name="data[name]" value="{$name}">
  {$error}
  <input type="submit" />
</form>

FORM;

sample-form-complete.html

<p>Thank you <?php echo $data['name'];?></p>



5
9,515 Views
Julian Hansen
CERTIFIED EXPERT

Comments (0)

Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.