Этот PHP скрипт проверяет заполнение элементов веб-формы по правилами, определенными в шаблоне формы. Он принимает как параметр шаблон HTML, который определяет макет формы с элементами ввода. Скрипт может проверить представленные значения формы с помощью правил, определенных в атрибутах HTML тегов элементов ввода.

Скрипт поддерживает правила, определенные в спецификации HTML 5 и другие пользовательские типы правил проверки данных: неделя, месяц, цвета, кредитная карта, URL, целое число, номера, диапазон значений, электронная почта, буквенный формат, буквенно-цифровой, время, дата и времея, капча, почтовый индекс, максимальная длина, минимальная длина, не менее, не более, набор значений. Скрипт также может разобрать шаблон для удаления атрибутов проверки, которые недопустимы в HTML 4 или HTML 5, а затем он заменяет входные значения атрибутов на заданные значения.

Большинство веб-страницы с формами имеют элементы ввода, которые должны удовлетворять определенным правилам валидации входных данных. Обычно представление формы определяется отдельно от правил валидации. Данный php скрипт обеспечивает альтернативный подход, который позволяет определить шаблон вывода формы и правила проверки в том же месте.

Лицензия GPL.

Исходник основного класса скрипта

Скачать полный архив со скриптом.


require 'class.is.php';
class jForm
{
 private $invalid_stuff = array(
  'html4' => array(
   'invalid_types'   => array(
           'captcha', 'color', 'date', 'datetime', 'datetime-local', 'email', 'month', 'number', 'range', 'search', 'tel', 'time', 'url', 'week', 'ipv4', 'ipv6', 'personnummer', 'organisationsnummer',
           'zipcode', 'creditcard', 'integer', 'alphanumeric', 'alphabetic',
           ),
   'invalid_attributes'  => array(
           'input' => array('transform', 'required', 'min', 'max', 'step', 'minlength', 'validate', 'countryelement', 'callback', 'mustmatch', 'country', 'format'),
           'textarea' => array('transform', 'required', 'min', 'max', 'minlength', 'validate', 'maxlength', 'callback', 'type'),
           'select' => array('required', 'validate', 'callback'),
           ),
  ),
  'html5' => array(
   'invalid_types'   => array(
           'captcha', 'zipcode', 'creditcard', 'integer', 'alphanumeric', 'alphabetic', 'ipv4', 'ipv6', 'personnummer', 'organisationsnummer',
           ),
   'invalid_attributes'  => array(
           'input' => array('transform', 'minlength', 'required', 'validate', 'countryelement', 'callback', 'mustmatch', 'country', 'format'),
           'textarea' => array('transform', 'validate', 'minlength', 'maxlength', 'callback', 'type'),
           'select' => array('validate', 'callback'),
           ),
  ),
 );
 protected $country     = '';
 protected $default_language   = 'en-GB';
 protected $language     = '';
 protected $charset     = 'UTF-8';
 protected $html      = '';
 protected $yii_active_record  = '';
 protected $rules     = array();
 protected $added_rules    = array();

 protected $error_messages   = array();
 protected $errors     = array();
 protected $forms     = array();
 protected $variables    = array();
 protected $set_values    = array();

 protected $settings     = array();
 /**
  * Get the error array
  * @return Array  The error array.
  */
 public function getErrors()
 {
  return $this->errors;
 }

 /**
  * Sets the language
  *
  * @param string $language //Two-letter ISO 639-1 language code, followed by the ISO 3166 country code
  * @example sv-SE, en-GB, ru-RU
  */
 public function setLanguage($language)
 {
  $this -> language = $language;
 }

 /**
  * Fetches the language
  *
  * @return string $language //Two-letter ISO 639-1 language code, followed by the ISO 3166 country code
  * @example sv-SE, en-GB, ru-RU
  */
 public function getLanguage()
 {
  if($this -> language)
  {
   return $this -> language;
  }
  elseif(defined('LANGUAGE'))
  {
   return LANGUAGE;
  }
  else
  {
   return $this->default_language;
  }
 }

 /**
  * Fetch the error message for a particular error type
  * @param String $errortype The type of error
  * @param String $language The language to use
  * @return String    The error message
  */
 public function getErrorMessage($errortype, $language = '')
 {
  $language = $language ? $language : $this ->getLanguage();
  $filename = dirname(__FILE__) . '/lang.' . $language . '.php';
  if(is_file($filename))
  {
   include_once($filename);
  }
/*

  else
  {
   throw new Exception('This language is not implemented.');
  }
*/
  if(isset($this -> error_messages[$language][$errortype]))
  {
   return $this -> error_messages[$language][$errortype];
  }
  else
  {
   if($language == 'en-GB')
   {
    return 'Error message for "' . $errortype . '" does not exist.';
   }
   else
   {
    return $this ->getErrorMessage($errortype, 'en-GB');
   }
  }
 }

 /**
  * Trigger an error in the form
  *
  * @param String $error_string The error string|The name of an error string: If for instance "required", it will fetch the "required" error string
  * @param String $element_name Which element to trigger the error for.
  *
  * @return void
  */
 public function triggerError($error_string, $element_name ='', $error_type='')
 {
  $error_message = $this->getErrorMessage($error_string);
  if($error_message!='Error message for "' . $error_string . '" does not exist.')
  {
   $error_string=$error_message;
  }
  if($element_name)
  {
   $this -> errors[$element_name]=array(trim($error_string),$error_type);
  }
  else
  {
   $this -> errors[]=array(trim($error_string),$error_type);
  }
 }

