You are here

webform_validation.validators.inc in Webform Validation 6

Same filename and directory in other branches
  1. 7 webform_validation.validators.inc

Provides validation functionality and hooks

File

webform_validation.validators.inc
View source
<?php

/**
 * @file
 * Provides validation functionality and hooks
 */

/**
 * Implementation of hook_webform_validation_validators().
 *
 * This function returns an array of validators, in the validator key => options array form.
 * Possible options:
 * - name (required): name of the validator
 * - component types (required): defines which component types can be validated by this validator. Specify 'all' to allow all types
 * - custom_error (optional): define whether a user can specify a custom error message upon creating the validation rule.
 * - custom_data (optional): define whether custom data can be added to the validation rule
 * - min_components (optional): define the minimum number of components to be selected for creating a validation rule
 * - max_components (optional): define the maximum number of components to be selected for creating a validation rule
 * - description (optional): provide a descriptive explanation about the validator
 */
function webform_validation_webform_validation_validators() {
  return array(
    'numeric' => array(
      'name' => "Numeric values",
      'component_types' => array(
        'textfield',
        'hidden',
      ),
      'custom_data' => array(
        'label' => t('Specify numeric validation range'),
        'description' => t('Optionally specify the minimum-maximum range to validate the user-entered numeric value against.') . ' ' . t('Usage') . ':' . theme('item_list', array(
          t('empty: no value validation'),
          t('"100": greater than or equal to 100'),
          t('"|100": less than or equal to 100 (including negative numbers)'),
          t('"0|100": greater than or equal to 0 &amp; less than or equal to 100'),
          t('"10|100": greater than or equal to 10 &amp; less than or equal to 100'),
          t('"-100|-10": greater than or equal to -100 &amp; less than or equal to -10'),
        )),
        'required' => FALSE,
      ),
      'description' => t('Verifies that user-entered values are numeric, with the option to specify min and / or max values.'),
    ),
    'min_length' => array(
      'name' => "Minimum length",
      'component_types' => array(
        'textfield',
        'textarea',
        'email',
        'hidden',
      ),
      'custom_data' => array(
        'label' => t('Minimum number of characters'),
        'description' => t('Specify the minimum number of characters that have to be entered to pass validation.'),
      ),
      'description' => t('Verifies that a user-entered value contains at least the specified number of characters'),
    ),
    'max_length' => array(
      'name' => "Maximum length",
      'component_types' => array(
        'textfield',
        'textarea',
        'email',
        'hidden',
      ),
      'custom_data' => array(
        'label' => t('Maximum number of characters'),
        'description' => t('Specify the maximum number of characters that can be entered to pass validation.'),
      ),
      'description' => t('Verifies that a user-entered value contains at most the specified number of characters'),
    ),
    'min_words' => array(
      'name' => "Minimum number of words",
      'component_types' => array(
        'textfield',
        'textarea',
        'hidden',
      ),
      'custom_data' => array(
        'label' => t('Minimum number of words'),
        'description' => t('Specify the minimum number of words that have to be entered to pass validation. Words are defined as strings of letters separated by spaces.'),
      ),
      'description' => t('Verifies that a user-entered value contains at least the specified number of words'),
    ),
    'max_words' => array(
      'name' => "Maximum number of words",
      'component_types' => array(
        'textfield',
        'textarea',
        'hidden',
      ),
      'custom_data' => array(
        'label' => t('Maximum number of words'),
        'description' => t('Specify the maximum number of words that have to be entered to pass validation. Words are defined as strings of letters separated by spaces.'),
      ),
      'description' => t('Verifies that a user-entered value contains at most the specified number of words'),
    ),
    'equal' => array(
      'name' => "Equal values",
      'component_types' => array(
        'textfield',
        'email',
        'select',
        'hidden',
      ),
      'min_components' => 2,
      'description' => t('Verifies that all specified components contain equal values'),
    ),
    'unique' => array(
      'name' => "Unique values",
      'component_types' => array(
        'textfield',
        'email',
        'select',
        'hidden',
      ),
      'min_components' => 2,
      'description' => t('Verifies that all specified components contain unique values'),
    ),
    'specific_value' => array(
      'name' => "Specific value(s)",
      'component_types' => array(
        'select',
        'textfield',
        'textarea',
        'email',
        'hidden',
      ),
      'custom_error' => TRUE,
      'custom_data' => array(
        'label' => t('(Key) value'),
        'description' => t('Specify the specific value(s) you want the component to contain. Separate multiple options by a comma. For components that have keys, use the key value instead.'),
      ),
      'max_components' => 1,
      'description' => t('Verifies that the specified component contains a defined value'),
    ),
    'not_default_value' => array(
      'name' => "Not default value",
      'component_types' => array(
        'select',
        'textfield',
        'textarea',
        'email',
        'hidden',
      ),
      'custom_error' => TRUE,
      'description' => t('Verifies that the user-entered value is not the default value for that component.'),
    ),
    'oneoftwo' => array(
      'name' => "Require at least one of two fields",
      'component_types' => array(
        'textfield',
        'textarea',
        'email',
        'select',
      ),
      'min_components' => 2,
      'max_components' => 2,
      'description' => t('Forces the user to specify / select at least one of two selected webform components'),
    ),
    'oneofseveral' => array(
      'name' => "Require at least one of several fields",
      'component_types' => array(
        'textfield',
        'textarea',
        'email',
        'select',
      ),
      'min_components' => 2,
      'description' => t('Forces the user to specify / select at least one of several selected webform components'),
    ),
    'select_min' => array(
      'name' => "Minimum number of selections required",
      'component_types' => array(
        'select',
      ),
      'custom_data' => array(
        'label' => t('Minimum number of selections'),
        'description' => t('Specify the minimum number of options a user should select.'),
      ),
      'description' => t('Forces the user to select at least a defined number of options from the specified webform components'),
    ),
    'select_max' => array(
      'name' => "Maximum number of selections allowed",
      'component_types' => array(
        'select',
      ),
      'custom_data' => array(
        'label' => t('Maximum number of selections'),
        'description' => t('Specify the maximum number of options a user can select.'),
      ),
      'description' => t('Forces the user to select at most a defined number of options from the specified webform components'),
    ),
    'select_exact' => array(
      'name' => "Exact number of selections required",
      'component_types' => array(
        'select',
      ),
      'custom_data' => array(
        'label' => t('Number of selections'),
        'description' => t('Specify how many options a user can select.'),
      ),
      'description' => t('Forces the user to select exactly the defined number of options from the specified webform components'),
    ),
    'plain_text' => array(
      'name' => "Plain text (disallow tags)",
      'component_types' => array(
        'textfield',
        'textarea',
        'email',
        'hidden',
      ),
      'description' => t("Verifies that user-entered data doesn't contain HTML tags"),
    ),
    'regex' => array(
      'name' => "Regular expression",
      'component_types' => array(
        'textfield',
        'textarea',
        'email',
        'hidden',
      ),
      'custom_error' => TRUE,
      'custom_data' => array(
        'label' => t('Regex code'),
        'description' => t('Specify regex code to validate the user input against.'),
      ),
      'description' => t("Validates user-entered text against a specified regular expression. Note: don't include delimiters such as /."),
    ),
    'must_be_empty' => array(
      'name' => "Must be empty",
      'component_types' => array(
        'textfield',
        'hidden',
      ),
      'description' => t('Verifies that a specified textfield remains empty - Recommended use case: used as an anti-spam measure by hiding the element with CSS'),
    ),
    'blacklist' => array(
      'name' => "Words blacklist",
      'component_types' => array(
        'textfield',
        'textarea',
        'email',
        'hidden',
      ),
      'custom_error' => TRUE,
      'custom_data' => array(
        'label' => t('Blacklisted words'),
        'description' => t('Specify illegal words, seperated by commas. Make sure to escape reserved regex characters with an escape (\\) character.'),
      ),
      'description' => t("Validates that user-entered data doesn't contain any of the specified illegal words"),
    ),
    'username' => array(
      'name' => "Must match a username",
      'component_types' => array(
        'textfield',
        'hidden',
      ),
      'description' => t("Validates that user-entered data matches a username"),
    ),
  );
}

