* @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:
*
* - path - the path where the file was uploaded to
* - file_name - the name the file was uploaded with
* - imageinfo - available only if the uploaded file is an image!
* 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:
* bits
* channels
* mime
* width
* height
* type ({@link http://php.net/manual/en/function.exif-imagetype.php possible types})
* html
*
* Note that the file name can be different than the original name of the uploaded file!
*
* 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 upload 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.
*
*
* $form->file_upload_permissions = '0777';
*
*
* 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.
*
*
* $form = new Zebra_Form('myform');
*
*
* @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 POST an GET
*
* Default is POST
*
* @param string $action (Optional) An URI to where to submit the form data set.
*
* If left empty, the form will submit to itself.
*
* 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!
*
* @param array $attributes (Optional) An array of attributes valid for a
* // create a new form
* $form = new Zebra_Form('my_form');
*
* // display all error messages of error blocks
* $form->show_all_error_messages(true);
*
*
* @param boolean $value Setting this argument to TRUE will display all 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 before 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('/(?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('/(?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 ' . $custom_rule_attributes[0] . '() 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('/(?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('/(? 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('/(? 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 $force 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 and 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 and 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 type 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 0, the width will be automatically adjusted, depending
* on the value of the height argument so that the image preserves
* its aspect ratio.
*
* If preserve_aspect_ratio is set to TRUE and both this and the
* height arguments are values greater than 0, the image will
* be resized to the exact required width and height and the aspect ratio
* will be preserved (see the description for the method argument
* below on how can this be done).
*
* If preserve_aspect_ratio is set to FALSE, the image will be
* resized to the required width and the aspect ratio will be ignored.
*
* If both width and height are set to 0, a copy of
* the source image will be created (jpeg_quality will still apply).
*
* If either width or height are set to 0, 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 0, the height will be automatically adjusted, depending
* on the value of the width argument so that the image preserves
* its aspect ratio.
*
* If preserve_aspect_ratio is set to TRUE and both this and the
* width arguments are values greater than 0, the image will
* be resized to the exact required width and height and the aspect ratio
* will be preserved (see the description for the method argument
* below on how can this be done).
*
* If preserve_aspect_ratio is set to FALSE, the image will be
* resized to the required height and the aspect ratio will be ignored.
*
* If both height and width are set to 0, a copy of
* the source image will be created (jpeg_quality will still apply).
*
* If either height or width are set to 0, 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
* width and height arguments are values greater than 0,
* 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:
*
* - ZEBRA_IMAGE_BOXED - 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 $background_color
* argument. (the blank area will be filled only if the image is not
* transparent!)
*
* - ZEBRA_IMAGE_NOT_BOXED - the image will be scalled so that it
* could 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
*
* - ZEBRA_IMAGE_CROP_TOPLEFT
* - ZEBRA_IMAGE_CROP_TOPCENTER
* - ZEBRA_IMAGE_CROP_TOPRIGHT
* - ZEBRA_IMAGE_CROP_MIDDLELEFT
* - ZEBRA_IMAGE_CROP_CENTER
* - ZEBRA_IMAGE_CROP_MIDDLERIGHT
* - ZEBRA_IMAGE_CROP_BOTTOMLEFT
* - ZEBRA_IMAGE_CROP_BOTTOMCENTER
* - ZEBRA_IMAGE_CROP_BOTTOMRIGHT
*
* 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 method 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}.
*
* - uploads would upload the files in the "upload" folder, at the
* path with the script
* - ../uploads would upload the files in the "upload" folder, one
* level up relative to the script's path
* - /uploads 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 '