 /**
  * Add a form to the jForm object
  *
  * @param string|filename $html
  */
 public function addForm($html)
 {
  if(strpos($html, '<')===false) // filenames cannot contain "<>"
  {
   if(is_file($html))
   {
    $this -> forms[] = $html;
    return;
   }
   else
   {
    throw new Exception('Form with file name "' . $html . '" not found');
   }
  }
  $this -> html.=$html;
 }


 /**
  * Returns the status of the validated form
  *
  * @return Boolean  True if the form input has passed validation, false otherwise
  */
 public function isOk()
 {
  return !sizeof($this -> errors);
 }

 /**
  * Set multiple values in the form
  *
  * @param array $array element_name => value
  */
 public function setValues(array $array, $container='')
 {
  if($container)
  {
   foreach ($array as $key => $value)
   {
    $this -> setValue($container.'['.$key.']', $value);
   }
  }
  else
  {
   foreach ($array as $key => $value)
   {
    $this -> setValue($key, $value);
   }
  }
 }

 /**
  * Set the value of a form element
  *
  * @param string $element_name
  * @param string $value
  */
 public function setValue($element_name, $value)
 {
  $this ->set_values[$element_name] = trim($value);
 }

 /**
  * Set multiple variables used in one or more included files
  *
  * @param array $array element_name => value
  */
 public function setVariables(array $array)
 {
  foreach ($array as $key => $value)
  {
   $this -> setVariable($key, $value);
  }
 }

 /**
  * Set a variable to be used in one or more included files
  *
  * @param string $variable_name
  * @param string $value
  */
 public function setVariable($variable_name, $value)
 {
  $this -> variables[$variable_name] = $value;
 }

 /**
  * Add a rule to a form element
  *
  * @param string $element_name
  * @param string $value
  *
  * @example $form->addRule('username', 'required', 'required');
  */
 public function addRule($element_name, $attribute_name, $rule)
 {
  if(!isset($this->added_rules[$element_name]))
  {
   $this->added_rules[$element_name]  =array();
  }
  $this->added_rules[$element_name][$attribute_name] = $rule;
 }

 /**
  * The constructor
  *
  * @param string|filename The file or the html that contains the form
  * @param $language the name of the language to use for error messages (conditional)
  * @param $object Yii cActiveRecord object Contains rules and possibly values for insertion into the object.
  */
 public function __construct($html)
 {
  $args = func_get_args();
  for($i=1;$i<sizeof($args);$i++)
  {
   $obj = $args[$i];
   if(is_string($obj))
   {
    $this->setLanguage($obj);
   }
   elseif(is_a($obj,'CActiveRecord'))
   {
    $this->yii_active_record = $obj;
   }
  }
  $this -> addForm($html);
 }

 /**
  * Used by the validate function to trim $_GET and $_POST variables
  *
  * @param string $item
  */

 /**
  * Used by the validate function to trim $_GET and $_POST variables
  *
  * @param String $string The string
  *
  * @return String  The trimmed string
  */
 private function jtrim($string)
 {
  $string = trim($string);
  if(get_magic_quotes_gpc())
  {
   $string = stripslashes($string);
  }
  return $string;
 }

 /**
  * Get the value of a DOM element, posted or set
  *
  * @param String $element_name the name of the DOM element
  *
  * @return String     the value of the element, returns false if not set
  */
 public function &getValue($element_name)
 {
  if(isset($this->posted_values))
  {
   if(strpos($element_name, '['))
   {
    $name   = substr($element_name, 0, strpos($element_name, '['));
    if(!isset($this -> posted_values[$name]))
    {
     $current_tag = '';
     return $current_tag;
    }
    $current_tag = &$this -> posted_values[$name];
    preg_match_all('/\[([^]]+)\]/', $element_name, $bracket_matches);
    foreach ($bracket_matches[1] as $temp)
    {
     if(isset($current_tag[$temp]))
     {
      $current_tag = &$current_tag[$temp];
     }
     else
     {
      $temp = '';
      return $temp;
     }
    }
   }
   else
   {
    if(isset($this -> posted_values[$element_name]))
    {
     $current_tag = &$this -> posted_values[$element_name];
    }
    else
    {
     $current_tag = '';
    }
   }
   $current_tag = self::jtrim($current_tag);
   return $current_tag;
  }
  elseif(isset($this->set_values[$element_name]))
  {
   return $this->set_values[$element_name];
  }
  else
  {
   $return = false;
   return $return;
  }
 }

