* @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
tag (i.e. style) * * Note that the following attributes are automatically set when the control is * created and should not be altered manually: * * action, method, enctype, name * * @return void */ function __construct($name, $method = 'POST', $action = '', $attributes = '') { $this->controls = $this->variables = $this->errors = $this->master_labels = array(); // default filesysyem permissions for uploaded files $this->file_upload_permissions = '0755'; // default values for the form's properties $this->form_properties = array( 'action' => ($action == '' ? $_SERVER['REQUEST_URI'] : $action), 'assets_server_path' => rtrim(dirname(__FILE__), '\\/') . DIRECTORY_SEPARATOR, 'assets_url' => rtrim(str_replace('\\', '/', 'http' . (isset($_SERVER['HTTPS']) || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') ? 's' : '') . '://' . rtrim($_SERVER['HTTP_HOST'], '\\/') . '/' . substr(rtrim(dirname(__FILE__), '\\/'), strlen($_SERVER['DOCUMENT_ROOT']))), '\\/') . '/', 'attributes' => $attributes, 'auto_fill' => false, 'captcha_storage' => 'cookie', 'csrf_cookie_config' => array('path' => '/', 'domain' => '', 'secure' => false, 'httponly' => true), 'csrf_cookie_name' => 'zebra_csrf_token_' . $name, 'csrf_storage_method' => 'auto', 'csrf_token' => '', 'csrf_token_lifetime' => 0, 'csrf_token_name' => 'zebra_csrf_token_' . $name, 'doctype' => 'html', 'has_upload' => false, 'honeypot' => 'zebra_honeypot_' . $name, 'identifier' => 'name_' . $name, 'language' => array(), 'method' => strtoupper($method), 'name' => $name, 'other_suffix' => '_other', 'secret_key' => '', 'show_all_error_messages' => false, ); // set default client-side validation properties $this->clientside_validation(true); // get the maximum allowed file size for uploads $upload_max_filesize = ini_get('upload_max_filesize'); // see what it is given in (G, M, K) $unit = strtolower(substr($upload_max_filesize, -1)); // get the numeric value $value = substr($upload_max_filesize, 0, -1); // convert to bytes // notice that there is no break switch (strtolower(substr($upload_max_filesize, -1))) { case 'g': $value*=1024; case 'm': $value*=1024; case 'k': $value*=1024; } // set the form's respective property $this->form_properties['max_file_size'] = $value; // include the XSS filter class - the Zebra_Form_Control class extends this class require_once dirname(__FILE__) . '/includes/XSSClean.php'; // include the Control.php file which contains the Zebra_Form_Control class which is // extended by all of the classes require_once dirname(__FILE__) . '/includes/Control.php'; // load the default language file $this->language('english'); // enable protection against CSRF attacks using the default values // note that this has no effect if this method was already called before $this->csrf(); } /** * Adds a control to the form. * * * // create a new form * $form = new Zebra_Form('my_form'); * * // add a text control to the form * $obj = $form->add('text', 'my_text'); * * // make the text field required * $obj->set_rule( * 'required' => array( * 'error', // variable to add the error message to * 'Field is required' // error message if value doesn't validate * ) * ); * * // don't forget to always call this method before rendering the form * if ($form->validate()) { * // put code here * } * * // output the form using an automatically generated template * $form->render(); * * * @param string $type Type of the control to add. * * Controls that can be added to a form: * * - {@link Zebra_Form_Button buttons} * - {@link Zebra_Form_Captcha CAPTCHAs} * - {@link Zebra_Form_Checkbox checkboxes} * - {@link Zebra_Form_Date date pickers} * - {@link Zebra_Form_File file upload controls} * - {@link Zebra_Form_Hidden hidden controls} * - {@link Zebra_Form_Image image button controls} * - {@link Zebra_Form_Label labels} * - {@link Zebra_Form_Note notes} * - {@link Zebra_Form_Password password controls} * - {@link Zebra_Form_Radio radio buttons} * - {@link Zebra_Form_Reset reset buttons} * - {@link Zebra_Form_Select selects} * - {@link Zebra_Form_Submit submit buttons} * - {@link Zebra_Form_Text text box controls} * - {@link Zebra_Form_Textarea textareas} * - {@link Zebra_Form_Time time pickers} * * @param mixed $arguments A list of arguments as required by the control that is added. * * @return reference Returns a reference to the newly created object */ function &add($type) { // if shortcut for multiple radio buttons or checkboxes if ($type == 'radios' || $type == 'checkboxes') { // if there are less than 3 arguments if (func_num_args() < 3) // trigger a warning _zebra_form_show_error('For ' . $type . ', the add() method requires at least 3 arguments', E_USER_WARNING); // if third argument is not an array elseif (!is_array(func_get_arg(2))) // trigger a warning _zebra_form_show_error('For ' . $type . ', the add() method requires the 3rd argument to be an array', E_USER_WARNING); // if everything is ok else { // controls' name $name = func_get_arg(1); // the values and labels $values = func_get_arg(2); // a 4th argument (the default option) was passed to the method if (func_num_args() >= 4) { // save the default value $default = func_get_arg(3); // if default value is not given as an array // (makes sense for checkboxes when there may be multiple preselected values) // make it an array if (!is_array($default)) $default = array($default); } if (func_num_args() >= 5) $additional = func_get_arg(4); $counter = 0; // iterate through values and their respective labels foreach ($values as $value => $caption) { // sanitize controls' name (remove square brackets) $sanitize_name = preg_replace('/\[\]$/', '', $name); // santize the value $value = preg_replace('/\_{1,}/', '_', preg_replace('/[^a-z0-9\_]/i', '_', $value)); // create control $obj = & $this->add( ($type == 'radios' ? 'radio' : 'checkbox'), $name, $value, (isset($default) && in_array($value, $default) ? (isset($additional) ? array_merge($additional, array('checked' => 'checked')) : array('checked' => 'checked')) : (isset($additional) ? $additional :''))); // if this is the first control in the array // we will later need to return a reference to it if ($counter++ == 0) $pointer = &$obj; // add the label for the control $this->add('label', 'label_' . $sanitize_name . '_' . $value, $sanitize_name . '_' . $value, $caption); } // if the array of values was not empty // return reference to the first control if (isset($pointer)) return $pointer; } // for all other controls } else { $file_name = ucfirst(strtolower($type)); // the classes have the "Zebra_Form_" prefix $class_name = 'Zebra_Form_' . ucfirst(strtolower($type)); // include the file containing the PHP class, if not already included require_once dirname(__FILE__) . '/includes/' . $file_name . '.php'; // if included file contains such a class if (class_exists($class_name)) { // prepare arguments passed to the add() method // notice that first argument is ignored as it refers to the type of the control to add // and we don't have to pass that to the class $arguments = array_slice(func_get_args(), 1); // if name was not specified trigger an error if (strlen(trim($arguments[0])) == 0) trigger_error('Name is required for control of type ' . $class_name, E_USER_ERROR); // use this method to instantiate the object with dynamic arguments $obj = call_user_func_array(array(new ReflectionClass($class_name), 'newInstance'), $arguments); // make available the form's properties in the newly created object $obj->form_properties = & $this->form_properties; // get some attributes for the newly created control $attributes = $obj->get_attributes(array('id', 'name')); // perform some extra tasks for different types of controls switch ($class_name) { // if the newly created control is a file upload control case 'Zebra_Form_File': // set a flag to be used at rendering $this->form_properties['has_upload'] = true; break; // if the newly created control is a radio button or a checkbox case 'Zebra_Form_Radio': case 'Zebra_Form_Checkbox': // radio buttons and checkboxes might have a "master label", a label that is tied to the radio buttons' or // checkboxes' name rather than individual controls' IDs. (as grouped radio buttons and checkboxes share // the same name but have different values) // we use this so that, if the controls have the "required" rule set, the asterisk is attached to the master // label rather than to one of the actual controls // therefore, we generate a "lookup" array of "master" labels for each group of radio buttons or // checkboxes. this does not means that there will be an actual master label - we use this lookup // array to easily determine if a master label exists when rendering the form // sanitize the control's name $attributes['name'] = preg_replace('/\[\]$/', '', $attributes['name']); // if there isn't a master label for the group the current control is part of if (!isset($this->master_labels[$attributes['name']])) // create the entry // the "control" index will hold the actual label's name if a "master" label is added to the form $this->master_labels[$attributes['name']] = array('control' => false); break; } // put the reference to the newly created object in the 'controls' array $this->controls[$attributes['id']] = &$obj; // return the identifier to the newly created object return $obj; } } } /** * Appends a message to an already existing {@link Zebra_Form_Control::set_rule() error block} * * * // create a new form * $form = new Zebra_Form('my_form'); * * // add a text control to the form * $obj = $form->add('text', 'my_text'); * * // make the text field required * $obj->set_rule( * 'required' => array( * 'error', // variable to add the error message to * 'Field is required' // error message if value doesn't validate * ) * ); * * // don't forget to always call this method before rendering the form * if ($form->validate()) { * * // for the purpose of this example, we will do a custom validation * // after calling the "validate" method. * // for custom validations, using the "custom" rule is recommended instead * * // check if value's is between 1 and 10 * if ((int)$_POST['my_text']) < 1 || (int)$_POST['my_text']) > 10) { * * $form->add_error('error', 'Value must be an integer between 1 and 10!'); * * } else { * * // put code here that is to be executed when the form values are ok * * } * * } * * // output the form using an automatically generated template * $form->render(); * * * @param string $error_block The name of the error block to append the error message to (also the name * of the PHP variable that will be available in the template file). * * @param string $error_message The error message to append to the error block. * * @return void */ function add_error($error_block, $error_message) { // if the error block was not yet created, create the error block if (!isset($this->errors[$error_block])) $this->errors[$error_block] = array(); // if the same exact message doesn't already exists if (!in_array(trim($error_message), $this->errors[$error_block])) // append the error message to the error block $this->errors[$error_block][] = trim($error_message); } /** * Set the server path and URL to the "process.php" and "mimes.json" files. * * These files are required for CAPTCHAs and uploads. * * By default, the location of these files is in the same folder as Zebra_Form.php and the script will automatically * try to determine both the server path and the URL to these files. However, when the script is run on a virtual * host, or if these files were moved, the script may not correctly determine the paths to these files. In these * instances, use this method to correctly set the server path - needed by the script to correctly include these * files, and the URL - needed by the client-side validation to include these files. * * Also, for security reasons, I recommend moving these two files by default to the root of your website (or another * publicly accessible place) and manually set the paths, in order to prevent malicious users from finding out * information about your directory structure. * * If you move these files don't forget to also move the font file from the "includes" folder, and to manually * adjust the path to the font file in "process.php"! * * @param string $server_path The server path (the one similar to what is in $_SERVER['DOCUMENT_ROOT']) to * the folder where the "process.php" and "mimes.json" files can be found. * * With trailing slash! * * @param string $url The URL to where the "process.php" and "mimes.json" files can be found. * * With trailing slash! * * @return void */ function assets_path($server_path, $url) { // set values $this->form_properties['assets_server_path'] = $server_path; $this->form_properties['assets_url'] = $url; } /** * Creates a PHP variable with the given value, available in the template file. * * * // create a new form * $form = new Zebra_Form('my_form'); * * // make available the $my_value variable in the template file * $form->assign('my_value', '100'); * * // don't forget to always call this method before rendering the form * if ($form->validate()) { * // put code here * } * * // output the form * // notice that we are using a custom template * // my_template.php file is expected to be found * // and in this file, you may now use the $my_value variable * $form->render('my_template.php'); * * * @param string $variable_name Name by which the variable will be available in the template file. * * @param mixed $value The value to be assigned to the variable. * * @return void */ function assign($variable_name, $value) { // save the variable in an array that we will make available in the template file upon rendering $this->variables[$variable_name] = $value; } /** * Call this method anytime *before* calling the {@link validate()} method (preferably, right after instantiating * the class) to instruct the library to automatically fill out all of the form's fields with random content while * obeying any rules that might be set for each control. * * You can also use this method to set defaults for the form's elements by setting the method's second argument to TRUE. * * Notes: * * - unless overridden, the value of {@link Zebra_Form_Password password} controls will always be "12345678"; * - unless overridden, the value of controls having the "email" or "emails" {@link Zebra_Form_Control::set_rule() rule} * set will be in the form of random_text@random_text.com; * - unless overridden, the value of controls having the "url" {@link Zebra_Form_Control::set_rule() rule} set will * be in the form of random_text.com, prefixed or not with "http://", depending on the rule's attributes; * - {@link Zebra_Form_File file upload} controls and controls having the "captcha" or "regexp" * {@link Zebra_Form_Control::set_rule() rule} set will *not* be autofilled; * * This method will produce results *only* if the form has not yet been submitted! Also, this method will fill * elements with random content *only* if the element does not already has a default value! And finally, this method * will produce no results unless at some point the form's {@link validate()} method is called. * * @param array $defaults An associative array in the form of $element => $value used for filling * out specific fields with specific values instead of random ones. * * For elements that may have more than one value (checkboxes and selects with * the "multiple" attribute set) you may set the value as an array. * * * // auto-fill all fields with random values * $form->auto_fill(); * * // auto-fill all fields with random values * // except the one called "email" which should have a custom value * $form->auto_fill(array( * 'email' => 'some@email.com', * )); * * // auto-fill all fields with random values * // except a checkboxes group where we select multiple values * // note that we use "my_checkboxes" insteas of "my_checkboxes[]" * // (the same goes for "selects" with the "multiple" attribute set) * $form->auto_fill(array( * 'my_checkboxes' => array('value_1', 'value_2'), * )); * * * @param boolean $specifics_only (Optional) If set to TRUE only the fields given in the $defaults argument * will be filled with the given values and the other fields will be skipped. * * Can be used as a handy shortcut for giving default values to elements instead * of putting default values in the constructor or using the {@link set_attributes()} * method. * * Default is FALSE. * * * @since 2.8.9 * * @return void */ function auto_fill($defaults = array(), $specifics_only = false) { $this->form_properties['auto_fill'] = array($defaults, $specifics_only); } /** * Sets the storage method for CAPTCHA values. * * By default, captcha values are triple md5 hashed and stored in cookies, and when the user enters the captcha * value the value is also triple md5 hashed and the two values are then compared. * * Sometimes, your users may have a very restrictive cookie policy and so cookies will not be set, and therefore, * they will never be able to get past the CAPTCHA control. If it's the case, call this method and set the storage * method to "session". * * In this case, call this method and set the the storage method to "session". * * @param string $method Storage method for CAPTCHA values. * * Valid values are "cookie" and "session". * * Default is "cookie". * * @since 2.8.9 * * @return void */ function captcha_storage($method) { // if storage method is "session" if ($method == 'session') // set the storage method $this->form_properties['captcha_storage'] = 'session'; // "cookie" otherwise else $this->form_properties['captcha_storage'] = 'cookie'; } /** * Alias of {@link clientside_validation()} method. * * Deprecated since 2.8.9 */ function client_side_validation($properties) { $this->clientside_validation($properties); } /** * Sets properties for the client-side validation. * * Client-side validation, when enabled, occurs on the "onsubmit" event of the form. * * * // create a new form * $form = new Zebra_Form('my_form'); * * // disable client-side validation * $form->clientside_validation(false); * * // enable client-side validation using default properties * $form->clientside_validation(true); * * // enable client-side validation using customized properties * $form->clientside_validation(array( * 'close_tips' => false, // don't show a "close" button on tips with error messages * 'on_ready' => false, // no function to be executed when the form is ready * 'disable_upload_validation' => true, // using a custom plugin for managing file uploads * 'scroll_to_error' => false, // don't scroll the browser window to the error message * 'tips_position' => 'right', // position tips with error messages to the right of the controls * 'validate_on_the_fly' => false, // don't validate controls on the fly * 'validate_all' => false, // show error messages one by one upon trying to submit an invalid form * )); * * * To access the JavaScript object and use the public methods provided by it, use $('#formname').data('Zebra_Form') * where formname is the form's name with any dashes turned into underscores! * * Therefore, if a form's name is "my-form", the JavaScript object would be accessed like $('my_form').data('Zebra_Form'). * * From JavaScript, these are the methods that can be called on this object: * * - attach_tip(element, message) - displays a custom error message, attached to the specified jQuery * element * - clear_errors() - hides all error messages; * - submit() - submits the form; * - validate() - checks if the form is valid; returns TRUE or FALSE; * if called with the "false" boolean argument, error messages will * not be shown in case form does not validate * * Here's how you can use these methods, in JavaScript: * * * // let's submit the form when clicking on a random button * * // get a reference to the Zebra_Form object * var $form = $('#formname').data('Zebra_Form'); * * // handle the onclick event on a random button * $('#somebutton').bind('click', function(e) { * * // stop default action * e.preventDefault(); * * // validate the form, and if the form validates * if ($form.validate()) { * * // do your thing here * * // when you're done, submit the form * $form.submit(); * * } * * // if the form is not valid, everything is handled automatically by the library * * }); * * * @param mixed $properties Can have the following values: * * - FALSE, disabling the client-side validation; * Note that the {@link Zebra_Form_Control::set_rule() dependencies} rule will * still be checked client-side so that callback functions get called, if it is * the case! * - TRUE, enabling the client-side validation with the default properties; * * - an associative array with customized properties for the client-side validation; * * In this last case, the available properties are: * * - close_tips, boolean, TRUE or FALSE
* Specifies whether the tips with error messages should have a "close" button * or not
* Default is TRUE. * * - disable_upload_validation, boolean, TRUE or FALSE
* Useful for disabling all client-side processing of file upload controls, so * that custom plugins may be used for it (like, for instance, * {@link http://blueimp.github.io/jQuery-File-Upload/basic.html this one}) * Default is FALSE. * * - on_ready, JavaScript function to be executed when the form is loaded. * Useful for getting a reference to the Zebra_Form object after everything is * loaded. * * * $form->clientside_validation(array( * // where $form is a global variable and 'id' is the form's id * 'on_ready': 'function() { $form = $("#id").data('Zebra_Form'); }', * )); * * * - scroll_to_error, boolean, TRUE or FALSE
* Specifies whether the browser window should be scrolled to the error message * or not.
* Default is TRUE. * * - tips_position, string, left, right or center
* Specifies where the error message tip should be positioned relative to the * control.
* Default is left. * * - validate_on_the_fly, boolean, TRUE or FALSE
* Specifies whether values should be validated as soon as the user leaves a * field; if set to TRUE and the validation of the control fails, the error * message will be shown right away
* Default is FALSE. * * - validate_all, boolean, TRUE or FALSE
* Specifies whether upon submitting the form, should all error messages be * shown at once if there are any errors
* Default is FALSE. * * @return void */ function clientside_validation($properties) { // default properties of the client-side validation $defaults = array( 'clientside_disabled' => false, 'close_tips' => true, 'disable_upload_validation' => false, 'on_ready' => false, 'scroll_to_error' => true, 'tips_position' => 'left', 'validate_on_the_fly' => false, 'validate_all' => false, ); // set the default properties for the client-side validation if (!isset($this->form_properties['clientside_validation'])) $this->form_properties['clientside_validation'] = $defaults; // if client-side validation needs to be disabled if ($properties == false) $this->form_properties['clientside_validation']['clientside_disabled'] = true; // if custom settings for client-side validation elseif (is_array($properties)) // merge the new settings with the old ones $this->form_properties['clientside_validation'] = array_merge($this->form_properties['clientside_validation'], $properties); } /** * By default, this class generates HTML 4.01 Strict markup. * * Use this method if you want the generated HTML markup to validate as XHTML 1.0 Strict. * * @param string $doctype (Optional) The DOCTYPE of the generated HTML markup. * * Possible (case-insensitive) values are HTML or XHTML * * Default is HTML. * * @return void */ function doctype($doctype = 'html') { // set the doctype $this->form_properties['doctype'] = (strtolower($doctype) == 'xhtml' ? 'xhtml' : 'html'); } /** * Enables protection against {@link http://en.wikipedia.org/wiki/Cross-site_request_forgery Cross-site request * forgery} (CSRF) attacks. * * Read more about specifics and a simple implementation on * {@link http://shiflett.org/articles/cross-site-request-forgeries Chris Shiflett's website}. * * This method is automatically called by the library, so protection against CSRF attacks is enabled by default for * all forms and the script will decide automatically on the method to use for storing the CSRF token: if a session * is already started then the CSRF token will be stored in a session variable or, if a session is not started, the * CSRF token will be stored in a session cookie (cookies that expire when the browser is closed) but, in this case, * it offers a lower level of security. * * If cookies are used for storage, you'll have to make sure that Zebra_Form is instantiated before any output from * your script (this is a protocol restriction) including <html> and <head> tags as well as any whitespace! * A workaround is to turn output buffering on in php.ini. * * You are encouraged to start a PHP session before instantiating this class in order to maximize the level of * security of your forms. * * The CSRF token is automatically regenerated when the form is submitted regardless if the form validated or not. * A notable exception is that the form doesn't validate but was submitted via AJAX the CSRF token will not be * regenerated - useful if you submit forms by AJAX. * * As an added benefit, protection against CSRF attacks prevents "double posts" by design. * * You only need to call this method if you want to change CSRF related settings. If you do call this method * then you should call it right after instantiating the class and *before* calling the form's {@link validate()} * and {@link render()} methods! * * * // recommended usage is: * * // call session_start() somewhere in your code but before outputting anything to the browser * session_start(); * * // include the Zebra_Form * require 'path/to/Zebra_Form.php'; * * // instantiate the class * // protection against CSRF attack will be automatically enabled * // but will be less secure if a session is not started (as it will * // rely on cookies) * $form = new Zebra_Form('my_form'); * * * @param string $csrf_storage_method (Optional) Sets whether the CSRF token should be stored in a cookie, in * a session variable, or let the script to automatically decide and use * sessions if available or a cookie otherwise. * * Possible values are "auto", "cookie", "session" or boolean FALSE. * * If value is "auto", the script will decide automatically on what to use: * if a session is already started then the CSRF token will be stored in a * session variable, or, if a session is not started, the CSRF token will be * stored in a cookie with the parameters as specified by the * csrf_cookie_config argument (read below). * * If value is "cookie" the CSRF token will be stored in a cookie with the * parameters as specified by the csrf_cookie_config argument (read * below). * * If value is "session" the CSRF token will be stored in a session variable * and thus a session must be started before instantiating the library. * * If value is boolean FALSE (not recommended), protection against CSRF * attack will be disabled. * * The stored value will be compared, upon for submission, with the value * stored in the associated hidden field, and if the two values do not match * the form will not validate. * * Default is "auto". * * @param integer $csrf_token_lifetime (Optional) The number of seconds after which the CSRF token is to be * considered as expired. * * If set to "0" the tokens will expire at the end of the session (when the * browser closes or session expires). * * Note that if csrf_storage_method is set to "session" this value cannot * be higher than the session's life time as, if idle, the session will time * out regardless of this value! * * Default is 0. * * @param array $csrf_cookie_config (Optional) An associative array containing the properties to be used when * setting the cookie with the CSRF token (if csrf_storage_method is * set to "cookie"). * * The properties that can be set are "path", "domain", "secure" and "httponly". * where: * * - path - the path on the server in which the cookie will * be available on. If set to "/", the cookie will * be available within the entire domain. If set to * '/foo/', the cookie will only be available within * the /foo/ directory and all subdirectories such * as /foo/bar/ of domain.
* Default is "/" * * - domain - The domain that the cookie will be available on. * To make the cookie available on all subdomains of * example.com, domain should be set to to * ".example.com". The . (dot) is not required but * makes it compatible with more browsers. Setting * it to "www.example.com" will make the cookie * available only in the www subdomain. * * - secure - Indicates whether cookie information should only * be transmitted over a HTTPS connection.
* Default is FALSE. * * - httponly - When set to TRUE the cookie will be made accessible * only through the HTTP protocol. This means that * the cookie won't be accessible by scripting languages, * such as JavaScript. It has been suggested that * this setting can effectively help to reduce identity * theft through XSS attacks (although it is not * supported by all browsers), but that claim is often * disputed. Available only in PHP 5.2.0+
* Default is FALSE * * Available only for PHP 5.2.0+ and will be ignored * if not available. * * Not all properties must be set - for the properties that are not set, the * default values will be used instead. * * @since 2.8.4 * * @return void */ function csrf($csrf_storage_method = 'auto', $csrf_token_lifetime = 0, $csrf_cookie_config = array('path' => '/', 'domain' => '', 'secure' => false, 'httponly' => true)) { // continue only if protection against CSRF attacks is not disabled and a token was not already generated if ($csrf_storage_method !== false && (func_num_args() > 0 || $this->form_properties['csrf_token'] == '')) { // set the storage method for the CSRF token $this->form_properties['csrf_storage_method'] = strtolower(trim($csrf_storage_method)); // if the script should decide what method to use and a session is already started if ($this->form_properties['csrf_storage_method'] == 'auto') { // use sessions as storage method if (isset($_SESSION)) $this->form_properties['csrf_storage_method'] = 'session'; // if a session is not already started, use cookies as storage method else $this->form_properties['csrf_storage_method'] = 'cookie'; } // set the life time of the CSRF token $this->form_properties['csrf_token_lifetime'] = ($csrf_token_lifetime <= 0 ? 0 : $csrf_token_lifetime); // set the configuration options for cookies $this->form_properties['csrf_cookie_config'] = array_merge($this->form_properties['csrf_cookie_config'], $csrf_cookie_config); // generate a new CSRF token (if it is the case) // (if this method is called with any arguments it means it is called by the user and therefore the // token should be regenerated) $this->_csrf_generate_token(func_num_args() > 0 ? true : false); // if protection against CSRF attacks is disabled, save the option for later use } else $this->form_properties['csrf_storage_method'] = false; } /** * Sets the language to be used by some of the form's controls (the date control, the select control, etc.) * * The default language is English. * * @param string $language The name of the language file to be used, from the "languages" folder. * * Must be specified without extension ("german" for the german language * not "german.php")! * * @var string * * @return void */ function language($language) { // include the language file require rtrim(dirname(__FILE__), DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . 'languages/' . strtolower(trim($language)) . '.php'; // make the language available in the control $this->form_properties['language'] = &$this->language; } /** * Renders the form. * * @param string $template The output of the form can be generated automatically, can be given from a template * file or can be generated programmatically by a callback function. * * For the automatically generated template there are two options: * * - when $template is an empty string or is "*vertical", the script * will automatically generate an output where the labels are above the controls * and controls come one under another (vertical view) * * - when $template is "*horizontal", the script will automatically * generate an output where the labels are positioned to the left of the controls * while the controls come one under another (horizontal view) * * When templates are user-defined, $template needs to be a string representing * the path/to/the/template.php. * * The template file itself must be a plain PHP file where all the controls * added to the form (except for the hidden controls, which are handled automatically) * will be available as variables with the names as described in the documentation * for each of the controls. Also, error messages will be available as described at * {@link Zebra_Form_Control::set_rule() set_rule()}. * * A special variable will also be available in the template file - a variable with * the name of the form and being an associative array containing all the controls * added to the form, as objects. * * The template file must not contain the and tags, nor any of the * controls added to the form as these are generated automatically!
* * There is a third method of generating the output and that is programmatically, * through a callback function. In this case $template needs to be the name * of an existing function. * * The function will be called with two arguments: * * - an associative array with the form's controls' ids and their respective * generated HTML, ready for echo-ing (except for the hidden controls which will * still be handled automatically); * * note that this array will also contain variables assigned through the * {@link assign()} method as well as any server-side error messages, as you * would in a custom template (see {@link Zebra_Form_Control::set_rule() set_rule()} * method and read until the second highlighted box, inclusive) * * - an associative array with all the controls added to the form, as objects * * THE USER FUNCTION MUST RETURN THE GENERATED OUTPUT! * * @param boolean $return (Optional) If set to TRUE, the output will be returned instead of being printed * to the screen. * * Default is FALSE. * * @param array $variables (Optional) An associative array in the form of "variable_name" => "value" * representing variable names and their associated values, to be made available * in custom template files. * * This represents a quicker alternative for assigning many variables at once * instead of calling the {@link assign()} method for each variable. * * @return mixed Returns or displays the rendered form. */ function render($template = '', $return = false, $variables = '') { // if if ( // "process.php" file could not be found !file_exists($this->form_properties['assets_server_path'] . 'process.php') || // or "mimes.json" file could not be found !file_exists($this->form_properties['assets_server_path'] . 'mimes.json') ) // it means the most probably the script is run on a virtual host and that paths need to be set manually so // we inform the user about that _zebra_form_show_error('Zebra_Form could not automatically determine the correct path to the "process.php" and "mimes.json" files - this may happen if the script is run on a virtual host. To fix this, use the assets_path() method and manually set the correct server path and URL to these file!', E_USER_ERROR); // if variables is an array if (is_array($variables)) // iterate through the values in the array foreach ($variables as $name => $value) // make each value available in the template $this->assign($name, $value); // start generating the output $output = '
form_properties['doctype'] == 'html' ? 'name="' . $this->form_properties['name'] . '" ' : '') . 'id="' . $this->form_properties['name'] . '" ' . 'action="' . htmlspecialchars($this->form_properties['action']) . '" ' . 'method="' . strtolower($this->form_properties['method']) . '" ' . 'novalidate="novalidate"'; // if custom classes are to be set for the form if (isset($this->form_properties['attributes']['class'])) // add the "Zebra_Form" required class $this->form_properties['attributes']['class'] .= ' Zebra_Form'; // if no custom classes are set, set the required "Zebra_Form" class else $this->form_properties['attributes']['class'] = 'Zebra_Form'; // if any form attributes have been specified if (is_array($this->form_properties['attributes'])) // iterate through the form's attributes foreach ($this->form_properties['attributes'] as $attribute => $value) // write them $output .= ' ' . $attribute . '="' . $value . '"'; // if the form has file upload controls if ($this->form_properties['has_upload'] === true) { // add the enctype to the attributes of the tag $output .= ' enctype="multipart/form-data"'; // and add this required hidden field containing the maximum allowed file size $this->add('hidden', 'MAX_FILE_SIZE', $this->form_properties['max_file_size']); // if client-side validation is not disabled if (!$this->form_properties['clientside_validation']['clientside_disabled'] && !$this->form_properties['clientside_validation']['disable_upload_validation']) // add a new property for the client-side validation $this->clientside_validation(array('assets_path' => rawurlencode($this->form_properties['assets_url']))); } $output .= '>'; // iterate through the form's controls foreach ($this->controls as $key => $control) { // treat "email" and "number" types as "text" if (in_array($control->attributes['type'], array('email', 'number'))) $control->attributes['type'] = 'text'; // get some attributes for each control $attributes = $control->get_attributes(array('type', 'for', 'name', 'id', 'multiple', 'other', 'class', 'default_other', 'disable_zebra_datepicker')); // sanitize the control's name $attributes['name'] = preg_replace('/\[\]/', '', $attributes['name']); // validate the control's name switch ($attributes['name']) { // if control has the same name as the form case $this->form_properties['name']: // if control has the same name as the name of the honeypot's name case $this->form_properties['honeypot']: // if control has the same name as the name of the field containing the CSRF token case $this->form_properties['csrf_token_name']: // if control has the name "submit" case 'submit': // stop the execution of the script _zebra_form_show_error('You are not allowed to have a control named "' . $attributes['name'] . '" in form "' . $this->form_properties['name'] . '"', E_USER_ERROR); break; } // if control name is not allowed because it looks like the automatically generated controls for