/**
 * Implementation of hook_webform_validation_validate().
 */
function webform_validation_webform_validation_validate($validator_name, $items, $components, $rule) {

  /**
   * Preparation for select_* validation rules and the handling of key 0 for select components
   *
   * Only webform 3 handles a select list with key 0 properly.
   * The array_filter callback function is only loaded for webform 3 to make sure
   * it works perfectly for webform 3, and works in all cases except with key 0 for webform 2
   */
  $version = webform_validation_check_version();
  $check_false_callback = $version == 3 ? '_webform_validation_check_false' : NULL;
  if ($items) {
    $errors = array();
    switch ($validator_name) {
      case 'numeric':
        $num_range = _webform_numeric_check_data($rule['data']);
        foreach ($items as $key => $val) {
          if ($val != '') {

            // first check if the value is numeric
            if (!is_numeric($val)) {
              $errors[$key] = t('%item is not numeric', array(
                '%item' => $components[$key]['name'],
              ));
            }

            // now validate the entered numeric value against the validator range settings, if appropriate
            // a. validate min & max
            if (isset($num_range['min']) && isset($num_range['max'])) {

              // validate the min - max range
              if ($val < $num_range['min'] || $val > $num_range['max']) {
                $errors[$key] = t('%item is not within the allowed range %range', array(
                  '%item' => $components[$key]['name'],
                  '%range' => str_replace('|', ' - ', $rule['data']),
                ));
              }
            }
            else {

              // b. validate min
              if (isset($num_range['min'])) {
                if ($val < $num_range['min']) {
                  $errors[$key] = t('%item should be greater than or equal to %val', array(
                    '%item' => $components[$key]['name'],
                    '%val' => $num_range['min'],
                  ));
                }
              }

              // c. validate max
              if (isset($num_range['max'])) {
                if ($val > $num_range['max']) {
                  $errors[$key] = t('%item should be less than or equal to %val', array(
                    '%item' => $components[$key]['name'],
                    '%val' => $num_range['max'],
                  ));
                }
              }
            }
          }
        }
        return $errors;
        break;
      case 'min_length':
        $min_length = $rule['data'];
        foreach ($items as $key => $val) {
          if ($val != '' && drupal_strlen($val) < $min_length) {
            $errors[$key] = t('%item needs to be at least %num characters long', array(
              '%item' => $components[$key]['name'],
              '%num' => $min_length,
            ));
          }
        }
        return $errors;
        break;
      case 'max_length':
        $max_length = $rule['data'];
        foreach ($items as $key => $val) {
          if ($val != '' && drupal_strlen($val) > $max_length) {
            $errors[$key] = t('%item can be maximum %num characters long', array(
              '%item' => $components[$key]['name'],
              '%num' => $max_length,
            ));
          }
        }
        return $errors;
        break;
      case 'min_words':
        $min_words = $rule['data'];
        foreach ($items as $key => $val) {
          if ($val != '' && count(preg_split("/[\\s]+/", trim($val))) < $min_words) {
            $error = format_plural($min_words, '%item needs to be at least 1 word long', '%item needs to be at least @count words long', array(
              '%item' => $components[$key]['name'],
            ));
            $errors[$key] = $error;
          }
        }
        return $errors;
        break;
      case 'max_words':
        $max_words = $rule['data'];
        foreach ($items as $key => $val) {
          if ($val != '' && count(preg_split("/[\\s]+/", trim($val))) > $max_words) {
            $error = format_plural($max_words, '%item can be maximum 1 word long', '%item can be maximum @count words long', array(
              '%item' => $components[$key]['name'],
            ));
            $errors[$key] = $error;
          }
        }
        return $errors;
        break;
      case "equal":
        $first_entry_key = key($items);
        $first_entry = array_shift($items);
        $first_entry = _webform_validation_flatten_array($first_entry);

        // flatten in case of array
        // now check if following components equal the first one
        foreach ($items as $key => $val) {
          $val = _webform_validation_flatten_array($val);

          // flatten in case of array
          if ($val !== $first_entry) {
            $errors[$key] = t('%item_checked does not match %item_first', array(
              '%item_checked' => $components[$key]['name'],
              '%item_first' => $components[$first_entry_key]['name'],
            ));
          }
        }
        return $errors;
        break;
      case "unique":
        foreach ($items as $key => $val) {
          if (is_array($val)) {

            // make sure to flatten arrays first
            $items[$key] = _webform_validation_flatten_array($val);
          }
          if (empty($items[$key])) {

            // items without a value selected shouldn't be validated
            unset($items[$key]);
          }
        }

        // now we count how many times each value appears, and find out which values appear more than once
        $items_count = array_count_values(array_map('strtolower', array_map('trim', $items)));
        $doubles = array_filter($items_count, create_function('$x', 'return $x > 1;'));
        foreach ($items as $key => $val) {
          if (in_array(strtolower($val), array_keys($doubles))) {
            $errors[$key] = t('The value of %item is not unique', array(
              '%item' => $components[$key]['name'],
            ));
          }
        }
        return $errors;
        break;
      case "specific_value":
        $specific_values = explode(',', $rule['data']);
        $specific_values = array_map('trim', $specific_values);
        foreach ($items as $key => $val) {
          if (is_array($val)) {
            $val = _webform_validation_flatten_array($val);
          }
          if (!in_array($val, $specific_values)) {
            $errors[$key] = _webform_validation_i18n_error_message($rule);
          }
        }
        return $errors;
        break;
      case "not_default_value":
        foreach ($items as $key => $val) {
          if (is_array($val)) {
            $val = _webform_validation_flatten_array($val);
          }
          if ($val == $components[$key]['value']) {
            $errors[$key] = _webform_validation_i18n_error_message($rule);
          }
        }
        return $errors;
        break;
      case "oneoftwo":

        // $components should have 2 items
        $keys = array_keys($items);
        $item1 = array_shift($keys);
        $item2 = array_shift($keys);
        $entry1 = _webform_validation_flatten_array($items[$item1]);
        $entry2 = _webform_validation_flatten_array($items[$item2]);
        if (empty($entry1) && empty($entry2)) {
          return array(
            $item1 => t('You have to specify %item1 or %item2 (or both)', array(
              '%item1' => $components[$item1]['name'],
              '%item2' => $components[$item2]['name'],
            )),
          );
        }
        break;
      case "oneofseveral":
        foreach ($items as $key => $val) {
          if (is_array($val)) {

            // make sure to flatten arrays first
            $items[$key] = _webform_validation_flatten_array($val);
          }
        }

        // $components should have at least one of several items
        if (count(array_filter($items)) < 1) {
          $keys = array_keys($items);
          $names = array();
          foreach ($keys as $value) {
            $names[] = _webform_filter_xss($components[$value]['name']);
          }
          return array(
            $keys[0] => t('You have to specify at least one of these items:') . theme('item_list', $names),
          );
        }
        break;
      case "select_min":
        $min_selections = $rule['data'];
        foreach ($items as $key => $val) {
          $val = is_array($val) ? $val : array(
            $val,
          );

          // workaround for single select components
          $selected_values = isset($check_false_callback) ? array_filter($val, $check_false_callback) : array_filter($val);
          if (is_array($val) && count($selected_values) < $min_selections) {
            $errors[$key] = t('Please select at least %num options for %item', array(
              '%num' => $min_selections,
              '%item' => $components[$key]['name'],
            ));
          }
        }
        return $errors;
        break;
      case "select_max":
        $max_selections = $rule['data'];
        foreach ($items as $key => $val) {
          $val = is_array($val) ? $val : array(
            $val,
          );

          // workaround for single select components
          $selected_values = isset($check_false_callback) ? array_filter($val, $check_false_callback) : array_filter($val);
          if (is_array($val) && count($selected_values) > $max_selections) {
            $errors[$key] = t('Please select maximum %num options for %item', array(
              '%num' => $max_selections,
              '%item' => $components[$key]['name'],
            ));
          }
        }
        return $errors;
        break;
      case "select_exact":
        $allowed_selections = $rule['data'];
        foreach ($items as $key => $val) {
          $selected_values = isset($check_false_callback) ? array_filter($val, $check_false_callback) : array_filter($val);
          if (is_array($val) && count($selected_values) != $allowed_selections) {
            $errors[$key] = t('Please select %num options for %item', array(
              '%num' => $allowed_selections,
              '%item' => $components[$key]['name'],
            ));
          }
        }
        return $errors;
        break;
      case "plain_text":
        foreach ($items as $key => $val) {
          if ($val != '' && strcmp($val, strip_tags($val))) {
            $errors[$key] = t('%item only allows the use of plain text', array(
              '%item' => $components[$key]['name'],
            ));
          }
        }
        return $errors;
        break;
      case "regex":
        mb_regex_encoding('UTF-8');
        $regex = $rule['data'];
        foreach ($items as $key => $val) {
          if ($val != '' && !mb_ereg("{$regex}", $val)) {
            $errors[$key] = _webform_validation_i18n_error_message($rule);
          }
        }
        return $errors;
        break;
      case 'must_be_empty':
        foreach ($items as $key => $val) {
          if ($val) {
            $errors[$key] = t('%item does not contain the correct data', array(
              '%item' => $components[$key]['name'],
            ));
          }
        }
        return $errors;
        break;
      case "blacklist":
        $blacklist = explode(',', $rule['data']);
        $blacklist = array_map('trim', $blacklist);
        $blacklist_regex = implode('|', $blacklist);
        foreach ($items as $key => $val) {
          if ($val != '' && preg_match("/{$blacklist_regex}/i", $val)) {
            $errors[$key] = _webform_validation_i18n_error_message($rule);
          }
        }
        return $errors;
        break;
      case "username":
        foreach ($items as $key => $val) {

          // load user - if username does not match or status 0 throw error
          if ($val != '') {
            if (!db_result(db_query("SELECT COUNT(*) FROM {users} WHERE name = '%s' AND status = 1", $val))) {

              // Username doesn't exist
              $errors[$key] = t('The %item field does not match an active username.', array(
                '%item' => $components[$key]['name'],
              ));
            }
          }
        }
        return $errors;
        break;
    }
  }
}