 /**
  * Validates the form
  *
  * @param array $values The values to be validated, usually $_POST or $_GET, but I suppose you could validate $_SESSION and $_COOKIE as well. Or any array of values.
  *
  * @return Boolean   Returns true if validation passes, false otherwise.
  */
 public function validate(array &$values)
 {
  $this -> posted_values = &$values;

  $rules = & $this -> getRules();
  foreach ($rules as $element_name => $element)
  {
   $element_attributes=$element['attributes'];
   $error_str = '';
   $error_type = 'required';
   $value = &$this -> getValue($element_name);

   /**
    * the transform attribute was added in version 1.3
    */
   if(isset($element_attributes['transform']))
   {
    $value = $this->transform($value,$element_attributes['transform']);
   }

   /**
    * first, check if it is required.
    */
   $length = strlen($value);

   if(!$length and isset($element_attributes['required']))
   {
    if($element_attributes['required']=='required')
    {
     $error_str = $this -> getErrorMessage('required');
    }
    elseif($element_attributes['required']=='')
    {
     $error_str = 'Malformed required-syntax.';
    }
    else // there is a condition!
    {
     /**
      * Had to do it trixy because of the following:
      * ''!=point[survey_answer][answer_text][question_id=1][Gymnasiet(minst 2 år)][points]
      * The "=" in [question_id=1] messes it up. I wish I didn't have to do this but I do. The above weirdness is from a live project.
      */
     $condition_values = explode('&',$element_attributes['required']);
     $booleans  = array();
     $pattern = '/(!=|«=|»=|«|»|==|=)/'; // « and » are used instead of < and >. the latter ones would mess up getDomElements() completely
     $operands = array(
      "==" => "==",
      "!=" => "!=",
      "=" => "==",
      "«" => "<",
      "»" => ">",
      "«=" => "<=",
      "»=" => ">="
     );

     foreach($condition_values as $val)
     {
      $results = preg_split($pattern,$val,-1,PREG_SPLIT_DELIM_CAPTURE);
      if(sizeof($results)==3)
      {
       $first_value=$results[0];
       $operand=$results[1];
       $second_value=$results[2];
      }
      else
      {
       /**
        * Try putting together the pieces and see if any fit
        */
       $first_value = '';
       $temp = '';
       for($i=0;$i<sizeof($results);$i++)
       {
        $temp.= $results[$i];
        if(isset($rules[$temp]))
        {
         $first_value = $temp;
         $operand=$results[++$i];
         $second_value='';
         $i++;
         for(;$i<sizeof($results);$i++)
         {
          $second_value.=$results[$i];
         }
        }
       }
       /**
        * Try the same thing starting from the end
        */
       if(!$first_value)
       {
        $temp='';
        for($i=sizeof($results)-1;$i>=0;$i--)
        {
         $temp= $results[$i] . $temp;
         if(isset($rules[$temp]))
         {
          /**
           * @bugfix 1.2.1 I had the variables switched around, which would have messed up <,>,>= and <=
           */
          $second_value = $temp;
          $operand=$results[--$i];
          $first_value='';
          $i--;
          for(;$i>=0;$i--)
          {
           $first_value.=$results[$i];
          }
         }
        }
       }
       if(!isset($first_value))
       {
        $error_str = 'Malformed required-syntax.';
       }
      }
      $first_value = isset($rules[$first_value]) ? $this->getValue($first_value) : trim($first_value,"'");
      $second_value = isset($rules[$second_value]) ? $this->getValue($second_value) : trim($second_value,"'");
      // maybe I should work on getDomElements instead so we can use < and >. Maybe.
      $operand = $operands[$operand];
      $booleans[]= '('.var_export($first_value,1) . $operand . var_export($second_value,1).')';
     }
     if(!$error_str)
     {
      $eval_str = '$boolean='.implode(' & ', $booleans).';';
      eval($eval_str);
      if($boolean)
      {
       $error_str = $this -> getErrorMessage('required');
      }
     }
    }
   }

   /**
    * next, check the rules.
    */
   if(!$error_str)
   {
    $type = isset($element_attributes['type']) ? $element_attributes['type'] : '';

    /**
     * The validate-attribute overrides the "type". So we can validate hidden fields. And textareas
     */
    $type = isset($element_attributes['validate']) ? $element_attributes['validate'] : $type;
    $error_type = $type;

    switch ($type)
    {
     case 'ipv4'     : if($length && !is::Ipv4($value))          {$error_str = $this -> getErrorMessage($type);} break;// added in 1.4
     case 'ipv6'     : if($length && !is::Ipv6($value))          {$error_str = $this -> getErrorMessage($type);} break;// added in 1.4
     case 'personnummer'   : if($length && !is::PersonNummer($value))        {$error_str = $this -> getErrorMessage($type);} break;// added in 1.4
     case 'organisationsnummer' : if($length && !is::OrganisationsNummer($value))      {$error_str = $this -> getErrorMessage($type);} break;// added in 1.4
     case 'week'     : if($length && !is::Week($value))          {$error_str = $this -> getErrorMessage($type);} break;
     case 'month'    : if($length && !is::Month($value))          {$error_str = $this -> getErrorMessage($type);} break;
     case 'color'    : if($length && !is::Color($value))          {$error_str = $this -> getErrorMessage($type);} break;
     case 'creditcard'   : if($length && !is::CreditCard($value))        {$error_str = $this -> getErrorMessage($type);} break;
     case 'url'     : if($length && !is::Url($value))          {$error_str = $this -> getErrorMessage($type);} break;
     case 'integer'    : if($length && !is::Integer($value))         {$error_str = $this -> getErrorMessage($type);} break;
     case 'number'    : if($length && !is::Number($value))         {$error_str = $this -> getErrorMessage($type);} break;
     case 'range'    : if($length && !is::Number($value))         {$error_str = $this -> getErrorMessage('number');} break;
     case 'email'    : if($length && !is::Email($value))          {$error_str = $this -> getErrorMessage($type);} break;
     case 'alphabetic'   : if($length && !is::Alphabetic($value))        {$error_str = $this -> getErrorMessage($type);} break;
     case 'alphanumeric'   : if($length && !is::AlphaNumeric($value))        {$error_str = $this -> getErrorMessage($type);} break;
     case 'time'     : if($length && !is::Time($value))          {$error_str = $this -> getErrorMessage($type);} break;
     case 'datetime'    : if($length && !is::DateTime($value))         {$error_str = $this -> getErrorMessage($type);} break;
     case 'datetime-local'  : if($length && !is::DateTimeLocal($value))        {$error_str = $this -> getErrorMessage($type);} break;
     case 'date'     : if($length && !is::Date($value,isset($element_attributes['format']) ? $element_attributes['format'] : 'yyyy-mm-dd')){$error_str = $this -> getErrorMessage($type);}break;
     case 'captcha'    : // added in 1.2
      if($length)
      {
       include_once 'securimage/securimage.php';
       $securimage = new Securimage();
       if ($securimage->check($value) == false) {
        $error_str = $this -> getErrorMessage($type);
       }
      }
      break;

     case 'zipcode'    :
      if($length)
      {
       /**
        * The countryelement attribute was deprecated in 1.4, replaced with the country attribute, but it still works.
        */
       //$country = isset($element_attributes['countryelement']) ? $this -> getValue($element_attributes['countryelement']) : (isset($element_attributes['country']) ? $this -> getValue($element_attributes['country']) : '');
       $country = isset($element_attributes['countryelement']) ? $element_attributes['countryelement'] : (isset($element_attributes['country']) ? $element_attributes['country'] : '');
       /*
       see if there is a dom element with the country name. If not, it is a hard coded country
       */
       if(isset($this->rules[$country]))
       {
        $country = $this -> getValue($country);
       }

       /**
        * added the auto-transform functionality in version 1.3
        */
       if(isset(is::$zipcode_patterns[$country]['transform']))
       {
        $value  = $this->transform($value, is::$zipcode_patterns[$country]['transform']);
       }

       if(!is::Zipcode($value, $country))
       {
        $error_str = $this -> getErrorMessage($type);
       }
       else
       {
        // no country is set, so no validation is possible
       }
      }
      break;
     case 'tel'    :
      if($length)
      {
       $country = isset($element_attributes['country']) ? $element_attributes['country'] : '';

       if(isset($this->rules[$country]))
       {
        $country = $this -> getValue($country);
       }

       if(!is::Tel($value, $country))
       {
        $error_str = $this -> getErrorMessage($type);
       }
       else
       {
        // no country is set, so no validation is possible
       }
      }
      break;
    }
   }
   if(!$error_str && $length && isset($element_attributes['maxlength']) && $length > $element_attributes['maxlength'])
   {
    $error_str = str_replace('{length}',$element_attributes['maxlength'],$this -> getErrorMessage('maxlength'));
    $error_str = str_replace('{current}',$length,$error_str);
    $error_type='maxlength';
   }
   if(!$error_str && $length && isset($element_attributes['minlength']) && $length < $element_attributes['minlength'])
   {
    $error_str = str_replace('{length}',$element_attributes['minlength'],$this -> getErrorMessage('minlength'));
    $error_str = str_replace('{current}',$length,$error_str);
    $error_type='minlength';
   }
   if(!$error_str && $length && isset($element_attributes['min']) && is::Numeric($value) && $value < $element_attributes['min'])
   {
    $error_str = str_replace('{value}',$element_attributes['min'],$this -> getErrorMessage('min'));
    $error_type='min';
   }
   if(!$error_str && $length && isset($element_attributes['max']) && is::Numeric($value) && $value > $element_attributes['max'])
   {
    $error_str = str_replace('{value}',$element_attributes['max'],$this -> getErrorMessage('max'));
    $error_type='max';
   }
   if(!$error_str && isset($element_attributes['mustmatch']) && $this->getValue($element_attributes['mustmatch']) !== $value)
   {
    $error_str = $this -> getErrorMessage('mustmatch');
    $error_type='mustmatch';
   }

   if(!$error_str && isset($element['enum']) && $length)
   {
    if (!in_array($value, $element['enum']))
    {
     $error_str = str_replace('{enum}',implode(', ', $element['enum']),$this -> getErrorMessage('enum'));
    }
    $error_type='enum';

   }
   if(!$error_str && isset($element_attributes['callback']))
   {
    $error_str = $this -> executeCallback($element_attributes['callback'], $value);
    $error_type='callback';
   }

   if($error_str)
   {
    $this ->triggerError($error_str, $element_name, $error_type);
   }

  }
  return !sizeof($this -> errors);
 }

