Writing your own Prestashop Module - Part 4

Form Validation and Security

Introduction

While being sufficiently functional for what it does, the module we created in Part 2 does present us with some issues to consider when implementing "real world" modules to extend Prestashop. In particular the user input we captured with our form was written directly to the configuration entry without any checking to determine whether it was valid, nor did we take account of the type of data being entered.

In this tutorial we will look at the general issue of form input checking and security, both for Back Office and Front Office forms and user input as well as looking at improving our code both functionally and aesthetically.

Updated for Prestashop version 1.4 onwards.

The Configuration class revisited

In Part 3 we touched briefly on an additional parameter that may be passed to the Configuration::updateValue() class method. If you recall the function has the following form:

phpupdateValue($key, $values, $html = false);

In Part 3 we ignored the $html parameter and allowed the function to use the default value of "false". In doing this we actually inadvertently added the first element of security and validation to our code. When set to "false" the updateValue() method actually pre-processes the value to be written using the following code ($string is the input value passed to the function):

php$string = strip_tags(nl2br2($string));

You can test this out by entering some HTML into the configuration screen for the module we created in Part 3. You should see that any html tags in your input are removed. We could modify the TutorialSecond module to allow input of html in the form, by changing line 29 in the source file to:

phpConfiguration::updateValue($this->name.'_message', Tools::getValue('our_message', true));

This illustrates the fundamental principle that we need to employ throughout our own code to ensure that it is secure and operates predictably. Thankfully Prestashop provides us with some tools that we can use in our own code to make it more secure and robust.

Validation

Prestashop provides us with a class called Validate that we can use to determine whether any user input we accept is valid or not. The list of member functions is rather large, but it's worth reproducing some of them here to illustrate the point -- for a complete list you should consult the classes/Validate.php file in your Prestashop distribution.

isEmail($email);
isFloat($float);
isUnsignedFloat($float);
isCleanHtml($html);
isDate($date);
isBool($bool);
isPhoneNumber($phoneNumber);
isPostCode($postcode);
isInt($int);
isUnsignedInt($int);
isUnsignedId($id);
isNullOrUnsignedId($id);
isUrl($url);
isAbsoluteUrl($url);
isFileName($name);

As an example we can use the isCleanHtml() function from the list above as a test in our module to prevent XSS (cross site scripting) -- that way we can allow html input reasonable safely.

Validating our form data

As before we're going to create a new module based on the previous version. Why not try modifying the Tutorialsecond class yourself to create the Tutorialthird module? If you'd rather not, then just expand and copy the code below as appropriate!

class Tutorialthird extends Module
{
  private $_html = '';

  function __construct()
  {
    $version_mask = explode('.', _PS_VERSION_, 2);
    $version_test = $version_mask[0] > 0 && $version_mask[1] > 3;
    $this->name = 'tutorialthird';
      $this->tab = $version_test ? 'front_office_features' : 'Tools';
    if ($version_test)
      $this->author = 'eCartService.net';
    $this->version = '0.1.0';
    parent::__construct();
    $this->displayName = $this->l('Third Tutorial Module');
    $this->description = $this->l('Our third module - Security and Validation');
  }

  public function install()
  {
    parent::install();
    if (!$this->registerHook('leftColumn'))
      return false;
  }

  public function getContent()
  {
    if (Tools::isSubmit('submit'))
    {
      Configuration::updateValue($this->name.'_message', Tools::getValue('our_message'));
    }
    $this->_displayForm();
    return $this->_html;
  }

  private function _displayForm()
  {
    $this->_html .= '
      <form action="'.$_SERVER['REQUEST_URI'].'" method="post">;
        <label>'.$this->l('Message to the world').'</label>
        <div class="margin-form">
          <input type="text" name="our_message" />
        </div>
        <input type="submit" name="submit" value="'.$this->l('Update').'" class="button" />
      </form>';
  }

  public function hookLeftColumn()
  {
    return '<div class="block"><h4>'. Configuration::get($this->name.'_message') . '</h4></div>';
  }
}
// End of: tutorialthird.php

The first stage in the process is to modify our getContent() function to add the validation step:

public function getContent()
{
  if (Tools::isSubmit('submit'))
  {
    $this->_postValidation();
    if (!sizeof($this->_postErrors))
    {
      Configuration::updateValue($this->name.'_message', Tools::getValue('our_message'), true);
      $this->_html .= '<div class="conf confirm">'.$this->l('Settings updated').'</div>';
    }
    else
    {
      foreach ($this->_postErrors AS $err)
      {
        $this->_html .= '<div class="alert error">'.$err.'</div>';
      }
    }
  }
  $this->_displayForm();
  return $this->_html;
}

We also need to add a declaration for the $_postErrors member variable we've introduced at the beginning of our class definition e.g.

class Tutorialthird extends Module
{
  private $_html = '';
  private $_postErrors = array();

The logic flow when we post data to getContent() is now to call our Validation() function to test the fields submitted and to set our $postErrors array with any error messages. If there are errors we can display them prior to redisplaying the form. If the validation checks are passed, then we display a "success" message to give visual feedback that the configuration has been updated. A simple XSS test in the _Validation() function for our example could be:

private function _postValidation()
{
  if (!Validate::isCleanHtml(Tools::getValue('our_message')))
    $this->_postErrors[] = $this->l('The message you entered was not allowed, sorry');
}

Obviously you aren't limited to using the tests supplied by the Validate class, and you can also apply multiple tests to each field submitted -- to test for length etc. The basic principle is the same in all cases, no matter how complex your validation requirements are.

Summary

In this article we've improved on our form handling by adding validation of user input for added security and/or reliability. We've also added some visual feedback when the configuration has been updated successfully. In the final part of this series we'll add some final cosmetic touches to our form to standardise the interface and improve usability. We'll also look at ideas for improving and extending the "template" module we've created.