2017-05-26 11:41:26 +02:00

2107 lines
86 KiB
PHP

<?php
/**
* A generic class containing common methods, shared by all the controls.
*
* @author Stefan Gabos <contact@stefangabos.ro>
* @copyright (c) 2006 - 2016 Stefan Gabos
* @package Generic
*/
class Zebra_Form_Control extends XSS_Clean
{
/**
* Array of HTML attributes of the element
*
* @var array
*
* @access private
*/
public $attributes;
/**
* Array of HTML attributes that the control's {@link render_attributes()} method should skip
*
* @var array
*
* @access private
*/
public $private_attributes;
/**
* Array of validation rules set for the control
*
* @var array
*
* @access private
*/
public $rules;
/**
* Constructor of the class
*
* @return void
*
* @access private
*/
function __construct()
{
$this->attributes = array(
'locked' => false,
'disable_xss_filters' => false,
);
$this->private_attributes = array();
$this->rules = array();
}
/**
* Call this method to instruct the script to force all letters typed by the user, to either uppercase or lowercase,
* in real-time.
*
* Works only on {@link Zebra_Form_Text text}, {@link Zebra_Form_Textarea textarea} and
* {@link Zebra_Form_Password password} controls.
*
* <code>
* // create a new form
* $form = new Zebra_Form('my_form');
*
* // add a text control to the form
* $obj = $form->add('text', 'my_text');
*
* // entered characters will be upper-case
* $obj->change_case('upper');
*
* // 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 $case The case to convert all entered characters to.
*
* Can be (case-insensitive) "upper" or "lower".
*
* @since 2.8
*
* @return void
*/
function change_case($case)
{
// make sure the argument is lowercase
$case = strtolower($case);
// if valid case specified
if ($case == 'upper' || $case == 'lower')
// add an extra class to the element
$this->set_attributes(array('class' => 'modifier-' . $case . 'case'), false);
}
/**
* Disables the SPAM filter for the control.
*
* By default, for checkboxes, radio buttons and select boxes, the library will prevent the submission of other
* values than those declared when creating the form, by triggering the error: "SPAM attempt detected!". Therefore,
* if you plan on adding/removing values dynamically, from JavaScript, you will have to call this method to prevent
* that from happening.
*
* Works only for {@link Zebra_Form_Checkbox checkbox}, {@link Zebra_Form_Radio radio} and
* {@link Zebra_Form_Select select} controls.
*
* @return void
*/
function disable_spam_filter()
{
// set the "disable_xss_filters" private attribute of the control
$this->set_attributes(array('disable_spam_filter' => true));
}
/**
* Disables XSS filtering of the control's submitted value.
*
* By default, all submitted values are filtered for XSS (Cross Site Scripting) injections. The script will
* automatically remove possibly malicious content (event handlers, javascript code, etc). While in general this is
* the right thing to do, there may be the case where this behaviour is not wanted: for example, for a CMS where
* the WYSIWYG editor inserts JavaScript code.
*
* <code>
* // $obj is a reference to a control
* $obj->disable_xss_filters();
* </code>
*
* @return void
*/
function disable_xss_filters()
{
// set the "disable_xss_filters" private attribute of the control
$this->set_attributes(array('disable_xss_filters' => true));
}
/**
* Returns the values of requested attributes.
*
* <code>
* // create a new form
* $form = new Zebra_Form('my_form');
*
* // add a text field to the form
* $obj = $form->add('text', 'my_text');
*
* // set some attributes for the text field
* $obj->set_attributes(array(
* 'readonly' => 'readonly',
* 'style' => 'font-size:20px',
* ));
*
* // retrieve the attributes
* $attributes = $obj->get_attributes(array('readonly', 'style'));
*
* // the result will be an associative array
* //
* // $attributes = Array(
* // [readonly] => "readonly",
* // [style] => "font-size:20px"
* // )
* </code>
*
* @param mixed $attributes A single or an array of attributes for which the values to be returned.
*
* @return array Returns an associative array where keys are the attributes and the values are
* each attribute's value, respectively.
*/
function get_attributes($attributes)
{
// initialize the array that will be returned
$result = array();
// if the request was for a single attribute,
// treat it as an array of attributes
if (!is_array($attributes)) $attributes = array($attributes);
// iterate through the array of attributes to look for
foreach ($attributes as $attribute)
// if attribute exists
if (array_key_exists($attribute, $this->attributes))
// populate the $result array
$result[$attribute] = $this->attributes[$attribute];
// return the results
return $result;
}
/**
* Returns the control's value <b>after</b> the form is submitted.
*
* <i>This method is automatically called by the form's {@link Zebra_Form::validate() validate()} method!</i>
*
* <code>
* // $obj is a reference to a control
* $obj->get_submitted_value();
* </code>
*
* @return void
*
* @access private
*/
function get_submitted_value()
{
// get some attributes of the control
$attribute = $this->get_attributes(array('name', 'type', 'value', 'disable_xss_filters', 'locked'));
// if control's value is not locked to the default value
if ($attribute['locked'] !== true) {
// strip any [] from the control's name (usually used in conjunction with multi-select select boxes and
// checkboxes)
$attribute['name'] = preg_replace('/\[\]/', '', $attribute['name']);
// reference to the form submission method
global ${'_' . $this->form_properties['method']};
$method = & ${'_' . $this->form_properties['method']};
// if form was submitted
if (
isset($method[$this->form_properties['identifier']]) &&
$method[$this->form_properties['identifier']] == $this->form_properties['name']
) {
// if control is a time picker control
if ($attribute['type'] == 'time') {
// combine hour, minutes and seconds into one single string (values separated by :)
// hours
$combined = (isset($method[$attribute['name'] . '_hours']) ? $method[$attribute['name'] . '_hours'] : '');
// minutes
$combined .= (isset($method[$attribute['name'] . '_minutes']) ? ($combined != '' ? ':' : '') . $method[$attribute['name'] . '_minutes'] : '');
// seconds
$combined .= (isset($method[$attribute['name'] . '_seconds']) ? ($combined != '' ? ':' : '') . $method[$attribute['name'] . '_seconds'] : '');
// AM/PM
$combined .= (isset($method[$attribute['name'] . '_ampm']) ? ($combined != '' ? ' ' : '') . $method[$attribute['name'] . '_ampm'] : '');
// create a super global having the name of our time picker control
// (remember, we don't have a control with the time picker's control name but three other controls
// having the time picker's control name as prefix and _hours, _minutes and _seconds respectively
// as suffix)
// we need to do this so that the values will also be filtered for XSS injection
$method[$attribute['name']] = $combined;
// unset the three temporary fields as we want to return to the user the result in a single field
// having the name he supplied
unset($method[$attribute['name'] . '_hours']);
unset($method[$attribute['name'] . '_minutes']);
unset($method[$attribute['name'] . '_seconds']);
unset($method[$attribute['name'] . '_ampm']);
}
// if control was submitted
if (isset($method[$attribute['name']])) {
// create the submitted_value property for the control and
// assign to it the submitted value of the control
$this->submitted_value = $method[$attribute['name']];
// if submitted value is an array
if (is_array($this->submitted_value)) {
// iterate through the submitted values
foreach ($this->submitted_value as $key => $value)
// and also, if magic_quotes_gpc is on (meaning that
// both single and double quotes are escaped)
// strip those slashes
if (get_magic_quotes_gpc()) $this->submitted_value[$key] = stripslashes($value);
// if submitted value is not an array
} else
// and also, if magic_quotes_gpc is on (meaning that both
// single and double quotes are escaped)
// strip those slashes
if (get_magic_quotes_gpc()) $this->submitted_value = stripslashes($this->submitted_value);
// if submitted value is an array
if (is_array($this->submitted_value))
// iterate through the submitted values
foreach ($this->submitted_value as $key => $value)
// filter the control's value for XSS injection and/or convert applicable characters to their equivalent HTML entities
$this->submitted_value[$key] = htmlspecialchars(!$attribute['disable_xss_filters'] ? $this->sanitize($value) : $value);
// if submitted value is not an array, filter the control's value for XSS injection and/or convert applicable characters to their equivalent HTML entities
else {
// don't apply htmlspecialchars to URLs and don't use rawurldecode neither
if (isset($this->rules['url'])) $this->submitted_value = (!$attribute['disable_xss_filters'] ? $this->sanitize($this->submitted_value, false) : $this->submitted_value);
// for all other values
else $this->submitted_value = htmlspecialchars(!$attribute['disable_xss_filters'] ? $this->sanitize($this->submitted_value) : $this->submitted_value);
}
// set the respective $_POST/$_GET value to the filtered value
$method[$attribute['name']] = $this->submitted_value;
// if control is a file upload control and a file was indeed uploaded
} elseif ($attribute['type'] == 'file' && isset($_FILES[$attribute['name']]))
$this->submitted_value = true;
// if control was not submitted
// we set this for those controls that are not submitted even
// when the form they reside in is (i.e. unchecked checkboxes)
// so that we know that they were indeed submitted but they
// just don't have a value
else $this->submitted_value = false;
if (
//if type is password, textarea or text OR
($attribute['type'] == 'password' || $attribute['type'] == 'textarea' || $attribute['type'] == 'text') &&
// control has the "uppercase" or "lowercase" modifier set
preg_match('/\bmodifier\-uppercase\b|\bmodifier\-lowercase\b/i', $this->attributes['class'], $modifiers)
) {
// if string must be uppercase, update the value accordingly
if ($modifiers[0] == 'modifier-uppercase') $this->submitted_value = strtoupper($this->submitted_value);
// otherwise, string needs to be lowercase
else $this->submitted_value = strtolower($this->submitted_value);
// set the respective $_POST/$_GET value to the updated value
$method[$attribute['name']] = $this->submitted_value;
}
}
// if control was submitted
if (isset($this->submitted_value)) {
// the assignment of the submitted value is type dependant
switch ($attribute['type']) {
// if control is a checkbox
case 'checkbox':
if (
(
// if is submitted value is an array
is_array($this->submitted_value) &&
// and the checkbox's value is in the array
in_array($attribute['value'], $this->submitted_value)
// OR
) ||
// assume submitted value is not an array and the
// checkbox's value is the same as the submitted value
$attribute['value'] == $this->submitted_value
// set the "checked" attribute of the control
) $this->set_attributes(array('checked' => 'checked'));
// if checkbox was "submitted" as not checked
// and if control's default state is checked, uncheck it
elseif (isset($this->attributes['checked'])) unset($this->attributes['checked']);
break;
// if control is a radio button
case 'radio':
if (
// if the radio button's value is the same as the
// submitted value
($attribute['value'] == $this->submitted_value)
// set the "checked" attribute of the control
) $this->set_attributes(array('checked' => 'checked'));
break;
// if control is a select box
case 'select':
// set the "value" private attribute of the control
// the attribute will be handled by the
// Zebra_Form_Select::_render_attributes() method
$this->set_attributes(array('value' => $this->submitted_value));
break;
// if control is a file upload control, a hidden control, a password field, a text field or a textarea control
case 'file':
case 'hidden':
case 'password':
case 'text':
case 'textarea':
case 'time':
// set the "value" standard HTML attribute of the control
$this->set_attributes(array('value' => $this->submitted_value));
break;
}
}
}
}
/**
* Locks the control's value. A <i>locked</i> control will preserve its <b>default</b> value after the form is submitted
* even if the user altered it.
*
* <i>This doesn't mean that the submitted value will be the default one! It will still be the one selected by the
* user, but when and if the form is repainted, the value shown in the control will be the locked one, not the one
* selected by the user</i>
*
* <code>
* // $obj is a reference to a control
* $obj->lock();
* </code>
*
* @return void
*/
function lock() {
// set the "locked" private attribute of the control
$this->set_attributes(array('locked' => true));
}
/**
* Resets the control's submitted value (empties text fields, unchecks radio buttons/checkboxes, etc).
*
* <i>This method also resets the associated POST/GET/FILES superglobals!</i>
*
* <code>
* // $obj is a reference to a control
* $obj->reset();
* </code>
*
* @return void
*/
function reset()
{
// reference to the form submission method
global ${'_' . $this->form_properties['method']};
$method = & ${'_' . $this->form_properties['method']};
// get some attributes of the control
$attributes = $this->get_attributes(array('type', 'name', 'other'));
// sanitize the control's name
$attributes['name'] = preg_replace('/\[\]/', '', $attributes['name']);
// see of what type is the current control
switch ($attributes['type']) {
// control is any of the types below
case 'checkbox':
case 'radio':
// unset the "checked" attribute
unset($this->attributes['checked']);
// unset the associated superglobal
unset($method[$attributes['name']]);
break;
// control is any of the types below
case 'date':
case 'hidden':
case 'password':
case 'select':
case 'text':
case 'textarea':
// simply empty the "value" attribute
$this->attributes['value'] = '';
// unset the associated superglobal
unset($method[$attributes['name']]);
// if control has the "other" attribute set
if (isset($attributes['other']))
// clear the associated superglobal's value
unset($method[$attributes['name'] . '_other']);
break;
// control is a file upload control
case 'file':
// unset the related superglobal
unset($_FILES[$attributes['name']]);
break;
// for any other control types
default:
// as long as control is not label, note nor captcha
if (
$attributes['type'] != 'label' &&
$attributes['type'] != 'note' &&
$attributes['type'] != 'captcha'
// unset the associated superglobal
) unset($method[$attributes['name']]);
}
}
/**
* Sets one or more of the control's attributes.
*
* <code>
* // create a new form
* $form = new Zebra_Form('my_form');
*
* // add a text field to the form
* $obj = $form->add('text', 'my_text');
*
* // set some attributes for the text field
* $obj->set_attributes(array(
* 'readonly' => 'readonly',
* 'style' => 'font-size:20px',
* ));
*
* // retrieve the attributes
* $attributes = $obj->get_attributes(array('readonly', 'style'));
*
* // the result will be an associative array
* //
* // $attributes = Array(
* // [readonly] => "readonly",
* // [style] => "font-size:20px"
* // )
* </code>
*
* @param array $attributes An associative array, in the form of <i>attribute => value</i>.
*
* @param boolean $overwrite Setting this argument to FALSE will instruct the script to append the values
* of the attributes to the already existing ones (if any) rather then overwriting
* them.
*
* Useful, for adding an extra CSS class to the already existing ones.
*
* For example, the {@link Zebra_Form_Text text} control has, by default, the
* <b>class</b> attribute set and already containing some classes needed both
* for styling and for JavaScript functionality. If there's the need to add one
* more class to the existing ones, without breaking styles nor functionality,
* one would use:
*
* <code>
* // obj is a reference to a control
* $obj->set_attributes(array('class'=>'my_class'), false);
* </code>
*
* Default is TRUE
*
* @return void
*/
function set_attributes($attributes, $overwrite = true)
{
// check if $attributes is given as an array
if (is_array($attributes))
// iterate through the given attributes array
foreach ($attributes as $attribute => $value) {
// we need to url encode the prefix as it may contain HTML entities which would produce validation errors
if ($attribute == 'data-prefix') $value = urlencode($value);
// if the value is to be appended to the already existing one
// and there is a value set for the specified attribute
// and the values do not represent an array
if (!$overwrite && isset($this->attributes[$attribute]) && !is_array($this->attributes[$attribute]))
// append the value
$this->attributes[$attribute] = $this->attributes[$attribute] . ' ' . $value;
// otherwise, add attribute to attributes array
else $this->attributes[$attribute] = $value;
}
}
/**
* Sets a single or an array of validation rules for the control.
*
* <code>
* // $obj is a reference to a control
* $obj->set_rule(array(
* 'rule #1' => array($arg1, $arg2, ... $argn),
* 'rule #2' => array($arg1, $arg2, ... $argn),
* ...
* ...
* 'rule #n' => array($arg1, $arg2, ... $argn),
* ));
* // where 'rule #1', 'rule #2', 'rule #n' are any of the rules listed below
* // and $arg1, $arg2, $argn are arguments specific to each rule
* </code>
*
* When a validation rule is not passed, a variable becomes available in the template file, having the name
* as specified by the rule's <b>error_block</b> argument and having the value as specified by the rule's
* <b>error_message</b> argument.
*
* <samp>Validation rules are checked in the given order, the exceptions being the "dependencies", "required" and
* "upload" rules, which are *always* checked in the order of priority: "dependencies" has priority over "required"
* which in turn has priority over "upload".</samp>
*
* I usually have at the top of my custom templates something like (assuming all errors are sent to an error block
* named "error"):
*
* <code>echo (isset($zf_error) ? $zf_error : (isset($error) ? $error : ''));</code>
*
* <samp>The above code nees to be used only for custom templates, or when the output is generated via callback
* functions! For automatically generated templates it is all taken care for you automatically by the library! Notice
* the $zf_error variable which is automatically created by the library if there is a SPAM or a CSRF error! Unless
* you use it, these errors will not be visible for the user. Again, remember, we're talking about custom templates,
* or output generated via callback functions.</samp>
*
* One or all error messages can be displayed in an error block.
* See the {@link Zebra_Form::show_all_error_messages() show_all_error_messages()} method.
*
* <b>Everything related to error blocks applies only for server-side validation.</b><br>
* <b>See the {@link Zebra_Form::client_side_validation() client_side_validation()} method for configuring how errors
* are to be displayed to the user upon client-side validation.</b>
*
* Available rules are
* - age
* - alphabet
* - alphanumeric
* - captcha
* - compare
* - convert
* - custom
* - date
* - datecompare
* - dependencies
* - digits
* - email
* - emails
* - filesize
* - filetype
* - float
* - image
* - length
* - number
* - range
* - regexp
* - required
* - resize
* - upload
* - url
*
* Rules description:
*
* - <b>age</b>
*
* <code>'age' => array($range, $error_block, $error_message)</code>
*
* where
*
* - <i>range</i> is an array with 2 values, representing the minimum and maximum age allowed. 0 (zero) means
* "any age". Therefore, a range of array(21, 0) would validate ages above 20, array(6, 12) would
* validate ages between 6 and 12 (inclusive), while (0, 12) would validate ages below 13
*
* - <i>error_block</i> is the PHP variable to append the error message to, in case the rule does not validate
*
* - <i>error_message</i> is the error message to be shown when rule is not obeyed
*
* Validates if the difference in years between the current date and the date entered in the control is whitin the
* allowed range
*
* Available for the following controls: {@link Zebra_Form_Text text}
*
* <code>
* // $obj is a reference to a control
* $obj->set_rule(
* 'age' => array(
* array(21, 0) // allow ages above 20
* 'error', // variable to add the error message to
* 'Age must be above 20' // error message if value doesn't validate
* )
* );
* </code>
*
* - <b>alphabet</b>
*
* <code>'alphabet' => array($additional_characters, $error_block, $error_message)</code>
*
* where
*
* - <i>additional_characters</i> is a list of additionally allowed characters besides the alphabet (provide
* an empty string if none); note that if you want to use / (backslash) you need to specify it as three (3)
* backslashes ("///")!
*
* - <i>error_block</i> is the PHP variable to append the error message to, in case the rule does not validate
*
* - <i>error_message</i> is the error message to be shown when rule is not obeyed
*
* Validates if the value contains only characters from the alphabet (case-insensitive a to z) <b>plus</b> characters
* given as additional characters (if any).
*
* Available for the following controls: {@link Zebra_Form_Password password}, {@link Zebra_Form_Text text},
* {@link Zebra_Form_Textarea textarea}
*
* <code>
* // $obj is a reference to a control
* $obj->set_rule(
* 'alphabet' => array(
* '-' // allow alphabet plus dash
* 'error', // variable to add the error message to
* 'Only alphabetic characters allowed!' // error message if value doesn't validate
* )
* );
* </code>
*
* - <b>alphanumeric</b>
*
* <code>'alphanumeric' => array($additional_characters, $error_block, $error_message)</code>
*
* where
*
* - <i>additional_characters</i> is a list of additionally allowed characters besides the alphabet and
* digits 0 to 9 (provide an empty string if none); note that if you want to use / (backslash) you need to
* specify it as three (3) backslashes ("///")!
*
* - <i>error_block</i> is the PHP variable to append the error message to, in case the rule does not validate
*
* - <i>error_message</i> is the error message to be shown when rule is not obeyed
*
* Validates if the value contains only characters from the alphabet (case-insensitive a to z) and digits (0 to 9)
* <b>plus</b> characters given as additional characters (if any).
*
* Available for the following controls: {@link Zebra_Form_Password password}, {@link Zebra_Form_Text text},
* {@link Zebra_Form_Textarea textarea}
*
* <code>
* // $obj is a reference to a control
* $obj->set_rule(
* 'alphanumeric' => array(
* '-', // allow alphabet, digits and dash
* 'error', // variable to add the error message to
* 'Only alphanumeric characters allowed!' // error message if value doesn't validate
* )
* );
* </code>
*
* - <b>captcha</b>
*
* <code>'captcha' => array($error_block, $error_message)</code>
*
* where
*
* - <i>error_block</i> is the PHP variable to append the error message to, in case the rule does not validate
*
* - <i>error_message</i> is the error message to be shown when rule is not obeyed
*
* Validates if the value matches the characters seen in the {@link Zebra_Form_Captcha captcha} image
* (therefore, there must be a {@link Zebra_Form_Captcha captcha} image on the form)
*
* Available only for the {@link Zebra_Form_Text text} control
*
* <i>This rule is not available client-side!</i>
*
* <code>
* // $obj is a reference to a control
* $obj->set_rule(
* 'captcha' => array(
* 'error', // variable to add the error message to
* 'Characters not entered correctly!' // error message if value doesn't validate
* )
* );
* </code>
*
* - <b>compare</b>
*
* <code>'compare' => array($control, $error_block, $error_message)</code>
*
* where
*
* - <i>control</i> is the name of a control on the form to compare values with
*
* - <i>error_block</i> is the PHP variable to append the error message to, in case the rule does not validate
*
* - <i>error_message</i> is the error message to be shown when rule is not obeyed
*
* Validates if the value is the same as the value of the control indicated by <i>control</i>.
*
* Useful for password confirmation.
*
* Available for the following controls: {@link Zebra_Form_Password password}, {@link Zebra_Form_Text text},
* {@link Zebra_Form_Textarea textarea}
*
* <code>
* // $obj is a reference to a control
* $obj->set_rule(
* 'compare' => array(
* 'password' // name of the control to compare values with
* 'error', // variable to add the error message to
* 'Password not confirmed correctly!' // error message if value doesn't validate
* )
* );
* </code>
*
* - <b>convert</b>
*
* <samp>This rule requires the prior inclusion of the {@link http://stefangabos.ro/php-libraries/zebra-image Zebra_Image}
* library!</samp>
*
* <code>'convert' => array($type, $jpeg_quality, $preserve_original_file, $overwrite, $error_block, $error_message)</code>
*
* where
*
* - <i>type</i> the type to convert the image to; can be (case-insensitive) JPG, PNG or GIF
*
* - <i>jpeg_quality</i>: 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".
*
* - <i>preserve_original_file</i>: Should the original file be preserved after the conversion is done?
*
* - <i>$overwrite</i>: 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.
*
* - <i>error_block</i> is the PHP variable to append the error message to, in case the rule does not validate
*
* - <i>error_message</i> is the error message to be shown when rule is not obeyed
*
* This rule will convert an image file uploaded using the <b>upload</b> rule from whatever its type (as long as is one
* of the supported types) to the type indicated by <i>type</i>.
*
* Validates if the uploaded file is an image file and <i>type</i> is valid.
*
* This is not actually a "rule", but because it can generate an error message it is included here
*
* You should use this rule in conjunction with the <b>upload</b> and <b>image</b> rules.
*
* If you are also using the <b>resize</b> rule, make sure you are using it AFTER the <b>convert</b> rule!
*
* Available only for the {@link Zebra_Form_File file} control
*
* <i>This rule is not available client-side!</i>
*
* <code>
* // $obj is a reference to a file upload control
* $obj->set_rule(
* 'convert' => array(
* 'jpg', // type to convert to
* 85, // converted file quality
* false, // preserve original file?
* false, // overwrite if converted file already exists?
* 'error', // variable to add the error message to
* 'File could not be uploaded!' // error message if value doesn't validate
* )
* );
* </code>
*
* - <b>custom</b>
*
* Using this rule, custom rules can be applied to the submitted values.
*
* <code>'custom'=>array($callback_function_name, [optional arguments to be passed to the function], $error_block, $error_message)</code>
*
* where
*
* - <i>callback_function_name</i> is the name of the callback function
*
* - <i>error_block</i> is the PHP variable to append the error message to, in case the rule does not validate
*
* - <i>error_message</i> is the error message to be shown when rule is not obeyed
*
* <i>The callback function's first argument must ALWAYS be the control's submitted value. The optional arguments to
* be passed to the callback function will start as of the second argument!</i>
*
* <i>The callback function MUST return TRUE on success or FALSE on failure!</i>
*
* Multiple custom rules can also be set through an array of callback functions:
*
* <code>
* 'custom' => array(
*
* array($callback_function_name1, [optional arguments to be passed to the function], $error_block, $error_message),
* array($callback_function_name1, [optional arguments to be passed to the function], $error_block, $error_message)
*
* )
* </code>
*
* <b>If {@link Zebra_Form::client_side_validation() client-side validation} is enabled (enabled by default), the
* custom function needs to also be available in JavaScript, with the exact same name as the function in PHP!</b>
*
* For example, here's a custom rule for checking that an entered value is an integer, greater than 21:
*
* <code>
* // the custom function in JavaScript
* <script type="text/javascript">
* function is_valid_number(value)
* {
* // return false if the value is less than 21
* if (value < 21) return false;
* // return true otherwise
* return true;
* }
* <&92;script>
* </code>
*
* <code>
* // the callback function in PHP
* function is_valid_number($value)
* {
* // return false if the value is less than 21
* if ($value < 21) return false;
* // return true otherwise
* return true;
* }
*
* // create a new form
* $form = new Zebra_Form('my_form');
*
* // add a text control to the form
* $obj = $form->add('text', 'my_text');
*
* // set two rules:
* // on that requires the value to be an integer
* // and a custom rule that requires the value to be greater than 21
* $obj->set_rule(
* 'number' => array('', 'error', 'Value must be an integer!'),
* 'custom' => array(
* 'is_valid_number',
* 'error',
* 'Value must be greater than 21!'
* )
* );
* </code>
*
* And here's how I do validations using <b>AJAX</b>:
*
* In my website's main JavaScript file I have something like:
*
* <code>
* var valid = null;
*
* // I have functions like these for everything that I need checked through AJAX; note that they are in the global
* // namespace and outside the DOM-ready event
*
* // functions have to return TRUE in order for the rule to be considered as obeyed
* function username_not_taken(username) {
* $.ajax({data: 'username=' + username});
* return valid;
* }
*
* function emailaddress_not_taken(email) {
* $.ajax({data: 'email=' + email});
* return valid;
* }
*
* // in the DOM ready event
* $(document).ready(function() {
*
* // I setup an AJAX object that will handle all my AJAX calls
* $.ajaxSetup({
* url: 'path/to/validator/', // actual work will be done in PHP
* type: 'post',
* dataType: 'text',
* async: false, // this is important!
* global: false,
* beforeSend: function() {
* valid = null;
* },
* success: function(data, textStatus) {
* if (data == 'valid') valid = true;
* else valid = false;
* }
* });
*
* // ...other JavaScript code for your website...
*
* }
* </code>
*
* I also have a "validation.php" "helper" file which contains the PHP functions that do the actual checkings. This
* file is included both in the page where I create the form (used by the server-side validation) and also by the
* file defined by the "url" property of the AJAX object (used for client-side validation). This might look something
* like:
*
* <code>
* function username_not_taken($username) {
* // check for username and return TRUE if it's NOT taken, or FALSE otherwise
* }
*
* function emailaddress_not_taken($email) {
* // check for email address and return TRUE if it's NOT taken, or FALSE otherwise
* }
* </code>
*
* As stated above, when I create a form I include this "helper" file at the top, because the functions in it will
* be used by the server-side validation, and set the custom rules like this:
*
* <code>
* $obj->set_rule(array(
* 'custom' => array(
* 'username_not_taken',
* 'error',
* 'This user name is already taken!'
* ),
* ));
* </code>
*
* ...and finally, at the "url" set in the AJAX object, I have something like:
*
* <code>
* // include the "helper" file
* require 'path/to/validation.php';
*
* if (
*
* // make sure it's an AJAX request
* isset($_SERVER['HTTP_X_REQUESTED_WITH']) &&
*
* // make sure it has a referrer
* isset($_SERVER['HTTP_REFERER']) &&
*
* // make sure it comes from your website
* strpos($_SERVER['HTTP_REFERER'], 'your/website/base/url') === 0
*
* ) {
*
* if (
*
* // i run functions depending on what's in the $_POST and also make some extra sanity checks
* (isset($_POST['username']) && count($_POST) == 1 && username_not_taken($_POST['username'])) ||
* (isset($_POST['email']) && count($_POST) == 1 && emailaddress_not_taken($_POST['email']))
*
* // if whatever I'm checking is OK, I just echo "valid"
* // (this will be later used by the AJAX object)
* ) echo 'valid';
*
* }
*
* // do nothing for any other case
*
* </code>
*
* - <b>date</b>
*
* <code>'date' => array($error_block, $error_message)</code>
*
* where
*
* - <i>error_block</i> is the PHP variable to append the error message to, in case the rule does not validate
*
* - <i>error_message</i> is the error message to be shown when rule is not obeyed
*
* Validates if the value is a propper date, formated according to the format set through the
* {@link Zebra_Form_Date::format() format()} method.
*
* Available only for the {@link Zebra_Form_Date date} control.
*
* <i>Note that the validation is language dependant: if the form's language is other than English and month names
* are expected, the script will expect the month names to be given in that particular language, as set in the
* language file!</i>
*
* <code>
* // $obj is a reference to a control
* $obj->set_rule(
* 'date' => array(
* 'error', // variable to add the error message to
* 'Invalid date!' // error message if value doesn't validate
* )
* );
* </code>
*
* - <b>datecompare</b>
*
* <code>'datecompare' => array($control, $comparison_operator, $error_block, $error_message)</code>
*
* where
*
* - <i>control</i> is the name of a date control on the form to compare values with
*
* - <i>comparison_operator</i> indicates how the value should be, compared to the value of <i>control</i>.<br>
* Possible values are <, <=, >, >=
*
* - <i>error_block</i> is the PHP variable to append the error message to, in case the rule does not validate
*
* - <i>error_message</i> is the error message to be shown when rule is not obeyed
*
* Validates if the value satisfies the comparison operator when compared to the other date control's value.
*
* Available only for the {@link Zebra_Form_Date date} control.
*
* <code>
* // $obj is a reference to a control
* $obj->set_rule(
* 'datecompare' => array(
* 'another_date' // name of another date control on the form
* '>', // comparison operator
* 'error', // variable to add the error message to
* 'Date must be after another_date!' // error message if value doesn't validate
* )
* );
* </code>
*
* - <b>dependencies</b>
*
* <code>'dependencies' => array($conditions)</code>
*
* or
*
* <code>'dependencies' => array(array($conditions), 'callback_function_name[, arguments]')</code>
*
* where
*
* - <i>$conditions</i> an array of associative arrays where the keys represent the <b>names</b> of form controls
* (remember: <i>names</i>, not IDs, and without the square brackets ([]) used for checkbox groups and multiple
* selects), while the associated value/values represent the value/values that those controls need to have in
* order for the current control to be validated. Notable exceptions are the {@link Zebra_Form_Submit submit}
* and {@link Zebra_Form_Image image} elements, where the associated value must *always* be "click".
*
* <i>Only when all conditions are met, the control's other rules will be checked!</i>
*
* - <i>callback_function_name</i> is the name of an existing JavaScript function which will be executed whenever
* the value of any of the controls listed in the "dependencies" rule changes - useful for showing/hiding controls
* that depend on the values of other controls.
*
* An element's other existing rules will be checked only if this rule is passed.
*
* Conditions can be applied to an infinite depth and will be checked accordingly - so, a control may depend on
* another control which, in turn, may depend on another control and so on, and all this will be automatically taken
* care of: say control C depends on control B having the value "1", while control B depends on control A having the
* value "2"; now, even if B has the value "1", as long as A doesn't have the value "2", control C will not be
* validated.
*
* <samp>The library will terminate exection and will trigger an error message if an infinite loop of dependencies is
* detected. Also, dependencies on non-existing elements will be ignored. And finally, this rule should only be used
* with custom templates.</samp>
*
* Available for the following controls: {@link Zebra_Form_Checkbox checkbox}, {@link Zebra_Form_Date date},
* {@link Zebra_Form_File file}, {@link Zebra_Form_Password password}, {@link Zebra_Form_Radio radio}
* {@link Zebra_Form_Select select}, {@link Zebra_Form_Text text}, {@link Zebra_Form_Textarea textarea},
* {@link Zebra_Form_Time time}.
*
* <code>
* // $obj is a reference to a control
* $obj->set_rule(array(
*
* // any other rules will be checked only
* // if all of the following conditions are met
* 'dependencies' => array(
*
* // the value of the control named "input1" is "Value 1"
* 'input1' => 'Value 1',
*
* // the value of the control named "input2" is "Value 2" OR "Value 3"
* 'input2' => array('Value 2', 'Value 3'),
*
* // the value of the control named "radio1" is "Option 1" OR "Option 2"
* 'radio1' => array('Option 1', 'Option 2'),
*
* // the value of the control named "checkbox1" (where multiple options can
* // be checked, same as for multiple selects) is
* // 'Option 1' AND 'Option 2'
* // OR
* // 'Option 4'
* // OR
* // 'Option 1' AND 'Option 3'
* 'checkbox1' => array(
* array('Option 1', 'Option 2')
* 'Option 4',
* array('Option 1', 'Option 3')
* ),
*
* // the "submit" control having the "btnsubmit" ID is clicked
* 'btnsubmit' => 'click',
*
* ),
*
* // this rule will be checked only
* // if all of the conditions above are met
* 'required' => array('error', 'Value is required!'),
* ));
* </code>
*
* Whenever any of the elements in the "dependencies" rule changes value, the library will check if all the conditions
* are met and, if a callback function is attached, will execute the callback function. The callback function's first
* argument will be the boolean result of checking if all the conditions are met. Optionally, additional comma separated
* arguments may be passed to the callback function (separated with a comma from the function name). Optional arguments
* can be used for having a single callback function instead of more, and doing different actions depending on the
* optional argument/arguments
*
* To attach a callback function, declare the "dependencies" rule like this:
*
* <code>
* // $obj is a reference to a control
* $obj->set_rule(array(
*
* // notice an array of arrays...
* 'dependencies' => array(array(
*
* // conditions
*
* ), 'callback_function[, argument]'),
*
* ));
* </code>
*
* Here are some examples of how to define the callback function in JavaScript:
*
* <code>
* // in the global scope and outside the "domready" event
* var my_callback = function(valid) {
* // all conditions are met
* if (valid) {}
* // if not all conditions are met
* else {}
* }
*
* // ...here comes the rest of your code
* $(document).ready(function() {});
*
* /* ======================================================{@*}
*
* // the same as above, again, outside the "domready" event
* function my_callback(valid) {
* // all conditions are met
* if (valid) {}
* // if not all conditions are met
* else {}
* }
*
* // ...here comes the rest of your code
* $(document).ready(function() {});
*
* /* ======================================================{@*}
*
* // create a variable in the global scope
* var my_callback;
*
* // put your function inside the "doready" event
* $(document).ready(function() {
*
* // but tied to the variable from the global scope...
* my_callback = function(valid) {
* // all conditions are met
* if (valid) {}
* // if not all conditions are met
* else {}
* }
*
* });
*
* /* ======================================================{@*}
*
* // don't pollute the global scope, use namespaces
* $(document).ready(function() {
*
* myNameSpace = {
* my_callback: function(valid) {
* // all conditions are met
* if (valid) {}
* // if not all conditions are met
* else {}
* }
* }
*
* });
*
* // in PHP, refer to the callback function like myNameSpace.my_callback
* $obj->set_rule(array(
*
* // notice an array of arrays...
* 'dependencies' => array(array(
* // conditions
* ), 'myNameSpace.my_callback'),
*
* ));
*
* </code>
*
* - <b>digits</b>
*
* <code>'digits' => array($additional_characters, $error_block, $error_message)</code>
*
* where
*
* - <i>additional_characters</i> is a list of additionally allowed characters besides digits (provide
* an empty string if none); note that if you want to use / (backslash) you need to specify it as three (3)
* backslashes ("///")!
*
* - <i>error_block</i> is the PHP variable to append the error message to, in case the rule does not validate
*
* - <i>error_message</i> is the error message to be shown when rule is not obeyed
*
* Validates if the value contains only digits (0 to 9) <b>plus</b> characters given as additional characters (if any).
*
* <b>When this rule is set, element's "type" attribute will be automatically changed to "number", making
* the element more friendly to mobile users</b>
*
* Available for the following controls: {@link Zebra_Form_Password password}, {@link Zebra_Form_Text text},
* {@link Zebra_Form_Textarea textarea}
*
* <code>
* // $obj is a reference to a control
* $obj->set_rule(
* 'digits' => array(
* '-' // allow digits and dash
* 'error', // variable to add the error message to
* 'Only digits are allowed!' // error message if value doesn't validate
* )
* );
* </code>
*
* - <b>email</b>
*
* <code>'email' => array($error_block, $error_message)</code>
*
* where
*
* - <i>error_block</i> is the PHP variable to append the error message to, in case the rule does not validate
*
* - <i>error_message</i> is the error message to be shown when rule is not obeyed
*
* Validates if the value is a properly formatted email address.
*
* <b>When this rule is set, element's "type" attribute will be automatically changed to "email", making
* the element more friendly to mobile users</b>
*
* Available for the following controls: {@link Zebra_Form_Password password}, {@link Zebra_Form_Text text},
* {@link Zebra_Form_Textarea textarea}
*
* <code>
* // $obj is a reference to a control
* $obj->set_rule(
* 'email' => array(
* 'error', // variable to add the error message to
* 'Invalid email address!' // error message if value doesn't validate
* )
* );
* </code>
*
* - <b>emails</b>
*
* <code>'emails' => array($error_block, $error_message)</code>
*
* where
*
* - <i>error_block</i> is the PHP variable to append the error message to, in case the rule does not validate
*
* - <i>error_message</i> is the error message to be shown when rule is not obeyed
*
* Validates if the value is a properly formatted email address <b>or</b> a comma separated list of properly
* formatted email addresses.
*
* Available for the following controls: {@link Zebra_Form_Password password}, {@link Zebra_Form_Text text},
* {@link Zebra_Form_Textarea textarea}
*
* <code>
* // $obj is a reference to a control
* $obj->set_rule(
* 'emails' => array(
* 'error', // variable to add the error message to
* 'Invalid email address(es)!' // error message if value doesn't validate
* )
* );
* </code>
*
* - <b>filesize</b>
*
* <code>'filesize' => array($file_size, $error_block, $error_message)</code>
*
* where
*
* - <i>file_size</i> is the allowed file size, in bytes
*
* - <i>error_block</i> is the PHP variable to append the error message to, in case the rule does not validate
*
* - <i>error_message</i> is the error message to be shown when rule is not obeyed
*
* Validates if the size (in bytes) of the uploaded file is not larger than the value (in bytes) specified by
* <i>file_size</i>.
*
* <b>Note that $file_size should be lesser or equal to the value of upload_max_filesize set in php.ini!</b>
*
* Available only for the {@link Zebra_Form_File file} control.
*
* <code>
* // $obj is a reference to a control
* $obj->set_rule(
* 'filesize' => array(
* '102400', // maximum allowed file size (in bytes)
* 'error', // variable to add the error message to
* 'File size must not exceed 100Kb!' // error message if value doesn't validate
* )
* );
* </code>
*
* - <b>filetype</b>
*
* <b>If you want to check for images use the dedicated "image" rule instead!</b>
*
* <code>'filetype' => array($file_types, $error_block, $error_message)</code>
*
* where
*
* - <i>file_types</i> is a string of comma separated file extensions representing uploadable file types
*
* - <i>error_block</i> is the PHP variable to append the error message to, in case the rule does not validate
*
* - <i>error_message</i> is the error message to be shown when rule is not obeyed
*
* Validates only if the uploaded file's MIME type matches the MIME types associated with the extensions set by
* <i>file_types</i> as defined in <i>mimes.json</i> file.
*
* Note that for PHP versions 5.3.0+, compiled with the "php_fileinfo" extension, the uploaded file's mime type is
* determined using PHP's {@link http://php.net/manual/en/function.finfo-file.php finfo_file} function; Otherwise,
* the library relies on information available in the $_FILES super-global for determining an uploaded file's MIME
* type, which, as it turns out, is determined solely by the file's extension, representing a potential security
* risk;
*
* Available only for the {@link Zebra_Form_File file} control.
*
* <code>
* // $obj is a reference to a control
* $obj->set_rule(
* 'filetype' => array(
* 'xls, xlsx' // allow only EXCEL files to be uploaded
* 'error', // variable to add the error message to
* 'Not a valid Excel file!' // error message if value doesn't validate
* )
* );
* </code>
*
* - <b>float</b>
*
* <code>'float' => array($additional_characters, $error_block, $error_message)</code>
*
* where
*
* - <i>additional_characters</i> is a list of additionally allowed characters besides digits, one dot and one
* minus sign (provide an empty string if none); note that if you want to use / (backslash) you need to specify
* it as three (3) backslashes ("///")!
*
* - <i>error_block</i> is the PHP variable to append the error message to, in case the rule does not validate
*
* - <i>error_message</i> is the error message to be shown when rule is not obeyed
*
* Validates if the value contains only digits (0 to 9) and/or <b>one</b> dot (but not as the very first character)
* and/or <b>one</b> minus sign (but only if it is the very first character) <b>plus</b> characters given as
* additional characters (if any).
*
* <b>When this rule is set, element's "type" attribute will be automatically changed to "number", making
* the element more friendly to mobile users</b>
*
* Available for the following controls: {@link Zebra_Form_Password password}, {@link Zebra_Form_Text text},
* {@link Zebra_Form_Textarea textarea}
*
* <code>
* // $obj is a reference to a control
* $obj->set_rule(
* 'float' => array(
* '' // don't allow any extra characters
* 'error', // variable to add the error message to
* 'Invalid number!' // error message if value doesn't validate
* )
* );
* </code>
*
* - <b>image</b>
*
* <code>'image' => array($error_block, $error_message)</code>
*
* where
*
* - <i>error_block</i> is the PHP variable to append the error message to, in case the rule does not validate
*
* - <i>error_message</i> is the error message to be shown when rule is not obeyed
*
*
* Validates only if the uploaded file is a valid GIF, PNG or JPEG image file.
*
* Available only for the {@link Zebra_Form_File file} control.
*
* <code>
* // $obj is a reference to a control
* $obj->set_rule(
* 'image' => array(
* 'error', // variable to add the error message to
* 'Not a valid GIF, PNG or JPEG file!' // error message if value doesn't validate
* )
* );
* </code>
*
* - <b>length</b>
*
* <code>'length' => array($minimum_length, $maximum_length, $error_block, $error_message, $show_counter)</code>
*
* where
*
* - <i>minimum_length</i> is the minimum number of characters the values should contain
*
* - <i>maximum_length</i> is the maximum number of characters the values should contain
*
* - <i>error_block</i> is the PHP variable to append the error message to, in case the rule does not validate
*
* - <i>error_message</i> is the error message to be shown when rule is not obeyed
*
* - <i>show_counter</i> if set to TRUE, a counter showing the remaining characters will be displayed along with
* the element
*
* <i>If you want to change the counter's position, do so by setting margins for the .Zebra_Character_Counter
* class in the zebra_form.css file</i>
*
*
* Validates only if the number of characters of the value is between $minimum_length and $maximum_length.
*
* If an exact length is needed, set both $minimum_length and $maximum_length to the same value.
*
* Set $maximum_length to 0 (zero) if no upper limit needs to be set for the value's length.
*
* Available for the following controls: {@link Zebra_Form_Password password}, {@link Zebra_Form_Text text},
* {@link Zebra_Form_Textarea textarea}
*
* <code>
* // $obj is a reference to a control
* $obj->set_rule(
* 'length' => array(
* 3, // minimum length
* 6, // maximum length
* 'error', // variable to add the error message to
* 'Value must have between 3 and 6 characters!' // error message if value doesn't validate
* )
* );
* </code>
*
* - <b>number</b>
*
* <code>'number' => array($additional_characters, $error_block, $error_message)</code>
*
* where
*
* - <i>additional_characters</i> is a list of additionally allowed characters besides digits and one
* minus sign (provide an empty string if none); note that if you want to use / (backslash) you need to specify
* it as three (3) backslashes ("///")!
*
* - <i>error_block</i> is the PHP variable to append the error message to, in case the rule does not validate
*
* - <i>error_message</i> is the error message to be shown when rule is not obeyed
*
* Validates if the value contains only digits (0 to 9) and/or <b>one</b> minus sign (but only if it is the very
* first character) <b>plus</b> characters given as additional characters (if any).
*
* <b>When this rule is set, element's "type" attribute will be automatically changed to "number", making
* the element more friendly to mobile users</b>
*
* Available for the following controls: {@link Zebra_Form_Password password}, {@link Zebra_Form_Text text},
* {@link Zebra_Form_Textarea textarea}
*
* <code>
* // $obj is a reference to a control
* $obj->set_rule(
* 'number' => array(
* '' // don't allow any extra characters
* 'error', // variable to add the error message to
* 'Invalid integer!' // error message if value doesn't validate
* )
* );
* </code>
*
* - <b>range</b>
*
* <code>'range' => array($range, $error_block, $error_message)</code>
*
* where
*
* - <i>range</i> is an array with 2 values, representing the minimum and maximum allowed values. 0 (zero) means
* "any value". Therefore, a range of array(100, 0) would validate values of 100 and above, array(50, 100)
* would validate values between 50 and 100 (inclusive), while (0, 50) would validate values below 51
*
* - <i>error_block</i> is the PHP variable to append the error message to, in case the rule does not validate
*
* - <i>error_message</i> is the error message to be shown when rule is not obeyed
*
* Validates if the value is within range
*
* Available for the following controls: {@link Zebra_Form_Text text}
*
* <code>
* // $obj is a reference to a control
* $obj->set_rule(
* 'range' => array(
* array(50, 100) // allow values between 50 and 100 (inclusive)
* 'error', // variable to add the error message to
* 'The entered value must be between 50 and 100 (inclusive)' // error message if value doesn't validate
* )
* );
* </code>
*
* - <b>regexp</b>
*
* <code>'regexp' => array($regular_expression, $error_block, $error_message)</code>
*
* where
*
* - <i>regular_expression</i> is the regular expression pattern (without delimiters) to be tested on the value
*
* - <i>error_block</i> is the PHP variable to append the error message to, in case the rule does not validate
*
* - <i>error_message</i> is the error message to be shown when rule is not obeyed
*
* Validates if the value satisfies the given regular expression
*
* Available for the following controls: {@link Zebra_Form_Password password}, {@link Zebra_Form_Text text},
* {@link Zebra_Form_Textarea textarea}
*
* <code>
* // $obj is a reference to a control
* $obj->set_rule(
* 'regexp' => array(
* '^0123' // the regular expression
* 'error', // variable to add the error message to
* 'Value must begin with "0123"' // error message if value doesn't validate
* )
* );
* </code>
*
* - <b>required</b>
*
* <code>'required' => array($error_block, $error_message)</code>
*
* where
*
* - <i>error_block</i> is the PHP variable to append the error message to, in case the rule does not validate
*
* - <i>error_message</i> is the error message to be shown when rule is not obeyed
*
*
* Validates only if a value exists.
*
* Available for the following controls: {@link Zebra_Form_Checkbox checkbox}, {@link Zebra_Form_Date date},
* {@link Zebra_Form_File file}, {@link Zebra_Form_Password password}, {@link Zebra_Form_Radio radio},
* {@link Zebra_Form_Select select}, {@link Zebra_Form_Text text}, {@link Zebra_Form_Textarea textarea},
* {@link Zebra_Form_Time time}
*
* <code>
* // $obj is a reference to a control
* $obj->set_rule(
* 'required' => array(
* 'error', // variable to add the error message to
* 'Field is required' // error message if value doesn't validate
* )
* );
* </code>
*
* - <b>resize</b>
*
* <samp>This rule requires the prior inclusion of the {@link http://stefangabos.ro/php-libraries/zebra-image Zebra_Image}
* library!</samp>
*
* <code>'resize' => array(
* $prefix,
* $width,
* $height,
* $preserve_aspect_ratio,
* $method,
* $background_color,
* $enlarge_smaller_images,
* $jpeg_quality,
* $error_block,
* $error_message,
* )
* </code>
*
* where
*
* - <i>prefix</i>: 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.
*
* - <i>width</i> is 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
* <b>preserve_aspect_ratio</b> to bet set to TRUE regardless of its actual value!
*
* - <i>height</i> is 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
* <b>preserve_aspect_ratio</b> to bet set to TRUE regardless of its actual value!
*
* - <i>preserve_aspect_ratio</i>: 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
*
* - <i>method</i>: is the method to use when resizing images to exact width and height while preserving aspect
* ratio.
*
* If the <b>preserve_aspect_ratio</b> 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.
*
* - <i>background_color</i> is the hexadecimal color of the blank area (without the #). See the <b>method</b>
* argument.
*
* - <i>enlarge_smaller_images</i>: if set to FALSE, images having both width and height smaller than the required
* width and height, will be left untouched (<b>jpeg_quality</b> will still apply).
*
* - <i>jpeg_quality</i> indicates the quality of the output image (better quality means bigger file size).
*
* Range is 0 - 100
*
* Available only for JPEG files.
*
* - <i>error_block</i> is the PHP variable to append the error message to, in case the rule does not validate
*
* - <i>error_message</i> is the error message to be shown when rule is not obeyed
*
* <i>This rule must come</i> <b>after</b> <i>the</i> <b>upload</b> <i>rule!</i>
*
* This is not an actual "rule", but because it can generate an error message it is included here.
*
* Available only for the {@link Zebra_Form_File file} control
*
* <i>This rule is not available client-side!</i>
*
* <code>
* // $obj is a reference to a control
* $obj->set_rule(
* 'resize' => array(
* 'thumb_', // prefix
* '150', // width
* '150', // height
* true, // preserve aspect ratio
* ZEBRA_IMAGE_BOXED, // method to be used
* 'FFFFFF', // background color
* true, // enlarge smaller images
* 85, // jpeg quality
* 'error', // variable to add the error message to
* 'Thumbnail could not be created!' // error message if value doesn't validate
* )
* );
*
* // for multiple resizes, use an array of arrays:
* $obj->set_rule(
* 'resize' => array(
* array('thumb1_', 150, 150, true, ZEBRA_IMAGE_BOXED, 'FFFFFF', true, 85, 'error', 'Error!'),
* array('thumb2_', 300, 300, true, ZEBRA_IMAGE_BOXED, 'FFFFFF', true, 85, 'error', 'Error!'),
* )
* );
* </code>
*
* - <b>upload</b>
*
* <code>'upload' => array($upload_path, $file_name, $error_block, $error_message)</code>
*
* where
*
* - <i>upload_path</i> the path where to upload the file to, relative to the currently running script.
*
* - <i>file_name</i>: 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 <b>TRUE</b>: the uploaded file's original name will be preserved; <b>FALSE</b> (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; <b>a string</b>: 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.
*
* - <i>error_block</i> is the PHP variable to append the error message to, in case the rule does not validate
*
* - <i>error_message</i> is the error message to be shown when rule is not obeyed
*
* Validates if the file was successfully uploaded to the folder specified by <b>upload_path</b>.
*
* <i>Remember to check the form's {@link Zebra_Form::$file_upload $file_upload} property for information about the
* uploaded file after the form is submitted!</i>
*
* <i>Remember to check the form's {@link Zebra_Form::$file_upload_permissions $file_upload_permissions} property
* for how to set the filesystem permissions of the uploaded files!</i>
*
* <i>Note that once this rule is run client-side, the DOM element the rule is attached to, will get a data-attribute
* called</i> <b>file_info</b> <i>which will contain information about the uploaded file, accessible via JavaScript.</i>
*
* <code>
* console.log($('#element_id').data('file_info'))
* </code>
*
* This is not actually a "rule", but because it can generate an error message it is included here
*
* You should use this rule in conjunction with the <b>filesize</b> rule
*
* Available only for the {@link Zebra_Form_File file} control
*
* <i>This rule is not available client-side!</i>
*
* <code>
* // $obj is a reference to a control
* $obj->set_rule(
* 'upload' => array(
* 'tmp', // path to upload file to
* ZEBRA_FORM_UPLOAD_RANDOM_NAMES, // upload file with random-generated name
* 'error', // variable to add the error message to
* 'File could not be uploaded!' // error message if value doesn't validate
* )
* );
* </code>
*
* - <b>url</b>
*
* <code>'url' => array($require_protocol, $error_block, $error_message)</code>
*
* where
*
* - <i>require_protocol</i> indicates whether the <i>http</i> or <i>https</i> prefix should be mandatory or not
*
* - <i>error_block</i> is the PHP variable to append the error message to, in case the rule does not validate
*
* - <i>error_message</i> is the error message to be shown when rule is not obeyed
*
* Validates if the value represents a valid URL
*
* The regular expression used is the following:
*
* <code>/^(http(s)?\:\/\/)?[^\s\.]+\..{2,}/i</code>
*
* Some example URLs that are considered valid by this rule are:
* - google.com (if the <i>require_protocol</i> attribut is set to FALSE)
* - http://google.com
* - http://www.google.com
* - http://www.google.com?foo=bar
* - http://www.google.com?foo=bar#anchor
*
* <samp>Note that this rule will only validate common URLs and does not attempt to be a validator for all possible
* valid URLs, and therefore it will fail on most of the more exotic URLs used in the tests {@link http://mathiasbynens.be/demo/url-regex here}.
* Keep that in mind when deciding on whether to use this rule or not. Nevertheless, this should be enough for
* validating most of the URLs you encountered on a daily basis.</samp>
*
* Available for the following controls: {@link Zebra_Form_Password password}, {@link Zebra_Form_Text text},
* {@link Zebra_Form_Textarea textarea}
*
* <code>
* // $obj is a reference to a control
* $obj->set_rule(
* 'url' => array(
* true, // require users to start the URL with http:// or https:// in order for the URL to be valid
* 'error', // variable to add the error message to
* 'Not a valid URL!' // error message if value doesn't validate
* )
* );
* </code>
*
* @param array $rules An associative array
*
* See above how it needs to be specified for each rule
*
* @return void
*/
function set_rule($rules)
{
// continue only if argument is an array
if (is_array($rules))
// iterate through the given rules
foreach ($rules as $rule_name => $rule_properties) {
// make sure the rule's name is lowercase
$rule_name = strtolower($rule_name);
// if custom rule
if ($rule_name == 'custom')
// if more custom rules are specified at once
if (is_array($rule_properties[0]) && count($rule_properties[0]) >= 3)
// iterate through the custom rules
// and add them one by one
foreach ($rule_properties as $rule) $this->rules[$rule_name][] = $rule;
// if a single custom rule is specified
// save the custom rule to the "custom" rules array
else $this->rules[$rule_name][] = $rule_properties;
// for all the other rules
// add the rule to the rules array
else $this->rules[$rule_name] = $rule_properties;
// for some rules we do some additional settings
switch ($rule_name) {
// we set a reserved attribute for the control by which we're telling the
// _render_attributes() method to append a special class to the control when rendering it
// so that we can also control user input from javascript
case 'alphabet':
case 'digits':
case 'alphanumeric':
case 'number':
case 'float':
$this->set_attributes(array('onkeypress' => 'javascript:return $(\'#' . $this->form_properties['name'] . '\').data(\'Zebra_Form\').filter_input(\'' . $rule_name . '\', event' . ($rule_properties[0] != '' ? ', \'' . addcslashes($rule_properties[0], '\'') . '\'' : '') . ');'));
// for controls having these rules,
if (in_array($rule_name, array('digits', 'number', 'float')))
// change the control's type to "number"
$this->set_attributes(array('type' => 'number'));
break;
// if the rule is about the length of the input
case 'length':
// if there is a maximum of allowed characters
if ($rule_properties[1] > 0) {
// set the maxlength attribute of the control
$this->set_attributes(array('maxlength' => $rule_properties[1]));
// if there is a 5th argument to the rule, the argument is boolean true
if (isset($rule_properties[4]) && $rule_properties[4] === true) {
// add an extra class so that the JavaScript library will know to show the character counter
$this->set_attributes(array('class' => 'show-character-counter'), false);
}
}
break;
// if the "email" rule is set
case 'email':
// change the element's type to "email"
$this->set_attributes(array('type' => 'email'));
break;
}
}
}
/**
* Converts the array with control's attributes to valid HTML markup interpreted by the {@link toHTML()} method
*
* Note that this method skips {@link $private_attributes}
*
* @return string Returns a string with the control's attributes
*
* @access private
*/
protected function _render_attributes()
{
// the string to be returned
$attributes = '';
// if
if (
// control has the "disabled" attribute set
isset($this->attributes['disabled']) &&
$this->attributes['disabled'] == 'disabled' &&
// control is not a radio button
$this->attributes['type'] != 'radio' &&
// control is not a checkbox
$this->attributes['type'] != 'checkbox'
// add another class to the control
) $this->set_attributes(array('class' => 'disabled'), false);
// iterates through the control's attributes
foreach ($this->attributes as $attribute => $value)
if (
// if control has no private attributes or the attribute is not a private attribute
(!isset($this->private_attributes) || !in_array($attribute, $this->private_attributes)) &&
// and control has no private javascript attributes or the attribute is not in a javascript private attribute
(!isset($this->javascript_attributes) || !in_array($attribute, $this->javascript_attributes))
)
// add attribute => value pair to the return string
$attributes .=
($attributes != '' ? ' ' : '') . $attribute . '="' . preg_replace('/\"/', '&quot;', $value) . '"';
// returns string
return $attributes;
}
}
?>