 /**
  * Transforms a value, much the same as css's text-transform, except this is server side
  *
  * @param String $value The value to be transformed
  * @param String $transformation What kind of transformation. uppercase|lowercase|capitalize at the moment.
  *
  * @return String  The transformed string
  */
 private function transform($value, $transformation)
 {
  switch($transformation)
  {
   case 'uppercase':
    $value = strtoupper($value);
    break;
   case 'lowercase':
    $value = strtolower($value);
    break;
   case 'capitalize':
    $value = ucwords($value);
    break;
   default:
    break;
  }
  return $value;
 }


 /**
  * Get JavaScript class names for an element from the rules
  *
  * @param array $attributes The element attributes
  *
  * @return String   The compounded class name
  */
 private function _getJavascriptClassNameFromRules(array $attributes){
  if(!$this->settings['generate_js_class_names'])
  {
   return '';
  }

  $class = '';
  if(isset($attributes['type']))
  {
   switch($attributes['type'])
   {
    // don't add these as a class
    case 'checkbox' :
    case 'radio' :
    case 'text' :
    case 'hidden' :
    case 'textarea' :
    case 'button' :
    case 'submit' :
    case 'password' :
    case 'image' :
    case 'file' :
//    case 'range' : // does not work in jQuery validator
     break;
    default:
     $class.= ' '.$attributes['type']; break;
     break;
   }
   // added in 1.4, jquery can handle this stuff now!
   if($attributes['type'] == 'range' and isset($attributes['min']) and isset($attributes['max']) and isset($attributes['step']))
   {
    $temp = array();
    $temp[]=$attributes['min'];
    $temp[]=$attributes['max'];
    $temp[]=$attributes['step'];
    $class.= '(';
    $class.= implode(', ',$temp);
    $class.= ')';
   }
   // added in 1.4, jquery can handle this stuff now!
   if($attributes['type']=='date' and isset($attributes['format']))
   {
    $class.= '(';
    $class.= $attributes['format'];
    $class.= ')';
   }
   if(($attributes['type']=='zipcode' or $attributes['type']=='tel') and isset($attributes['country']))
   {
    $class.= '(';
    $class.= $attributes['country'];
    $class.= ')';
   }
  }
  if(isset($attributes['required'])){
   $class.=' required';
   if($attributes['required']!=='required')
   {
//    $class.='('.$attributes['required'].')';
    $class.='('.str_replace('&', '&',$attributes['required']).')';
   }
  }

  if(isset($attributes['min'])){   $class.=' min('. $attributes['min'] .')';}
  if(isset($attributes['max'])){   $class.=' max('. $attributes['max'] .')';}
  if(isset($attributes['minlength'])){ $class.=' minlength('. $attributes['minlength'] .')';}
  if(isset($attributes['mustmatch'])){ $class.=' mustmatch('. $attributes['mustmatch'] .')';}
  if(isset($attributes['maxlength']) && $attributes['type']=='textarea'){$class.=' maxlength('. $attributes['maxlength'] .')';} // 1.4.2

  return trim($class);
 }