/**
 * Helper function to deal with submitted values that are arrays (e.g. multiple select component)
 * We flatten the array as a comma-separated list to do the comparison.
 */
function _webform_validation_flatten_array($val) {
  if (is_array($val)) {
    $arr = array_filter($val, '_webform_validation_check_false');
    return implode(',', $arr);
  }
  return $val;
}

/**
 * Get a list of validator definitions
 */
function webform_validation_get_validators() {
  $validators = module_invoke_all("webform_validation_validators");

  // let modules use hook_webform_validator_alter($validators) to change validator settings
  drupal_alter('webform_validator', $validators);
  return $validators;
}
function webform_validation_get_validators_selection() {
  $selection = array();
  $validators = webform_validation_get_validators();
  if ($validators) {
    foreach ($validators as $validator_key => $validator_info) {
      $selection[$validator_key] = $validator_info['name'];
    }
  }
  return $selection;
}

/**
 * Get a list of valid component types per validator, as defined via hook_webform_validation_validators().
 * If 'all' is specified, all available component types will be returned.
 */
function webform_validation_valid_component_types($validator) {
  $validators = webform_validation_get_validators();
  if ($info = $validators[$validator]) {
    $allowed_types = $info['component_types'];
    if (_webform_validation_all_allowed($allowed_types)) {
      $version = webform_validation_check_version();
      if ($version == 2) {
        return webform_load_components();
      }
      else {
        return array_keys(webform_components());
      }
      return $componentslist_function();
    }
    return $info['component_types'];
  }
}

