5093 lines
235 KiB
PHP
5093 lines
235 KiB
PHP
<?php
|
|
|
|
define('ZEBRA_FORM_UPLOAD_RANDOM_NAMES', false);
|
|
|
|
/**
|
|
* Zebra_Form, a jQuery augmented PHP library for creating and validating HTML forms
|
|
*
|
|
* It provides an easy and intuitive way of creating template-driven, visually appealing forms, complex client-side and
|
|
* server-side validations and prevention against cross-site scripting (XSS) and cross-site request forgery (CSRF) attacks
|
|
* prevention.
|
|
*
|
|
* For the form validation part you can use the built-in rules (i.e. required fields, emails, minimum/maximum length,
|
|
* etc) and you can also define custom rules, with extreme ease, depending on your specific needs.
|
|
*
|
|
* All the basic controls that you would find in a form are available plus a few extra: text, textarea, submit, image,
|
|
* reset, button, file, password, radio buttons, checkboxes, hidden, captcha, date and time pickers.
|
|
*
|
|
* One additional note: this class is not a drag and drop utility - it is intended for coders who are comfortable with
|
|
* PHP, HTML, CSS and JavaScript/jQuery - you will have to build your forms when using this class, but it saves a great
|
|
* deal of time when it comes to validation and assures that your forms are secure and have a consistent look and feel
|
|
* throughout your projects!
|
|
*
|
|
* Requires PHP 5.3.0+ (compiled with the php_fileinfo extension), and jQuery 1.6.2+
|
|
*
|
|
* Visit {@link http://stefangabos.ro/php-libraries/zebra-form/} for more information.
|
|
*
|
|
* For more resources visit {@link http://stefangabos.ro/}
|
|
*
|
|
* @author Stefan Gabos <contact@stefangabos.ro>
|
|
* @version 2.9.8 (last revision: May 18, 2016)
|
|
* @copyright (c) 2006 - 2016 Stefan Gabos
|
|
* @license http://www.gnu.org/licenses/lgpl-3.0.txt GNU LESSER GENERAL PUBLIC LICENSE
|
|
* @package Zebra_Form
|
|
*/
|
|
|
|
class Zebra_Form
|
|
{
|
|
|
|
/**
|
|
* Array containing all the controls added to the form
|
|
*
|
|
* @var array
|
|
*
|
|
* @access private
|
|
*/
|
|
var $controls;
|
|
|
|
/**
|
|
* Array containing all the error messages generated by the form
|
|
*
|
|
* @var array
|
|
*
|
|
* @access private
|
|
*/
|
|
var $errors;
|
|
|
|
/**
|
|
* An associative array of items uploaded to the current script via the HTTP POST method.
|
|
* This property, available only if a file upload has occurred, will have the same values as
|
|
* {@link http://php.net/manual/en/reserved.variables.files.php $_FILES} plus some extra values:
|
|
*
|
|
* - <b>path</b> - the path where the file was uploaded to
|
|
* - <b>file_name</b> - the name the file was uploaded with
|
|
* - <b>imageinfo</b> - <b>available only if the uploaded file is an image!</b><br>
|
|
* an array of attributes specific to the uploaded image as returned by
|
|
* {@link http://www.php.net/manual/en/function.getimagesize.php getimagesize()} but
|
|
* with meaningful names:<br>
|
|
* <b>bits</b><br>
|
|
* <b>channels</b><br>
|
|
* <b>mime</b><br>
|
|
* <b>width</b><br>
|
|
* <b>height</b><br>
|
|
* <b>type</b> ({@link http://php.net/manual/en/function.exif-imagetype.php possible types})<br>
|
|
* <b>html</b><br>
|
|
*
|
|
* <b>Note that the file name can be different than the original name of the uploaded file!</b>
|
|
*
|
|
* By design, the script will append
|
|
* a number to the end of a file's name if at the path where the file is uploaded to there is another file with the
|
|
* same name (for example, if at the path where a file named "example.txt" is uploaded to, a file with the same name
|
|
* exists, the file's new name will be "example1.txt").
|
|
*
|
|
* The file names can also be random-generated. See the {@link Zebra_Form_Control::set_rule() set_rule()} method and
|
|
* the <b>upload</b> rule
|
|
*
|
|
* @var array
|
|
*/
|
|
var $file_upload;
|
|
|
|
/**
|
|
* Indicates the {@link http://en.wikipedia.org/wiki/Filesystem_permissions filesystem} permissions to be set for
|
|
* files uploaded through the {@link Zebra_Form_Control::set_rule() upload} rule.
|
|
*
|
|
* <code>
|
|
* $form->file_upload_permissions = '0777';
|
|
* </code>
|
|
*
|
|
* The permissions are set using PHP's {@link http://php.net/manual/en/function.chmod.php chmod} function which may
|
|
* or may not be available or be disabled on your environment. If so, this action will fail silently (no errors or
|
|
* notices will be shown by the library).
|
|
*
|
|
* Better to leave this setting as it is.
|
|
*
|
|
* If you know what you are doing, here is how you can calculate the permission levels:
|
|
*
|
|
* - 400 Owner Read
|
|
* - 200 Owner Write
|
|
* - 100 Owner Execute
|
|
* - 40 Group Read
|
|
* - 20 Group Write
|
|
* - 10 Group Execute
|
|
* - 4 Global Read
|
|
* - 2 Global Write
|
|
* - 1 Global Execute
|
|
*
|
|
* Default is '0755'
|
|
*
|
|
* @var string
|
|
*/
|
|
var $file_upload_permissions;
|
|
|
|
/**
|
|
* Array containing the variables to be made available in the template file (added through the {@link assign()}
|
|
* method)
|
|
*
|
|
* @var array
|
|
*
|
|
* @access private
|
|
*/
|
|
var $variables;
|
|
|
|
/**
|
|
* Constructor of the class
|
|
*
|
|
* Initializes the form.
|
|
*
|
|
* <code>
|
|
* $form = new Zebra_Form('myform');
|
|
* </code>
|
|
*
|
|
* @param string $name Name of the form
|
|
*
|
|
* @param string $method (Optional) Specifies which HTTP method will be used to submit the form data set.
|
|
*
|
|
* Possible (case-insensitive) values are <b>POST</b> an <b>GET</b>
|
|
*
|
|
* Default is <b>POST</b>
|
|
*
|
|
* @param string $action (Optional) An URI to where to submit the form data set.
|
|
*
|
|
* If left empty, the form will submit to itself.
|
|
*
|
|
* <samp>You should *always* submit the form to itself, or server-side validation
|
|
* will not take place and you will have a great security risk. Submit the form
|
|
* to itself, let it do the server-side validation, and then redirect accordingly!</samp>
|
|
*
|
|
* @param array $attributes (Optional) An array of attributes valid for a <form> tag (i.e. style)
|
|
*
|
|
* Note that the following attributes are automatically set when the control is
|
|
* created and should not be altered manually:
|
|
*
|
|
* <b>action</b>, <b>method</b>, <b>enctype</b>, <b>name</b>
|
|
*
|
|
* @return void
|
|
*/
|
|
function __construct($name, $method = 'POST', $action = '', $attributes = '')
|
|
{
|
|
|
|
$this->controls = $this->variables = $this->errors = $this->master_labels = array();
|
|
|
|
// default filesysyem permissions for uploaded files
|
|
$this->file_upload_permissions = '0755';
|
|
|
|
// default values for the form's properties
|
|
$this->form_properties = array(
|
|
|
|
'action' => ($action == '' ? $_SERVER['REQUEST_URI'] : $action),
|
|
'assets_server_path' => rtrim(dirname(__FILE__), '\\/') . DIRECTORY_SEPARATOR,
|
|
'assets_url' => rtrim(str_replace('\\', '/', 'http' . (isset($_SERVER['HTTPS']) || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') ? 's' : '') . '://' . rtrim($_SERVER['HTTP_HOST'], '\\/') . '/' . substr(rtrim(dirname(__FILE__), '\\/'), strlen($_SERVER['DOCUMENT_ROOT']))), '\\/') . '/',
|
|
'attributes' => $attributes,
|
|
'auto_fill' => false,
|
|
'captcha_storage' => 'cookie',
|
|
'csrf_cookie_config' => array('path' => '/', 'domain' => '', 'secure' => false, 'httponly' => true),
|
|
'csrf_cookie_name' => 'zebra_csrf_token_' . $name,
|
|
'csrf_storage_method' => 'auto',
|
|
'csrf_token' => '',
|
|
'csrf_token_lifetime' => 0,
|
|
'csrf_token_name' => 'zebra_csrf_token_' . $name,
|
|
'doctype' => 'html',
|
|
'has_upload' => false,
|
|
'honeypot' => 'zebra_honeypot_' . $name,
|
|
'identifier' => 'name_' . $name,
|
|
'language' => array(),
|
|
'method' => strtoupper($method),
|
|
'name' => $name,
|
|
'other_suffix' => '_other',
|
|
'secret_key' => '',
|
|
'show_all_error_messages' => false,
|
|
|
|
);
|
|
|
|
// set default client-side validation properties
|
|
$this->clientside_validation(true);
|
|
|
|
// get the maximum allowed file size for uploads
|
|
$upload_max_filesize = ini_get('upload_max_filesize');
|
|
|
|
// see what it is given in (G, M, K)
|
|
$unit = strtolower(substr($upload_max_filesize, -1));
|
|
|
|
// get the numeric value
|
|
$value = substr($upload_max_filesize, 0, -1);
|
|
|
|
// convert to bytes
|
|
// notice that there is no break
|
|
switch (strtolower(substr($upload_max_filesize, -1))) {
|
|
|
|
case 'g':
|
|
$value*=1024;
|
|
|
|
case 'm':
|
|
$value*=1024;
|
|
|
|
case 'k':
|
|
$value*=1024;
|
|
|
|
}
|
|
|
|
// set the form's respective property
|
|
$this->form_properties['max_file_size'] = $value;
|
|
|
|
// include the XSS filter class - the Zebra_Form_Control class extends this class
|
|
require_once dirname(__FILE__) . '/includes/XSSClean.php';
|
|
|
|
// include the Control.php file which contains the Zebra_Form_Control class which is
|
|
// extended by all of the classes
|
|
require_once dirname(__FILE__) . '/includes/Control.php';
|
|
|
|
// load the default language file
|
|
$this->language('english');
|
|
|
|
// enable protection against CSRF attacks using the default values
|
|
// note that this has no effect if this method was already called before
|
|
$this->csrf();
|
|
|
|
}
|
|
|
|
/**
|
|
* Adds a control to the form.
|
|
*
|
|
* <code>
|
|
* // create a new form
|
|
* $form = new Zebra_Form('my_form');
|
|
*
|
|
* // add a text control to the form
|
|
* $obj = $form->add('text', 'my_text');
|
|
*
|
|
* // make the text field required
|
|
* $obj->set_rule(
|
|
* 'required' => array(
|
|
* 'error', // variable to add the error message to
|
|
* 'Field is required' // error message if value doesn't validate
|
|
* )
|
|
* );
|
|
*
|
|
* // don't forget to always call this method before rendering the form
|
|
* if ($form->validate()) {
|
|
* // put code here
|
|
* }
|
|
*
|
|
* // output the form using an automatically generated template
|
|
* $form->render();
|
|
* </code>
|
|
*
|
|
* @param string $type Type of the control to add.
|
|
*
|
|
* Controls that can be added to a form:
|
|
*
|
|
* - {@link Zebra_Form_Button buttons}
|
|
* - {@link Zebra_Form_Captcha CAPTCHAs}
|
|
* - {@link Zebra_Form_Checkbox checkboxes}
|
|
* - {@link Zebra_Form_Date date pickers}
|
|
* - {@link Zebra_Form_File file upload controls}
|
|
* - {@link Zebra_Form_Hidden hidden controls}
|
|
* - {@link Zebra_Form_Image image button controls}
|
|
* - {@link Zebra_Form_Label labels}
|
|
* - {@link Zebra_Form_Note notes}
|
|
* - {@link Zebra_Form_Password password controls}
|
|
* - {@link Zebra_Form_Radio radio buttons}
|
|
* - {@link Zebra_Form_Reset reset buttons}
|
|
* - {@link Zebra_Form_Select selects}
|
|
* - {@link Zebra_Form_Submit submit buttons}
|
|
* - {@link Zebra_Form_Text text box controls}
|
|
* - {@link Zebra_Form_Textarea textareas}
|
|
* - {@link Zebra_Form_Time time pickers}
|
|
*
|
|
* @param mixed $arguments A list of arguments as required by the control that is added.
|
|
*
|
|
* @return reference Returns a reference to the newly created object
|
|
*/
|
|
function &add($type)
|
|
{
|
|
|
|
// if shortcut for multiple radio buttons or checkboxes
|
|
if ($type == 'radios' || $type == 'checkboxes') {
|
|
|
|
// if there are less than 3 arguments
|
|
if (func_num_args() < 3)
|
|
|
|
// trigger a warning
|
|
_zebra_form_show_error('For <strong>' . $type . '</strong>, the <strong>add()</strong> method requires at least 3 arguments', E_USER_WARNING);
|
|
|
|
// if third argument is not an array
|
|
elseif (!is_array(func_get_arg(2)))
|
|
|
|
// trigger a warning
|
|
_zebra_form_show_error('For <strong>' . $type . '</strong>, the <strong>add()</strong> method requires the 3rd argument to be an array', E_USER_WARNING);
|
|
|
|
// if everything is ok
|
|
else {
|
|
|
|
// controls' name
|
|
$name = func_get_arg(1);
|
|
|
|
// the values and labels
|
|
$values = func_get_arg(2);
|
|
|
|
// a 4th argument (the default option) was passed to the method
|
|
if (func_num_args() >= 4) {
|
|
|
|
// save the default value
|
|
$default = func_get_arg(3);
|
|
|
|
// if default value is not given as an array
|
|
// (makes sense for checkboxes when there may be multiple preselected values)
|
|
// make it an array
|
|
if (!is_array($default)) $default = array($default);
|
|
|
|
}
|
|
|
|
if (func_num_args() >= 5) $additional = func_get_arg(4);
|
|
|
|
$counter = 0;
|
|
|
|
// iterate through values and their respective labels
|
|
foreach ($values as $value => $caption) {
|
|
|
|
// sanitize controls' name (remove square brackets)
|
|
$sanitize_name = preg_replace('/\[\]$/', '', $name);
|
|
|
|
// santize the value
|
|
$value = preg_replace('/\_{1,}/', '_', preg_replace('/[^a-z0-9\_]/i', '_', $value));
|
|
|
|
// create control
|
|
$obj = & $this->add(
|
|
($type == 'radios' ? 'radio' : 'checkbox'), $name, $value,
|
|
(isset($default) && in_array($value, $default) ?
|
|
(isset($additional) ? array_merge($additional, array('checked' => 'checked')) : array('checked' => 'checked')) :
|
|
(isset($additional) ? $additional :'')));
|
|
|
|
// if this is the first control in the array
|
|
// we will later need to return a reference to it
|
|
if ($counter++ == 0) $pointer = &$obj;
|
|
|
|
// add the label for the control
|
|
$this->add('label', 'label_' . $sanitize_name . '_' . $value, $sanitize_name . '_' . $value, $caption);
|
|
|
|
}
|
|
|
|
// if the array of values was not empty
|
|
// return reference to the first control
|
|
if (isset($pointer)) return $pointer;
|
|
|
|
}
|
|
|
|
// for all other controls
|
|
} else {
|
|
|
|
$file_name = ucfirst(strtolower($type));
|
|
|
|
// the classes have the "Zebra_Form_" prefix
|
|
$class_name = 'Zebra_Form_' . ucfirst(strtolower($type));
|
|
|
|
// include the file containing the PHP class, if not already included
|
|
require_once dirname(__FILE__) . '/includes/' . $file_name . '.php';
|
|
|
|
// if included file contains such a class
|
|
if (class_exists($class_name)) {
|
|
|
|
// prepare arguments passed to the add() method
|
|
// notice that first argument is ignored as it refers to the type of the control to add
|
|
// and we don't have to pass that to the class
|
|
$arguments = array_slice(func_get_args(), 1);
|
|
|
|
// if name was not specified trigger an error
|
|
if (strlen(trim($arguments[0])) == 0) trigger_error('Name is required for control of type ' . $class_name, E_USER_ERROR);
|
|
|
|
// use this method to instantiate the object with dynamic arguments
|
|
$obj = call_user_func_array(array(new ReflectionClass($class_name), 'newInstance'), $arguments);
|
|
|
|
// make available the form's properties in the newly created object
|
|
$obj->form_properties = & $this->form_properties;
|
|
|
|
// get some attributes for the newly created control
|
|
$attributes = $obj->get_attributes(array('id', 'name'));
|
|
|
|
// perform some extra tasks for different types of controls
|
|
switch ($class_name) {
|
|
|
|
// if the newly created control is a file upload control
|
|
case 'Zebra_Form_File':
|
|
|
|
// set a flag to be used at rendering
|
|
$this->form_properties['has_upload'] = true;
|
|
|
|
break;
|
|
|
|
// if the newly created control is a radio button or a checkbox
|
|
case 'Zebra_Form_Radio':
|
|
case 'Zebra_Form_Checkbox':
|
|
|
|
// radio buttons and checkboxes might have a "master label", a label that is tied to the radio buttons' or
|
|
// checkboxes' name rather than individual controls' IDs. (as grouped radio buttons and checkboxes share
|
|
// the same name but have different values)
|
|
// we use this so that, if the controls have the "required" rule set, the asterisk is attached to the master
|
|
// label rather than to one of the actual controls
|
|
|
|
// therefore, we generate a "lookup" array of "master" labels for each group of radio buttons or
|
|
// checkboxes. this does not means that there will be an actual master label - we use this lookup
|
|
// array to easily determine if a master label exists when rendering the form
|
|
|
|
// sanitize the control's name
|
|
$attributes['name'] = preg_replace('/\[\]$/', '', $attributes['name']);
|
|
|
|
// if there isn't a master label for the group the current control is part of
|
|
if (!isset($this->master_labels[$attributes['name']]))
|
|
|
|
// create the entry
|
|
// the "control" index will hold the actual label's name if a "master" label is added to the form
|
|
$this->master_labels[$attributes['name']] = array('control' => false);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// put the reference to the newly created object in the 'controls' array
|
|
$this->controls[$attributes['id']] = &$obj;
|
|
|
|
// return the identifier to the newly created object
|
|
return $obj;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Appends a message to an already existing {@link Zebra_Form_Control::set_rule() error block}
|
|
*
|
|
* <code>
|
|
* // create a new form
|
|
* $form = new Zebra_Form('my_form');
|
|
*
|
|
* // add a text control to the form
|
|
* $obj = $form->add('text', 'my_text');
|
|
*
|
|
* // make the text field required
|
|
* $obj->set_rule(
|
|
* 'required' => array(
|
|
* 'error', // variable to add the error message to
|
|
* 'Field is required' // error message if value doesn't validate
|
|
* )
|
|
* );
|
|
*
|
|
* // don't forget to always call this method before rendering the form
|
|
* if ($form->validate()) {
|
|
*
|
|
* // for the purpose of this example, we will do a custom validation
|
|
* // after calling the "validate" method.
|
|
* // for custom validations, using the "custom" rule is recommended instead
|
|
*
|
|
* // check if value's is between 1 and 10
|
|
* if ((int)$_POST['my_text']) < 1 || (int)$_POST['my_text']) > 10) {
|
|
*
|
|
* $form->add_error('error', 'Value must be an integer between 1 and 10!');
|
|
*
|
|
* } else {
|
|
*
|
|
* // put code here that is to be executed when the form values are ok
|
|
*
|
|
* }
|
|
*
|
|
* }
|
|
*
|
|
* // output the form using an automatically generated template
|
|
* $form->render();
|
|
* </code>
|
|
*
|
|
* @param string $error_block The name of the error block to append the error message to (also the name
|
|
* of the PHP variable that will be available in the template file).
|
|
*
|
|
* @param string $error_message The error message to append to the error block.
|
|
*
|
|
* @return void
|
|
*/
|
|
function add_error($error_block, $error_message)
|
|
{
|
|
|
|
// if the error block was not yet created, create the error block
|
|
if (!isset($this->errors[$error_block])) $this->errors[$error_block] = array();
|
|
|
|
// if the same exact message doesn't already exists
|
|
if (!in_array(trim($error_message), $this->errors[$error_block]))
|
|
|
|
// append the error message to the error block
|
|
$this->errors[$error_block][] = trim($error_message);
|
|
|
|
}
|
|
|
|
/**
|
|
* Set the server path and URL to the "process.php" and "mimes.json" files.
|
|
*
|
|
* These files are required for CAPTCHAs and uploads.
|
|
*
|
|
* By default, the location of these files is in the same folder as Zebra_Form.php and the script will automatically
|
|
* try to determine both the server path and the URL to these files. However, when the script is run on a virtual
|
|
* host, or if these files were moved, the script may not correctly determine the paths to these files. In these
|
|
* instances, use this method to correctly set the server path - needed by the script to correctly include these
|
|
* files, and the URL - needed by the client-side validation to include these files.
|
|
*
|
|
* Also, for security reasons, I recommend moving these two files by default to the root of your website (or another
|
|
* publicly accessible place) and manually set the paths, in order to prevent malicious users from finding out
|
|
* information about your directory structure.
|
|
*
|
|
* <samp>If you move these files don't forget to also move the font file from the "includes" folder, and to manually
|
|
* adjust the path to the font file in "process.php"!</samp>
|
|
*
|
|
* @param string $server_path The server path (the one similar to what is in $_SERVER['DOCUMENT_ROOT']) to
|
|
* the folder where the "process.php" and "mimes.json" files can be found.
|
|
*
|
|
* <i>With trailing slash!</i>
|
|
*
|
|
* @param string $url The URL to where the "process.php" and "mimes.json" files can be found.
|
|
*
|
|
* <i>With trailing slash!</i>
|
|
*
|
|
* @return void
|
|
*/
|
|
function assets_path($server_path, $url)
|
|
{
|
|
|
|
// set values
|
|
$this->form_properties['assets_server_path'] = $server_path;
|
|
$this->form_properties['assets_url'] = $url;
|
|
|
|
}
|
|
|
|
/**
|
|
* Creates a PHP variable with the given value, available in the template file.
|
|
*
|
|
* <code>
|
|
* // create a new form
|
|
* $form = new Zebra_Form('my_form');
|
|
*
|
|
* // make available the $my_value variable in the template file
|
|
* $form->assign('my_value', '100');
|
|
*
|
|
* // don't forget to always call this method before rendering the form
|
|
* if ($form->validate()) {
|
|
* // put code here
|
|
* }
|
|
*
|
|
* // output the form
|
|
* // notice that we are using a custom template
|
|
* // my_template.php file is expected to be found
|
|
* // and in this file, you may now use the $my_value variable
|
|
* $form->render('my_template.php');
|
|
* </code>
|
|
*
|
|
* @param string $variable_name Name by which the variable will be available in the template file.
|
|
*
|
|
* @param mixed $value The value to be assigned to the variable.
|
|
*
|
|
* @return void
|
|
*/
|
|
function assign($variable_name, $value)
|
|
{
|
|
|
|
// save the variable in an array that we will make available in the template file upon rendering
|
|
$this->variables[$variable_name] = $value;
|
|
|
|
}
|
|
|
|
/**
|
|
* Call this method anytime *before* calling the {@link validate()} method (preferably, right after instantiating
|
|
* the class) to instruct the library to automatically fill out all of the form's fields with random content while
|
|
* obeying any rules that might be set for each control.
|
|
*
|
|
* You can also use this method to set defaults for the form's elements by setting the method's second argument to TRUE.
|
|
*
|
|
* <b>Notes:</b>
|
|
*
|
|
* - unless overridden, the value of {@link Zebra_Form_Password password} controls will always be "12345678";
|
|
* - unless overridden, the value of controls having the "email" or "emails" {@link Zebra_Form_Control::set_rule() rule}
|
|
* set will be in the form of <i>random_text@random_text.com</i>;
|
|
* - unless overridden, the value of controls having the "url" {@link Zebra_Form_Control::set_rule() rule} set will
|
|
* be in the form of <i>random_text.com</i>, prefixed or not with "http://", depending on the rule's attributes;
|
|
* - {@link Zebra_Form_File file upload} controls and controls having the "captcha" or "regexp"
|
|
* {@link Zebra_Form_Control::set_rule() rule} set will *not* be autofilled;
|
|
*
|
|
* <samp>This method will produce results *only* if the form has not yet been submitted! Also, this method will fill
|
|
* elements with random content *only* if the element does not already has a default value! And finally, this method
|
|
* will produce no results unless at some point the form's {@link validate()} method is called.</samp>
|
|
*
|
|
* @param array $defaults An associative array in the form of <i>$element => $value</i> used for filling
|
|
* out specific fields with specific values instead of random ones.
|
|
*
|
|
* For elements that may have more than one value (checkboxes and selects with
|
|
* the "multiple" attribute set) you may set the value as an array.
|
|
*
|
|
* <code>
|
|
* // auto-fill all fields with random values
|
|
* $form->auto_fill();
|
|
*
|
|
* // auto-fill all fields with random values
|
|
* // except the one called "email" which should have a custom value
|
|
* $form->auto_fill(array(
|
|
* 'email' => 'some@email.com',
|
|
* ));
|
|
*
|
|
* // auto-fill all fields with random values
|
|
* // except a checkboxes group where we select multiple values
|
|
* // note that we use "my_checkboxes" insteas of "my_checkboxes[]"
|
|
* // (the same goes for "selects" with the "multiple" attribute set)
|
|
* $form->auto_fill(array(
|
|
* 'my_checkboxes' => array('value_1', 'value_2'),
|
|
* ));
|
|
* </code>
|
|
*
|
|
* @param boolean $specifics_only (Optional) If set to TRUE only the fields given in the $defaults argument
|
|
* will be filled with the given values and the other fields will be skipped.
|
|
*
|
|
* Can be used as a handy shortcut for giving default values to elements instead
|
|
* of putting default values in the constructor or using the {@link set_attributes()}
|
|
* method.
|
|
*
|
|
* Default is FALSE.
|
|
*
|
|
*
|
|
* @since 2.8.9
|
|
*
|
|
* @return void
|
|
*/
|
|
function auto_fill($defaults = array(), $specifics_only = false)
|
|
{
|
|
|
|
$this->form_properties['auto_fill'] = array($defaults, $specifics_only);
|
|
|
|
}
|
|
|
|
/**
|
|
* Sets the storage method for CAPTCHA values.
|
|
*
|
|
* By default, captcha values are triple md5 hashed and stored in cookies, and when the user enters the captcha
|
|
* value the value is also triple md5 hashed and the two values are then compared.
|
|
*
|
|
* Sometimes, your users may have a very restrictive cookie policy and so cookies will not be set, and therefore,
|
|
* they will never be able to get past the CAPTCHA control. If it's the case, call this method and set the storage
|
|
* method to "session".
|
|
*
|
|
* In this case, call this method and set the the storage method to "session".
|
|
*
|
|
* @param string $method Storage method for CAPTCHA values.
|
|
*
|
|
* Valid values are "cookie" and "session".
|
|
*
|
|
* Default is "cookie".
|
|
*
|
|
* @since 2.8.9
|
|
*
|
|
* @return void
|
|
*/
|
|
function captcha_storage($method)
|
|
{
|
|
|
|
// if storage method is "session"
|
|
if ($method == 'session')
|
|
|
|
// set the storage method
|
|
$this->form_properties['captcha_storage'] = 'session';
|
|
|
|
// "cookie" otherwise
|
|
else $this->form_properties['captcha_storage'] = 'cookie';
|
|
|
|
}
|
|
|
|
/**
|
|
* Alias of {@link clientside_validation()} method.
|
|
*
|
|
* Deprecated since 2.8.9
|
|
*/
|
|
function client_side_validation($properties)
|
|
{
|
|
|
|
$this->clientside_validation($properties);
|
|
|
|
}
|
|
|
|
/**
|
|
* Sets properties for the client-side validation.
|
|
*
|
|
* Client-side validation, when enabled, occurs on the "onsubmit" event of the form.
|
|
*
|
|
* <code>
|
|
* // create a new form
|
|
* $form = new Zebra_Form('my_form');
|
|
*
|
|
* // disable client-side validation
|
|
* $form->clientside_validation(false);
|
|
*
|
|
* // enable client-side validation using default properties
|
|
* $form->clientside_validation(true);
|
|
*
|
|
* // enable client-side validation using customized properties
|
|
* $form->clientside_validation(array(
|
|
* 'close_tips' => false, // don't show a "close" button on tips with error messages
|
|
* 'on_ready' => false, // no function to be executed when the form is ready
|
|
* 'disable_upload_validation' => true, // using a custom plugin for managing file uploads
|
|
* 'scroll_to_error' => false, // don't scroll the browser window to the error message
|
|
* 'tips_position' => 'right', // position tips with error messages to the right of the controls
|
|
* 'validate_on_the_fly' => false, // don't validate controls on the fly
|
|
* 'validate_all' => false, // show error messages one by one upon trying to submit an invalid form
|
|
* ));
|
|
* </code>
|
|
*
|
|
* To access the JavaScript object and use the public methods provided by it, use $('#formname').data('Zebra_Form')
|
|
* where <i>formname</i> is the form's name <b>with any dashes turned into underscores!</b>
|
|
*
|
|
* <i>Therefore, if a form's name is "my-form", the JavaScript object would be accessed like $('my_form').data('Zebra_Form').</i>
|
|
*
|
|
* From JavaScript, these are the methods that can be called on this object:
|
|
*
|
|
* - <b>attach_tip(element, message)</b> - displays a custom error message, attached to the specified jQuery
|
|
* element
|
|
* - <b>clear_errors()</b> - hides all error messages;
|
|
* - <b>submit()</b> - submits the form;
|
|
* - <b>validate()</b> - checks if the form is valid; returns TRUE or FALSE;
|
|
* <i>if called with the "false" boolean argument, error messages will
|
|
* not be shown in case form does not validate</i>
|
|
*
|
|
* Here's how you can use these methods, in JavaScript:
|
|
*
|
|
* <code>
|
|
* // let's submit the form when clicking on a random button
|
|
*
|
|
* // get a reference to the Zebra_Form object
|
|
* var $form = $('#formname').data('Zebra_Form');
|
|
*
|
|
* // handle the onclick event on a random button
|
|
* $('#somebutton').bind('click', function(e) {
|
|
*
|
|
* // stop default action
|
|
* e.preventDefault();
|
|
*
|
|
* // validate the form, and if the form validates
|
|
* if ($form.validate()) {
|
|
*
|
|
* // do your thing here
|
|
*
|
|
* // when you're done, submit the form
|
|
* $form.submit();
|
|
*
|
|
* }
|
|
*
|
|
* // if the form is not valid, everything is handled automatically by the library
|
|
*
|
|
* });
|
|
* </code>
|
|
*
|
|
* @param mixed $properties Can have the following values:
|
|
*
|
|
* - FALSE, disabling the client-side validation;
|
|
* <i>Note that the {@link Zebra_Form_Control::set_rule() dependencies} rule will
|
|
* still be checked client-side so that callback functions get called, if it is
|
|
* the case!</i>
|
|
* - TRUE, enabling the client-side validation with the default properties;
|
|
*
|
|
* - an associative array with customized properties for the client-side validation;
|
|
*
|
|
* In this last case, the available properties are:
|
|
*
|
|
* - <b>close_tips</b>, boolean, TRUE or FALSE<br>
|
|
* Specifies whether the tips with error messages should have a "close" button
|
|
* or not<br>
|
|
* Default is <b>TRUE</b>.
|
|
*
|
|
* - <b>disable_upload_validation</b>, boolean, TRUE or FALSE<br>
|
|
* Useful for disabling all client-side processing of file upload controls, so
|
|
* that custom plugins may be used for it (like, for instance,
|
|
* {@link http://blueimp.github.io/jQuery-File-Upload/basic.html this one})
|
|
* Default is <b>FALSE</b>.
|
|
*
|
|
* - <b>on_ready</b>, JavaScript function to be executed when the form is loaded.
|
|
* Useful for getting a reference to the Zebra_Form object after everything is
|
|
* loaded.
|
|
*
|
|
* <code>
|
|
* $form->clientside_validation(array(
|
|
* // where $form is a global variable and 'id' is the form's id
|
|
* 'on_ready': 'function() { $form = $("#id").data('Zebra_Form'); }',
|
|
* ));
|
|
* </code>
|
|
*
|
|
* - <b>scroll_to_error</b>, boolean, TRUE or FALSE<br>
|
|
* Specifies whether the browser window should be scrolled to the error message
|
|
* or not.<br>
|
|
* Default is <b>TRUE</b>.
|
|
*
|
|
* - <b>tips_position</b>, string, <i>left</i>, <i>right</i> or <i>center</i><br>
|
|
* Specifies where the error message tip should be positioned relative to the
|
|
* control.<br>
|
|
* Default is <b>left</b>.
|
|
*
|
|
* - <b>validate_on_the_fly</b>, boolean, TRUE or FALSE<br>
|
|
* Specifies whether values should be validated as soon as the user leaves a
|
|
* field; if set to TRUE and the validation of the control fails, the error
|
|
* message will be shown right away<br>
|
|
* Default is <b>FALSE</b>.
|
|
*
|
|
* - <b>validate_all</b>, boolean, TRUE or FALSE<br>
|
|
* Specifies whether upon submitting the form, should all error messages be
|
|
* shown at once if there are any errors<br>
|
|
* Default is <b>FALSE</b>.
|
|
*
|
|
* @return void
|
|
*/
|
|
function clientside_validation($properties)
|
|
{
|
|
|
|
// default properties of the client-side validation
|
|
$defaults = array(
|
|
'clientside_disabled' => false,
|
|
'close_tips' => true,
|
|
'disable_upload_validation' => false,
|
|
'on_ready' => false,
|
|
'scroll_to_error' => true,
|
|
'tips_position' => 'left',
|
|
'validate_on_the_fly' => false,
|
|
'validate_all' => false,
|
|
);
|
|
|
|
// set the default properties for the client-side validation
|
|
if (!isset($this->form_properties['clientside_validation'])) $this->form_properties['clientside_validation'] = $defaults;
|
|
|
|
// if client-side validation needs to be disabled
|
|
if ($properties == false) $this->form_properties['clientside_validation']['clientside_disabled'] = true;
|
|
|
|
// if custom settings for client-side validation
|
|
elseif (is_array($properties))
|
|
|
|
// merge the new settings with the old ones
|
|
$this->form_properties['clientside_validation'] = array_merge($this->form_properties['clientside_validation'], $properties);
|
|
|
|
}
|
|
|
|
/**
|
|
* By default, this class generates <b>HTML 4.01 Strict</b> markup.
|
|
*
|
|
* Use this method if you want the generated HTML markup to validate as <b>XHTML 1.0 Strict</b>.
|
|
*
|
|
* @param string $doctype (Optional) The DOCTYPE of the generated HTML markup.
|
|
*
|
|
* Possible (case-insensitive) values are <b>HTML</b> or <b>XHTML</b>
|
|
*
|
|
* Default is HTML.
|
|
*
|
|
* @return void
|
|
*/
|
|
function doctype($doctype = 'html')
|
|
{
|
|
|
|
// set the doctype
|
|
$this->form_properties['doctype'] = (strtolower($doctype) == 'xhtml' ? 'xhtml' : 'html');
|
|
|
|
}
|
|
|
|
/**
|
|
* Enables protection against {@link http://en.wikipedia.org/wiki/Cross-site_request_forgery Cross-site request
|
|
* forgery} (CSRF) attacks.
|
|
*
|
|
* Read more about specifics and a simple implementation on
|
|
* {@link http://shiflett.org/articles/cross-site-request-forgeries Chris Shiflett's website}.
|
|
*
|
|
* This method is automatically called by the library, so protection against CSRF attacks is enabled by default for
|
|
* all forms and the script will decide automatically on the method to use for storing the CSRF token: if a session
|
|
* is already started then the CSRF token will be stored in a session variable or, if a session is not started, the
|
|
* CSRF token will be stored in a session cookie (cookies that expire when the browser is closed) but, in this case,
|
|
* it offers a lower level of security.
|
|
*
|
|
* If cookies are used for storage, you'll have to make sure that Zebra_Form is instantiated before any output from
|
|
* your script (this is a protocol restriction) including <html> and <head> tags as well as any whitespace!
|
|
* A workaround is to turn output buffering on in php.ini.
|
|
*
|
|
* <i>You are encouraged to start a PHP session before instantiating this class in order to maximize the level of
|
|
* security of your forms.</i>
|
|
*
|
|
* The CSRF token is automatically regenerated when the form is submitted regardless if the form validated or not.
|
|
* A notable exception is that the form doesn't validate but was submitted via AJAX the CSRF token will not be
|
|
* regenerated - useful if you submit forms by AJAX.
|
|
*
|
|
* As an added benefit, protection against CSRF attacks prevents "double posts" by design.
|
|
*
|
|
* <samp>You only need to call this method if you want to change CSRF related settings. If you do call this method
|
|
* then you should call it right after instantiating the class and *before* calling the form's {@link validate()}
|
|
* and {@link render()} methods!</samp>
|
|
*
|
|
* <code>
|
|
* // recommended usage is:
|
|
*
|
|
* // call session_start() somewhere in your code but before outputting anything to the browser
|
|
* session_start();
|
|
*
|
|
* // include the Zebra_Form
|
|
* require 'path/to/Zebra_Form.php';
|
|
*
|
|
* // instantiate the class
|
|
* // protection against CSRF attack will be automatically enabled
|
|
* // but will be less secure if a session is not started (as it will
|
|
* // rely on cookies)
|
|
* $form = new Zebra_Form('my_form');
|
|
* </code>
|
|
*
|
|
* @param string $csrf_storage_method (Optional) Sets whether the CSRF token should be stored in a cookie, in
|
|
* a session variable, or let the script to automatically decide and use
|
|
* sessions if available or a cookie otherwise.
|
|
*
|
|
* Possible values are "auto", "cookie", "session" or boolean FALSE.
|
|
*
|
|
* If value is "auto", the script will decide automatically on what to use:
|
|
* if a session is already started then the CSRF token will be stored in a
|
|
* session variable, or, if a session is not started, the CSRF token will be
|
|
* stored in a cookie with the parameters as specified by the
|
|
* <b>csrf_cookie_config</b> argument (read below).
|
|
*
|
|
* If value is "cookie" the CSRF token will be stored in a cookie with the
|
|
* parameters as specified by the <b>csrf_cookie_config</b> argument (read
|
|
* below).
|
|
*
|
|
* If value is "session" the CSRF token will be stored in a session variable
|
|
* and thus a session must be started before instantiating the library.
|
|
*
|
|
* If value is boolean FALSE (not recommended), protection against CSRF
|
|
* attack will be disabled.
|
|
*
|
|
* The stored value will be compared, upon for submission, with the value
|
|
* stored in the associated hidden field, and if the two values do not match
|
|
* the form will not validate.
|
|
*
|
|
* Default is "auto".
|
|
*
|
|
* @param integer $csrf_token_lifetime (Optional) The number of seconds after which the CSRF token is to be
|
|
* considered as expired.
|
|
*
|
|
* If set to "0" the tokens will expire at the end of the session (when the
|
|
* browser closes or session expires).
|
|
*
|
|
* <i>Note that if csrf_storage_method is set to "session" this value cannot
|
|
* be higher than the session's life time as, if idle, the session will time
|
|
* out regardless of this value!</i>
|
|
*
|
|
* Default is 0.
|
|
*
|
|
* @param array $csrf_cookie_config (Optional) An associative array containing the properties to be used when
|
|
* setting the cookie with the CSRF token (if <b>csrf_storage_method</b> is
|
|
* set to "cookie").
|
|
*
|
|
* The properties that can be set are "path", "domain", "secure" and "httponly".
|
|
* where:
|
|
*
|
|
* - <b>path</b> - the path on the server in which the cookie will
|
|
* be available on. If set to "/", the cookie will
|
|
* be available within the entire domain. If set to
|
|
* '/foo/', the cookie will only be available within
|
|
* the /foo/ directory and all subdirectories such
|
|
* as /foo/bar/ of domain.<br>
|
|
* Default is "/"
|
|
*
|
|
* - <b>domain</b> - The domain that the cookie will be available on.
|
|
* To make the cookie available on all subdomains of
|
|
* example.com, domain should be set to to
|
|
* ".example.com". The . (dot) is not required but
|
|
* makes it compatible with more browsers. Setting
|
|
* it to "www.example.com" will make the cookie
|
|
* available only in the www subdomain.
|
|
*
|
|
* - <b>secure</b> - Indicates whether cookie information should only
|
|
* be transmitted over a HTTPS connection.<br>
|
|
* Default is FALSE.
|
|
*
|
|
* - <b>httponly</b> - When set to TRUE the cookie will be made accessible
|
|
* only through the HTTP protocol. This means that
|
|
* the cookie won't be accessible by scripting languages,
|
|
* such as JavaScript. It has been suggested that
|
|
* this setting can effectively help to reduce identity
|
|
* theft through XSS attacks (although it is not
|
|
* supported by all browsers), but that claim is often
|
|
* disputed. Available only in PHP 5.2.0+<br>
|
|
* Default is FALSE
|
|
*
|
|
* Available only for PHP 5.2.0+ and will be ignored
|
|
* if not available.
|
|
*
|
|
* Not all properties must be set - for the properties that are not set, the
|
|
* default values will be used instead.
|
|
*
|
|
* @since 2.8.4
|
|
*
|
|
* @return void
|
|
*/
|
|
function csrf($csrf_storage_method = 'auto', $csrf_token_lifetime = 0, $csrf_cookie_config = array('path' => '/', 'domain' => '', 'secure' => false, 'httponly' => true))
|
|
{
|
|
|
|
// continue only if protection against CSRF attacks is not disabled and a token was not already generated
|
|
if ($csrf_storage_method !== false && (func_num_args() > 0 || $this->form_properties['csrf_token'] == '')) {
|
|
|
|
// set the storage method for the CSRF token
|
|
$this->form_properties['csrf_storage_method'] = strtolower(trim($csrf_storage_method));
|
|
|
|
// if the script should decide what method to use and a session is already started
|
|
if ($this->form_properties['csrf_storage_method'] == 'auto') {
|
|
|
|
// use sessions as storage method
|
|
if (isset($_SESSION)) $this->form_properties['csrf_storage_method'] = 'session';
|
|
|
|
// if a session is not already started, use cookies as storage method
|
|
else $this->form_properties['csrf_storage_method'] = 'cookie';
|
|
|
|
}
|
|
|
|
// set the life time of the CSRF token
|
|
$this->form_properties['csrf_token_lifetime'] = ($csrf_token_lifetime <= 0 ? 0 : $csrf_token_lifetime);
|
|
|
|
// set the configuration options for cookies
|
|
$this->form_properties['csrf_cookie_config'] = array_merge($this->form_properties['csrf_cookie_config'], $csrf_cookie_config);
|
|
|
|
// generate a new CSRF token (if it is the case)
|
|
// (if this method is called with any arguments it means it is called by the user and therefore the
|
|
// token should be regenerated)
|
|
$this->_csrf_generate_token(func_num_args() > 0 ? true : false);
|
|
|
|
// if protection against CSRF attacks is disabled, save the option for later use
|
|
} else $this->form_properties['csrf_storage_method'] = false;
|
|
|
|
}
|
|
|
|
/**
|
|
* Sets the language to be used by some of the form's controls (the date control, the select control, etc.)
|
|
*
|
|
* The default language is English.
|
|
*
|
|
* @param string $language The name of the language file to be used, from the "languages" folder.
|
|
*
|
|
* Must be specified without extension ("german" for the german language
|
|
* not "german.php")!
|
|
*
|
|
* @var string
|
|
*
|
|
* @return void
|
|
*/
|
|
function language($language)
|
|
{
|
|
|
|
// include the language file
|
|
require rtrim(dirname(__FILE__), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . 'languages/' . strtolower(trim($language)) . '.php';
|
|
|
|
// make the language available in the control
|
|
$this->form_properties['language'] = &$this->language;
|
|
|
|
}
|
|
|
|
/**
|
|
* Renders the form.
|
|
*
|
|
* @param string $template The output of the form can be generated automatically, can be given from a template
|
|
* file or can be generated programmatically by a callback function.
|
|
*
|
|
* For the automatically generated template there are two options:
|
|
*
|
|
* - when <i>$template</i> is an empty string or is "<i>*vertical</i>", the script
|
|
* will automatically generate an output where the labels are above the controls
|
|
* and controls come one under another (vertical view)
|
|
*
|
|
* - when <i>$template</i> is "<i>*horizontal</i>", the script will automatically
|
|
* generate an output where the labels are positioned to the left of the controls
|
|
* while the controls come one under another (horizontal view)
|
|
*
|
|
* When templates are user-defined, <i>$template</i> needs to be a string representing
|
|
* the <i>path/to/the/template.php</i>.
|
|
*
|
|
* The template file itself must be a plain PHP file where all the controls
|
|
* added to the form (except for the hidden controls, which are handled automatically)
|
|
* will be available as variables with the names as described in the documentation
|
|
* for each of the controls. Also, error messages will be available as described at
|
|
* {@link Zebra_Form_Control::set_rule() set_rule()}.
|
|
*
|
|
* A special variable will also be available in the template file - a variable with
|
|
* the name of the form and being an associative array containing all the controls
|
|
* added to the form, as objects.
|
|
*
|
|
* <i>The template file must not contain the <form> and </form> tags, nor any of the
|
|
* <hidden> controls added to the form as these are generated automatically!</i>
|
|
*
|
|
* There is a third method of generating the output and that is programmatically,
|
|
* through a callback function. In this case <i>$template</i> needs to be the name
|
|
* of an existing function.
|
|
*
|
|
* The function will be called with two arguments:
|
|
*
|
|
* - an associative array with the form's controls' ids and their respective
|
|
* generated HTML, ready for echo-ing (except for the hidden controls which will
|
|
* still be handled automatically);
|
|
*
|
|
* <i>note that this array will also contain variables assigned through the
|
|
* {@link assign()} method as well as any server-side error messages, as you
|
|
* would in a custom template (see {@link Zebra_Form_Control::set_rule() set_rule()}
|
|
* method and read until the second highlighted box, inclusive)</i>
|
|
*
|
|
* - an associative array with all the controls added to the form, as objects
|
|
*
|
|
* THE USER FUNCTION MUST RETURN THE GENERATED OUTPUT!
|
|
*
|
|
* @param boolean $return (Optional) If set to TRUE, the output will be returned instead of being printed
|
|
* to the screen.
|
|
*
|
|
* Default is FALSE.
|
|
*
|
|
* @param array $variables (Optional) An associative array in the form of "variable_name" => "value"
|
|
* representing variable names and their associated values, to be made available
|
|
* in custom template files.
|
|
*
|
|
* This represents a quicker alternative for assigning many variables at once
|
|
* instead of calling the {@link assign()} method for each variable.
|
|
*
|
|
* @return mixed Returns or displays the rendered form.
|
|
*/
|
|
function render($template = '', $return = false, $variables = '')
|
|
{
|
|
|
|
// if
|
|
if (
|
|
|
|
// "process.php" file could not be found
|
|
!file_exists($this->form_properties['assets_server_path'] . 'process.php') ||
|
|
|
|
// or "mimes.json" file could not be found
|
|
!file_exists($this->form_properties['assets_server_path'] . 'mimes.json')
|
|
|
|
)
|
|
|
|
// it means the most probably the script is run on a virtual host and that paths need to be set manually so
|
|
// we inform the user about that
|
|
_zebra_form_show_error('<strong>Zebra_Form</strong> could not automatically determine the correct path to the "process.php"
|
|
and "mimes.json" files - this may happen if the script is run on a virtual host. To fix this, use the <u>assets_path()</u>
|
|
method and manually set the correct <strong>server path</strong> and <strong>URL</strong> to these file!', E_USER_ERROR);
|
|
|
|
// if variables is an array
|
|
if (is_array($variables))
|
|
|
|
// iterate through the values in the array
|
|
foreach ($variables as $name => $value)
|
|
|
|
// make each value available in the template
|
|
$this->assign($name, $value);
|
|
|
|
// start generating the output
|
|
$output = '<form ' .
|
|
($this->form_properties['doctype'] == 'html' ? 'name="' . $this->form_properties['name'] . '" ' : '') .
|
|
'id="' . $this->form_properties['name'] . '" ' .
|
|
'action="' . htmlspecialchars($this->form_properties['action']) . '" ' .
|
|
'method="' . strtolower($this->form_properties['method']) . '" ' .
|
|
'novalidate="novalidate"';
|
|
|
|
// if custom classes are to be set for the form
|
|
if (isset($this->form_properties['attributes']['class']))
|
|
|
|
// add the "Zebra_Form" required class
|
|
$this->form_properties['attributes']['class'] .= ' Zebra_Form';
|
|
|
|
// if no custom classes are set, set the required "Zebra_Form" class
|
|
else $this->form_properties['attributes']['class'] = 'Zebra_Form';
|
|
|
|
// if any form attributes have been specified
|
|
if (is_array($this->form_properties['attributes']))
|
|
|
|
// iterate through the form's attributes
|
|
foreach ($this->form_properties['attributes'] as $attribute => $value)
|
|
|
|
// write them
|
|
$output .= ' ' . $attribute . '="' . $value . '"';
|
|
|
|
// if the form has file upload controls
|
|
if ($this->form_properties['has_upload'] === true) {
|
|
|
|
// add the enctype to the attributes of the <form> tag
|
|
$output .= ' enctype="multipart/form-data"';
|
|
|
|
// and add this required hidden field containing the maximum allowed file size
|
|
$this->add('hidden', 'MAX_FILE_SIZE', $this->form_properties['max_file_size']);
|
|
|
|
// if client-side validation is not disabled
|
|
if (!$this->form_properties['clientside_validation']['clientside_disabled'] && !$this->form_properties['clientside_validation']['disable_upload_validation'])
|
|
|
|
// add a new property for the client-side validation
|
|
$this->clientside_validation(array('assets_path' => rawurlencode($this->form_properties['assets_url'])));
|
|
|
|
}
|
|
|
|
$output .= '>';
|
|
|
|
// iterate through the form's controls
|
|
foreach ($this->controls as $key => $control) {
|
|
|
|
// treat "email" and "number" types as "text"
|
|
if (in_array($control->attributes['type'], array('email', 'number'))) $control->attributes['type'] = 'text';
|
|
|
|
// get some attributes for each control
|
|
$attributes = $control->get_attributes(array('type', 'for', 'name', 'id', 'multiple', 'other', 'class', 'default_other', 'disable_zebra_datepicker'));
|
|
|
|
// sanitize the control's name
|
|
$attributes['name'] = preg_replace('/\[\]/', '', $attributes['name']);
|
|
|
|
// validate the control's name
|
|
switch ($attributes['name']) {
|
|
|
|
// if control has the same name as the form
|
|
case $this->form_properties['name']:
|
|
// if control has the same name as the name of the honeypot's name
|
|
case $this->form_properties['honeypot']:
|
|
// if control has the same name as the name of the field containing the CSRF token
|
|
case $this->form_properties['csrf_token_name']:
|
|
// if control has the name "submit"
|
|
case 'submit':
|
|
|
|
// stop the execution of the script
|
|
_zebra_form_show_error('You are not allowed to have a control named "<strong>' .
|
|
$attributes['name'] . '</strong>" in form "<strong>' .
|
|
$this->form_properties['name'] . '</strong>"',
|
|
E_USER_ERROR);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// if control name is not allowed because it looks like the automatically generated controls for <select> controls
|
|
// with the "other" option attached
|
|
if (preg_match('/' . preg_quote($this->form_properties['other_suffix']) . '$/', $attributes['name']) > 0)
|
|
|
|
// stop the execution of the script
|
|
_zebra_form_show_error('You are not allowed to have a control with the name ending in "<strong>' .
|
|
$this->form_properties['other_suffix'] . '</strong>" in form "<strong>' .
|
|
$this->form_properties['name'] . '</strong>"', E_USER_ERROR);
|
|
|
|
// if control has any rules attached to it
|
|
if (!empty($control->rules)) {
|
|
|
|
// if client-side validation is not disabled and this variable not created yet,
|
|
// create the variable holding client-side error messages
|
|
if (!$this->form_properties['clientside_validation']['clientside_disabled'] && !isset($clientside_validation)) $clientside_validation = array();
|
|
|
|
// if we applied the "age" rule to an element not being of "date" type
|
|
if (isset($control->rules['age']) && !array_key_exists('pair', $control->attributes))
|
|
|
|
// trigger an error message
|
|
_zebra_form_show_error('The <strong>age</strong> rule can only be applied to a <strong>date</strong> element', E_USER_ERROR);
|
|
|
|
// we need to make sure that rules are in propper order, the order of priority being "dependencies",
|
|
// "required" and "upload"
|
|
|
|
// if the upload rule exists
|
|
if (isset($control->rules['upload'])) {
|
|
|
|
// remove it from wherever it is
|
|
$rule = array_splice($control->rules, array_search('upload', array_keys($control->rules)), 1, array());
|
|
|
|
// and make sure it's the first rule
|
|
$control->rules = array_merge($rule, $control->rules);
|
|
|
|
}
|
|
|
|
// if the "required" rule exists
|
|
if (isset($control->rules['required'])) {
|
|
|
|
// remove it from wherever it is
|
|
$rule = array_splice($control->rules, array_search('required', array_keys($control->rules)), 1, array());
|
|
|
|
// and make sure it's the first rule (it has to be checked prior to the "upload" rule)
|
|
$control->rules = array_merge($rule, $control->rules);
|
|
|
|
}
|
|
|
|
// if the "dependencies" rule exists
|
|
if (isset($control->rules['dependencies'])) {
|
|
|
|
// remove it from wherever it is
|
|
$rule = array_splice($control->rules, array_search('dependencies', array_keys($control->rules)), 1, array());
|
|
|
|
// and make sure it's the first rule (it has to be checked prior to the "required" and "upload" rules)
|
|
$control->rules = array_merge($rule, $control->rules);
|
|
|
|
}
|
|
|
|
// iterate through the rules attached to the control
|
|
foreach ($control->rules as $rule => $properties) {
|
|
|
|
// these rules are not checked client side
|
|
if (
|
|
|
|
$rule == 'captcha' || $rule == 'convert' || $rule == 'resize' ||
|
|
|
|
// also, if we're not checking files clientside, also ignore these rules
|
|
(
|
|
!$this->form_properties['clientside_validation']['clientside_disabled'] &&
|
|
$this->form_properties['clientside_validation']['disable_upload_validation'] &&
|
|
in_array($rule, array('upload', 'filetype', 'filesize', 'image'))
|
|
)
|
|
|
|
) continue;
|
|
|
|
// we need to remove the error_block part as it is not needed for client-side validation
|
|
switch ($rule) {
|
|
|
|
// for these rules
|
|
case 'age':
|
|
case 'alphabet':
|
|
case 'alphanumeric':
|
|
case 'compare':
|
|
case 'digits':
|
|
case 'filesize':
|
|
case 'filetype':
|
|
case 'float':
|
|
case 'number':
|
|
case 'range':
|
|
case 'regexp':
|
|
case 'url':
|
|
|
|
// the error block is the second argument; remove it
|
|
array_splice($properties, 1, 1);
|
|
|
|
break;
|
|
|
|
// for these rules
|
|
case 'date':
|
|
case 'email':
|
|
case 'emails':
|
|
case 'image':
|
|
case 'required':
|
|
|
|
// the error block is the first argument; remove it
|
|
array_splice($properties, 0, 1);
|
|
|
|
break;
|
|
|
|
// for these rules
|
|
case 'datecompare':
|
|
case 'length':
|
|
case 'upload':
|
|
|
|
// the error block is the third argument; remove it
|
|
array_splice($properties, 2, 1);
|
|
|
|
// for the "length" rule
|
|
if ($rule == 'length' && $properties[1] > 0)
|
|
|
|
// we also set the "maxlength" attribute of the control
|
|
$this->controls[$key]->set_attributes(array('maxlength' => $properties[1]));
|
|
|
|
// fot the "length" rule, for text and textarea elements
|
|
if ($rule == 'length' && in_array($attributes['type'], array('text', 'textarea')) && $properties[1] > 0)
|
|
|
|
// make sure the default value, if there is one, is not longer than the maximum allowed length
|
|
$control->set_attributes(array('value' => mb_substr($control->attributes['value'], 0, $properties[1])));
|
|
|
|
break;
|
|
|
|
// for the "custom" rule
|
|
case 'custom':
|
|
|
|
// custom rules are always given as an array of rules
|
|
// so, iterate over the custom rules
|
|
foreach ($properties as $index => $values)
|
|
|
|
// and remove the error block
|
|
array_splice($properties[$index], -2, 1);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// if client-side validation is not disabled
|
|
if (!$this->form_properties['clientside_validation']['clientside_disabled'])
|
|
|
|
// this array will be fed to the JavaScript as a JSON
|
|
$clientside_validation[$attributes['name']][$rule] = $properties;
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
// if control is a select control, doesn't have the "multiple" attribute set and has the "other" attribute set
|
|
if (isset($attributes['type']) && $attributes['type'] == 'select' && !isset($attributes['multiple']) && isset($attributes['other'])) {
|
|
|
|
// set a special class for the select control so that we know that it has a textbox attached to it
|
|
|
|
// add a special class to the control
|
|
$this->controls[$key]->set_attributes(array('class' => 'other'), false);
|
|
|
|
// add a text control
|
|
$obj = & $this->add('text', $attributes['id'] . $this->form_properties['other_suffix'], $attributes['default_other']);
|
|
|
|
// set a special class for the control
|
|
$obj->set_attributes(array('class' => 'other'), false);
|
|
|
|
// if the select control was not submitted OR it was submitted but the selected option is other than
|
|
// the "other" option
|
|
if (!isset($control->submitted_value) || $control->submitted_value != 'other')
|
|
|
|
// hide the text box
|
|
$obj->set_attributes(array('class' => 'other-invisible'), false);
|
|
|
|
// make sure the value in the control propagates
|
|
$obj->get_submitted_value();
|
|
|
|
// because we want this control to appear right beneath the select control when the form is auto-generated
|
|
// we need to have it after the select control in the "controls" property
|
|
|
|
// as is we just added the control, it means it is at the end of the array
|
|
// we take it off the end of array
|
|
$obj = array_pop($this->controls);
|
|
|
|
// find the position of the parent control
|
|
$parent_position = array_search($attributes['name'], array_keys($this->controls));
|
|
|
|
// insert the control right after the parent control
|
|
$this->controls =
|
|
|
|
array_slice($this->controls, 0, $parent_position + 1, true) +
|
|
|
|
array($attributes['id'] . $this->form_properties['other_suffix'] => $obj) +
|
|
|
|
array_slice($this->controls, $parent_position + 1, count($this->controls) - $parent_position, true);
|
|
|
|
|
|
}
|
|
|
|
// if control is a label and is a "master" label
|
|
if (isset($attributes['type']) && $attributes['type'] == 'label' && array_key_exists($attributes['for'], $this->master_labels))
|
|
|
|
// save the "master" label's name
|
|
$this->master_labels[$attributes['for']]['control'] = $attributes['name'];
|
|
|
|
// if control is a date control
|
|
if (isset($attributes['type']) && $attributes['type'] == 'text' && preg_match('/\bdate\b/i', $attributes['class'])) {
|
|
|
|
// if variable is not yet defined. define it
|
|
if (!isset($datepicker_javascript)) $datepicker_javascript = '';
|
|
|
|
// if Zebra_DatePicker is *not* disabled for this control
|
|
if (!$attributes['disable_zebra_datepicker']) {
|
|
|
|
// append the new date picker object
|
|
$datepicker_javascript .= '$(\'#' . $attributes['id'] . '\').Zebra_DatePicker(';
|
|
|
|
// take day names from the language file
|
|
$control->attributes['days'] = $this->form_properties['language']['days'];
|
|
|
|
// if we have custom day name abbreviations, use them
|
|
if (is_array($this->form_properties['language']['days_abbr'])) $control->attributes['days_abbr'] = $this->form_properties['language']['days_abbr'];
|
|
|
|
// take month names from the language file
|
|
$control->attributes['months'] = $this->form_properties['language']['months'];
|
|
|
|
// if we have custom month abbreviations, use them
|
|
if (is_array($this->form_properties['language']['months_abbr'])) $control->attributes['months_abbr'] = $this->form_properties['language']['months_abbr'];
|
|
|
|
// use the caption from the language file for the "Clear date" button
|
|
$control->attributes['lang_clear_date'] = $this->form_properties['language']['clear_date'];
|
|
|
|
// if the "Today" button is not disabled use the caption from the language file
|
|
if ($control->attributes['show_select_today'] === null) $control->attributes['show_select_today'] = $this->form_properties['language']['today'];
|
|
|
|
$properties = '';
|
|
|
|
// iterate through control's attributes
|
|
foreach ($control->attributes as $attribute => $value) {
|
|
|
|
// if attribute is an attribute intended for the javascript object and is not null
|
|
if (in_array($attribute, $control->javascript_attributes) && ($control->attributes[$attribute] !== null || ($attribute == 'direction' && $value === false))) {
|
|
|
|
// append to the properties list (we change "inside_icon" to "inside" as "inside" is reserved)
|
|
$properties .= ($properties != '' ? ',' : '') . ($attribute == 'inside_icon' ? 'inside' : $attribute) . ':';
|
|
|
|
// if value is an array
|
|
if (is_array($value)) {
|
|
|
|
// assume we don't need to convert this to a JavaScript object
|
|
$is_object = false;
|
|
|
|
// iterate through all the keys/values
|
|
foreach ($value as $key => $val)
|
|
|
|
// if at least one of the keys is not numeric
|
|
if (preg_match('/[^0-9]/', $key) > 0) {
|
|
|
|
// set this flag to true
|
|
$is_object = true;
|
|
|
|
// don't look further
|
|
break;
|
|
|
|
}
|
|
|
|
// format accordingly
|
|
$properties .= ($is_object ? '{' : '[');
|
|
|
|
// iterate through the values
|
|
foreach ($value as $key => $val)
|
|
|
|
$properties .= (!is_numeric($key) ? '\'' . $key . '\':' : '') . ($val === true ? 'true' : ($val === false ? 'false' : (is_numeric($val) ? $val : (is_array($val) ? json_encode($val) : '\'' . $val . '\'')))) . ',';
|
|
|
|
$properties = rtrim($properties, ',') . ($is_object ? '}' : ']');
|
|
|
|
// if value is a jQuery selector
|
|
} elseif (preg_match('/^\$\((\'|\").*?\1\)/', trim($value)) > 0) {
|
|
|
|
// use it as it is
|
|
$properties .= $value;
|
|
|
|
// if value is a string but is not a javascript object
|
|
} elseif (is_string($value) && !preg_match('/^\{.*\}$/', trim($value)))
|
|
|
|
// format accordingly and escape single quotes or we'll kill JavaScript
|
|
$properties .= '\'' . addcslashes($value, '\'') . '\'';
|
|
|
|
// for any other case (javascript object, boolean)
|
|
else
|
|
|
|
// format accordingly
|
|
$properties .= ($value === true ? 'true' : ($value === false ? 'false' : (is_numeric($value) ? $value : '\'' . $value . '\'')));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$properties .= ',onSelect:function(){$("#' . $this->form_properties['name'] . '").data("Zebra_Form").hide_error("' . $attributes['name'] . '")}';
|
|
|
|
// wrap up the javascript object
|
|
$datepicker_javascript .= ($properties != '' ? '{' . $properties . '}' : '') . ');';
|
|
|
|
// if Zebra_DatePicker is disabled
|
|
} else
|
|
|
|
// in order to preserve client-side validation,
|
|
// we still need to pass some data to it
|
|
$datepicker_javascript .= '$(\'#' . $attributes['id'] . '\').data("Zebra_DatePicker", new Object({settings: {days: ["' . implode('","', $this->form_properties['language']['days']) . '"], months: ["' . implode('","', $this->form_properties['language']['months']) . '"], format: "' . $control->attributes['format'] . '"}}));';
|
|
|
|
}
|
|
|
|
// if control has the "filetype" rule set, load MIME types now
|
|
if (isset($control->rules['filetype']) || isset($control->rules['image'])) $this->_load_mime_types();
|
|
|
|
}
|
|
|
|
// we add automatically this hidden control to the form, used to know that the form was submitted
|
|
$this->add('hidden', $this->form_properties['identifier'], $this->form_properties['name']);
|
|
|
|
// add a "honeypot" - a text field that we'll use to try and prevent spam-bots
|
|
// this field will be hidden from users and we expect only spam-bots to fill it. if this field will not be empty
|
|
// when submitting the form, we'll consider that the form was submitted by a spam-bot
|
|
$this->add('text', $this->form_properties['honeypot'], '', array('autocomplete' => 'off'));
|
|
|
|
// if CSRF protection is enabled (is not boolean FALSE)
|
|
if ($this->form_properties['csrf_storage_method'] !== false)
|
|
|
|
// add a hidden field to the form, containing the random token
|
|
// (we will later compare the value in this field with the value in the associated session/cookie)
|
|
$this->add('hidden', $this->form_properties['csrf_token_name'], $this->form_properties['csrf_token']);
|
|
|
|
// start rendering the form's hidden controls
|
|
$output .= '<div class="hidden">';
|
|
|
|
// iterate through the controls assigned to the form, looking for hidden controls
|
|
// also, use this opportunity to see which labels are attached to "required" controls
|
|
foreach ($this->controls as $key => $control) {
|
|
|
|
// get some attributes for each control
|
|
$attributes = $control->get_attributes(array('type', 'for', 'name', 'label', 'inside'));
|
|
|
|
// sanitize the control's name
|
|
$attributes['name'] = preg_replace('/\[\]$/', '', $attributes['name']);
|
|
|
|
// if control is a "hidden" control
|
|
if ($attributes['type'] == 'hidden') {
|
|
|
|
// append the hidden control to the hidden control's block
|
|
$output .= $control->toHTML();
|
|
|
|
// take the control out of the controls array because we don't have to bother with it anymore
|
|
unset($this->controls[$key]);
|
|
|
|
// if control is a text field and is the control intended for the "honeypot"
|
|
} elseif ($attributes['type'] == 'text' && $attributes['name'] == $this->form_properties['honeypot']) {
|
|
|
|
// because http://www.w3.org/WAI/GL/WCAG20/WD-WCAG20-TECHS/html.html#H44 requires it,
|
|
// attach a label to the control
|
|
$output .= '<label for="' . $this->form_properties['honeypot'] . '" style="display:none">Leave this field blank</label>';
|
|
|
|
// append the control to the hidden control's block (it will not be visible)
|
|
$output .= $control->toHTML();
|
|
|
|
// take the control out of the controls array so we don't have to bother with it anymore
|
|
unset($this->controls[$key]);
|
|
|
|
// if
|
|
} elseif (
|
|
|
|
// control is a label AND
|
|
$attributes['type'] == 'label' &&
|
|
|
|
// has the "for" attribute set
|
|
isset($attributes['for']) &&
|
|
|
|
(
|
|
|
|
// represents a label for a group of checkboxes or radio buttons
|
|
array_key_exists($attributes['for'], $this->master_labels) ||
|
|
|
|
// the label is attached to an existing control
|
|
array_key_exists($attributes['for'], $this->controls)
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
// if the label is attached to an existing control
|
|
if (array_key_exists($attributes['for'], $this->controls)) {
|
|
|
|
// get some attributes of the control the label is attached to
|
|
$ctrl_attributes = $this->controls[$attributes['for']]->get_attributes(array('name', 'id', 'type'));
|
|
|
|
// sanitize the control's name
|
|
$ctrl_attributes['name'] = preg_replace('/\[\]$/', '', $ctrl_attributes['name']);
|
|
|
|
// if
|
|
if (
|
|
|
|
// the label has the "inside" attribute set
|
|
isset($attributes['inside']) &&
|
|
|
|
// the label's "inside" attribute is set to TRUE AND
|
|
$attributes['inside'] === true &&
|
|
|
|
// the type of the control the label is attached to is either text, textarea or password
|
|
(isset($ctrl_attributes['type']) && (
|
|
|
|
$ctrl_attributes['type'] == 'text' ||
|
|
|
|
$ctrl_attributes['type'] == 'textarea' ||
|
|
|
|
$ctrl_attributes['type'] == 'password'
|
|
|
|
))
|
|
|
|
) {
|
|
|
|
// set some extra attributes for the control the label is attached to
|
|
$this->controls[$attributes['for']]->set_attributes(array(
|
|
|
|
// for textareas we set the "title" attribute while for the text and password
|
|
// controls we set the "alt" attribute
|
|
'title' => $attributes['label'],
|
|
|
|
));
|
|
|
|
// set some extra attributes for the control the label is attached to
|
|
$this->controls[$attributes['for']]->set_attributes(array(
|
|
|
|
// set a class, used by the JavaScript to set some extra attributes at runtime
|
|
'class' => 'inside',
|
|
|
|
), false);
|
|
|
|
// if the control the label is attached to a radio button or a checkbox
|
|
} elseif ($ctrl_attributes['type'] == 'radio' || $ctrl_attributes['type'] == 'checkbox')
|
|
|
|
// set a specific class for the label control
|
|
$control->set_attributes(array('class' => 'option'));
|
|
|
|
}
|
|
|
|
// if the control the label is attached to has the "disabled" attribute set
|
|
if (isset($this->controls[$attributes['for']]->attributes['disabled']))
|
|
|
|
// set a special class for the label
|
|
$control->set_attributes(array('class' => 'disabled'), false);
|
|
|
|
// if the control the label is attached to, has the "required" rule set
|
|
if (isset($this->controls[$attributes['for']]->rules['required']))
|
|
|
|
// if
|
|
if (
|
|
|
|
// a "master" label could exist for the control the label is attached to
|
|
array_key_exists($ctrl_attributes['name'], $this->master_labels) &&
|
|
|
|
// and a control that could be the "master" label exists
|
|
isset($this->controls[$this->master_labels[$ctrl_attributes['name']]['control']])
|
|
|
|
) {
|
|
|
|
// if asterisk is not already attached
|
|
if (strpos($this->controls[$this->master_labels[$ctrl_attributes['name']]['control']]->attributes['label'], '<span class="required">*</span>') === false)
|
|
|
|
// attach the asterisk to the "master" label instead rather than to the current label
|
|
$this->controls[$this->master_labels[$ctrl_attributes['name']]['control']]->set_attributes(array('label' => $this->controls[$this->master_labels[$ctrl_attributes['name']]['control']]->attributes['label'] . '<span class="required">*</span>'));
|
|
|
|
// otherwise
|
|
} else
|
|
|
|
// attach the asterisk to the current label
|
|
$this->controls[$key]->set_attributes(array('label' => $attributes['label'] . '<span class="required">*</span>'));
|
|
|
|
// if
|
|
} elseif (
|
|
|
|
// control is a label AND
|
|
$attributes['type'] == 'label' &&
|
|
|
|
// has the "for" attribute set
|
|
isset($attributes['for']) &&
|
|
|
|
// is neither a "master" label for a group of checkboxes or radio buttons
|
|
!array_key_exists($attributes['for'], $this->master_labels) &&
|
|
|
|
// nor is attached to an existing control
|
|
!array_key_exists($attributes['for'], $this->controls) &&
|
|
|
|
// we're not on autopilot (if we are, we will remove the "for" attribute later on)
|
|
($template != '' && $template != '*horizontal' && $template != '*vertical')
|
|
|
|
// remove the "for" attribute so that the form will pass the W3C validation
|
|
) unset($this->controls[$key]->attributes['for']);
|
|
|
|
}
|
|
|
|
// finish building the hidden controls block
|
|
$output .= '</div>';
|
|
|
|
// if there are any error messages
|
|
if (!empty($this->errors))
|
|
|
|
// iterate through each error block
|
|
foreach ($this->errors as $error_block => $error_messages) {
|
|
|
|
$content = '';
|
|
|
|
// iterate through each message of the error block
|
|
foreach ($error_messages as $error_message) {
|
|
|
|
// render each message in block
|
|
$content .= '<span>' . $error_message . '</span>';
|
|
|
|
// if only one error message is to be show
|
|
// break out from the foreach loop
|
|
if ($this->form_properties['show_all_error_messages'] === false) break;
|
|
|
|
}
|
|
|
|
// switch the array entry with it's rendered form
|
|
$this->errors[$error_block] = '<div class="error"><div class="container">' . $content . '<div class="close"><a href="javascript:void(0)">close</a></div></div></div>';
|
|
|
|
}
|
|
|
|
// if there are any SPAM or CSRF errors
|
|
if (isset($this->errors['zf_error_spam']) || isset($this->errors['zf_error_csrf'])) {
|
|
|
|
// if there's a CSRF error leave only that
|
|
if (isset($this->errors['zf_error_csrf'])) $this->errors['zf_error'] = $this->errors['zf_error_csrf'];
|
|
|
|
// else, if there's a SPAM error, leave just that
|
|
else $this->errors['zf_error'] = $this->errors['zf_error_spam'];
|
|
|
|
// remove these two errors
|
|
// as now there will be only one, under the name of "zf_error"
|
|
unset($this->errors['zf_error_csrf']);
|
|
unset($this->errors['zf_error_spam']);
|
|
|
|
}
|
|
|
|
// if output is to be auto-generated
|
|
if ($template == '' || $template == '*horizontal' || $template == '*vertical') {
|
|
|
|
$error_messages = '';
|
|
|
|
// iterate through any existing error blocks
|
|
// and render them at the top of the auto-generated output
|
|
foreach ($this->errors as $errors) $error_messages .= $errors;
|
|
|
|
// group controls in master-label/control/label/note
|
|
// so, every block looks like
|
|
// - master label (if any)
|
|
// - label (if any)
|
|
// - control
|
|
// - (in case of radio buttons and checkboxes the above two may be repeated)
|
|
// - note (if any)
|
|
$blocks = array();
|
|
|
|
// iterate through the form's controls
|
|
foreach ($this->controls as $key => $control) {
|
|
|
|
// get some attributes for the control
|
|
$attributes = $control->get_attributes(array('type', 'name', 'id', 'for', 'inside'));
|
|
|
|
// if control is a label that is to be placed inside another control, we skip it
|
|
if ($attributes['type'] == 'label' && isset($attributes['inside'])) continue;
|
|
|
|
// sanitize control's name
|
|
$attributes['name'] = preg_replace('/\[\]$/', '', $attributes['name']);
|
|
|
|
// if the control is a text box that is to be shown when user selects "other" in a select control
|
|
if (preg_match('/(.*)' . preg_quote($this->form_properties['other_suffix']) . '$/', $attributes['name'], $matches) > 0)
|
|
|
|
// the parent is the control to which the control is attached to
|
|
$parent = $matches[1];
|
|
|
|
// for other controls
|
|
else {
|
|
|
|
// check the control's type
|
|
switch ($attributes['type']) {
|
|
|
|
// if control is captcha, label, or note
|
|
case 'captcha':
|
|
case 'label':
|
|
case 'note':
|
|
|
|
// save the control the current control is attached to
|
|
$parent = $attributes['for'];
|
|
|
|
// if
|
|
if (
|
|
|
|
// parent control exist AND
|
|
isset($this->controls[$parent]) &&
|
|
|
|
// control is a checkbox or radio button
|
|
($this->controls[$parent]->attributes['type'] == 'checkbox' || $this->controls[$parent]->attributes['type'] == 'radio') &&
|
|
|
|
// the parent control's ID is different the parent control's name
|
|
// (as is the case for radio buttons and checkboxes)
|
|
$this->controls[$parent]->attributes['id'] != $this->controls[$parent]->attributes['name']
|
|
|
|
)
|
|
|
|
// save both the "master" parent and, separated by a dot, the actual parent
|
|
$parent = preg_replace('/\[\]$/', '', $this->controls[$parent]->attributes['name']) . '.' . $parent;
|
|
|
|
// if control is a label and the parent control doesn't exist (the label is most probably a "master" label)
|
|
elseif ($attributes['type'] == 'label' && !isset($this->controls[$parent]))
|
|
|
|
// remove the "for" attribute so that the form will pass the W3C validation
|
|
unset($this->controls[$key]->attributes['for']);
|
|
|
|
break;
|
|
|
|
// for any other controls
|
|
default:
|
|
|
|
// the parent is the control itself
|
|
$parent = $attributes['name'];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// as some controls (labels for checkboxes) can have multiple parent - the checkbox control and a master
|
|
// label - and multiple parents are separated by a dot, explode by dot
|
|
$parents = explode('.', $parent);
|
|
|
|
// iterate through the control's parents
|
|
foreach ($parents as $key => $parent) {
|
|
|
|
// if first entry
|
|
// make $array a pointer to the $blocks array
|
|
if ($key == 0) $array = & $blocks;
|
|
|
|
// if the parent control doesn't have its own key in the array
|
|
// (it may still be in the array but not as a "parent")
|
|
if (!isset($array[$parent])) {
|
|
|
|
// initialize the entry
|
|
$array[$parent] = array();
|
|
|
|
// if we already have the entry but not as a key
|
|
if (($pos = array_search($parent, $array)) !== false) {
|
|
|
|
// insert it in the newly created entry
|
|
$array[$parent][] = $array[$pos];
|
|
|
|
// and remove it from the old position
|
|
unset($array[$pos]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// make $array a pointer
|
|
$array = & $array[$parent];
|
|
|
|
// if we're at the last parent
|
|
if ($key == count($parents) - 1)
|
|
|
|
// if control already exists in the parent's array as a key (remember that $array is a pointer!)
|
|
if (array_key_exists($attributes['id'], $array))
|
|
|
|
// add the control to the array
|
|
$array[$attributes['id']][] = $attributes['id'];
|
|
|
|
// if control doesn't exists in the parent's array (remember that $array is a pointer!)
|
|
else
|
|
|
|
// add the control to the array
|
|
$array[] = $attributes['id'];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// if auto-generated output needs to be horizontal
|
|
if ($template == '*horizontal') {
|
|
|
|
// the output will be enclosed in a table
|
|
$contents = '<table>';
|
|
|
|
// if there are errors to be displayed
|
|
if ($error_messages != '')
|
|
|
|
// show the error messages
|
|
$contents .= '<tr><td colspan="2">' . $error_messages . '</td></tr>';
|
|
|
|
// keep track of odd/even rows
|
|
$counter = 0;
|
|
|
|
// total number of rows to be displayed
|
|
$rows = count($blocks);
|
|
|
|
// iterate through blocks
|
|
foreach ($blocks as $controls) {
|
|
|
|
++$counter;
|
|
|
|
// each block is in its own row
|
|
$contents .= '<tr class="row' . ($counter % 2 == 0 ? ' even' : '') . ($counter == $rows ? ' last' : '') . '">';
|
|
|
|
// the first cell will hold the label (if any)
|
|
$contents .= '<td>';
|
|
|
|
// as of PHP 5.3, array_shift required the argument to be a variable and not the result
|
|
// of a function so we need this intermediary step
|
|
$labels = array_values($controls);
|
|
|
|
// retrieve the first item in the block
|
|
$label = array_shift($labels);
|
|
|
|
// item is a label
|
|
if (!is_array($label) && $this->controls[$label]->attributes['type'] == 'label') {
|
|
|
|
// remove it from the block
|
|
array_shift($controls);
|
|
|
|
// render it
|
|
$contents .= $this->controls[$label]->toHTML();
|
|
|
|
}
|
|
|
|
// close the table cell
|
|
$contents .= '</td>';
|
|
|
|
// the second cell contains the actual controls
|
|
$contents .= '<td>';
|
|
|
|
// iterate through the controls to be rendered
|
|
foreach ($controls as $control) {
|
|
|
|
// if array of controls
|
|
// (radio buttons/checkboxes and their labels)
|
|
if (is_array($control)) {
|
|
|
|
// iterate through the array's items
|
|
foreach ($control as $ctrl)
|
|
|
|
// and display them on the same line
|
|
$contents .= '<div class="cell">' . $this->controls[$ctrl]->toHTML() . '</div>';
|
|
|
|
// clear floats
|
|
$contents .= '<div class="clear"></div>';
|
|
|
|
// if not an array of controls
|
|
} else
|
|
|
|
// if control is required but has the label as a tip inside the control
|
|
// we need to manually add the asterisk after the control
|
|
if (array_key_exists('required', $this->controls[$control]->rules) && preg_match('/\binside\b/', $this->controls[$control]->attributes['class'])) {
|
|
|
|
// first, make sure the control is inline so that the asterisk will be placed to the right of the control
|
|
$this->controls[$control]->set_attributes(array('class' => 'inline'), false);
|
|
|
|
// add the required symbol after the control
|
|
$contents .= $this->controls[$control]->toHTML() . '<span class="required">*</span>';
|
|
|
|
// else, render the control
|
|
} else $contents .= $this->controls[$control]->toHTML();
|
|
|
|
}
|
|
|
|
// close the cell
|
|
$contents .= '</td>';
|
|
|
|
// add a "separator" row
|
|
$contents .= '</tr>';
|
|
|
|
}
|
|
|
|
// finish rendering the table
|
|
$contents .= '</table>';
|
|
|
|
// if auto-generated output needs to be vertical
|
|
} else {
|
|
|
|
$contents = '';
|
|
|
|
// if there are errors to be displayed, show the error messages
|
|
if ($error_messages != '') $contents .= $error_messages;
|
|
|
|
$counter = 0;
|
|
|
|
// total number of rows to be displayed
|
|
$rows = count($blocks);
|
|
|
|
// iterate through blocks
|
|
foreach ($blocks as $controls) {
|
|
|
|
// ...then block is contained in its own row
|
|
$contents .= '<div class="row' . (++$counter % 2 == 0 ? ' even' : '') . ($counter == $rows ? ' last' : '') . '">';
|
|
|
|
// iterate through the controls to be rendered
|
|
foreach ($controls as $control) {
|
|
|
|
// if array of controls
|
|
// (radio buttons/checkboxes and their labels)
|
|
if (is_array($control)) {
|
|
|
|
// iterate through the array's items
|
|
foreach ($control as $ctrl)
|
|
|
|
// and display them on the same line
|
|
$contents .= '<div class="cell">' . $this->controls[$ctrl]->toHTML() . '</div>';
|
|
|
|
// clear floats
|
|
$contents .= '<div class="clear"></div>';
|
|
|
|
// if not an array of controls
|
|
} else
|
|
|
|
// if control is required but has the label as a tip inside the control
|
|
// we need to manually add the asterisk after the control
|
|
if (array_key_exists('required', $this->controls[$control]->rules) && preg_match('/\binside\b/', $this->controls[$control]->attributes['class'])) {
|
|
|
|
// first, make sure the control is inline so that the asterisk will be placed to the right of the control
|
|
$this->controls[$control]->set_attributes(array('class' => 'inline'), false);
|
|
|
|
// add the required symbol after the control
|
|
$contents .= $this->controls[$control]->toHTML() . '<span class="required">*</span>';
|
|
|
|
// else, render the control
|
|
} else $contents .= $this->controls[$control]->toHTML();
|
|
|
|
}
|
|
|
|
// ...finish rendering
|
|
$contents .= '</div>';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// if a function with the name given as $template, and the function exists
|
|
} elseif (is_callable($template)) {
|
|
|
|
// this variable will contain all the rendered controls
|
|
$controls = array();
|
|
|
|
// iterate through the controls assigned to the form
|
|
foreach ($this->controls as $control) {
|
|
|
|
// read some attributes of the control
|
|
$attributes = $control->get_attributes(array('id', 'inside'));
|
|
|
|
// render the control if the control is not a label that is to be displayed inside the control as it's
|
|
// default value
|
|
if (!isset($attributes['inside']))
|
|
|
|
// if control is required but has the label as a tip inside the control
|
|
// we need to manually add the asterisk after the control
|
|
if (array_key_exists('required', $control->rules) && preg_match('/\binside\b/', $control->attributes['class'])) {
|
|
|
|
// first, make sure the control is inline so that the asterisk will be placed to the right of the control
|
|
$control->set_attributes(array('class' => 'inline'), false);
|
|
|
|
// add the required symbol after the control
|
|
// and add generated HTML code to the $controls array
|
|
$controls[$attributes['id']] = $control->toHTML() . '<span class="required">*</span>';
|
|
|
|
// otherwise, add generated HTML code to the $controls array
|
|
} else $controls[$attributes['id']] = $control->toHTML();
|
|
|
|
}
|
|
|
|
// iterate through the variables assigned to the form
|
|
foreach ($this->variables as $variable_name => $variable_value)
|
|
|
|
// make available the assigned variables
|
|
$controls[$variable_name] = $variable_value;
|
|
|
|
// iterate through the error messages(if any)
|
|
foreach ($this->errors as $error_block => $error_message)
|
|
|
|
// make the error message available
|
|
$controls[$error_block] = $error_message;
|
|
|
|
// let the custom function generate the output
|
|
// we're passing two arguments
|
|
// an associative array with control ids and their respectively generated HTML
|
|
// and an array with all the form's objects
|
|
$contents = call_user_func_array($template, array($controls, &$this->controls));
|
|
|
|
// if a template was specified
|
|
} else {
|
|
|
|
// this variable will contain all the rendered controls
|
|
$controls = array();
|
|
|
|
// iterate through the controls assigned to the form
|
|
foreach ($this->controls as $control) {
|
|
|
|
// read some attributes of the control
|
|
$attributes = $control->get_attributes(array('id', 'inside'));
|
|
|
|
// render the control if the control is not a label that is to be displayed inside the control as it's
|
|
// default value
|
|
if (!isset($attributes['inside']))
|
|
|
|
// if control is required but has the label as a tip inside the control
|
|
// we need to manually add the asterisk after the control
|
|
if (array_key_exists('required', $control->rules) && preg_match('/\binside\b/', $control->attributes['class'])) {
|
|
|
|
// first, make sure the control is inline so that the asterisk will be placed to the right of the control
|
|
$control->set_attributes(array('class' => 'inline'), false);
|
|
|
|
// add the required symbol after the control
|
|
// and add generated HTML code to the $controls array
|
|
$controls[$attributes['id']] = $control->toHTML() . '<span class="required">*</span>';
|
|
|
|
// otherwise, add generated HTML code to the $controls array
|
|
} else {
|
|
|
|
// if element name was given as an array (i.e. car[name])
|
|
if (preg_match('/^([^\[]+)\[([^\]]+)\]$/', $attributes['id'], $matches))
|
|
|
|
// assign the variable accordingly
|
|
$controls[$matches[1]][$matches[2]] = $control->toHTML();
|
|
|
|
// if element name was not given as an array, assign the variable accordingly
|
|
else $controls[$attributes['id']] = $control->toHTML();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//start output buffering
|
|
ob_start();
|
|
|
|
// make available in the template all the form's objects
|
|
$controls[$this->form_properties['name']] = &$this->controls;
|
|
|
|
// make the user-defined variables available in the template file as PHP variables
|
|
extract($this->variables);
|
|
|
|
// make the rendered controls available in the template file as PHP variables
|
|
extract($controls);
|
|
|
|
// make the error messages available in the template file as PHP variables
|
|
extract($this->errors);
|
|
|
|
// include the template file
|
|
include $template;
|
|
|
|
// put the parsed content in a variable
|
|
$contents = ob_get_contents();
|
|
|
|
// clean buffers
|
|
ob_end_clean();
|
|
|
|
}
|
|
|
|
// finish building the output
|
|
$output = $output . $contents . '</form>';
|
|
|
|
// this will hold the properties to be set for the JavaScript object
|
|
$javascript_object_properties = '';
|
|
|
|
// if client-side validation is disabled
|
|
if ($this->form_properties['clientside_validation']['clientside_disabled']) {
|
|
|
|
// we still need to execute a function attached to the "on_ready" event (if any)
|
|
$javascript_object_properties .= 'on_ready:' . ($this->form_properties['clientside_validation']['on_ready'] === false ? 'false' : $this->form_properties['clientside_validation']['on_ready']);
|
|
|
|
// so we don't break the JavaScript code, we need this, too
|
|
$javascript_object_properties .= ',validation_rules:{}';
|
|
|
|
// if client-side validation is not disabled
|
|
} else {
|
|
|
|
// iterate through the properties
|
|
foreach ($this->form_properties['clientside_validation'] as $key => $value)
|
|
|
|
// save property
|
|
$javascript_object_properties .=
|
|
($javascript_object_properties != '' ? ',' : '') . $key . ':' .
|
|
($value === true ? 'true' : ($value === false ? 'false' : ($key == 'on_ready' ? $value : '\'' . $value . '\'')));
|
|
|
|
// save information about validation rules
|
|
$javascript_object_properties .= ($javascript_object_properties != '' ? ',' : '') . 'validation_rules:' . (isset($clientside_validation) ? json_encode($clientside_validation) : '{}');
|
|
|
|
}
|
|
|
|
// function name for initializing client-side validation
|
|
$function_name = 'init_' . md5(strtolower($this->form_properties['name'] . microtime()));
|
|
|
|
$output .=
|
|
'<script type="text/javascript">function ' . $function_name . '(){if(typeof jQuery=="undefined"||typeof jQuery.fn.Zebra_Form=="undefined"' .
|
|
(isset($datepicker_javascript) ? '|| jQuery.fn.Zebra_DatePicker=="undefined"' : '') . '){setTimeout("' . $function_name . '()",100);return}else{' .
|
|
'$(document).ready(function(){' .
|
|
(isset($datepicker_javascript) ? $datepicker_javascript : '') .
|
|
'$("#' . $this->form_properties['name'] . '").Zebra_Form(' . ($javascript_object_properties != '' ? '{' . $javascript_object_properties . '}' : '') . ')})}}' .
|
|
$function_name . '()</script>';
|
|
|
|
// if $return argument was TRUE, return the result
|
|
if ($return) return $output;
|
|
|
|
// if $return argument was FALSE, output the content
|
|
else echo $output;
|
|
|
|
}
|
|
|
|
/**
|
|
* Resets the submitted values for all of the form's controls (also resets the POST/GET/FILES superglobals)
|
|
*
|
|
* @return void
|
|
*/
|
|
function reset()
|
|
{
|
|
|
|
// iterate through the form's controls
|
|
foreach ($this->controls as $key => $control) {
|
|
|
|
// reference to the control
|
|
$obj = & $this->controls[$key];
|
|
|
|
// reset
|
|
$obj->reset();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Sets how error messages generated upon server-side validation are displayed in an
|
|
* {@link Zebra_Form_Control::set_rule() error block}.
|
|
*
|
|
* Client-side validation is done on the "onsubmit" event of the form. See {@link clientside_validation()} for
|
|
* more information on client-side validation.
|
|
*
|
|
* <code>
|
|
* // create a new form
|
|
* $form = new Zebra_Form('my_form');
|
|
*
|
|
* // display all error messages of error blocks
|
|
* $form->show_all_error_messages(true);
|
|
* </code>
|
|
*
|
|
* @param boolean $value Setting this argument to TRUE will display <i>all</i> error messages of an error block,
|
|
* while setting it to FALSE will display one error message at a time.
|
|
*
|
|
* Default is FALSE.
|
|
*
|
|
*
|
|
* @return void
|
|
*/
|
|
function show_all_error_messages($value = false)
|
|
{
|
|
|
|
// set the property
|
|
$this->form_properties['show_all_error_messages'] = $value;
|
|
|
|
}
|
|
|
|
/**
|
|
* This method performs the server-side validation of all the form's controls, making sure that all the values
|
|
* comply to the rules set for these controls through the {@link Zebra_Form_Control::set_rule() set_rule()} method.
|
|
*
|
|
* Only by calling this method will the form's controls update their values. If this method is not called, all
|
|
* the controls will preserve their default values after submission even if these values were altered prior to
|
|
* submission.
|
|
*
|
|
* This method must be called <b>before</b> the {@link render()} method or error messages will not be
|
|
* available.
|
|
*
|
|
* After calling this method, if there are {@link Zebra_Form_File file} controls on the form, you might want to check
|
|
* for the existence of the {@link $file_upload} property to see the details of uploaded files and take actions
|
|
* accordingly.
|
|
*
|
|
* Client-side validation is done on the "onsubmit" event of the form. See {@link clientside_validation()} for
|
|
* more information on client-side validation.
|
|
*
|
|
* @return boolean Returns TRUE if every rule was obeyed, FALSE if not.
|
|
*/
|
|
function validate()
|
|
{
|
|
|
|
// reference to the form submission method
|
|
global ${'_' . $this->form_properties['method']};
|
|
|
|
$method = & ${'_' . $this->form_properties['method']};
|
|
|
|
$this->proxies_cache = array();
|
|
|
|
// we assume the form is not valid (or it was not submitted)
|
|
$form_is_valid = false;
|
|
|
|
// continue only if form was submitted
|
|
if (
|
|
|
|
isset($method[$this->form_properties['identifier']]) &&
|
|
|
|
$method[$this->form_properties['identifier']] == $this->form_properties['name']
|
|
|
|
) {
|
|
|
|
// if
|
|
if (
|
|
|
|
// the "honeypot" field was submitted AND
|
|
isset($method[$this->form_properties['honeypot']]) &&
|
|
|
|
// the "honeypot" field is empty
|
|
$method[$this->form_properties['honeypot']] == '' &&
|
|
|
|
// no possible CSRF attacks detected
|
|
($csrf_status = $this->_csrf_validate())
|
|
|
|
) {
|
|
|
|
// remove the honeypot and csrf entries so that we don't polute the $_POST array
|
|
unset($method[$this->form_properties['honeypot']]);
|
|
unset($method[$this->form_properties['csrf_token_name']]);
|
|
|
|
// by default, we assume that the form is valid
|
|
$form_is_valid = true;
|
|
|
|
// iterate through the controls
|
|
foreach (array_keys($this->controls) as $key) {
|
|
|
|
// reference to control
|
|
$control = & $this->controls[$key];
|
|
|
|
// get some attributes of the control
|
|
$attribute = $control->get_attributes(array('type'));
|
|
|
|
// validate the control
|
|
$valid = $this->validate_control($key);
|
|
|
|
// do some extra checkings and cleanup
|
|
if (
|
|
|
|
//if type is password OR
|
|
$attribute['type'] == 'password' ||
|
|
|
|
//if type is text and has the "captcha" rule set
|
|
($attribute['type'] == 'text' && isset($control->rules['captcha']))
|
|
|
|
// clear the value in the field
|
|
) $control->set_attributes(array('value' => ''));
|
|
|
|
// if control is not valid, the form is not valid
|
|
if (!$valid) {
|
|
|
|
// unless the element is a file upload control, add the "error" class to it so we can apply
|
|
// custom styles to erroneous fields
|
|
if ($attribute['type'] != 'file') $control->set_attributes(array('class' => 'error'), false);
|
|
|
|
$form_is_valid = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// after iterating through all the controls,
|
|
// check if the form is still valid
|
|
if ($form_is_valid)
|
|
|
|
// if there are any actions to be performed when the form is valid
|
|
// (file upload, resize, convert)
|
|
if (isset($this->actions) && !empty($this->actions))
|
|
|
|
// iterate through the actions
|
|
foreach ($this->actions as $actions)
|
|
|
|
// if the respective action (method) exists
|
|
if (method_exists($this, $actions[0])) {
|
|
|
|
// if the method was erroneous
|
|
if (!call_user_func_array(array(&$this,$actions[0]), array_slice($actions, 1))) {
|
|
|
|
// add error message to indicated error block
|
|
$this->add_error($actions['block'], $actions['message']);
|
|
|
|
// set the form as not being valid
|
|
$form_is_valid = false;
|
|
|
|
}
|
|
|
|
// if the task (method) could not be found, trigger an error message
|
|
} else _zebra_form_show_error('Method ' . $actions[0] . ' does not exist!', E_USER_ERROR);
|
|
|
|
// else if
|
|
} elseif (
|
|
|
|
// honeypot field was not submitted
|
|
!isset($method[$this->form_properties['honeypot']]) ||
|
|
|
|
// honeypot field is not empty
|
|
$method[$this->form_properties['honeypot']] != ''
|
|
|
|
// show the appropriate error message to the user
|
|
) $this->add_error('zf_error_spam', $this->form_properties['language']['spam_detected']);
|
|
|
|
// else, if a possible CSRF attack was detected
|
|
// show the appropriate error message to the user
|
|
elseif (!$csrf_status) $this->add_error('zf_error_csrf', $this->form_properties['language']['csrf_detected']);
|
|
|
|
// if
|
|
if (
|
|
|
|
// form is valid
|
|
$form_is_valid ||
|
|
|
|
// form is invalid and the from was not submitted via AJAX
|
|
!isset($_SERVER['HTTP_X_REQUESTED_WITH'])
|
|
|
|
// regenerate the CSRF token
|
|
) $this->_csrf_generate_token(true);
|
|
|
|
// here's a special error check:
|
|
// due to a bug (?) when the POST/GET data is larger than allowed by upload_max_filesize/post_max_size the
|
|
// $_POST/$_GET/$_FILES superglobals are empty (see http://bugs.php.net/bug.php?id=49570)
|
|
// but still, we need to present the user with some error message...
|
|
} elseif (empty($method) && isset($_SERVER['CONTENT_LENGTH']) && (int)$_SERVER['CONTENT_LENGTH'] > 0)
|
|
|
|
$form_is_valid = false;
|
|
|
|
// if form was not submitted and fields are to be auto-filled
|
|
elseif ($this->form_properties['auto_fill'] !== false) {
|
|
|
|
// we'll use this variable to keep track of groups of radio buttons/checkboxes
|
|
// where we've randomly selected a value
|
|
$checked = array();
|
|
|
|
// iterate through the form's controls
|
|
foreach ($this->controls as $key => $control) {
|
|
|
|
// get some attributes for each control
|
|
$attributes = $control->get_attributes(array('type', 'name', 'value'));
|
|
|
|
// sanitize controls' name (remove square brackets)
|
|
$attributes['name'] = preg_replace('/\[\]$/', '', $attributes['name']);
|
|
|
|
// if we need to select predefined values for specific controls
|
|
if (isset($this->form_properties['auto_fill'][0][$attributes['name']])) {
|
|
|
|
// the value/values that is/are to be preselected
|
|
$value = $this->form_properties['auto_fill'][0][$attributes['name']];
|
|
|
|
// if control is a radio button or a checkbox
|
|
if ($attributes['type'] == 'checkbox' || $attributes['type'] == 'radio') {
|
|
|
|
// if
|
|
if (
|
|
|
|
// given value is not an array and the current element has that value OR
|
|
(!is_array($value) && $value == $attributes['value']) ||
|
|
|
|
// given value is an array and the current element's value is in that array
|
|
// also, make sure we don't select multiple values for radio buttons
|
|
(is_array($value) && $attributes['type'] != 'radio' && in_array($attributes['value'], $value))
|
|
|
|
// mark element as "checked"
|
|
) $control->set_attributes(array('checked' => 'checked'));
|
|
|
|
// for the other controls, simply set the value
|
|
} else $control->set_attributes(array('value' => $value));
|
|
|
|
// if no predefined value was given for the control and we don't auto-fill only specific controls
|
|
} elseif (!$this->form_properties['auto_fill'][1]) {
|
|
|
|
// if control is a radio button or a checkbox
|
|
if ($attributes['type'] == 'checkbox' || $attributes['type'] == 'radio') {
|
|
|
|
// if we've not already selected a random value for the group of radio buttons/checkboxes
|
|
// the current element is part of
|
|
if (!isset($checked[$attributes['name']])) {
|
|
|
|
// this will hold the randomly selected elements
|
|
$tmp_checked = array();
|
|
|
|
// iterate through all the form's controls
|
|
foreach ($this->controls as $element)
|
|
|
|
// if control is of the same type and has the same name
|
|
if ($element->attributes['type'] == $attributes['type'] && $element->attributes['name'] == $attributes['name']) {
|
|
|
|
// if element is checked by default, from when creating the form
|
|
if (isset($element->attributes['checked'])) {
|
|
|
|
// we will consider this group as checked
|
|
$checked[$attributes['name']] = true;
|
|
|
|
// ...and look no further
|
|
break;
|
|
|
|
// if element is not checked by default and we should select the current value,
|
|
// save it for later as we first need to check if an element of the group is not
|
|
// already checked
|
|
// (also, for radio buttons make sure we select a single option)
|
|
} elseif (rand(0, 1) == 1&& !($attributes['type'] == 'radio' && !empty($tmp_checked))) $tmp_checked[] = $element;
|
|
|
|
}
|
|
|
|
// if no element of the group was selected
|
|
if (!isset($checked[$attributes['name']])) {
|
|
|
|
// if there are any randomly selected elements
|
|
if (!empty($tmp_checked))
|
|
|
|
// iterate through the selected elements and mark them as "checked"
|
|
foreach ($tmp_checked as $element) $element->set_attributes(array('checked' => 'checked'));
|
|
|
|
// if there are no randomly selected elements then select the current (first) element
|
|
$control->set_attributes(array('checked' => 'checked'));
|
|
|
|
}
|
|
|
|
// flag this group of elements so we're only doing this once
|
|
$checked[$attributes['name']] = true;
|
|
|
|
}
|
|
|
|
// if control is a drop-down or a multiple select box and does not already has a value
|
|
} elseif ($attributes['type'] == 'select' && $attributes['value'] === '') {
|
|
|
|
// select a random value
|
|
// (if "multiple" attribute is set, select from all the values, or select starting from the second value otherwise)
|
|
$keys = array_slice(
|
|
array_keys($control->attributes['options']),
|
|
(isset($control->attributes['multiple']) && strtolower(trim($control->attributes['multiple'])) == 'multiple' ? 0 : 1)
|
|
);
|
|
|
|
// if the select has any values, set a random value
|
|
if (!empty($keys)) $control->set_attributes(array('value' => $keys[array_rand($keys)]));
|
|
|
|
// if control is "password"
|
|
} elseif ($attributes['type'] == 'password') {
|
|
|
|
// set the value to "12345678"
|
|
$control->set_attributes(array('value' => '12345678'));
|
|
|
|
// if control is "text" or "textarea" and does not already have a value
|
|
} elseif (in_array($attributes['type'], array('text', 'textarea', 'email', 'number')) && $attributes['value'] === '') {
|
|
|
|
// if this is a "date" control
|
|
if (strtolower(get_class($control)) == 'zebra_form_date') {
|
|
|
|
// get the date control's starting/ending date
|
|
$limits = $control->_init();
|
|
|
|
// set the value to the first selectabel date
|
|
$control->set_attributes(array('value' => date($control->attributes['format'], $limits[0] > 0 ? $limits[0] : time())));
|
|
|
|
// continue only if the control doesn't have the "regexp" or the "captcha" rule set
|
|
} elseif (!isset($control->rules['regexp']) && !isset($control->rules['captcha'])) {
|
|
|
|
unset($value);
|
|
|
|
// default character set to choose from
|
|
$characters = 'abcdefghijklmnopqrstuvwxyz';
|
|
|
|
// for controls having the "alphabet", "email" or "emails" rule set
|
|
if (isset($control->rules['alphabet']) || isset($control->rules['email']) || isset($control->rules['emails']))
|
|
|
|
// use these characters in the random string
|
|
$characters = 'abcdefghijklmnopqrstuvwxyz';
|
|
|
|
// for controls having the "alphanumeric" rule set
|
|
if (isset($control->rules['alphanumeric']))
|
|
|
|
// use these characters in the random string
|
|
$characters = 'abcdefghijklmnopqrstuvwxyz0123456789';
|
|
|
|
// for controls having the "digits" or "number" rule set
|
|
if (isset($control->rules['digits']) || isset($control->rules['number']))
|
|
|
|
// use these characters for the random value
|
|
$characters = '0123456789';
|
|
|
|
// for controls having the "float" rule set
|
|
if (isset($control->rules['float']))
|
|
|
|
// generate a random value
|
|
$value = number_format(mt_rand(0, 99999) / 100, 2);
|
|
|
|
// if a value was not yet generated
|
|
if (!isset($value)) {
|
|
|
|
$value = '';
|
|
|
|
// get a random length for our value
|
|
$length = rand(
|
|
|
|
// if a length is specified and it has a lower limit, use that as rand()'s lower limit, or "10" otherwise
|
|
(isset($control->rules['length']) && $control->rules['length'][0] != 0 ? $control->rules['length'][0] : 10),
|
|
|
|
// if a length is specified and it has an upper limit, use that as rand()'s upper limit, or "10" otherwise
|
|
// (for textareas not having an upper limit for length "100" will be used as rand()'s upper limit)
|
|
(isset($control->rules['length']) && $control->rules['length'][1] != 0 ? $control->rules['length'][1] : ($attributes['type'] == 'textarea' ? 100 : 10))
|
|
);
|
|
|
|
// get a random character until we get to the length defined above
|
|
// for textareas include a space every once in a while
|
|
for ($i = 0; $i < $length; $i++) $value .= ($attributes['type'] == 'textarea' && in_array(rand(0, 10), array(4, 6)) ? ' ' : $characters[rand(0, strlen($characters) - 1)]);
|
|
|
|
// if control has the "email" or "emails" rule set
|
|
if (isset($control->rules['email']) || isset($control->rules['emails'])) {
|
|
|
|
// append an "@" to the already generated value
|
|
$value .= '@';
|
|
|
|
// and then generate six more random characters
|
|
for ($i = 0; $i < 6; $i++) $value .= $characters[rand(0, strlen($characters) - 1)];
|
|
|
|
// finish up with a ".com"
|
|
$value .= '.com';
|
|
|
|
// if control has the "url" rule set add the "http://" prefix (if required) and the ".com" suffix
|
|
} elseif (isset($control->rules['url'])) $value = ($control->rules['url'][0] ? 'http://' : '') . $value . '.com';
|
|
|
|
}
|
|
|
|
// finally, if we have a random value for the control, set it
|
|
if (isset($value)) $control->set_attributes(array('value' => trim($value)));
|
|
|
|
}
|
|
|
|
// if control is "time"
|
|
} elseif ($attributes['type'] == 'time' && $attributes['value'] === '')
|
|
|
|
// select random values, from the exiting ones
|
|
$control->set_attributes(array('value' => $control->attributes['hours'][array_rand($control->attributes['hours'])] . ':' . $control->attributes['minutes'][array_rand($control->attributes['minutes'])] . ':' . $control->attributes['seconds'][array_rand($control->attributes['seconds'])]));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// return the state of the form
|
|
return $form_is_valid;
|
|
|
|
}
|
|
|
|
/**
|
|
* This method performs the server-side validation of a control, making sure that the value complies to the rules
|
|
* set for the control through the {@link Zebra_Form_Control::set_rule() set_rule()} method.
|
|
*
|
|
* @param string $control Unique name that identifies the control in the form.
|
|
*
|
|
* @return boolean Returns TRUE if every rule was obeyed, FALSE if not.
|
|
*/
|
|
function validate_control($control)
|
|
{
|
|
|
|
// reference to the form submission method
|
|
global ${'_' . $this->form_properties['method']};
|
|
|
|
$method = & ${'_' . $this->form_properties['method']};
|
|
|
|
// at this point, we assume that the control is not valid
|
|
$valid = false;
|
|
|
|
// continue only if form was submitted
|
|
if (
|
|
|
|
isset($method[$this->form_properties['identifier']]) &&
|
|
|
|
$method[$this->form_properties['identifier']] == $this->form_properties['name']
|
|
|
|
) {
|
|
|
|
// at this point, we assume that the control is valid
|
|
$valid = true;
|
|
|
|
// reference to control
|
|
$control = & $this->controls[$control];
|
|
|
|
// treat "email" and "number" types as "text"
|
|
if (in_array($control->attributes['type'], array('email', 'number'))) $control->attributes['type'] = 'text';
|
|
|
|
// manage submitted value
|
|
$control->get_submitted_value();
|
|
|
|
// get some attributes of the control
|
|
$attribute = $control->get_attributes(array('name', 'id', 'type', 'value', 'multiple', 'format', 'disable_spam_filter', 'other'));
|
|
|
|
// if control doesn't have the SPAM filter disabled
|
|
if (!isset($attribute['disable_spam_filter']) || $attribute['disable_spam_filter'] !== true) {
|
|
|
|
// check to see if there is SPAM/INJECTION attempt by checking if the values in select boxes, radio buttons
|
|
// and checkboxes are in the list of allowable values, as set when initializing the controls
|
|
|
|
// check controls by type
|
|
switch ($attribute['type']) {
|
|
|
|
// if control is a select box
|
|
case 'select':
|
|
|
|
// if control was submitted
|
|
// (as there can also be no selections for a select box with the "multiple" attribute set, case in
|
|
// which there's no submission)
|
|
// (also, the isset() check is for when we "lock" controls)
|
|
if (isset($control->submitted_value) && $control->submitted_value) {
|
|
|
|
// flatten array (in case we have select groups)
|
|
$values = $this->_extract_values($control->attributes['options']);
|
|
|
|
// if the "other" attribute is set, then "other" is a valid option
|
|
if (isset($attribute['other'])) $values[] = 'other';
|
|
|
|
// we need to treat all values as strings
|
|
// or the in_array below will fail in strict mode
|
|
array_walk($values, create_function('&$value', '$value = (string)$value;'));
|
|
|
|
// if an array was submitted and there are values that are not in the list allowable values
|
|
if (is_array($control->submitted_value) && $control->submitted_value != array_intersect($control->submitted_value, $values))
|
|
|
|
// set a flag accordingly
|
|
$valid = false;
|
|
|
|
// if submitted value is not an array and submitted value is not in the list of allowable values
|
|
// we use strict mode or any string, when compared to 0, will be valid...
|
|
if (!is_array($control->submitted_value) && !in_array($control->submitted_value, $values, true))
|
|
|
|
// set a flag accordingly
|
|
$valid = false;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
// if control is a checkbox control or a radio button
|
|
case 'checkbox':
|
|
case 'radio':
|
|
|
|
// if control was submitted
|
|
if ($control->submitted_value) {
|
|
|
|
$values = array();
|
|
|
|
// iterate through all the form's controls
|
|
foreach ($this->controls as $element)
|
|
|
|
// if control is of the same type and has the same name
|
|
if ($element->attributes['type'] == $attribute['type'] && $element->attributes['name'] == $attribute['name'])
|
|
|
|
// add the control's value to the list of valid values
|
|
$values[] = $element->attributes['value'];
|
|
|
|
// if an array was submitted and there are values that are not in the list allowable values
|
|
if (is_array($control->submitted_value) && $control->submitted_value != array_intersect($control->submitted_value, $values))
|
|
|
|
// set a flag accordingly
|
|
$valid = false;
|
|
|
|
// if submitted value is not an array and submitted value is not in the list of allowable values
|
|
if (!is_array($control->submitted_value) && !in_array($control->submitted_value, $values))
|
|
|
|
// set a flag accordingly
|
|
$valid = false;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// if spam attempt was detected
|
|
if (!$valid) {
|
|
|
|
// set the error message
|
|
$this->add_error('zf_error_spam', $this->form_properties['language']['spam_detected']);
|
|
|
|
// don't look further
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// if
|
|
if (
|
|
|
|
// control was submitted and has rules assigned
|
|
isset($control->submitted_value) && !empty($control->rules)
|
|
|
|
) {
|
|
|
|
// we need to make sure that rules are in propper order, the order of priority being "dependencies",
|
|
// "required" and "upload"
|
|
|
|
// if the upload rule exists
|
|
if (isset($control->rules['upload'])) {
|
|
|
|
// remove it from wherever it is
|
|
$rule = array_splice($control->rules, array_search('upload', array_keys($control->rules)), 1, array());
|
|
|
|
// and make sure it's the first rule
|
|
$control->rules = array_merge($rule, $control->rules);
|
|
|
|
}
|
|
|
|
// if the "required" rule exists
|
|
if (isset($control->rules['required'])) {
|
|
|
|
// remove it from wherever it is
|
|
$rule = array_splice($control->rules, array_search('required', array_keys($control->rules)), 1, array());
|
|
|
|
// and make sure it's the first rule (it has to be checked prior to the "upload" rule)
|
|
$control->rules = array_merge($rule, $control->rules);
|
|
|
|
}
|
|
|
|
// if the "dependencies" rule exists
|
|
if (isset($control->rules['dependencies'])) {
|
|
|
|
// remove it from wherever it is
|
|
$rule = array_splice($control->rules, array_search('dependencies', array_keys($control->rules)), 1, array());
|
|
|
|
// and make sure it's the first rule (it has to be checked prior to the "required" and "upload" rules)
|
|
$control->rules = array_merge($rule, $control->rules);
|
|
|
|
}
|
|
|
|
// iterate through rules assigned to the control
|
|
foreach ($control->rules as $rule_name => $rule_attributes) {
|
|
|
|
// make sure the rule name is in lowercase
|
|
$rule_name = strtolower($rule_name);
|
|
|
|
// check the rule's name
|
|
switch ($rule_name) {
|
|
|
|
// if rule is "age"
|
|
case 'age':
|
|
|
|
if (
|
|
|
|
// control is 'text'
|
|
$attribute['type'] == 'text' &&
|
|
|
|
// a value was entered
|
|
$attribute['value'] != '' &&
|
|
|
|
// control was validated
|
|
isset($control->attributes['date']) &&
|
|
|
|
// control contains a valid date
|
|
date('Y-m-d', strtotime($control->attributes['date'])) == $control->attributes['date']
|
|
|
|
) {
|
|
|
|
// the allowed age interval
|
|
$min_age = $rule_attributes[0][0];
|
|
$max_age = $rule_attributes[0][1];
|
|
|
|
// compute age
|
|
$datetime1 = new DateTime();
|
|
$datetime2 = new DateTime($control->attributes['date']);
|
|
$interval = $datetime1->diff($datetime2);
|
|
$age = $interval->format('%y');
|
|
|
|
// if age is invalid
|
|
if (!(($min_age == 0 || $age >= $min_age) && ($max_age == 0 || $age <= $max_age))) {
|
|
|
|
// add error message to indicated error block
|
|
$this->add_error($rule_attributes[1], $rule_attributes[2]);
|
|
|
|
// the control does not validate
|
|
$valid = false;
|
|
|
|
// no further checking needs to be done for the control, making sure that only one
|
|
// error message is displayed at a time for each erroneous control
|
|
break 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
// if rule is 'alphabet'
|
|
case 'alphabet':
|
|
|
|
if (
|
|
|
|
(
|
|
// control is 'password'
|
|
$attribute['type'] == 'password' ||
|
|
|
|
// control is 'text'
|
|
$attribute['type'] == 'text' ||
|
|
|
|
// control is 'textarea'
|
|
$attribute['type'] == 'textarea'
|
|
|
|
) &&
|
|
|
|
// a value was entered
|
|
$attribute['value'] != '' &&
|
|
|
|
// control does not contain only letters from the alphabet (and other allowed characters, if any)
|
|
// we're also fixing a bug where in PHP prior to 5.3, preg_quote was not escaping dashes (-)
|
|
!preg_match('/^[a-z' . preg_replace('/\//', '\/', preg_replace('/(?<!\\\)\-/', '\-', preg_quote($rule_attributes[0]))) . ']+$/i', $attribute['value'])
|
|
|
|
) {
|
|
|
|
// add error message to indicated error block
|
|
$this->add_error($rule_attributes[1], $rule_attributes[2]);
|
|
|
|
// the control does not validate
|
|
$valid = false;
|
|
|
|
// no further checking needs to be done for the control, making sure that only one
|
|
// error message is displayed at a time for each erroneous control
|
|
break 2;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
// if rule is 'alphanumeric'
|
|
case 'alphanumeric':
|
|
|
|
if (
|
|
|
|
(
|
|
// control is 'password'
|
|
$attribute['type'] == 'password' ||
|
|
|
|
// control is 'text'
|
|
$attribute['type'] == 'text' ||
|
|
|
|
// control is 'textarea'
|
|
$attribute['type'] == 'textarea'
|
|
|
|
) &&
|
|
|
|
// a value was entered
|
|
$attribute['value'] != '' &&
|
|
|
|
// control does not contain only allowed characters
|
|
// we're also fixing a bug where in PHP prior to 5.3, preg_quote was not escaping dashes (-)
|
|
!preg_match('/^[a-z0-9' . preg_replace('/\//', '\/', preg_replace('/(?<!\\\)\-/', '\-', preg_quote($rule_attributes[0]))) . ']+$/i', $attribute['value'])
|
|
|
|
) {
|
|
|
|
// add error message to indicated error block
|
|
$this->add_error($rule_attributes[1], $rule_attributes[2]);
|
|
|
|
// the control does not validate
|
|
$valid = false;
|
|
|
|
// no further checking needs to be done for the control, making sure that only one
|
|
// error message is displayed at a time for each erroneous control
|
|
break 2;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
// if 'captcha'
|
|
case 'captcha':
|
|
|
|
if (
|
|
|
|
// control is 'text'
|
|
$attribute['type'] == 'text' &&
|
|
|
|
// control's value is not the one showed in the picture
|
|
md5(md5(md5(strtolower($control->submitted_value)))) != ($this->form_properties['captcha_storage'] == 'session' ? @$_SESSION['captcha'] : @$_COOKIE['captcha'])
|
|
|
|
) {
|
|
|
|
// add error message to indicated error block
|
|
$this->add_error($rule_attributes[0], $rule_attributes[1]);
|
|
|
|
// the control does not validate
|
|
$valid = false;
|
|
|
|
// no further checking needs to be done for the control, making sure that only one
|
|
// error message is displayed at a time for each erroneous control
|
|
break 2;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
// if 'compare'
|
|
case 'compare':
|
|
|
|
if (
|
|
|
|
(
|
|
// control is 'password'
|
|
$attribute['type'] == 'password' ||
|
|
|
|
// control is 'text'
|
|
$attribute['type'] == 'text' ||
|
|
|
|
// control is 'textarea'
|
|
$attribute['type'] == 'textarea'
|
|
|
|
) && (
|
|
|
|
// the control to compare to was not submitted
|
|
!isset($method[$rule_attributes[0]]) ||
|
|
|
|
// OR
|
|
(
|
|
|
|
// the control to compare to was submitted
|
|
isset($method[$rule_attributes[0]]) &&
|
|
|
|
// and the values don't match
|
|
$control->submitted_value != $method[$rule_attributes[0]]
|
|
|
|
)
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
// add error message to indicated error block
|
|
$this->add_error($rule_attributes[1], $rule_attributes[2]);
|
|
|
|
// the control does not validate
|
|
$valid = false;
|
|
|
|
// no further checking needs to be done for the control, making sure that only one
|
|
// error message is displayed at a time for each erroneous control
|
|
break 2;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
// if 'dependencies'
|
|
case 'dependencies':
|
|
|
|
// if not all conditions are met, don't validate the control
|
|
if (!$this->_validate_dependencies($attribute['id'])) return true;
|
|
|
|
break;
|
|
|
|
// if 'convert'
|
|
case 'convert':
|
|
|
|
if (
|
|
|
|
// control is 'file'
|
|
$attribute['type'] == 'file' &&
|
|
|
|
// and a file was uploaded
|
|
isset($_FILES[$attribute['name']]) &&
|
|
|
|
// and file was uploaded without any errors
|
|
$_FILES[$attribute['name']]['error'] == 0
|
|
|
|
) {
|
|
|
|
// as conversions are done only when the form is valid
|
|
// for now we only save some data that will be processed if the form is valid
|
|
// (we're adding keys so that we don't have duplicate actions if validate_control method is called repeatedly)
|
|
$this->actions[$attribute['name'] . '_convert'] = array(
|
|
|
|
'_convert', // method to be called
|
|
$attribute['name'], // the file upload control's name
|
|
'extension' => $rule_attributes[0], // extension to convert to
|
|
'quality' => $rule_attributes[1], // quality (available only for JPEG files)
|
|
'preserve_original_file' => $rule_attributes[2], // preserve original file?
|
|
'overwrite' => $rule_attributes[3], // overwrite if file with new extension exists
|
|
'block' => $rule_attributes[4], // error block
|
|
'message' => $rule_attributes[5], // error message
|
|
|
|
);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
// if 'custom' rule
|
|
case 'custom':
|
|
|
|
// custom rules are stored as an array
|
|
// iterate through the custom rules
|
|
foreach ($rule_attributes as $custom_rule_attributes) {
|
|
|
|
// if custom function exists
|
|
if (is_callable($custom_rule_attributes[0])) {
|
|
|
|
// the arguments that we are passing to the custom function are the control's
|
|
// submitted value and all other arguments passed when setting the custom rule
|
|
// except the first one which is the custom function's and the last two which are
|
|
// the error block name and the error message respectively
|
|
$arguments = array_merge(array($control->submitted_value), array_slice($custom_rule_attributes, 1, -2));
|
|
|
|
// run the custom function
|
|
// and if the function returns false
|
|
if (!call_user_func_array($custom_rule_attributes[0], $arguments)) {
|
|
|
|
// count the arguments passed when declaring the rules
|
|
$attributes_count = count($custom_rule_attributes);
|
|
|
|
// add error message to indicated error block
|
|
$this->add_error($custom_rule_attributes[$attributes_count - 2], $custom_rule_attributes[$attributes_count - 1]);
|
|
|
|
// the control does not validate
|
|
$valid = false;
|
|
|
|
// no further checking needs to be done for the control, making sure that only one
|
|
// error message is displayed at a time for each erroneous control
|
|
break 3;
|
|
|
|
}
|
|
|
|
// if custom function doesn't exist, trigger an error message
|
|
} else _zebra_form_show_error('Function <strong>' . $custom_rule_attributes[0] . '()</strong> doesn\'t exist.', E_USER_ERROR);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
// if date
|
|
case 'date':
|
|
|
|
if (
|
|
|
|
// control is 'text'
|
|
$attribute['type'] == 'text' &&
|
|
|
|
// is a 'date' control
|
|
isset($attribute['format']) &&
|
|
|
|
// a value was entered
|
|
$attribute['value'] != ''
|
|
|
|
) {
|
|
|
|
// if
|
|
if (
|
|
|
|
// initialize the datepicker's data for further calculations
|
|
$control->_init() &&
|
|
|
|
// date has an invalid format
|
|
!($timestamp = $control->_is_format_valid($attribute['value'])) ||
|
|
|
|
// or date is disabled
|
|
$control->_is_disabled(date('Y', $timestamp), date('n', $timestamp), date('d', $timestamp))
|
|
|
|
) {
|
|
|
|
// add error message to indicated error block
|
|
$this->add_error($rule_attributes[0], $rule_attributes[1]);
|
|
|
|
// the control does not validate
|
|
$valid = false;
|
|
|
|
// no further checking needs to be done for the control, making sure that only one
|
|
// error message is displayed at a time for each erroneous control
|
|
break 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
// if "datecompare"
|
|
case 'datecompare':
|
|
|
|
if (
|
|
|
|
// control is 'text'
|
|
$attribute['type'] == 'text' &&
|
|
|
|
// is a 'date' control
|
|
isset($attribute['format']) &&
|
|
|
|
// a value was entered
|
|
$attribute['value'] != '' &&
|
|
|
|
// control to compare with, exists
|
|
isset($this->controls[$rule_attributes[0]]) &&
|
|
|
|
// control to compare with, is a 'text' control
|
|
$this->controls[$rule_attributes[0]]->attributes['type'] == 'text' &&
|
|
|
|
// control to compare with, is a 'date' control
|
|
($this->controls[$rule_attributes[0]]->attributes['format']) &&
|
|
|
|
// control validates
|
|
$this->validate_control($this->controls[$rule_attributes[0]]->attributes['id'])
|
|
|
|
) {
|
|
|
|
// we assume the control is invalid
|
|
$valid = false;
|
|
|
|
// compare the controls according to the comparison operator
|
|
switch ($rule_attributes[1]) {
|
|
case '>':
|
|
$valid = ($control->attributes['date'] > $this->controls[$rule_attributes[0]]->attributes['date']);
|
|
break;
|
|
case '>=':
|
|
$valid = ($control->attributes['date'] >= $this->controls[$rule_attributes[0]]->attributes['date']);
|
|
break;
|
|
case '<':
|
|
$valid = ($control->attributes['date'] < $this->controls[$rule_attributes[0]]->attributes['date']);
|
|
break;
|
|
case '<=':
|
|
$valid = ($control->attributes['date'] <= $this->controls[$rule_attributes[0]]->attributes['date']);
|
|
break;
|
|
}
|
|
|
|
// if invalid
|
|
if (!$valid) {
|
|
|
|
// add error message to indicated error block
|
|
$this->add_error($rule_attributes[2], $rule_attributes[3]);
|
|
|
|
// the control does not validate
|
|
$valid = false;
|
|
|
|
// no further checking needs to be done for the control, making sure that only one
|
|
// error message is displayed at a time for each erroneous control
|
|
break 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
// if rule is 'digits'
|
|
case 'digits':
|
|
|
|
if (
|
|
|
|
(
|
|
// control is 'password'
|
|
$attribute['type'] == 'password' ||
|
|
|
|
// control is 'text'
|
|
$attribute['type'] == 'text' ||
|
|
|
|
// control is 'textarea'
|
|
$attribute['type'] == 'textarea'
|
|
|
|
) &&
|
|
|
|
// a value was entered
|
|
$attribute['value'] != '' &&
|
|
|
|
// but entered value does not contain digits only (and other allowed characters, if any)
|
|
// we're also fixing a bug where in PHP prior to 5.3, preg_quote was not escaping dashes (-)
|
|
!preg_match('/^[0-9' . preg_replace('/\//', '\/', preg_replace('/(?<!\\\)\-/', '\-', preg_quote($rule_attributes[0]))) . ']+$/', $attribute['value'])
|
|
|
|
) {
|
|
|
|
// add error message to indicated error block
|
|
$this->add_error($rule_attributes[1], $rule_attributes[2]);
|
|
|
|
// the control does not validate
|
|
$valid = false;
|
|
|
|
// no further checking needs to be done for the control, making sure that only one
|
|
// error message is displayed at a time for each erroneous control
|
|
break 2;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
// if "email"
|
|
case 'email':
|
|
|
|
if (
|
|
|
|
(
|
|
// control is 'password'
|
|
$attribute['type'] == 'password' ||
|
|
|
|
// control is 'text'
|
|
$attribute['type'] == 'text' ||
|
|
|
|
// control is 'textarea'
|
|
$attribute['type'] == 'textarea'
|
|
|
|
) &&
|
|
|
|
// a value was entered
|
|
$attribute['value'] != '' && (
|
|
|
|
// email address contains consecutive dots
|
|
preg_match('/\.{2,}/', $attribute['value']) ||
|
|
|
|
// email address is longer than the maximum allowed length
|
|
strlen($attribute['value']) > 254 ||
|
|
|
|
// email address has an invalid format
|
|
!preg_match('/^[^\.][a-z0-9_\-\+\~\^\{\}\.]{1,64}@[a-z0-9_\-\+\~\^\{\}\.]{1,255}\.[a-z0-9]{2,}$/i', $attribute['value'])
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
// add error message to indicated error block
|
|
$this->add_error($rule_attributes[0], $rule_attributes[1]);
|
|
|
|
// the control does not validate
|
|
$valid = false;
|
|
|
|
// no further checking needs to be done for the control, making sure that only one
|
|
// error message is displayed at a time for each erroneous control
|
|
break 2;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
// if "list of emails"
|
|
case 'emails':
|
|
|
|
if (
|
|
|
|
(
|
|
// control is 'password'
|
|
$attribute['type'] == 'password' ||
|
|
|
|
// control is 'text'
|
|
$attribute['type'] == 'text' ||
|
|
|
|
// control is 'textarea'
|
|
$attribute['type'] == 'textarea'
|
|
|
|
) &&
|
|
|
|
// a value was entered
|
|
$attribute['value'] != ''
|
|
|
|
) {
|
|
|
|
// convert string to an array of addresses
|
|
$addresses = explode(',', $attribute['value']);
|
|
|
|
// iterate through the addresses
|
|
foreach ($addresses as $address)
|
|
|
|
// not a valid email address
|
|
if (!preg_match('/^([a-zA-Z0-9_\-\+\~\^\{\}]+[\.]?)+@{1}([a-zA-Z0-9_\-\+\~\^\{\}]+[\.]?)+\.[A-Za-z0-9]{2,}$/', trim($address))) {
|
|
|
|
// add error message to indicated error block
|
|
$this->add_error($rule_attributes[0], $rule_attributes[1]);
|
|
|
|
// the control does not validate
|
|
$valid = false;
|
|
|
|
// no further checking needs to be done for the control, making sure that only one
|
|
// error message is displayed at a time for each erroneous control
|
|
break 3;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
// if "filesize"
|
|
case 'filesize':
|
|
|
|
if (
|
|
|
|
// control is 'file'
|
|
$attribute['type'] == 'file' &&
|
|
|
|
// and a file was uploaded
|
|
isset($_FILES[$attribute['name']]) &&
|
|
|
|
(
|
|
|
|
// uploaded file size exceeds the size imposed when creating the form
|
|
$_FILES[$attribute['name']]['size'] > $rule_attributes[0] ||
|
|
|
|
// the uploaded file exceeds the upload_max_filesize directive in php.ini
|
|
$_FILES[$attribute['name']]['error'] == 1 ||
|
|
|
|
// the uploaded file exceeds the MAX_FILE_SIZE directive that was specified
|
|
// in the HTML form
|
|
$_FILES[$attribute['name']]['error'] == 2
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
// add error message to indicated error block
|
|
$this->add_error($rule_attributes[1], $rule_attributes[2]);
|
|
|
|
// the control does not validate
|
|
$valid = false;
|
|
|
|
// no further checking needs to be done for the control, making sure that only one
|
|
// error message is displayed at a time for each erroneous control
|
|
break 2;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
// if "filetype"
|
|
case 'filetype':
|
|
|
|
if (
|
|
|
|
// control is 'file'
|
|
$attribute['type'] == 'file' &&
|
|
|
|
// and a file was uploaded
|
|
isset($_FILES[$attribute['name']]) &&
|
|
|
|
// and file was uploaded without errors
|
|
$_FILES[$attribute['name']]['error'] == 0
|
|
|
|
) {
|
|
|
|
// if "finfo_open" function exists (from PHP 5.3.0)
|
|
if (function_exists('finfo_open')) {
|
|
|
|
// determine the "true" mime type of the uploaded file
|
|
$finfo = finfo_open(FILEINFO_MIME_TYPE);
|
|
$mime = finfo_file($finfo, $_FILES[$attribute['name']]['tmp_name']);
|
|
finfo_close($finfo);
|
|
|
|
// otherwise, rely on the information returned by $_FILES which uses the file's
|
|
// extension to determine the uploaded file's mime type and is therefore unreliable
|
|
} else $mime = $_FILES[$attribute['name']]['type'];
|
|
|
|
// get the allowed file types
|
|
$allowed_file_types = array_map(create_function('$value', 'return trim($value);'), explode(',', $rule_attributes[0]));
|
|
|
|
// this will contain an array of file types that match for the currently uploaded file's
|
|
// mime type
|
|
$matching_file_types = array();
|
|
|
|
// load mime file types
|
|
$this->_load_mime_types();
|
|
|
|
// iterate through the known mime types
|
|
foreach ($this->form_properties['mimes'] as $extension => $type)
|
|
|
|
// if
|
|
if (
|
|
|
|
// there are more mime types associated with the file extension and
|
|
// the uploaded file's type is among them
|
|
is_array($type) && in_array($mime, $type) ||
|
|
|
|
// a single mime type is associated with the file extension and
|
|
// the uploaded file's type matches the mime type
|
|
!is_array($type) && $type == $mime
|
|
|
|
// add file type to the list of file types that match for the currently uploaded
|
|
// file's mime type
|
|
) $matching_file_types[] = $extension;
|
|
|
|
// is the file allowed?
|
|
$matches = array_intersect($matching_file_types, $allowed_file_types);
|
|
|
|
// if file is not allowed
|
|
if (empty($matches)) {
|
|
|
|
// add error message to indicated error block
|
|
$this->add_error($rule_attributes[1], $rule_attributes[2]);
|
|
|
|
// the control does not validate
|
|
$valid = false;
|
|
|
|
// no further checking needs to be done for the control, making sure that only one
|
|
// error message is displayed at a time for each erroneous control
|
|
break 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
// if rule is 'float'
|
|
case 'float':
|
|
|
|
if (
|
|
|
|
(
|
|
// control is 'password'
|
|
$attribute['type'] == 'password' ||
|
|
|
|
// control is 'text'
|
|
$attribute['type'] == 'text' ||
|
|
|
|
// control is 'textarea'
|
|
$attribute['type'] == 'textarea'
|
|
|
|
) &&
|
|
|
|
// a value was entered
|
|
$attribute['value'] != '' &&
|
|
|
|
(
|
|
|
|
// only a dot given
|
|
trim($attribute['value']) == '.' ||
|
|
|
|
// only minus given
|
|
trim($attribute['value']) == '-' ||
|
|
|
|
// has too many minus sign
|
|
preg_match_all('/\-/', $attribute['value'], $matches) > 1 ||
|
|
|
|
// has too many dots in it
|
|
preg_match_all('/\./', $attribute['value'], $matches) > 1 ||
|
|
|
|
// not a floating point number
|
|
// we're also fixing a bug where in PHP prior to 5.3, preg_quote was not escaping dashes (-)
|
|
!preg_match('/^[0-9\-\.' . preg_replace('/\//', '\/', preg_replace('/(?<!\\\)\-/', '\-', preg_quote($rule_attributes[0]))) . ']+$/', $attribute['value']) ||
|
|
|
|
// has a minus sign in it but is not at the very beginning
|
|
(strpos($attribute['value'], '-') !== false && strpos($attribute['value'], '-') > 0)
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
// add error message to indicated error block
|
|
$this->add_error($rule_attributes[1], $rule_attributes[2]);
|
|
|
|
// the control does not validate
|
|
$valid = false;
|
|
|
|
// no further checking needs to be done for the control, making sure that only one
|
|
// error message is displayed at a time for each erroneous control
|
|
break 2;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
// if "image"
|
|
case 'image':
|
|
|
|
if (
|
|
|
|
// control is 'file'
|
|
$attribute['type'] == 'file' &&
|
|
|
|
// and a file was uploaded
|
|
isset($_FILES[$attribute['name']]) &&
|
|
|
|
// and file was uploaded without errors
|
|
$_FILES[$attribute['name']]['error'] == 0
|
|
|
|
) {
|
|
|
|
// get some information about the file
|
|
list($width, $height, $type, $attr) = @getimagesize($_FILES[$attribute['name']]['tmp_name']);
|
|
|
|
// if file is not an image or image is not gif, png or jpeg
|
|
if ($type === false || $type < 1 || $type > 3) {
|
|
|
|
// add error message to indicated error block
|
|
$this->add_error($rule_attributes[0], $rule_attributes[1]);
|
|
|
|
// the control does not validate
|
|
$valid = false;
|
|
|
|
// no further checking needs to be done for the control, making sure that only one
|
|
// error message is displayed at a time for each erroneous control
|
|
break 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
// if "length"
|
|
case 'length':
|
|
|
|
// the rule will be considered as not obeyed when
|
|
if (
|
|
|
|
(
|
|
// control is 'password'
|
|
$attribute['type'] == 'password' ||
|
|
|
|
// control is 'text'
|
|
$attribute['type'] == 'text' ||
|
|
|
|
// control is 'textarea'
|
|
$attribute['type'] == 'textarea'
|
|
|
|
) &&
|
|
|
|
// a value was entered
|
|
$attribute['value'] != '' &&
|
|
|
|
(
|
|
// the length of the value exceeds boundaries
|
|
strlen(utf8_decode(html_entity_decode($attribute['value']))) < $rule_attributes[0] ||
|
|
|
|
// we use the utf8_decode because some characters have 2 bytes and some 3 bytes
|
|
// read more at http://globalizer.wordpress.com/2007/01/16/utf-8-and-string-length-limitations/
|
|
($rule_attributes[1] > 0 && strlen(utf8_decode(html_entity_decode($attribute['value']))) > $rule_attributes[1])
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
// add error message to indicated error block
|
|
$this->add_error($rule_attributes[2], $rule_attributes[3]);
|
|
|
|
// the control does not validate
|
|
$valid = false;
|
|
|
|
// no further checking needs to be done for the control, making sure that only one
|
|
// error message is displayed at a time for each erroneous control
|
|
break 2;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
// if rule is 'number'
|
|
case 'number':
|
|
|
|
if (
|
|
|
|
(
|
|
// control is 'password'
|
|
$attribute['type'] == 'password' ||
|
|
|
|
// control is 'text'
|
|
$attribute['type'] == 'text' ||
|
|
|
|
// control is 'textarea'
|
|
$attribute['type'] == 'textarea'
|
|
|
|
) &&
|
|
|
|
// a value was entered
|
|
$attribute['value'] != '' &&
|
|
|
|
(
|
|
|
|
// only minus given
|
|
trim($attribute['value']) == '-' ||
|
|
|
|
// has too many minus sign
|
|
preg_match_all('/\-/', $attribute['value'], $matches) > 1 ||
|
|
|
|
// not a number
|
|
// we're also fixing a bug where in PHP prior to 5.3, preg_quote was not escaping dashes (-)
|
|
!preg_match('/^[0-9\-' . preg_replace('/\//', '\/', preg_replace('/(?<!\\\)\-/', '\-', preg_quote($rule_attributes[0]))) . ']+$/', $attribute['value']) ||
|
|
|
|
// has a minus sign in it but is not at the very beginning
|
|
(strpos($attribute['value'], '-') !== false && strpos($attribute['value'], '-') > 0)
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
// add error message to indicated error block
|
|
$this->add_error($rule_attributes[1], $rule_attributes[2]);
|
|
|
|
// the control does not validate
|
|
$valid = false;
|
|
|
|
// no further checking needs to be done for the control, making sure that only one
|
|
// error message is displayed at a time for each erroneous control
|
|
break 2;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
// if rule is "range"
|
|
case 'range':
|
|
|
|
if (
|
|
|
|
// control is 'text'
|
|
$attribute['type'] == 'text' &&
|
|
|
|
// a value was entered
|
|
$attribute['value'] != ''
|
|
|
|
) {
|
|
|
|
// get the allowed min and max
|
|
$min = $rule_attributes[0][0];
|
|
$max = $rule_attributes[0][1];
|
|
|
|
// make sure the value is a number
|
|
$value = (float)$attribute['value'];
|
|
|
|
|
|
// if
|
|
if (
|
|
|
|
// parsed value is different than what the user entered
|
|
$value != $attribute['value'] ||
|
|
|
|
// or the value is not within range
|
|
!(($min == 0 || $value >= $min) && ($max == 0 || $value <= $max))
|
|
|
|
) {
|
|
|
|
// add error message to indicated error block
|
|
$this->add_error($rule_attributes[1], $rule_attributes[2]);
|
|
|
|
// the control does not validate
|
|
$valid = false;
|
|
|
|
// no further checking needs to be done for the control, making sure that only one
|
|
// error message is displayed at a time for each erroneous control
|
|
break 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
// if "regexp"
|
|
case 'regexp':
|
|
|
|
if (
|
|
|
|
(
|
|
// control is 'password'
|
|
$attribute['type'] == 'password' ||
|
|
|
|
// control is 'text'
|
|
$attribute['type'] == 'text' ||
|
|
|
|
// control is 'textarea'
|
|
$attribute['type'] == 'textarea'
|
|
|
|
) &&
|
|
|
|
// a value was entered
|
|
$attribute['value'] != '' &&
|
|
|
|
// value does not match regular expression
|
|
!preg_match('/' . $rule_attributes[0] . '/', $attribute['value'])
|
|
|
|
) {
|
|
|
|
// add error message to indicated error block
|
|
$this->add_error($rule_attributes[1], $rule_attributes[2]);
|
|
|
|
// the control does not validate
|
|
$valid = false;
|
|
|
|
// no further checking needs to be done for the control, making sure that only one
|
|
// error message is displayed at a time for each erroneous control
|
|
break 2;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
// if "required"
|
|
case 'required':
|
|
|
|
// if it's a drop-down that is part of a time control
|
|
if ($attribute['type'] == 'time') {
|
|
|
|
// if invalid format specified, revert to the default "hm"
|
|
if (preg_match('/^[hmsg]+$/i', $attribute['format']) == 0 || strlen(preg_replace('/([a-z]{2,})/i', '$1', $attribute['format'])) != strlen($attribute['format'])) $attribute['format'] = 'hm';
|
|
|
|
$regexp = '';
|
|
|
|
// build the regular expression for validating the time
|
|
for ($i = 0; $i < strlen($attribute['format']); $i++) {
|
|
|
|
// for each characher in the format we use a particular regular expression
|
|
switch (strtolower(substr($attribute['format'], $i, 1))) {
|
|
|
|
case 'h':
|
|
|
|
// if 12 hour format is used use this expression...
|
|
if (strpos(strtolower($attribute['format']), 'g')) $regexp .= '0[1-9]|1[012]';
|
|
|
|
// ...and different expression for the 24 hour format
|
|
else $regexp .= '([0-1][0-9]|2[0-3])';
|
|
|
|
break;
|
|
|
|
case 'm':
|
|
case 's':
|
|
|
|
// regular expression for validating minutes and seconds
|
|
$regexp .= '[0-5][0-9]';
|
|
|
|
break;
|
|
|
|
case 'g':
|
|
|
|
// validate am/pm
|
|
$regexp .= '(am|pm)';
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// if time does not validate
|
|
if (preg_match('/' . $regexp . '/i', str_replace(array(':', ' '), '', $attribute['value'])) == 0) {
|
|
|
|
// add error message to indicated error block
|
|
$this->add_error($rule_attributes[0], $rule_attributes[1]);
|
|
|
|
// the control does not validate
|
|
$valid = false;
|
|
|
|
// no further checking needs to be done for the control, making sure that only one
|
|
// error message is displayed at a time for each erroneous control
|
|
break 2;
|
|
|
|
}
|
|
|
|
// for other controls
|
|
} else {
|
|
|
|
// if control is 'select'
|
|
if ($attribute['type'] == 'select') {
|
|
|
|
// as of PHP 5.3, array_shift required the argument to be a variable and not the result
|
|
// of a function so we need this intermediary step
|
|
$notSelectedIndex = array_keys($control->attributes['options']);
|
|
|
|
// get the index which when selected indicated that 'nothing is selected'
|
|
$notSelectedIndex = array_shift($notSelectedIndex);
|
|
|
|
}
|
|
|
|
// the rule will be considered as not obeyed when
|
|
if (
|
|
|
|
// control is 'password' or 'text' or 'textarea' and the 'value' attribute is empty
|
|
(($attribute['type'] == 'password' || $attribute['type'] == 'text' || $attribute['type'] == 'textarea') && trim($attribute['value']) == '') ||
|
|
|
|
// control is 'file' and no file specified
|
|
($attribute['type'] == 'file' && isset($_FILES[$attribute['name']]) && trim($_FILES[$attribute['name']]['name']) == '') ||
|
|
|
|
// control is 'checkbox' or 'radio' and the control was not submitted
|
|
(($attribute['type'] == 'checkbox' || $attribute['type'] == 'radio') && $control->submitted_value === false) ||
|
|
|
|
// control is 'select', the 'multiple' attribute is set and control was not submitted
|
|
($attribute['type'] == 'select' && isset($attribute['multiple']) && $control->submitted_value === false) ||
|
|
|
|
// control is 'select', the 'multiple' attribute is not set and the select control's first value is selected
|
|
($attribute['type'] == 'select' && !isset($attribute['multiple']) && (is_array($control->submitted_value) || strcmp($control->submitted_value, $notSelectedIndex) == 0)) ||
|
|
|
|
// control is 'select', the 'multiple' attribute is not set, the select control's value is "other" and the "other" control is empty
|
|
($attribute['type'] == 'select' && !isset($attribute['multiple']) && $control->submitted_value == 'other' && trim($method[$attribute['name'] . $this->form_properties['other_suffix']]) == '')
|
|
|
|
) {
|
|
|
|
// add error message to indicated error block
|
|
$this->add_error($rule_attributes[0], $rule_attributes[1]);
|
|
|
|
// the control does not validate
|
|
$valid = false;
|
|
|
|
// no further checking needs to be done for the control, making sure that only one
|
|
// error message is displayed at a time for each erroneous control
|
|
break 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
// if 'resize'
|
|
case 'resize':
|
|
|
|
if (
|
|
|
|
// control is 'file'
|
|
$attribute['type'] == 'file' &&
|
|
|
|
// and a file was uploaded
|
|
isset($_FILES[$attribute['name']]) &&
|
|
|
|
// and file was uploaded without any errors
|
|
$_FILES[$attribute['name']]['error'] == 0
|
|
|
|
) {
|
|
|
|
// as of PHP 5.3, array_shift required the argument to be a variable and not the result
|
|
// of a function so we need this intermediary step
|
|
$tmp = array_values($rule_attributes);
|
|
|
|
// if not multiple resize calls
|
|
// make it look like multiple resize call
|
|
if (!is_array(array_shift($tmp)))
|
|
|
|
$rule_attributes = array($rule_attributes);
|
|
|
|
// iterate through the resize calls
|
|
foreach ($rule_attributes as $index => $rule_attribute)
|
|
|
|
// as resizes are done only when the form is valid and after the file has been
|
|
// uploaded, for now we only save some data that will be processed if the form is valid
|
|
// (we're adding keys so that we don't have duplicate actions if validate_control method is called repeatedly)
|
|
$this->actions[$attribute['name'] . '_resize_' . $index] = array(
|
|
|
|
'_resize', // method that needs to be called
|
|
$attribute['name'], // the file upload control's name
|
|
$rule_attribute[0], // prefix for the resized file
|
|
$rule_attribute[1], // width
|
|
$rule_attribute[2], // height
|
|
$rule_attribute[3], // preserve aspect ratio?
|
|
$rule_attribute[4], // method,
|
|
$rule_attribute[5], // background color
|
|
$rule_attribute[6], // enlarge smaller images?
|
|
$rule_attribute[7], // jpeg quality
|
|
'block' => $rule_attribute[8], // error block
|
|
'message' => $rule_attribute[9], // error message
|
|
|
|
);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
// if 'upload'
|
|
case 'upload':
|
|
|
|
if (
|
|
|
|
// control is 'file'
|
|
$attribute['type'] == 'file' &&
|
|
|
|
// and a file was uploaded
|
|
isset($_FILES[$attribute['name']]) &&
|
|
|
|
// and file was uploaded without any errors
|
|
$_FILES[$attribute['name']]['error'] == 0
|
|
|
|
)
|
|
|
|
// as uploads are done only when the form is valid
|
|
// for now we only save some data that will be processed if the form is valid
|
|
// (we're adding keys so that we don't have duplicate actions if validate_control method is called repeatedly)
|
|
$this->actions[$attribute['name'] . '_upload'] = array(
|
|
|
|
'_upload', // method to be called
|
|
$attribute['name'], // the file upload control's name
|
|
$rule_attributes[0], // the folder where the file to be uploaded to
|
|
$rule_attributes[1], // should the original file name be preserved
|
|
'block' => $rule_attributes[2], // error block
|
|
'message' => $rule_attributes[3], // error message
|
|
|
|
);
|
|
|
|
break;
|
|
|
|
// if "url"
|
|
case 'url':
|
|
|
|
if (
|
|
|
|
(
|
|
// control is 'password'
|
|
$attribute['type'] == 'password' ||
|
|
|
|
// control is 'text'
|
|
$attribute['type'] == 'text' ||
|
|
|
|
// control is 'textarea'
|
|
$attribute['type'] == 'textarea'
|
|
|
|
) &&
|
|
|
|
// a value was entered
|
|
$attribute['value'] != '' &&
|
|
|
|
// value does not match regular expression
|
|
!preg_match('/^(https?\:\/\/)' . ($rule_attributes[0] === true ? '' : '?') . '[^\s\.]+\..{2,}/i', $attribute['value'])
|
|
|
|
) {
|
|
|
|
// add error message to indicated error block
|
|
$this->add_error($rule_attributes[1], $rule_attributes[2]);
|
|
|
|
// the control does not validate
|
|
$valid = false;
|
|
|
|
// no further checking needs to be done for the control, making sure that only one
|
|
// error message is displayed at a time for each erroneous control
|
|
break 2;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $valid;
|
|
|
|
}
|
|
|
|
/**
|
|
* Generates a CSRF token, unique to the current form.
|
|
*
|
|
* Note that this will generate a new CSRF token only when the form is generated and not also when the form is
|
|
* submitted - unless the <b>$force</b> argument is set to TRUE.
|
|
*
|
|
* @param boolean $force (Optional) Instructs the method to forcefully generate a new CSRF token.
|
|
*
|
|
* This parameter will be TRUE when the method is called after an unsuccessful
|
|
* CSRF token validation or after a successful form validation.
|
|
*
|
|
* By default, this method will generate a new CSRF token *only* if the form
|
|
* is not being currently submitted (form information is not available in the $_POST
|
|
* superglobal).
|
|
*
|
|
* Default is FALSE.
|
|
*
|
|
* @return void
|
|
*
|
|
* @access private
|
|
*/
|
|
private function _csrf_generate_token($force = false)
|
|
{
|
|
|
|
// if CSRF protection is enabled (is not boolean FALSE) and CSRF token was not already generated
|
|
if ($this->form_properties['csrf_storage_method'] !== false) {
|
|
|
|
// reference to the form submission method
|
|
global ${'_' . $this->form_properties['method']};
|
|
|
|
$method = & ${'_' . $this->form_properties['method']};
|
|
|
|
// if
|
|
if (
|
|
|
|
// form was submitted and we don't need to forcefully generate a new token
|
|
isset($method[$this->form_properties['identifier']]) && $force === false &&
|
|
// CSRF token is stored in a session variable
|
|
$this->form_properties['csrf_storage_method'] == 'session' &&
|
|
// the session variable exists
|
|
isset($_SESSION[$this->form_properties['csrf_cookie_name']]) &&
|
|
// the session variable holds an array
|
|
is_array($_SESSION[$this->form_properties['csrf_cookie_name']]) &&
|
|
// the array has 2 entries
|
|
count($_SESSION[$this->form_properties['csrf_cookie_name']]) == 2
|
|
|
|
// use the already existing CSRF token
|
|
) $this->form_properties['csrf_token'] = $_SESSION[$this->form_properties['csrf_cookie_name']][0];
|
|
|
|
// else if
|
|
elseif (
|
|
|
|
// form was submitted and we don't need to forcefully generate a new token
|
|
isset($method[$this->form_properties['identifier']]) && $force === false &&
|
|
// CSRF token is stored in a cookie
|
|
$this->form_properties['csrf_storage_method'] == 'cookie' &&
|
|
// the cookie exists
|
|
isset($_COOKIE[$this->form_properties['csrf_cookie_name']])
|
|
|
|
// use the already existing CSRF token
|
|
) $this->form_properties['csrf_token'] = $_COOKIE[$this->form_properties['csrf_cookie_name']];
|
|
|
|
// else, if form was not submitted, or we force new token generation
|
|
elseif (!isset($method[$this->form_properties['identifier']])|| $force === true) {
|
|
|
|
// generate a random token
|
|
$this->form_properties['csrf_token'] = md5(uniqid(rand(), true));
|
|
|
|
// compute token expiry timestamp
|
|
$csrf_token_expiry = $this->form_properties['csrf_token_lifetime'] == 0 ? 0 : time() + $this->form_properties['csrf_token_lifetime'];
|
|
|
|
// if storage method is "session"
|
|
if ($this->form_properties['csrf_storage_method'] == 'session') {
|
|
|
|
// if no session is started, trigger an error message
|
|
if (!isset($_SESSION)) _zebra_form_show_error('You have chosen to enable protection against cross-site request forgery (CSRF) attacks and to use sessions for storing the CSRF token, but a session is not started! Start a session prior to calling the "csrf()" method', E_USER_ERROR);
|
|
|
|
// if sessions are on, store the CSRF token and the expiration data in session
|
|
$_SESSION[$this->form_properties['csrf_cookie_name']] = array($this->form_properties['csrf_token'], $csrf_token_expiry);
|
|
|
|
// if storage method is "cookie"
|
|
} else {
|
|
|
|
// if PHP version is 5.2.0+
|
|
if (version_compare(PHP_VERSION, '5.2.0', '>='))
|
|
|
|
// store the CSRF token in a cookie and use also the httponly argument
|
|
if (!setcookie(
|
|
$this->form_properties['csrf_cookie_name'],
|
|
$this->form_properties['csrf_token'],
|
|
$csrf_token_expiry,
|
|
$this->form_properties['csrf_cookie_config']['path'],
|
|
$this->form_properties['csrf_cookie_config']['domain'],
|
|
$this->form_properties['csrf_cookie_config']['secure'],
|
|
$this->form_properties['csrf_cookie_config']['httponly']
|
|
)) trigger_error('The library tried to store the CSRF token in a cookie but was unable to do so because there was output already sent to the browser. You should either start a session prior to instantiating the library (recommended), have no output (including <html> and <head> tags, as well as any whitespace) sent to the browser prior to instantiating the library, or turn output buffering on in php.ini.', E_USER_ERROR);
|
|
|
|
// if PHP version is lower than 5.2.0
|
|
else
|
|
|
|
// store the CSRF token in a cookie without also using the httponly argument
|
|
if (!setcookie(
|
|
$this->form_properties['csrf_cookie_name'],
|
|
$this->form_properties['csrf_token'],
|
|
$csrf_token_expiry,
|
|
$this->form_properties['csrf_cookie_config']['path'],
|
|
$this->form_properties['csrf_cookie_config']['domain'],
|
|
$this->form_properties['csrf_cookie_config']['secure']
|
|
)) trigger_error('The library tried to store the CSRF token in a cookie but was unable to do so because there was output already sent to the browser. You should either start a session prior to instantiating the library (recommended), have no output (including <html> and <head> tags, as well as any whitespace) sent to the browser prior to instantiating the library, or turn output buffering on in php.ini.', E_USER_ERROR);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Validates CSRF token.
|
|
*
|
|
* @return boolean Returns TRUE if protection against CSRF attacks is disabled or it is enabled and the CSRF
|
|
* token validates, or FALSE otherwise.
|
|
*
|
|
* @access private
|
|
*/
|
|
private function _csrf_validate()
|
|
{
|
|
|
|
// if CSRF protection is enabled (is not boolean FALSE)
|
|
if ($this->form_properties['csrf_storage_method'] !== false) {
|
|
|
|
// reference to the form submission method
|
|
global ${'_' . $this->form_properties['method']};
|
|
|
|
$method = & ${'_' . $this->form_properties['method']};
|
|
|
|
// if
|
|
if (
|
|
|
|
// the hidden field with the CSRF token was submitted
|
|
isset($method[$this->form_properties['csrf_token_name']]) && (
|
|
|
|
// CSRF token is stored in a session variable
|
|
($this->form_properties['csrf_storage_method'] == 'session' &&
|
|
// the session variable exists
|
|
isset($_SESSION[$this->form_properties['csrf_cookie_name']]) &&
|
|
// the session variable holds an array
|
|
is_array($_SESSION[$this->form_properties['csrf_cookie_name']]) &&
|
|
// the array has 2 entries
|
|
count($_SESSION[$this->form_properties['csrf_cookie_name']]) == 2 &&
|
|
// the value of the hidden field and the value in the session match
|
|
$method[$this->form_properties['csrf_token_name']] == $_SESSION[$this->form_properties['csrf_cookie_name']][0] &&
|
|
// if CSRF token doesn't expire or it does but it didn't yet
|
|
($_SESSION[$this->form_properties['csrf_cookie_name']][1] == 0 || $_SESSION[$this->form_properties['csrf_cookie_name']][1] > time()))
|
|
|
|
||
|
|
|
|
// CSRF token is stored in a cookie
|
|
($this->form_properties['csrf_storage_method'] == 'cookie' &&
|
|
// the cookie exists
|
|
isset($_COOKIE[$this->form_properties['csrf_cookie_name']]) &&
|
|
// the value of the hidden field and the value in the cookie match
|
|
$method[$this->form_properties['csrf_token_name']] == $_COOKIE[$this->form_properties['csrf_cookie_name']])
|
|
|
|
)
|
|
|
|
// everything seems in order, then
|
|
) return true;
|
|
|
|
// if we get here something was fishy...
|
|
return false;
|
|
|
|
}
|
|
|
|
// if protection against CSRF attacks is not enabled, pretend nothing happened
|
|
return true;
|
|
|
|
}
|
|
|
|
/**
|
|
* Converts an image from one type to another.
|
|
*
|
|
* Note that this method will update the entries in the {@link $file_upload} property as the converted file will
|
|
* become the "uploaded" file!
|
|
*
|
|
* @param string $control The file upload control's name
|
|
*
|
|
* @param string $type Type to convert an image to.
|
|
*
|
|
* Can be (case-insensitive) JPG, PNG or GIF
|
|
*
|
|
* @param integer $jpeg_quality (Optional) Indicates the quality of the output image (better quality
|
|
* means bigger file size).
|
|
*
|
|
* Range is 0 - 100
|
|
*
|
|
* Available only if <b>type</b> is "jpg".
|
|
*
|
|
* Default is 85.
|
|
*
|
|
* @param integer $preserve_original_file (Optional) Should the original file be preserved after the conversion
|
|
* is done?
|
|
*
|
|
* Default is FALSE.
|
|
*
|
|
* @param boolean $overwrite (Optional) If a file with the same name as the converted file already
|
|
* exists, should it be overwritten or should the name be automatically
|
|
* computed.
|
|
*
|
|
* If a file with the same name as the converted file already exists and
|
|
* this argument is FALSE, a suffix of "_n" (where n is an integer) will
|
|
* be appended to the file name.
|
|
*
|
|
* Default is FALSE
|
|
*
|
|
* @return boolean Returns TRUE on success or FALSE otherwise
|
|
*
|
|
* @access private
|
|
*/
|
|
private function _convert($control, $type, $jpeg_quality = 85, $preserve_original_file = false, $overwrite = false)
|
|
{
|
|
|
|
// make sure the new extension is in lowercase
|
|
$type = strtolower($type);
|
|
|
|
// if
|
|
if (
|
|
|
|
// file was uploaded
|
|
isset($this->file_upload[$control]) &&
|
|
|
|
// and file is indeed an image file
|
|
isset($this->file_upload[$control]['imageinfo']) &&
|
|
|
|
// we're trying to convert to a supported file type
|
|
($type == 'gif' || $type == 'png' || $type == 'jpg')
|
|
|
|
) {
|
|
|
|
// get file's current name
|
|
$current_file_name = substr($this->file_upload[$control]['file_name'], 0, strrpos($this->file_upload[$control]['file_name'], '.'));
|
|
|
|
// get file's current extension
|
|
$current_file_extension = strtolower(substr($this->file_upload[$control]['file_name'], strrpos($this->file_upload[$control]['file_name'], '.') + 1));
|
|
|
|
// if extension is a variation of "jpeg", revert to default "jpg"
|
|
if ($current_file_extension == 'jpeg') $current_file_extension = 'jpg';
|
|
|
|
// if new extension is different than the file's current extension
|
|
if ($type != $current_file_extension) {
|
|
|
|
// if no overwrite and a file with the same name as the converted file already exists
|
|
if (!$overwrite && is_file($this->file_upload[$control]['path'] . $current_file_name . '.' . $type)) {
|
|
|
|
$suffix = '';
|
|
|
|
// knowing the suffix...
|
|
// loop as long as
|
|
while (
|
|
|
|
// a file with the same name exists in the upload folder
|
|
// (file_exists returns also TRUE if a folder with that name exists)
|
|
is_file($this->file_upload[$control]['path'] . $current_file_name . $suffix . '.' . $type)
|
|
|
|
)
|
|
|
|
// if no suffix was yet set
|
|
if ($suffix === '')
|
|
|
|
// start the suffix like this
|
|
$suffix = '_1';
|
|
|
|
// if suffix was already initialized
|
|
else {
|
|
|
|
// drop the "_" from the suffix
|
|
$suffix = str_replace('_', '', $suffix);
|
|
|
|
// increment the suffix
|
|
$suffix = '_' . ++$suffix;
|
|
|
|
}
|
|
|
|
// the final file name
|
|
$current_file_name = $current_file_name . $suffix;
|
|
|
|
}
|
|
|
|
// if the image transformation class was not already instantiated
|
|
if (!isset($this->Zebra_Image))
|
|
|
|
// create a new instance of the image transformation class
|
|
$this->Zebra_Image = new Zebra_Image();
|
|
|
|
// set the source file
|
|
$this->Zebra_Image->source_path = $this->file_upload[$control]['path'] . $this->file_upload[$control]['file_name'];
|
|
|
|
// set the target file
|
|
$this->Zebra_Image->target_path = $this->file_upload[$control]['path'] . $current_file_name . '.' . $type;
|
|
|
|
// set the quality of the output image (better quality means bigger file size)
|
|
// available only for jpeg files; ignored for other image types
|
|
$this->Zebra_Image->jpeg_quality = $jpeg_quality;
|
|
|
|
// if there was an error when resizing the image, return false
|
|
if (!$this->Zebra_Image->resize(0, 0)) return false;
|
|
|
|
// update entries in the file_upload property
|
|
|
|
// get the size of the new file
|
|
$this->file_upload[$control]['size'] = filesize($this->Zebra_Image->target_path);
|
|
|
|
// update the file name (the file was converted and has a new extension)
|
|
$this->file_upload[$control]['file_name'] = $current_file_name . '.' . $type;
|
|
|
|
// get some info about the new file
|
|
$imageinfo = @getimagesize($this->Zebra_Image->target_path);
|
|
|
|
// rename some of the attributes returned by getimagesize
|
|
$imageinfo['width'] = $imageinfo[0]; unset($imageinfo[0]);
|
|
|
|
$imageinfo['height'] = $imageinfo[1]; unset($imageinfo[1]);
|
|
|
|
$imageinfo['type'] = $imageinfo[2]; unset($imageinfo[2]);
|
|
|
|
$imageinfo['html'] = $imageinfo[3]; unset($imageinfo[3]);
|
|
|
|
// append image info to the file_upload property
|
|
$this->file_upload[$control]['imageinfo'] = $imageinfo;
|
|
|
|
// update the mime type as returned by getimagesize
|
|
$this->file_upload[$control]['type'] = $imageinfo['mime'];
|
|
|
|
// if original file is not to be preserved, delete original file
|
|
if (!$preserve_original_file && (!$overwrite || $type != $current_file_extension)) @unlink($this->Zebra_Image->source_path);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// if the script gets this far, it means that everything went as planned and we return true
|
|
return true;
|
|
|
|
}
|
|
|
|
/**
|
|
* Helper method for validating select boxes. It extract all the values from an infinitely nested array and puts
|
|
* them in an uni-dimensional array so that we can check if the submitted value is allowed.
|
|
*
|
|
* @param array $array The array to transform.
|
|
*
|
|
* @return array Returns the flat array.
|
|
*
|
|
* @access private
|
|
*/
|
|
private function _extract_values($array)
|
|
{
|
|
|
|
$result = array();
|
|
|
|
// iterate through the array's values
|
|
foreach ($array as $index => $value)
|
|
|
|
// if entry is an array, flatten array recursively
|
|
if (is_array($value)) $result = array_merge($result, $this->_extract_values($value));
|
|
|
|
// otherwise, add the index to the result array
|
|
else $result[] = $index;
|
|
|
|
// return found values
|
|
return $result;
|
|
|
|
}
|
|
|
|
/**
|
|
* Load MIME types from mimes.json
|
|
*
|
|
* @return void
|
|
*
|
|
* @access private
|
|
*/
|
|
private function _load_mime_types()
|
|
{
|
|
|
|
// if file with mime types was not already loaded
|
|
if (!isset($this->form_properties['mimes'])) {
|
|
|
|
// read file into an array
|
|
$rows = file($this->form_properties['assets_server_path'] . 'mimes.json');
|
|
|
|
// convert JSON to array
|
|
// i'm aware that in PHP 5.2+ there is json_decode, but i want this library to be
|
|
// as backward compatible as possible so, since the values in mimes.json has a
|
|
// specific structure, i wrote my own decoder
|
|
$this->form_properties['mimes'] = array();
|
|
|
|
// iterate through all the rows
|
|
foreach ($rows as $row) {
|
|
|
|
// if valid row found
|
|
if (strpos($row, ':') !== false) {
|
|
|
|
// explode the string by :
|
|
$items = explode(':', $row);
|
|
|
|
// the file type (extension)
|
|
$index = trim(str_replace('"', '', $items[0]));
|
|
|
|
// if there are more mime types attached
|
|
if (strpos($items[1], '[') !== false)
|
|
|
|
// convert to array
|
|
$value = array_diff(array_map(create_function('&$value', 'return trim($value);'), explode(',', str_replace(array('[', ']', '"', '\/'), array('', '', '', '/'), $items[1]))), array(''));
|
|
|
|
// if a single mime type is attached
|
|
else
|
|
|
|
// convert to string
|
|
$value = trim(str_replace(array('"', ',', '\/'), array('', '', '/'), $items[1]));
|
|
|
|
// save entry
|
|
$this->form_properties['mimes'][$index] = $value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Resize an uploaded image
|
|
*
|
|
* This method will do nothing if the file is not a supported image file.
|
|
*
|
|
* @param string $control The file upload control's name
|
|
*
|
|
* @param string $prefix If the resized image is to be saved as a new file and the originally
|
|
* uploaded file needs to be preserved, specify a prefix to be used for the
|
|
* new file. This way, the resized image will have the same name as the
|
|
* original file but prefixed with the given value (i.e. "thumb_").
|
|
*
|
|
* Specifying an empty string as argument will instruct the script to apply
|
|
* the resizing to the uploaded image and therefore overwriting the
|
|
* originally uploaded file.
|
|
*
|
|
* @param integer $width The width to resize the image to.
|
|
*
|
|
* If set to <b>0</b>, the width will be automatically adjusted, depending
|
|
* on the value of the <b>height</b> argument so that the image preserves
|
|
* its aspect ratio.
|
|
*
|
|
* If <b>preserve_aspect_ratio</b> is set to TRUE and both this and the
|
|
* <b>height</b> arguments are values greater than <b>0</b>, the image will
|
|
* be resized to the exact required width and height and the aspect ratio
|
|
* will be preserved (see the description for the <b>method</b> argument
|
|
* below on how can this be done).
|
|
*
|
|
* If <b>preserve_aspect_ratio</b> is set to FALSE, the image will be
|
|
* resized to the required width and the aspect ratio will be ignored.
|
|
*
|
|
* If both <b>width</b> and <b>height</b> are set to <b>0</b>, a copy of
|
|
* the source image will be created (<b>jpeg_quality</b> will still apply).
|
|
*
|
|
* If either <b>width</b> or <b>height</b> are set to <b>0</b>, the script
|
|
* will consider the value of the {@link preserve_aspect_ratio} to bet set
|
|
* to TRUE regardless of its actual value!
|
|
*
|
|
* @param integer $height The height to resize the image to.
|
|
*
|
|
* If set to <b>0</b>, the height will be automatically adjusted, depending
|
|
* on the value of the <b>width</b> argument so that the image preserves
|
|
* its aspect ratio.
|
|
*
|
|
* If <b>preserve_aspect_ratio</b> is set to TRUE and both this and the
|
|
* <b>width</b> arguments are values greater than <b>0</b>, the image will
|
|
* be resized to the exact required width and height and the aspect ratio
|
|
* will be preserved (see the description for the <b>method</b> argument
|
|
* below on how can this be done).
|
|
*
|
|
* If <b>preserve_aspect_ratio</b> is set to FALSE, the image will be
|
|
* resized to the required height and the aspect ratio will be ignored.
|
|
*
|
|
* If both <b>height</b> and <b>width</b> are set to <b>0</b>, a copy of
|
|
* the source image will be created (<b>jpeg_quality</b> will still apply).
|
|
*
|
|
* If either <b>height</b> or <b>width</b> are set to <b>0</b>, the script
|
|
* will consider the value of the {@link preserve_aspect_ratio} to bet set
|
|
* to TRUE regardless of its actual value!
|
|
*
|
|
* @param boolean $preserve_aspect_ratio (Optional) If set to TRUE, the image will be resized to the given width
|
|
* and height and the aspect ratio will be preserved.
|
|
*
|
|
* Set this to FALSE if you want the image forcefully resized to the exact
|
|
* dimensions given by width and height ignoring the aspect ratio
|
|
*
|
|
* Default is TRUE.
|
|
*
|
|
* @param int $method (Optional) Method to use when resizing images to exact width and height
|
|
* while preserving aspect ratio.
|
|
*
|
|
* If the $preserve_aspect_ratio property is set to TRUE and both the
|
|
* <b>width</b> and <b>height</b> arguments are values greater than <b>0</b>,
|
|
* the image will be resized to the exact given width and height and the
|
|
* aspect ratio will be preserved by using on of the following methods:
|
|
*
|
|
* - <b>ZEBRA_IMAGE_BOXED</b> - the image will be scalled so that it will
|
|
* fit in a box with the given width and height (both width/height will
|
|
* be smaller or equal to the required width/height) and then it will
|
|
* be centered both horizontally and vertically. The blank area will be
|
|
* filled with the color specified by the <b>$background_color</b>
|
|
* argument. (the blank area will be filled only if the image is not
|
|
* transparent!)
|
|
*
|
|
* - <b>ZEBRA_IMAGE_NOT_BOXED</b> - the image will be scalled so that it
|
|
* <i>could</i> fit in a box with the given width and height but will
|
|
* not be enclosed in a box with given width and height. The new width/
|
|
* height will be both smaller or equal to the required width/height
|
|
*
|
|
* - <b>ZEBRA_IMAGE_CROP_TOPLEFT</b>
|
|
* - <b>ZEBRA_IMAGE_CROP_TOPCENTER</b>
|
|
* - <b>ZEBRA_IMAGE_CROP_TOPRIGHT</b>
|
|
* - <b>ZEBRA_IMAGE_CROP_MIDDLELEFT</b>
|
|
* - <b>ZEBRA_IMAGE_CROP_CENTER</b>
|
|
* - <b>ZEBRA_IMAGE_CROP_MIDDLERIGHT</b>
|
|
* - <b>ZEBRA_IMAGE_CROP_BOTTOMLEFT</b>
|
|
* - <b>ZEBRA_IMAGE_CROP_BOTTOMCENTER</b>
|
|
* - <b>ZEBRA_IMAGE_CROP_BOTTOMRIGHT</b>
|
|
*
|
|
* For the methods involving crop, first the image is scaled so that both
|
|
* its sides are equal or greater than the respective sizes of the bounding
|
|
* box; next, a region of required width and height will be cropped from
|
|
* indicated region of the resulted image.
|
|
*
|
|
* Default is ZEBRA_IMAGE_BOXED
|
|
*
|
|
* @param boolean $background_color (Optional) The hexadecimal color of the blank area (without the #).
|
|
* See the <b>method</b> argument.
|
|
*
|
|
* Default is 'FFFFFF'
|
|
*
|
|
* @param boolean $enlarge_smaller_images (Optional) If set to FALSE, images having both width and height smaller
|
|
* than the required width and height, will be left untouched ({@link jpeg_quality}
|
|
* will still apply).
|
|
*
|
|
* Default is TRUE
|
|
*
|
|
* @param boolean $quality (Optional) Indicates the quality of the output image (better quality
|
|
* means bigger file size).
|
|
*
|
|
* Range is 0 - 100
|
|
*
|
|
* Available only for JPEG files.
|
|
*
|
|
* Default is 85
|
|
*
|
|
* @return boolean Returns TRUE on success or FALSE otherwise
|
|
*
|
|
* @access private
|
|
*/
|
|
private function _resize($control, $prefix, $width, $height, $preserve_aspect_ratio = true, $method = ZEBRA_IMAGE_BOXED, $background_color = 'FFFFFF', $enlarge_smaller_images = true, $jpeg_quality = 85)
|
|
{
|
|
|
|
// if
|
|
if (
|
|
|
|
// file was uploaded
|
|
isset($this->file_upload[$control]) &&
|
|
|
|
// and file is indeed an image file
|
|
isset($this->file_upload[$control]['imageinfo'])
|
|
|
|
) {
|
|
|
|
// if the image transformation class was not already instantiated
|
|
if (!isset($this->Zebra_Image))
|
|
|
|
// create a new instance of the image transformation class
|
|
$this->Zebra_Image = new Zebra_Image();
|
|
|
|
// set the file permissions as per Zebra_Form's settings
|
|
$this->Zebra_Image->chmod_value = $this->file_upload_permissions;
|
|
|
|
// set the source file
|
|
$this->Zebra_Image->source_path = $this->file_upload[$control]['path'] . $this->file_upload[$control]['file_name'];
|
|
|
|
// set the target file
|
|
$this->Zebra_Image->target_path = $this->file_upload[$control]['path'] . trim($prefix) . $this->file_upload[$control]['file_name'];
|
|
|
|
// set whether aspect ratio should be maintained or not
|
|
$this->Zebra_Image->maintain_ratio = $preserve_aspect_ratio;
|
|
|
|
// set the quality of the output image (better quality means bigger file size)
|
|
// available only for jpeg files; ignored for other image types
|
|
$this->Zebra_Image->jpeg_quality = $jpeg_quality;
|
|
|
|
// should smaller images be enlarged?
|
|
$this->Zebra_Image->enlarge_smaller_images = $enlarge_smaller_images;
|
|
|
|
// if there was an error when resizing the image, return false
|
|
if (!$this->Zebra_Image->resize($width, $height, $method, $background_color)) return false;
|
|
|
|
}
|
|
|
|
// if the script gets this far, it means that everything went as planned and we return true
|
|
return true;
|
|
|
|
}
|
|
|
|
/**
|
|
* Checks if all the conditions set by the "dependencies" rule are met or not.
|
|
*
|
|
* @param string $id The ID of the element to check.
|
|
*
|
|
* @param array $referer (Private) Used by the library to prevent entering an infinite loop of dependencies.
|
|
*
|
|
* @return boolean Returns TRUE if all the conditions are met or FALSE otherwise.
|
|
*
|
|
* @access private
|
|
*/
|
|
private function _validate_dependencies($id, $referer = array())
|
|
{
|
|
|
|
// reference to the form submission method
|
|
global ${'_' . $this->form_properties['method']};
|
|
|
|
$method = & ${'_' . $this->form_properties['method']};
|
|
|
|
// if the rule is applied to a radio button group or a checkbox group
|
|
// there will be no entry with the given id as all group's elements will have their ID in the form of [name]_[value]
|
|
if (!isset($this->controls[$id]))
|
|
|
|
// ...therefore, we have to iterate over all the form's controls
|
|
foreach ($this->controls as $control)
|
|
|
|
// and if we find the control we're looking for
|
|
if (preg_replace('/\[\]$/', '', $control->attributes['name']) == $id) {
|
|
|
|
// get the ID of the control
|
|
$id = $control->attributes['id'];
|
|
|
|
// don't look any further
|
|
break;
|
|
|
|
}
|
|
|
|
// if there are more than 2 entries in the referer array, remove the first one
|
|
if (count($referer) > 2) array_shift($referer);
|
|
|
|
// if current element is the referer array
|
|
if (in_array($id, $referer))
|
|
|
|
// we're having a recursion and we're stopping execution
|
|
_zebra_form_show_error('Infinite recursion detected. The loop of dependencies is created by the following elements: "' . implode('", "', $referer) . '"', E_USER_ERROR);
|
|
|
|
// add current element to the stack
|
|
array_push($referer, $id);
|
|
|
|
$result = true;
|
|
|
|
// if the control exists
|
|
if (isset($this->controls[$id])) {
|
|
|
|
// if we're checking if a proxy depends on another proxy, but it doesn't, return TRUE now
|
|
if (!isset($this->controls[$id]->rules['dependencies'])) return true;
|
|
|
|
// get all the conditions needed to validate the element
|
|
$conditions = $this->controls[$id]->rules['dependencies'];
|
|
|
|
// if the name of a callback function is also given
|
|
// the actual conditions are in the first entry of the array
|
|
if (isset($conditions[1])) $conditions = $conditions[0];
|
|
|
|
// iterate through the elements the validation of the current element depends on (proxies)
|
|
foreach ($conditions as $proxy => $required_values) {
|
|
|
|
// if we have a cached result of the result
|
|
if (isset($this->proxies_cache[$proxy][serialize($required_values)]))
|
|
|
|
// get the result from cache
|
|
$result = $this->proxies_cache[$proxy][serialize($required_values)];
|
|
|
|
// if we don't have a cached result of the result
|
|
else {
|
|
|
|
$is_radio_or_checkbox = false;
|
|
|
|
// $this->controls[$proxy] will never be set for radio or checkbox controls (as $proxy is the ID, not the name)
|
|
if (!isset($this->controls[$proxy]))
|
|
|
|
// iterate through all the controls
|
|
foreach ($this->controls as $control_properties)
|
|
|
|
// if we found a control radio/checkbox element with the sought name
|
|
if ($control_properties->attributes['name'] == $proxy && ($control_properties->attributes['type'] == 'radio' || $control_properties->attributes['name'] == 'checkbox')) {
|
|
|
|
// set this flag
|
|
$is_radio_or_checkbox = true;
|
|
|
|
// don't look further
|
|
break;
|
|
|
|
}
|
|
|
|
$found = false;
|
|
|
|
// a proxy may also depend on the values of or or more other proxies
|
|
// therefore, continue only if those conditions are met
|
|
if (
|
|
(isset($this->controls[$proxy]) || $is_radio_or_checkbox) &&
|
|
((!$is_radio_or_checkbox && $this->controls[$proxy]->attributes['type'] == 'image' && isset($method[$proxy . '_x']) && isset($method[$proxy . '_y'])) || isset($method[$proxy])) &&
|
|
$this->_validate_dependencies($proxy, $referer)
|
|
) {
|
|
|
|
// if proxy is a submit or an image submit button
|
|
if (!$is_radio_or_checkbox && in_array($this->controls[$proxy]->attributes['type'], array('image', 'submit'))) $current_values = array('click');
|
|
|
|
// otherwise, get the proxy's current value/values
|
|
// (we'll treat the values as an array even if there's only a single value)
|
|
else $current_values = !is_array($method[$proxy]) ? array($method[$proxy]) : $method[$proxy];
|
|
|
|
// if condition is not an array
|
|
if (!is_array($required_values)) {
|
|
|
|
// iterate through the proxy's values
|
|
// (remember, we store it as an array even if there's a single value)
|
|
foreach ($current_values as $current_value)
|
|
|
|
// if the value of the condition is amongst the proxy's values, flag it
|
|
if ($current_value == $required_values) $found = true;
|
|
|
|
// if condition is given as an array
|
|
} else {
|
|
|
|
// iterate through all the conditions
|
|
foreach ($required_values as $required_value) {
|
|
|
|
$matches = 0;
|
|
|
|
// iterate through the values of the proxy element
|
|
// (remember, we store it as an array even if there's a single value)
|
|
foreach ($current_values as $current_value) {
|
|
|
|
// if current entry in the conditions list is not an array
|
|
// and its value is equal to the current value
|
|
if (!is_array($required_value) && $current_value == $required_value) $found = true;
|
|
|
|
// if current entry in the conditions list is an array
|
|
// and the current value is part of that array
|
|
else if (is_array($required_value) && in_array($current_value, $required_value)) $matches++;
|
|
|
|
}
|
|
|
|
// if all conditions are met
|
|
if (!$found && $matches == count($required_values)) $result = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// if not all conditions are met, don't check any further
|
|
if (!$found) { $result = false; break; }
|
|
|
|
// if proxy is not submitted, or proxy's dependendecies are not ok, don't check the other conditions
|
|
} else $result = false;
|
|
|
|
}
|
|
|
|
// cache the result
|
|
if (!isset($this->proxies_cache[$proxy][serialize($required_values)]))
|
|
|
|
$this->proxies_cache[$proxy][serialize($required_values)] = $result;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// if script gets this far, consider all the conditions are met, and validate the other rules
|
|
return $result;
|
|
|
|
}
|
|
|
|
/**
|
|
* Uploads a file
|
|
*
|
|
* @param string $control The file upload control's name
|
|
*
|
|
* @param string $upload_path The path where the file to be uploaded to
|
|
*
|
|
* The path is relative to the script containing the form, unless the path
|
|
* starts with "/" when it is relative to the
|
|
* {@link http://php.net/manual/en/reserved.variables.server.php DOCUMENT_ROOT}.
|
|
*
|
|
* - <b>uploads</b> would upload the files in the "upload" folder, at the
|
|
* path with the script
|
|
* - <b>../uploads</b> would upload the files in the "upload" folder, one
|
|
* level up relative to the script's path
|
|
* - <b>/uploads</b> would upload the files in the "upload" folder, in the
|
|
* DOCUMENT_ROOT
|
|
*
|
|
* @param boolean $filename (Optional) Specifies whether the uploaded file's original name should be
|
|
* preserved, should it be prefixed with a string, or should it be randomly
|
|
* generated.
|
|
*
|
|
* Possible values can be
|
|
*
|
|
* - TRUE - the uploaded file's original name will be preserved;
|
|
* - FALSE (or, for better code readability, you should use the "ZEBRA_FORM_UPLOAD_RANDOM_NAMES"
|
|
* constant instead of "FALSE")- the uploaded file will have a randomly generated name;
|
|
* - a string - the uploaded file's original name will be preserved but
|
|
* it will be prefixed with the given string (i.e. "original_", or "tmp_")
|
|
*
|
|
* Note that when set to TRUE or a string, a suffix of "_n" (where n is an
|
|
* integer) will be appended to the file name if a file with the same name
|
|
* already exists at the given path.
|
|
*
|
|
* Default is TRUE
|
|
*
|
|
* @return boolean Returns TRUE on success or FALSE otherwise
|
|
*
|
|
* @access private
|
|
*/
|
|
private function _upload($control, $upload_path, $filename = true)
|
|
{
|
|
|
|
// trim trailing slash from folder
|
|
$path = rtrim($upload_path, '\\/');
|
|
|
|
// if upload folder does not have a trailing slash, add the trailing slash
|
|
$path = $path . (substr($path, -1) != DIRECTORY_SEPARATOR ? DIRECTORY_SEPARATOR : '');
|
|
|
|
// if
|
|
if (
|
|
|
|
// the file upload control with the given name exists
|
|
isset($_FILES[$control]) &&
|
|
|
|
// file is ready to be uploaded
|
|
$_FILES[$control]['error'] == 0 &&
|
|
|
|
// the upload folder exists
|
|
is_dir($path)
|
|
|
|
) {
|
|
|
|
// if file names should be random
|
|
if ($filename === ZEBRA_FORM_UPLOAD_RANDOM_NAMES)
|
|
|
|
// generate a random name for the file we're about to upload
|
|
$file_name = md5(mt_rand() . microtime() . $_FILES[$control]['name']) . (strrpos($_FILES[$control]['name'], '.') !== false ? substr($_FILES[$control]['name'], strrpos($_FILES[$control]['name'], '.')) : '');
|
|
|
|
// if file names are to be preserved
|
|
else {
|
|
|
|
// if the file we are about to upload does have an extension
|
|
if (strrpos($_FILES[$control]['name'], '.') !== false) {
|
|
|
|
// split the file name into "file name"...
|
|
$file_name = substr($_FILES[$control]['name'], 0, strrpos($_FILES[$control]['name'], '.'));
|
|
|
|
// ...and "file extension"
|
|
$file_extension = substr($_FILES[$control]['name'], strrpos($_FILES[$control]['name'], '.'));
|
|
|
|
// if the file we are about to upload does not have an extension
|
|
} else {
|
|
|
|
// the file name will be the actual file name...
|
|
$file_name = $_FILES[$control]['name'];
|
|
|
|
// ...while the extension will be an empty string
|
|
$file_extension = '';
|
|
|
|
}
|
|
|
|
// prefix the file name if required
|
|
$file_name = ($filename !== true ? $filename : '') . $file_name;
|
|
|
|
$suffix = '';
|
|
|
|
// knowing the suffix...
|
|
// loop as long as
|
|
while (
|
|
|
|
// a file with the same name exists in the upload folder
|
|
// (file_exists returns also TRUE if a folder with that name exists)
|
|
is_file($path . $file_name . $suffix . $file_extension)
|
|
|
|
) {
|
|
|
|
// if no suffix was yet set
|
|
if ($suffix === '')
|
|
|
|
// start the suffix like this
|
|
$suffix = '_1';
|
|
|
|
// if suffix was already initialized
|
|
else {
|
|
|
|
// drop the "_" from the suffix
|
|
$suffix = str_replace('_', '', $suffix);
|
|
|
|
// increment the suffix
|
|
$suffix = '_' . ++$suffix;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// the final file name
|
|
$file_name = $file_name . $suffix . $file_extension;
|
|
|
|
}
|
|
|
|
// if file could be uploaded
|
|
if (@move_uploaded_file($_FILES[$control]['tmp_name'], $path . $file_name)) {
|
|
|
|
// get a list of functions disabled via configuration
|
|
$disabled_functions = @ini_get('disable_functions');
|
|
|
|
// if the 'chmod' function is not disabled via configuration
|
|
if ($disabled_functions != '' && strpos('chmod', $disabled_functions) === false)
|
|
|
|
// chmod the file
|
|
chmod($path . $file_name, intval($this->file_upload_permissions, 8));
|
|
|
|
// set a special property
|
|
// the value of the property will be an array will information about the uploaded file
|
|
$this->file_upload[$control] = $_FILES[$control];
|
|
|
|
$this->file_upload[$control]['path'] = rtrim($upload_path, '/') . '/';
|
|
|
|
$this->file_upload[$control]['file_name'] = $file_name;
|
|
|
|
// if uploaded file is an image
|
|
if ($imageinfo = @getimagesize($path . $this->file_upload[$control]['file_name'])) {
|
|
|
|
// rename some of the attributes returned by getimagesize
|
|
$imageinfo['width'] = $imageinfo[0]; unset($imageinfo[0]);
|
|
|
|
$imageinfo['height'] = $imageinfo[1]; unset($imageinfo[1]);
|
|
|
|
$imageinfo['type'] = $imageinfo[2]; unset($imageinfo[2]);
|
|
|
|
$imageinfo['html'] = $imageinfo[3]; unset($imageinfo[3]);
|
|
|
|
// append image info to the file_upload property
|
|
$this->file_upload[$control]['imageinfo'] = $imageinfo;
|
|
|
|
}
|
|
|
|
// return true, as everything went as planned
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// if script gets this far, return false as something must've gone wrong
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* A custom function for showing error messages in the Zebra_Form's environment.
|
|
*
|
|
* We need this so we show correct file/line number information when reporting errors as PHP's trigger_error() shows the
|
|
* file and the line number where the function is called and it is not what we need here (we always trigger the errors
|
|
* from one of the Zebra_Form's file but the errors come from user files).
|
|
*
|
|
* I didn't use a custom error handler so I don't interfere with the one you might be using.
|
|
*
|
|
* @param string $message The message to be shown to the user.
|
|
*
|
|
* @param mixed $type Severity of the error message.
|
|
*
|
|
* Can be E_USER_ERROR, E_USER_NOTICE or E_USER_WARNING.
|
|
*
|
|
* When set to E_USER_ERROR the execution of the script will be halted after the error
|
|
* message is displayed.
|
|
*
|
|
* @return void
|
|
*
|
|
* @access private
|
|
*/
|
|
function _zebra_form_show_error($message, $type)
|
|
{
|
|
|
|
// if error reporting is on
|
|
if (($type & error_reporting()) == $type) {
|
|
|
|
// get backtrace information
|
|
$backtraceInfo = debug_backtrace();
|
|
|
|
// this is where the error actually occurred
|
|
// (produces a "Strict Standards" warning unless muted)
|
|
$errorInfo = @array_pop(array_slice($backtraceInfo, 2, 1));
|
|
|
|
// show error message
|
|
echo '<br><strong>' . ($type == E_USER_WARNING ? 'Warning' : ($type == E_USER_NOTICE ? 'Notice' : 'Fatal error')) . '</strong>:<br><br>' . $message . (isset($errorInfo['file']) ? '<br><br>in <strong>' . basename($errorInfo['file']) . '</strong> on line <strong>' . $errorInfo['line'] . '</strong>' : '') . '<br>';
|
|
|
|
// die if necessary
|
|
if ($type == E_USER_ERROR) die();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
?>
|