 /**
  * Parses the form, removes invalid tags, inserts values, etc.
  *
  * @param array $invalid_stuff Invalid element types and attributes to be stripped
  *
  * @return String     The valid html4/html5
  */
 private function _getForm(array $invalid_stuff){
  $rules = $this -> getRules();
  $html = $this -> html;
  foreach($rules as $tag_name => $rule)
  {
   //echo "<pre>\n".__FILE__.': '.__LINE__."\n"; echo htmlentities(print_r($rules,1))."\n\n</pre>";exit;
   $value = $this ->getValue($tag_name);
   /**
    * start editing the extracted data
    */

   if(!isset($rule['tag']))
   {
    continue 1;
   }

   $new_html = '';
   switch ($rule['tag'])
   {
    case 'select' :
      if($js = self::_getJavascriptClassNameFromRules($rule['attributes']))
      {
       $rule['attributes']['class'] = isset($rule['attributes']['class']) ? $rule['attributes']['class'] . ' ' . $js : $js;
      }
      $content = $rule['content'];
      if($value!==false)
      {
       $value = htmlspecialchars($value, ENT_COMPAT, $this->charset);
       $content = str_replace(' selected="selected"', '', $content);
       $content = str_replace('value="' . $value . '"', 'value="' . $value . '" selected="selected"', $content);
      }
      $new_html = '<select';
      foreach($rule['attributes'] as $key => $val)
      {
       if(!in_array($key, $invalid_stuff['invalid_attributes']['select']))
       {
        $new_html .= ' ' . $key . '="' . $val . '"';
       }
      }
      $new_html.='>' . $content . '</select>';
     break;
    case 'textarea' :
     $rule['attributes']['type']='textarea';
      if($js = self::_getJavascriptClassNameFromRules($rule['attributes']))
      {
       $rule['attributes']['class'] = isset($rule['attributes']['class']) ? $rule['attributes']['class'] . ' ' . $js : $js;
      }
      $new_html = '<textarea';
      foreach($rule['attributes'] as $key => $val)
      {
       if(!in_array($key, $invalid_stuff['invalid_attributes']['textarea']))
       {
        $new_html .= ' ' . $key . '="' . $val . '"';
       }
      }
      if($value==false)
      {
       $value = $rule['content'];
      }
      $new_html.='>' . htmlspecialchars($value, ENT_COMPAT, $this->charset) . '</textarea>';
     break;
    case 'input' :
     switch ($rule['attributes']['type'])
     {
      case 'radio' :
       $first_loop = 1;
       foreach($rule['elements'] as $val => $element)
       {
        if($js = self::_getJavascriptClassNameFromRules($rule['attributes']))
        {
         $element['attributes']['class'] = isset($element['attributes']['class']) ? $element['attributes']['class'] . ' ' . $js : $js;
        }
        /**
         * If the value is set, but it is not this value
         */
        if($value!==false)
        {
         if($value == $val)
         {
          $element['attributes']['checked'] = 'checked';
         }
         else
         {
          unset($element['attributes']['checked']);
         }
        }

        $new_html = '<input';
        foreach($element['attributes'] as $key => $avalue)
        {
         if(!in_array($key, $invalid_stuff['invalid_attributes']['input']))
         {
          $new_html .= ' ' . $key . '="' . $avalue . '"';
         }
        }
        $new_html .= ' />';

        /**
         * So the error is only displayed on the first radio.
         * Hm. Maybe it should be shown on all of them?
         */
        if(isset($this->errors[$tag_name]) and $first_loop)
        {
         $id = '';
         if(isset($rule['attributes']['id']))
         {
          $id = ' id="validator-'.$rule['attributes']['id'].'"';
         }
         $new_html .= '<span'.$id.' class="validator-status validator-error ' . $this->errors[$tag_name][1] . '">' . $this->errors[$tag_name][0] . '</span>';
        }
        $first_loop = 0;
        $html = str_replace($element['raw_html'], $new_html, $html);
       }
       continue 3;// so we don't try to do all the replacements again.
      case 'checkbox' :
       if($js = self::_getJavascriptClassNameFromRules($rule['attributes']))
       {
        $rule['attributes']['class'] = isset($rule['attributes']['class']) ? $rule['attributes']['class'] . ' ' . $js : $js;
       }
       if($value!==false)
       {
        if($rule['attributes']['value'] == $value)
        {
         $rule['attributes']['checked']='checked';
        }
        else
        {
         unset($rule['attributes']['checked']);
        }
       }
       $new_html.= '<input';
       foreach($rule['attributes'] as $key => $value)
       {
        if(!in_array($key, $invalid_stuff['invalid_attributes']['input']))
        {
         $new_html .= ' ' . $key . '="' . $value . '"';
        }
       }
       $new_html .= ' />';
       break;
      case 'captcha' :
       $url = $_SERVER['REQUEST_URI'];
       $url.= (strpos($url,'?')=== false ? '?' : '&') . 'jform-captcha&';
       $new_html.='<img alt="Something has gone wrong with captcha." id="jform-captcha" src="' . $url . '" width="215" height="80" border="0" /><a href="#" onclick="document.getElementById(\'jform-captcha\').src = document.getElementById(\'jform-captcha\').src + Math.random(); return false"><img src="' . $url . 'jform-captcha-refresh-button" alt="Change image" /></a>';
      default :
       if($js = self::_getJavascriptClassNameFromRules($rule['attributes']))
       {
        $rule['attributes']['class'] = isset($rule['attributes']['class']) ? $rule['attributes']['class'] . ' ' . $js : $js;
       }
       if($value!==false)
       {
        $rule['attributes']['value'] = htmlspecialchars($value, ENT_COMPAT, $this->charset);
       }
       if(in_array($rule['attributes']['type'], $invalid_stuff['invalid_types']))
       {
        $rule['attributes']['type'] = 'text';
       }
       $new_html.= '<input';
       foreach($rule['attributes'] as $key => $value)
       {
        if(!in_array($key, $invalid_stuff['invalid_attributes']['input']))
        {
         // removes empty attributes. added in 1.4
         if(strlen($value))
         {
          $new_html .= ' ' . $key . '="' . $value . '"';
         }
        }
       }
       $new_html .= ' />';
       break;
     }
     break;
    default :
     continue 2;
   }
   if(isset($this->errors[$tag_name]))
   {
    $id = '';
    if(isset($rule['attributes']['id']))
    {
     $id = ' id="validator-'.$rule['attributes']['id'].'"';
    }
    $new_html .= '<span'.$id.' class="validator-status validator-error ' . $this->errors[$tag_name][1] . '">' . $this->errors[$tag_name][0] . '</span>';
//    $new_html .= htmlentities('<span'.$id.' class="validator-status validator-error">' . $this->errors[$tag_name] . '</span>');
   }
   $html = str_replace($rule['raw_html'], $new_html, $html);
  }
  return $html;
 }