/**
 * Helper function to check whether all components are allowed to be used for a certain validator
 */
function _webform_validation_all_allowed($allowed) {
  if ($allowed) {
    foreach ($allowed as $type) {
      if ($type == "all") {
        return TRUE;
      }
    }
  }
  return FALSE;
}
function webform_validation_get_validator_info($validator_key) {
  $validators = webform_validation_get_validators();
  return $validators[$validator_key];
}

/**
 * Handle translatable error messages, if available
 */
function _webform_validation_i18n_error_message($rule) {
  $rule['error_message'] = filter_xss($rule['error_message']);
  if (module_exists('i18nstrings')) {
    return i18nstrings('webform_validation:error_message:' . $rule['ruleid'] . ':message', $rule['error_message']);
  }
  return $rule['error_message'];
}

/**
 * Helper function used by array_filter to determine if a value was selected or not
 */
function _webform_validation_check_false($var) {
  return $var !== FALSE;
}

/**
 * Process the numeric value validation range that was provided in the numeric validator options
 */
function _webform_numeric_check_data($data) {
  $range = array(
    'min' => NULL,
    'max' => NULL,
  );

  // if no value was specified, don't validate
  if ($data == '') {
    return $range;
  }

  // If only one numeric value was specified, this is the min value
  if (is_numeric($data)) {
    $range['min'] = (int) $data;
  }
  if (strpos($data, '|') !== FALSE) {
    list($min, $max) = explode('|', $data);
    if ($min != '' && is_numeric($min)) {
      $range['min'] = (int) $min;
    }
    if ($max != '' && is_numeric($max)) {
      $range['max'] = (int) $max;
    }
  }
  return $range;
}

Functions

Namesort descending Description
webform_validation_get_validators Get a list of validator definitions
webform_validation_get_validators_selection
webform_validation_get_validator_info
webform_validation_valid_component_types Get a list of valid component types per validator, as defined via hook_webform_validation_validators(). If 'all' is specified, all available component types will be returned.
webform_validation_webform_validation_validate Implementation of hook_webform_validation_validate().
webform_validation_webform_validation_validators Implementation of hook_webform_validation_validators().
_webform_numeric_check_data Process the numeric value validation range that was provided in the numeric validator options
_webform_validation_all_allowed Helper function to check whether all components are allowed to be used for a certain validator
_webform_validation_check_false Helper function used by array_filter to determine if a value was selected or not
_webform_validation_flatten_array Helper function to deal with submitted values that are arrays (e.g. multiple select component) We flatten the array as a comma-separated list to do the comparison.
_webform_validation_i18n_error_message Handle translatable error messages, if available