 /**
  * Makes sure there are no elements with name of type name="foo[][]"
  * jForm cannot validate empty brackets. I don't think it's even theoretically possible.
  */
 private function _replaceEmptyBracketsInTagNames(){
  $weird_tag_pattern = '@<(input|select|textarea)([^>]*(name="([^"]+)(\[\])+[^"]*"))[^>]*(/>|>)@si'; // tar fucking allt.
  preg_match_all($weird_tag_pattern, $this -> html, $weird_tag_matches, PREG_SET_ORDER );

  while (!empty($weird_tag_matches))
  {
   foreach ($weird_tag_matches as $match)
   {
    $counter_name = 'counter_' . $match[4];
    if(!isset($$counter_name))
    {
     $$counter_name = 0;
    }
    $old_tag_name= $match[4] . '[]';
    $new_tag_name= $match[4] . '[' . $$counter_name++ . ']';
    $new_tag_str = str_replace($old_tag_name, $new_tag_name, $match[0]);
    $this -> html = preg_replace('/'. preg_quote($match[0], '/') .'/', $new_tag_str, $this -> html, 1);
   }
   preg_match_all($weird_tag_pattern, $this -> html, $weird_tag_matches, PREG_SET_ORDER );
  }

 }

 /**
  * Parses included files into a string
  *
  * @return String  The resulting plain html
  */
 private function generateRawHtml(){
  extract($this -> variables);
  foreach($this->forms as $really_strangely_named_key => $really_strangely_named_file_name) // hopefully, these variable names are not set in $this->variables
  {
   ob_start();
   include($really_strangely_named_file_name);
   $this-> html.= ob_get_clean();
   unset($this ->forms[$really_strangely_named_key]);
  }
  return $this -> html;
 }

 /**
  * Extracts the rules from an html form into a rule array
  *
  * @return array
  */
 public function getDomElements($html){
  $tags = array();

  $this -> _replaceEmptyBracketsInTagNames();
  $tag_pattern  = '@<(input|select|textarea)([^>]+)@si';

  /**
   * This may seem like a weird way of doing it, and I agree. But I had a project where the normal
   * way of doing it, with preg_match_all() just failed, for no apparent reason. It returned no matches
   * when there obviously were plenty. I guess a bug somewhere deep down in PHP.
   * But this method worked, so here we are. It has yet to fail. :)
   */
  $results = preg_split($tag_pattern,$this->html,-1,PREG_SPLIT_DELIM_CAPTURE);
  $i = 0;
  $len = sizeof($results);

  $attribute_pattern = '@([a-zA-Z_0-9]+)="([^"]*)"*@si';

  while($i<$len)
  {
   $tag = $results[$i];
   if($tag == 'input' or $tag == 'textarea' or $tag == 'select')
   {
    $attribute_string = $results[++$i];
    $attributes=array();
    preg_match_all($attribute_pattern, $attribute_string, $attribute_matches, PREG_SET_ORDER );
    if(!empty($attribute_matches))
    {
     $attributes = array();
     foreach ($attribute_matches as $attribute_matches_value)
     {
      $attribute_key  = $attribute_matches_value[1];
      $attribute_value = $attribute_matches_value[2];
      $attributes[$attribute_key] = $attribute_value;
     }
    }
    if(isset($attributes['name']))
    {
     switch($tag)
     {
      case 'input':
       $tags[]= array(
             'tag' => $tag,
             'attributes' => $attributes,
             'raw_html' => '<input' . $attribute_string  .'>',
             );
       break;
      case 'textarea':
      case 'select':
       $content = $results[++$i];
       $end_tag_pos = strpos($content, '</' . $tag . '>');
       $content = substr($content,1,$end_tag_pos-1);
       $tags[]= array(
             'tag' => $tag,
             'start_tag' => '<' . $tag . '' . $attribute_string . '>',
             'end_tag' => '</' . $tag . '>',
             'attributes' => $attributes,
             'content' => $content,
             'raw_html' => '<' . $tag . '' . $attribute_string . '>' . $content.'</' . $tag . '>',
             );
       break;
      default:
       break;
     }
    }
   }
   $i++;
  }
  return $tags;
 }


 /**
  * Extract the rules from the form
  *
  * @return Array the rules
  */
 public function &getRules(){
  if(empty($this->rules))
  {
   $this -> generateRawHtml();
   $this -> _replaceEmptyBracketsInTagNames();
   $elements = $this->getDomElements($this->html);
   $rules = array();
   foreach($elements as $element)
   {
    $attributes = &$element['attributes'];
    $tag_name = $attributes['name'];
    $tag=$element['tag'];
    /**
     * If nothing is set, the input is a text-input.
     */
    if($tag == 'input' and empty($attributes['type']))
    {
     $attributes['type'] = 'text';
    }

    if($tag == 'input' and ($attributes['type'] == 'range' or $attributes['type'] == 'number') and isset($attributes['step']))
    {
     $element['enum']= array();
     for($i = $attributes['min'] ; $i <= $attributes['max']; $i= $i+ $attributes['step'])
     {
      $element['enum'][]= $i;
     }
    }
    /**
     * if it's a radio button, merge it with the other buttons
     */
    if($tag == 'input' and $attributes['type'] == 'radio')
    {
     $this_val = $attributes['value'];
     if(!isset($rules[$tag_name]))
     {
      $rules[$tag_name]['enum']  = array();
      $rules[$tag_name]['attributes'] = array();
      $rules[$tag_name]['elements'] = array();
      $rules[$tag_name]['tag']  = $tag;
      $rules[$tag_name]['type']  = 'radio';
     }
     foreach($attributes as $key => $value)
     {
      $rules[$tag_name]['attributes'][$key]=$value;
     }
     $rules[$tag_name]['elements'][$this_val] = $element;
     $rules[$tag_name]['enum'][$this_val]=$this_val;
     continue 1;
    }
    /**
     * Fetch all the possible values of the select, put in $attributes['enum']
     * Makes sure only values from the select can be posted
     *
     * @todo Hmm. makes javascript-populated lists impractical. Maybe I should ditch this?
     * then again, the form must be generated to evaluate, so even if something has been
     * posted, it must be rebuilt first.
     *
     */
    if($tag == 'select')
    {
     $options = $element['content'];
     $option_value_pattern = '@value="([^"]+)"@si';
     preg_match_all($option_value_pattern, $options, $option_matches);
     $element['enum']=  $option_matches[1]; // @bugfix version 1.2.1 wrong variable name -> enum was not populated.
    }
    $rules[$tag_name]=$element;
   }
   $this->rules = $rules;
   $this->getYiiRules();
  }
  foreach($this->added_rules as $key => $rule)
  {
   foreach($rule as $element_name => $attribute)
   {
    $this->rules[$key]['attributes'][$element_name] = $attribute;
   }
  }
  return $this->rules;
 }

 /**
  * Extract rules from a yii CActiveRecord
  *
  * @return String  The html
  */
 private function getYiiRules(){
  if($this->yii_active_record)
  {
   $table_name = $this->yii_active_record->tableName();
   $rules = $this->yii_active_record->rules();
   $add_table_name = false;
   foreach($rules as $rule_array)
   {
    switch($rule_array[1])
    {
     case 'required':
      $temp = explode(', ',$rule_array[0]);
      foreach($temp as $field_name)
      {
       if(isset($this->rules[$table_name.'['.$field_name.']']))
       {
        $add_table_name = true;
        $this->addRule($table_name.'['.$field_name.']', 'required', 'required');
       }
       elseif(isset($this->rules[$field_name]))
       {
        $this->addRule($field_name,'required', 'required');
       }
      }
      break;
     case 'length':
      $temp = explode(', ',$rule_array[0]);
      foreach($temp as $field_name)
      {
       if(isset($this->rules[$table_name.'['.$field_name.']']))
       {
        $add_table_name = true;
        $this->addRule($table_name.'['.$field_name.']', 'maxlength',$rule_array['max']);
       }
       elseif(isset($this->rules[$field_name]))
       {
        $this->addRule($field_name,'maxlength',$rule_array['max']);
       }
      }
      break;
     case 'numerical':
      $temp = explode(', ',$rule_array[0]);
      foreach($temp as $field_name)
      {
       if(isset($this->rules[$table_name.'['.$field_name.']']))
       {
        $add_table_name = true;
        $this->addRule($table_name.'['.$field_name.']', 'type',isset($rule_array['integerOnly'])? 'integer' : 'number');
       }
       elseif(isset($this->rules[$field_name]))
       {
        $this->addRule($field_name,'type',isset($rule_array['integerOnly'])? 'integer' : 'number');
       }
      }
      break;
     default:
      break;
    }
   }
   if($add_table_name)
   {
    $this->setValues($this->yii_active_record->attributes,$table_name);
   }
   else
   {
    $this->setValues($this->yii_active_record->attributes);
   }
  }
 }
 /**
  * Gets the form html, in valid html5
  *
  * @return String  The html
  * @param Boolean
  */
 public function html5($generate_js_class_names = true){
  $this->settings['generate_js_class_names'] = $generate_js_class_names;
  $html = $this -> _getForm($this -> invalid_stuff['html5']);
  return $html;
 }

 /**
  * Gets the form html, in valid html4
  *
  * @return String  The html
  */
 public function html4($generate_js_class_names = true){
  $this->settings['generate_js_class_names'] = $generate_js_class_names;
  $html = $this -> _getForm($this -> invalid_stuff['html4']);
  return $html;
 }

 /**
  * A function used in the provided example and that's it.
  *
  * @param String $value The value to be validated
  *
  * @return String   An error string if invalid, otherwise an empty string
  */
 private function testCallback($value){
  $str = '';
  if($value !== 'text')
  {
   $str = 'Please enter the word "text". (Error generated by callback)';
  }
  return $str;
 }

 /**
  * Executes a callback function
  * If you are using namespaces, make sure to include them in the function name
  *
  * @param String $function_name The name of the function to be executed
  * @param String $value  The value to validate
  *
  * @return String    The value returned by the executed callback function
  *
  * @version 1.2.1 : renamed doCallback to executeCallback
  */
 private function executeCallback($function, $value){
  $pattern = '/(.*?)\s*::\s*(\w+)$/';
  if(preg_match($pattern, $function, $matches))
  {
   $function = array($matches[1], $matches[2]);
  }
  return call_user_func($function, $value);
 }

 /**
  * Generates images used by captcha. Must be called before any output is sent to the browser.
  */
 public static function init(){
  if (isset($_GET['jform-captcha']))
  {
   @ob_end_clean();
   if(isset($_GET['jform-captcha-refresh-button']))
   {
    $name = dirname(__FILE__) . '/securimage/images/refresh.png';
    $fp = fopen($name, 'rb');
    header("Content-Type: image/png");
    header("Content-Length: " . filesize($name));
    fpassthru($fp);
    exit;
   }
   else
   {
    include_once('securimage/securimage.php');
    $img = new securimage();
    $img->image_height    = 80;                                // width in pixels of the image
    $img->image_width     = $img->image_height * M_E;          // a good formula for image size
    $img->show();  // outputs the image and content headers to the browser
    exit;
   }
  }
  // added in 1.4.2
  if (isset($_GET['jform-validator-image']))
  {
   @ob_end_clean();
   $name = dirname(__FILE__) . '/validator.png';
   $fp = fopen($name, 'rb');
   header("Content-Type: image/png");
   header("Content-Length: " . filesize($name));
   fpassthru($fp);
   exit;
  }
 }
}
jForm::init();

Системные требования скрипта:

PHP не младше 5.0 версии.

Скачать архивы

Скачать zip-архив со скриптом.
Скачать tar.gz-архив со скриптом.

captcha

Что такое облачное хранилище и как им пользоваться? Что такое облачное хранилище и как им пользоваться?
Как выбрать пластины для теплообменника? Пластины для теплообменника
Изучение языка ECMAScript: основа современной веб-разработки История языка ECMAScript
Как добавить комментарий в HTML? HTML примеры
Apple позволит разработчикам распространять приложения напрямую с их сайтов Apple позволит разработчикам распространять приложения напрямую с их сайтов
jQuery 4.0 сокращает поддержку браузеров, удаляет API Новое в jQuery 4.0
Как продвинуть Telegram-канал: с нуля до результата Как продвинуть канал в Telegram с нуля?
Получить IT профессию с нуля: академия Eduson Получить IT профессию с нуля
Перспективы эволюции SEO: встречайте будущее продвижения в поисковых системах Будущее SEO-продвижения
Создание сайтов в Алматы: агентство site-promote.com Разработка сайта компании
Антипкин Михаил Сергеевич о метавселенной, открывающей безграничные возможности для инноваций и экспериментов Антипкин Михаил Сергеевич о метавселенной
Сёрфинг с браузером FraudFox: исчерпывающее руководство Сёрфинг с браузером FraudFox
Где найти лицензионные ключи активации к ПО? Где найти лицензионные ключи активации к ПО?
Курсы веб дизайна: обучение онлайн Курсы веб дизайна: обучение онлайн
Как продлить срок службы бытовой техники? Как продлить срок службы бытовой техники?
Основы VPS/VDS: что нужно знать перед арендой? Основы VPS/VDS: что нужно знать перед арендой?
Откройте для себя азарт Mostbet KZ - ведущего онлайн-казино для геймеров Откройте для себя азарт Mostbet KZ - ведущего онлайн-казино для геймеров
Топ-10 игр для Android стоимостью менее $5 Топ-10 игр для Android стоимостью менее $5
Проверка авто в базе ГИБДД перед покупкой Проверка авто в базе ГИБДД перед покупкой
Бизнес-психология в онлайн-институте Smart Бизнес-психология в онлайн-институте Smart