Zebra_Form::render PHP Method

render() public method

@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!
public render ( string $template = '', boolean $return = false, array $variables = '' ) : mixed
$template string
$return boolean (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.
$variables array
return mixed
    function render($template = '', $return = false, $variables = '')
    {
        // if
        if (!file_exists($this->form_properties['assets_server_path'] . 'process.php') || !file_exists($this->form_properties['assets_server_path'] . 'mimes.json')) {
            // it means the most probably the script is run on a virtual host and that paths need to be set manually so
            // we inform the user about that
            _zebra_form_show_error('<strong>Zebra_Form</strong> could not automatically determine the correct path to the "process.php"
            and "mimes.json" files - this may happen if the script is run on a virtual host. To fix this, use the <u>assets_path()</u>
            method and manually set the correct <strong>server path</strong> and <strong>URL</strong> to these file!', E_USER_ERROR);
        }
        // if variables is an array
        if (is_array($variables)) {
            // iterate through the values in the array
            foreach ($variables as $name => $value) {
                // make each value available in the template
                $this->assign($name, $value);
            }
        }
        // start generating the output
        $output = '<form ' . ($this->form_properties['doctype'] == 'html' ? 'name="' . $this->form_properties['name'] . '" ' : '') . 'id="' . $this->form_properties['name'] . '" ' . 'action="' . htmlspecialchars($this->form_properties['action']) . '" ' . 'method="' . strtolower($this->form_properties['method']) . '" ' . 'novalidate="novalidate"';
        // if custom classes are to be set for the form
        if (isset($this->form_properties['attributes']['class'])) {
            // add the "Zebra_Form" required class
            $this->form_properties['attributes']['class'] .= ' Zebra_Form';
        } else {
            $this->form_properties['attributes']['class'] = 'Zebra_Form';
        }
        // if any form attributes have been specified
        if (is_array($this->form_properties['attributes'])) {
            // iterate through the form's attributes
            foreach ($this->form_properties['attributes'] as $attribute => $value) {
                // write them
                $output .= ' ' . $attribute . '="' . $value . '"';
            }
        }
        // if the form has file upload controls
        if ($this->form_properties['has_upload'] === true) {
            // add the enctype to the attributes of the <form> tag
            $output .= ' enctype="multipart/form-data"';
            // and add this required hidden field containing the maximum allowed file size
            $this->add('hidden', 'MAX_FILE_SIZE', $this->form_properties['max_file_size']);
            // if client-side validation is not disabled
            if (!$this->form_properties['clientside_validation']['clientside_disabled'] && !$this->form_properties['clientside_validation']['disable_upload_validation']) {
                // add a new property for the client-side validation
                $this->clientside_validation(array('assets_path' => rawurlencode($this->form_properties['assets_url'])));
            }
        }
        $output .= '>';
        // iterate through the form's controls
        foreach ($this->controls as $key => $control) {
            // treat "email" and "number" types as "text"
            if (in_array($control->attributes['type'], array('email', 'number'))) {
                $control->attributes['type'] = 'text';
            }
            // get some attributes for each control
            $attributes = $control->get_attributes(array('type', 'for', 'name', 'id', 'multiple', 'other', 'class', 'default_other', 'disable_zebra_datepicker'));
            // sanitize the control's name
            $attributes['name'] = preg_replace('/\\[\\]/', '', $attributes['name']);
            // validate the control's name
            switch ($attributes['name']) {
                // if control has the same name as the form
                case $this->form_properties['name']:
                    // if control has the same name as the name of the honeypot's name
                // 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
                // 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"
                // if control has the name "submit"
                case 'submit':
                    // stop the execution of the script
                    _zebra_form_show_error('You are not allowed to have a control named "<strong>' . $attributes['name'] . '</strong>" in form "<strong>' . $this->form_properties['name'] . '</strong>"', E_USER_ERROR);
                    break;
            }
            // if control name is not allowed because it looks like the automatically generated controls for <select> controls
            // with the "other" option attached
            if (preg_match('/' . preg_quote($this->form_properties['other_suffix']) . '$/', $attributes['name']) > 0) {
                // stop the execution of the script
                _zebra_form_show_error('You are not allowed to have a control with the name ending in "<strong>' . $this->form_properties['other_suffix'] . '</strong>" in form "<strong>' . $this->form_properties['name'] . '</strong>"', E_USER_ERROR);
            }
            // if control has any rules attached to it
            if (!empty($control->rules)) {
                // if client-side validation is not disabled and this variable not created yet,
                // create the variable holding client-side error messages
                if (!$this->form_properties['clientside_validation']['clientside_disabled'] && !isset($clientside_validation)) {
                    $clientside_validation = array();
                }
                // if we applied the "age" rule to an element not being of "date" type
                if (isset($control->rules['age']) && !array_key_exists('pair', $control->attributes)) {
                    // trigger an error message
                    _zebra_form_show_error('The <strong>age</strong> rule can only be applied to a <strong>date</strong> element', E_USER_ERROR);
                }
                // we need to make sure that rules are in propper order, the order of priority being "dependencies",
                // "required" and "upload"
                // if the upload rule exists
                if (isset($control->rules['upload'])) {
                    // remove it from wherever it is
                    $rule = array_splice($control->rules, array_search('upload', array_keys($control->rules)), 1, array());
                    // and make sure it's the first rule
                    $control->rules = array_merge($rule, $control->rules);
                }
                // if the "required" rule exists
                if (isset($control->rules['required'])) {
                    // remove it from wherever it is
                    $rule = array_splice($control->rules, array_search('required', array_keys($control->rules)), 1, array());
                    // and make sure it's the first rule (it has to be checked prior to the "upload" rule)
                    $control->rules = array_merge($rule, $control->rules);
                }
                // if the "dependencies" rule exists
                if (isset($control->rules['dependencies'])) {
                    // remove it from wherever it is
                    $rule = array_splice($control->rules, array_search('dependencies', array_keys($control->rules)), 1, array());
                    // and make sure it's the first rule (it has to be checked prior to the "required" and "upload" rules)
                    $control->rules = array_merge($rule, $control->rules);
                }
                // iterate through the rules attached to the control
                foreach ($control->rules as $rule => $properties) {
                    // these rules are not checked client side
                    if ($rule == 'captcha' || $rule == 'convert' || $rule == 'resize' || !$this->form_properties['clientside_validation']['clientside_disabled'] && $this->form_properties['clientside_validation']['disable_upload_validation'] && in_array($rule, array('upload', 'filetype', 'filesize', 'image'))) {
                        continue;
                    }
                    // we need to remove the error_block part as it is not needed for client-side validation
                    switch ($rule) {
                        // for these rules
                        case 'age':
                        case 'alphabet':
                        case 'alphanumeric':
                        case 'compare':
                        case 'digits':
                        case 'filesize':
                        case 'filetype':
                        case 'float':
                        case 'number':
                        case 'range':
                        case 'regexp':
                        case 'url':
                            // the error block is the second argument; remove it
                            array_splice($properties, 1, 1);
                            break;
                            // for these rules
                        // for these rules
                        case 'date':
                        case 'email':
                        case 'emails':
                        case 'image':
                        case 'required':
                            // the error block is the first argument; remove it
                            array_splice($properties, 0, 1);
                            break;
                            // for these rules
                        // for these rules
                        case 'datecompare':
                        case 'length':
                        case 'upload':
                            // the error block is the third argument; remove it
                            array_splice($properties, 2, 1);
                            // for the "length" rule
                            if ($rule == 'length' && $properties[1] > 0) {
                                // we also set the "maxlength" attribute of the control
                                $this->controls[$key]->set_attributes(array('maxlength' => $properties[1]));
                            }
                            // fot the "length" rule, for text and textarea elements
                            if ($rule == 'length' && in_array($attributes['type'], array('text', 'textarea')) && $properties[1] > 0) {
                                // make sure the default value, if there is one, is not longer than the maximum allowed length
                                $control->set_attributes(array('value' => mb_substr($control->attributes['value'], 0, $properties[1])));
                            }
                            break;
                            // for the "custom" rule
                        // for the "custom" rule
                        case 'custom':
                            // custom rules are always given as an array of rules
                            // so, iterate over the custom rules
                            foreach ($properties as $index => $values) {
                                // and remove the error block
                                array_splice($properties[$index], -2, 1);
                            }
                            break;
                    }
                    // if client-side validation is not disabled
                    if (!$this->form_properties['clientside_validation']['clientside_disabled']) {
                        // this array will be fed to the JavaScript as a JSON
                        $clientside_validation[$attributes['name']][$rule] = $properties;
                    }
                }
            }
            // if control is a select control, doesn't have the "multiple" attribute set and has the "other" attribute set
            if (isset($attributes['type']) && $attributes['type'] == 'select' && !isset($attributes['multiple']) && isset($attributes['other'])) {
                // set a special class for the select control so that we know that it has a textbox attached to it
                // add a special class to the control
                $this->controls[$key]->set_attributes(array('class' => 'other'), false);
                // add a text control
                $obj =& $this->add('text', $attributes['id'] . $this->form_properties['other_suffix'], $attributes['default_other']);
                // set a special class for the control
                $obj->set_attributes(array('class' => 'other'), false);
                // if the select control was not submitted OR it was submitted but the selected option is other than
                // the "other" option
                if (!isset($control->submitted_value) || $control->submitted_value != 'other') {
                    // hide the text box
                    $obj->set_attributes(array('class' => 'other-invisible'), false);
                }
                // make sure the value in the control propagates
                $obj->get_submitted_value();
                // because we want this control to appear right beneath the select control when the form is auto-generated
                // we need to have it after the select control in the "controls" property
                // as is we just added the control, it means it is at the end of the array
                // we take it off the end of array
                $obj = array_pop($this->controls);
                // find the position of the parent control
                $parent_position = array_search($attributes['name'], array_keys($this->controls));
                // insert the control right after the parent control
                $this->controls = array_slice($this->controls, 0, $parent_position + 1, true) + array($attributes['id'] . $this->form_properties['other_suffix'] => $obj) + array_slice($this->controls, $parent_position + 1, count($this->controls) - $parent_position, true);
            }
            // if control is a label and is a "master" label
            if (isset($attributes['type']) && $attributes['type'] == 'label' && array_key_exists($attributes['for'], $this->master_labels)) {
                // save the "master" label's name
                $this->master_labels[$attributes['for']]['control'] = $attributes['name'];
            }
            // if control is a date control
            if (isset($attributes['type']) && $attributes['type'] == 'text' && preg_match('/\\bdate\\b/i', $attributes['class'])) {
                // if variable is not yet defined. define it
                if (!isset($datepicker_javascript)) {
                    $datepicker_javascript = '';
                }
                // if Zebra_DatePicker is *not* disabled for this control
                if (!$attributes['disable_zebra_datepicker']) {
                    // append the new date picker object
                    $datepicker_javascript .= '$(\'#' . $attributes['id'] . '\').Zebra_DatePicker(';
                    // take day names from the language file
                    $control->attributes['days'] = $this->form_properties['language']['days'];
                    // if we have custom day name abbreviations, use them
                    if (is_array($this->form_properties['language']['days_abbr'])) {
                        $control->attributes['days_abbr'] = $this->form_properties['language']['days_abbr'];
                    }
                    // take month names from the language file
                    $control->attributes['months'] = $this->form_properties['language']['months'];
                    // if we have custom month abbreviations, use them
                    if (is_array($this->form_properties['language']['months_abbr'])) {
                        $control->attributes['months_abbr'] = $this->form_properties['language']['months_abbr'];
                    }
                    // use the caption from the language file for the "Clear date" button
                    $control->attributes['lang_clear_date'] = $this->form_properties['language']['clear_date'];
                    // if the "Today" button is not disabled use the caption from the language file
                    if ($control->attributes['show_select_today'] === null) {
                        $control->attributes['show_select_today'] = $this->form_properties['language']['today'];
                    }
                    $properties = '';
                    // iterate through control's attributes
                    foreach ($control->attributes as $attribute => $value) {
                        // if attribute is an attribute intended for the javascript object and is not null
                        if (in_array($attribute, $control->javascript_attributes) && ($control->attributes[$attribute] !== null || $attribute == 'direction' && $value === false)) {
                            // append to the properties list (we change "inside_icon" to "inside" as "inside" is reserved)
                            $properties .= ($properties != '' ? ',' : '') . ($attribute == 'inside_icon' ? 'inside' : $attribute) . ':';
                            // if value is an array
                            if (is_array($value)) {
                                // assume we don't need to convert this to a JavaScript object
                                $is_object = false;
                                // iterate through all the keys/values
                                foreach ($value as $key => $val) {
                                    // if at least one of the keys is not numeric
                                    if (preg_match('/[^0-9]/', $key) > 0) {
                                        // set this flag to true
                                        $is_object = true;
                                        // don't look further
                                        break;
                                    }
                                }
                                // format accordingly
                                $properties .= $is_object ? '{' : '[';
                                // iterate through the values
                                foreach ($value as $key => $val) {
                                    $properties .= (!is_numeric($key) ? '\'' . $key . '\':' : '') . ($val === true ? 'true' : ($val === false ? 'false' : (is_numeric($val) ? $val : (is_array($val) ? json_encode($val) : '\'' . $val . '\'')))) . ',';
                                }
                                $properties = rtrim($properties, ',') . ($is_object ? '}' : ']');
                                // if value is a jQuery selector
                            } elseif (preg_match('/^\\$\\((\'|\\").*?\\1\\)/', trim($value)) > 0) {
                                // use it as it is
                                $properties .= $value;
                                // if value is a string but is not a javascript object
                            } elseif (is_string($value) && !preg_match('/^\\{.*\\}$/', trim($value))) {
                                // format accordingly and escape single quotes or we'll kill JavaScript
                                $properties .= '\'' . addcslashes($value, '\'') . '\'';
                            } else {
                                // format accordingly
                                $properties .= $value === true ? 'true' : ($value === false ? 'false' : (is_numeric($value) ? $value : '\'' . $value . '\''));
                            }
                        }
                    }
                    $properties .= ',onSelect:function(){$("#' . $this->form_properties['name'] . '").data("Zebra_Form").hide_error("' . $attributes['name'] . '")}';
                    // wrap up the javascript object
                    $datepicker_javascript .= ($properties != '' ? '{' . $properties . '}' : '') . ');';
                    // if Zebra_DatePicker is disabled
                } else {
                    // in order to preserve client-side validation,
                    // we still need to pass some data to it
                    $datepicker_javascript .= '$(\'#' . $attributes['id'] . '\').data("Zebra_DatePicker", new Object({settings: {days: ["' . implode('","', $this->form_properties['language']['days']) . '"], months: ["' . implode('","', $this->form_properties['language']['months']) . '"], format: "' . $control->attributes['format'] . '"}}));';
                }
            }
            // if control has the "filetype" rule set, load MIME types now
            if (isset($control->rules['filetype']) || isset($control->rules['image'])) {
                $this->_load_mime_types();
            }
        }
        // we add automatically this hidden control to the form, used to know that the form was submitted
        $this->add('hidden', $this->form_properties['identifier'], $this->form_properties['name']);
        // add a "honeypot" - a text field that we'll use to try and prevent spam-bots
        // this field will be hidden from users and we expect only spam-bots to fill it. if this field will not be empty
        // when submitting the form, we'll consider that the form was submitted by a spam-bot
        $this->add('text', $this->form_properties['honeypot'], '', array('autocomplete' => 'off'));
        // if CSRF protection is enabled (is not boolean FALSE)
        if ($this->form_properties['csrf_storage_method'] !== false) {
            // add a hidden field to the form, containing the random token
            // (we will later compare the value in this field with the value in the associated session/cookie)
            $this->add('hidden', $this->form_properties['csrf_token_name'], $this->form_properties['csrf_token']);
        }
        // start rendering the form's hidden controls
        $output .= '<div class="hidden">';
        // iterate through the controls assigned to the form, looking for hidden controls
        // also, use this opportunity to see which labels are attached to "required" controls
        foreach ($this->controls as $key => $control) {
            // get some attributes for each control
            $attributes = $control->get_attributes(array('type', 'for', 'name', 'label', 'inside'));
            // sanitize the control's name
            $attributes['name'] = preg_replace('/\\[\\]$/', '', $attributes['name']);
            // if control is a "hidden" control
            if ($attributes['type'] == 'hidden') {
                // append the hidden control to the hidden control's block
                $output .= $control->toHTML();
                // take the control out of the controls array because we don't have to bother with it anymore
                unset($this->controls[$key]);
                // if control is a text field and is the control intended for the "honeypot"
            } elseif ($attributes['type'] == 'text' && $attributes['name'] == $this->form_properties['honeypot']) {
                // because http://www.w3.org/WAI/GL/WCAG20/WD-WCAG20-TECHS/html.html#H44 requires it,
                // attach a label to the control
                $output .= '<label for="' . $this->form_properties['honeypot'] . '" style="display:none">Leave this field blank</label>';
                // append the control to the hidden control's block (it will not be visible)
                $output .= $control->toHTML();
                // take the control out of the controls array so we don't have to bother with it anymore
                unset($this->controls[$key]);
                // if
            } elseif ($attributes['type'] == 'label' && isset($attributes['for']) && (array_key_exists($attributes['for'], $this->master_labels) || array_key_exists($attributes['for'], $this->controls))) {
                // if the label is attached to an existing control
                if (array_key_exists($attributes['for'], $this->controls)) {
                    // get some attributes of the control the label is attached to
                    $ctrl_attributes = $this->controls[$attributes['for']]->get_attributes(array('name', 'id', 'type'));
                    // sanitize the control's name
                    $ctrl_attributes['name'] = preg_replace('/\\[\\]$/', '', $ctrl_attributes['name']);
                    // if
                    if (isset($attributes['inside']) && $attributes['inside'] === true && (isset($ctrl_attributes['type']) && ($ctrl_attributes['type'] == 'text' || $ctrl_attributes['type'] == 'textarea' || $ctrl_attributes['type'] == 'password'))) {
                        // set some extra attributes for the control the label is attached to
                        $this->controls[$attributes['for']]->set_attributes(array('title' => $attributes['label']));
                        // set some extra attributes for the control the label is attached to
                        $this->controls[$attributes['for']]->set_attributes(array('class' => 'inside'), false);
                        // if the control the label is attached to a radio button or a checkbox
                    } elseif ($ctrl_attributes['type'] == 'radio' || $ctrl_attributes['type'] == 'checkbox') {
                        // set a specific class for the label control
                        $control->set_attributes(array('class' => 'option'));
                    }
                }
                // if the control the label is attached to has the "disabled" attribute set
                if (isset($this->controls[$attributes['for']]->attributes['disabled'])) {
                    // set a special class for the label
                    $control->set_attributes(array('class' => 'disabled'), false);
                }
                // if the control the label is attached to, has the "required" rule set
                if (isset($this->controls[$attributes['for']]->rules['required'])) {
                    // if
                    if (array_key_exists($ctrl_attributes['name'], $this->master_labels) && isset($this->controls[$this->master_labels[$ctrl_attributes['name']]['control']])) {
                        // if asterisk is not already attached
                        if (strpos($this->controls[$this->master_labels[$ctrl_attributes['name']]['control']]->attributes['label'], '<span class="required">*</span>') === false) {
                            // attach the asterisk to the "master" label instead rather than to the current label
                            $this->controls[$this->master_labels[$ctrl_attributes['name']]['control']]->set_attributes(array('label' => $this->controls[$this->master_labels[$ctrl_attributes['name']]['control']]->attributes['label'] . '<span class="required">*</span>'));
                        }
                        // otherwise
                    } else {
                        // attach the asterisk to the current label
                        $this->controls[$key]->set_attributes(array('label' => $attributes['label'] . '<span class="required">*</span>'));
                    }
                }
                // if
            } elseif ($attributes['type'] == 'label' && isset($attributes['for']) && !array_key_exists($attributes['for'], $this->master_labels) && !array_key_exists($attributes['for'], $this->controls) && ($template != '' && $template != '*horizontal' && $template != '*vertical')) {
                unset($this->controls[$key]->attributes['for']);
            }
        }
        // finish building the hidden controls block
        $output .= '</div>';
        // if there are any error messages
        if (!empty($this->errors)) {
            // iterate through each error block
            foreach ($this->errors as $error_block => $error_messages) {
                $content = '';
                // iterate through each message of the error block
                foreach ($error_messages as $error_message) {
                    // render each message in block
                    $content .= '<span>' . $error_message . '</span>';
                    // if only one error message is to be show
                    // break out from the foreach loop
                    if ($this->form_properties['show_all_error_messages'] === false) {
                        break;
                    }
                }
                // switch the array entry with it's rendered form
                $this->errors[$error_block] = '<div class="error"><div class="container">' . $content . '<div class="close"><a href="javascript:void(0)">close</a></div></div></div>';
            }
        }
        // if there are any SPAM or CSRF errors
        if (isset($this->errors['zf_error_spam']) || isset($this->errors['zf_error_csrf'])) {
            // if there's a CSRF error leave only that
            if (isset($this->errors['zf_error_csrf'])) {
                $this->errors['zf_error'] = $this->errors['zf_error_csrf'];
            } else {
                $this->errors['zf_error'] = $this->errors['zf_error_spam'];
            }
            // remove these two errors
            // as now there will be only one, under the name of "zf_error"
            unset($this->errors['zf_error_csrf']);
            unset($this->errors['zf_error_spam']);
        }
        // if output is to be auto-generated
        if ($template == '' || $template == '*horizontal' || $template == '*vertical') {
            $error_messages = '';
            // iterate through any existing error blocks
            // and render them at the top of the auto-generated output
            foreach ($this->errors as $errors) {
                $error_messages .= $errors;
            }
            // group controls in master-label/control/label/note
            // so, every block looks like
            // - master label (if any)
            // - label (if any)
            // - control
            // - (in case of radio buttons and checkboxes the above two may be repeated)
            // - note (if any)
            $blocks = array();
            // iterate through the form's controls
            foreach ($this->controls as $key => $control) {
                // get some attributes for the control
                $attributes = $control->get_attributes(array('type', 'name', 'id', 'for', 'inside'));
                // if control is a label that is to be placed inside another control, we skip it
                if ($attributes['type'] == 'label' && isset($attributes['inside'])) {
                    continue;
                }
                // sanitize control's name
                $attributes['name'] = preg_replace('/\\[\\]$/', '', $attributes['name']);
                // if the control is a text box that is to be shown when user selects "other" in a select control
                if (preg_match('/(.*)' . preg_quote($this->form_properties['other_suffix']) . '$/', $attributes['name'], $matches) > 0) {
                    // the parent is the control to which the control is attached to
                    $parent = $matches[1];
                } else {
                    // check the control's type
                    switch ($attributes['type']) {
                        // if control is captcha, label, or note
                        case 'captcha':
                        case 'label':
                        case 'note':
                            // save the control the current control is attached to
                            $parent = $attributes['for'];
                            // if
                            if (isset($this->controls[$parent]) && ($this->controls[$parent]->attributes['type'] == 'checkbox' || $this->controls[$parent]->attributes['type'] == 'radio') && $this->controls[$parent]->attributes['id'] != $this->controls[$parent]->attributes['name']) {
                                // save both the "master" parent and, separated by a dot, the actual parent
                                $parent = preg_replace('/\\[\\]$/', '', $this->controls[$parent]->attributes['name']) . '.' . $parent;
                            } elseif ($attributes['type'] == 'label' && !isset($this->controls[$parent])) {
                                // remove the "for" attribute so that the form will pass the W3C validation
                                unset($this->controls[$key]->attributes['for']);
                            }
                            break;
                            // for any other controls
                        // for any other controls
                        default:
                            // the parent is the control itself
                            $parent = $attributes['name'];
                    }
                }
                // as some controls (labels for checkboxes) can have multiple parent - the checkbox control and a master
                // label - and multiple parents are separated by a dot, explode by dot
                $parents = explode('.', $parent);
                // iterate through the control's parents
                foreach ($parents as $key => $parent) {
                    // if first entry
                    // make $array a pointer to the $blocks array
                    if ($key == 0) {
                        $array =& $blocks;
                    }
                    // if the parent control doesn't have its own key in the array
                    // (it may still be in the array but not as a "parent")
                    if (!isset($array[$parent])) {
                        // initialize the entry
                        $array[$parent] = array();
                        // if we already have the entry but not as a key
                        if (($pos = array_search($parent, $array)) !== false) {
                            // insert it in the newly created entry
                            $array[$parent][] = $array[$pos];
                            // and remove it from the old position
                            unset($array[$pos]);
                        }
                    }
                    // make $array a pointer
                    $array =& $array[$parent];
                    // if we're at the last parent
                    if ($key == count($parents) - 1) {
                        // if control already exists in the parent's array as a key (remember that $array is a pointer!)
                        if (array_key_exists($attributes['id'], $array)) {
                            // add the control to the array
                            $array[$attributes['id']][] = $attributes['id'];
                        } else {
                            // add the control to the array
                            $array[] = $attributes['id'];
                        }
                    }
                }
            }
            // if auto-generated output needs to be horizontal
            if ($template == '*horizontal') {
                // the output will be enclosed in a table
                $contents = '<table>';
                // if there are errors to be displayed
                if ($error_messages != '') {
                    // show the error messages
                    $contents .= '<tr><td colspan="2">' . $error_messages . '</td></tr>';
                }
                // keep track of odd/even rows
                $counter = 0;
                // total number of rows to be displayed
                $rows = count($blocks);
                // iterate through blocks
                foreach ($blocks as $controls) {
                    ++$counter;
                    // each block is in its own row
                    $contents .= '<tr class="row' . ($counter % 2 == 0 ? ' even' : '') . ($counter == $rows ? ' last' : '') . '">';
                    // the first cell will hold the label (if any)
                    $contents .= '<td>';
                    // as of PHP 5.3, array_shift required the argument to be a variable and not the result
                    // of a function so we need this intermediary step
                    $labels = array_values($controls);
                    // retrieve the first item in the block
                    $label = array_shift($labels);
                    // item is a label
                    if (!is_array($label) && $this->controls[$label]->attributes['type'] == 'label') {
                        // remove it from the block
                        array_shift($controls);
                        // render it
                        $contents .= $this->controls[$label]->toHTML();
                    }
                    // close the table cell
                    $contents .= '</td>';
                    // the second cell contains the actual controls
                    $contents .= '<td>';
                    // iterate through the controls to be rendered
                    foreach ($controls as $control) {
                        // if array of controls
                        // (radio buttons/checkboxes and their labels)
                        if (is_array($control)) {
                            // iterate through the array's items
                            foreach ($control as $ctrl) {
                                // and display them on the same line
                                $contents .= '<div class="cell">' . $this->controls[$ctrl]->toHTML() . '</div>';
                            }
                            // clear floats
                            $contents .= '<div class="clear"></div>';
                            // if not an array of controls
                        } else {
                            // if control is required but has the label as a tip inside the control
                            // we need to manually add the asterisk after the control
                            if (array_key_exists('required', $this->controls[$control]->rules) && preg_match('/\\binside\\b/', $this->controls[$control]->attributes['class'])) {
                                // first, make sure the control is inline so that the asterisk will be placed to the right of the control
                                $this->controls[$control]->set_attributes(array('class' => 'inline'), false);
                                // add the required symbol after the control
                                $contents .= $this->controls[$control]->toHTML() . '<span class="required">*</span>';
                                // else, render the control
                            } else {
                                $contents .= $this->controls[$control]->toHTML();
                            }
                        }
                    }
                    // close the cell
                    $contents .= '</td>';
                    // add a "separator" row
                    $contents .= '</tr>';
                }
                // finish rendering the table
                $contents .= '</table>';
                // if auto-generated output needs to be vertical
            } else {
                $contents = '';
                // if there are errors to be displayed, show the error messages
                if ($error_messages != '') {
                    $contents .= $error_messages;
                }
                $counter = 0;
                // total number of rows to be displayed
                $rows = count($blocks);
                // iterate through blocks
                foreach ($blocks as $controls) {
                    // ...then block is contained in its own row
                    $contents .= '<div class="row' . (++$counter % 2 == 0 ? ' even' : '') . ($counter == $rows ? ' last' : '') . '">';
                    // iterate through the controls to be rendered
                    foreach ($controls as $control) {
                        // if array of controls
                        // (radio buttons/checkboxes and their labels)
                        if (is_array($control)) {
                            // iterate through the array's items
                            foreach ($control as $ctrl) {
                                // and display them on the same line
                                $contents .= '<div class="cell">' . $this->controls[$ctrl]->toHTML() . '</div>';
                            }
                            // clear floats
                            $contents .= '<div class="clear"></div>';
                            // if not an array of controls
                        } else {
                            // if control is required but has the label as a tip inside the control
                            // we need to manually add the asterisk after the control
                            if (array_key_exists('required', $this->controls[$control]->rules) && preg_match('/\\binside\\b/', $this->controls[$control]->attributes['class'])) {
                                // first, make sure the control is inline so that the asterisk will be placed to the right of the control
                                $this->controls[$control]->set_attributes(array('class' => 'inline'), false);
                                // add the required symbol after the control
                                $contents .= $this->controls[$control]->toHTML() . '<span class="required">*</span>';
                                // else, render the control
                            } else {
                                $contents .= $this->controls[$control]->toHTML();
                            }
                        }
                    }
                    // ...finish rendering
                    $contents .= '</div>';
                }
            }
            // if a function with the name given as $template, and the function exists
        } elseif (is_callable($template)) {
            // this variable will contain all the rendered controls
            $controls = array();
            // iterate through the controls assigned to the form
            foreach ($this->controls as $control) {
                // read some attributes of the control
                $attributes = $control->get_attributes(array('id', 'inside'));
                // render the control if the control is not a label that is to be displayed inside the control as it's
                // default value
                if (!isset($attributes['inside'])) {
                    // if control is required but has the label as a tip inside the control
                    // we need to manually add the asterisk after the control
                    if (array_key_exists('required', $control->rules) && preg_match('/\\binside\\b/', $control->attributes['class'])) {
                        // first, make sure the control is inline so that the asterisk will be placed to the right of the control
                        $control->set_attributes(array('class' => 'inline'), false);
                        // add the required symbol after the control
                        // and add generated HTML code to the $controls array
                        $controls[$attributes['id']] = $control->toHTML() . '<span class="required">*</span>';
                        // otherwise, add generated HTML code to the $controls array
                    } else {
                        $controls[$attributes['id']] = $control->toHTML();
                    }
                }
            }
            // iterate through the variables assigned to the form
            foreach ($this->variables as $variable_name => $variable_value) {
                // make available the assigned variables
                $controls[$variable_name] = $variable_value;
            }
            // iterate through the error messages(if any)
            foreach ($this->errors as $error_block => $error_message) {
                // make the error message available
                $controls[$error_block] = $error_message;
            }
            // let the custom function generate the output
            // we're passing two arguments
            // an associative array with control ids and their respectively generated HTML
            // and an array with all the form's objects
            $contents = call_user_func_array($template, array($controls, &$this->controls));
            // if a template was specified
        } else {
            // this variable will contain all the rendered controls
            $controls = array();
            // iterate through the controls assigned to the form
            foreach ($this->controls as $control) {
                // read some attributes of the control
                $attributes = $control->get_attributes(array('id', 'inside'));
                // render the control if the control is not a label that is to be displayed inside the control as it's
                // default value
                if (!isset($attributes['inside'])) {
                    // if control is required but has the label as a tip inside the control
                    // we need to manually add the asterisk after the control
                    if (array_key_exists('required', $control->rules) && preg_match('/\\binside\\b/', $control->attributes['class'])) {
                        // first, make sure the control is inline so that the asterisk will be placed to the right of the control
                        $control->set_attributes(array('class' => 'inline'), false);
                        // add the required symbol after the control
                        // and add generated HTML code to the $controls array
                        $controls[$attributes['id']] = $control->toHTML() . '<span class="required">*</span>';
                        // otherwise, add generated HTML code to the $controls array
                    } else {
                        // if element name was given as an array (i.e. car[name])
                        if (preg_match('/^([^\\[]+)\\[([^\\]]+)\\]$/', $attributes['id'], $matches)) {
                            // assign the variable accordingly
                            $controls[$matches[1]][$matches[2]] = $control->toHTML();
                        } else {
                            $controls[$attributes['id']] = $control->toHTML();
                        }
                    }
                }
            }
            //start output buffering
            ob_start();
            // make available in the template all the form's objects
            $controls[$this->form_properties['name']] =& $this->controls;
            // make the user-defined variables available in the template file as PHP variables
            extract($this->variables);
            // make the rendered controls available in the template file as PHP variables
            extract($controls);
            // make the error messages available in the template file as PHP variables
            extract($this->errors);
            // include the template file
            include $template;
            // put the parsed content in a variable
            $contents = ob_get_contents();
            // clean buffers
            ob_end_clean();
        }
        // finish building the output
        $output = $output . $contents . '</form>';
        // this will hold the properties to be set for the JavaScript object
        $javascript_object_properties = '';
        // if client-side validation is disabled
        if ($this->form_properties['clientside_validation']['clientside_disabled']) {
            // we still need to execute a function attached to the "on_ready" event (if any)
            $javascript_object_properties .= 'on_ready:' . ($this->form_properties['clientside_validation']['on_ready'] === false ? 'false' : $this->form_properties['clientside_validation']['on_ready']);
            // so we don't break the JavaScript code, we need this, too
            $javascript_object_properties .= ',validation_rules:{}';
            // if client-side validation is not disabled
        } else {
            // iterate through the properties
            foreach ($this->form_properties['clientside_validation'] as $key => $value) {
                // save property
                $javascript_object_properties .= ($javascript_object_properties != '' ? ',' : '') . $key . ':' . ($value === true ? 'true' : ($value === false ? 'false' : ($key == 'on_ready' ? $value : '\'' . $value . '\'')));
            }
            // save information about validation rules
            $javascript_object_properties .= ($javascript_object_properties != '' ? ',' : '') . 'validation_rules:' . (isset($clientside_validation) ? json_encode($clientside_validation) : '{}');
        }
        // function name for initializing client-side validation
        $function_name = 'init_' . md5(strtolower($this->form_properties['name'] . microtime()));
        $output .= '<script type="text/javascript">function ' . $function_name . '(){if(typeof jQuery=="undefined"||typeof jQuery.fn.Zebra_Form=="undefined"' . (isset($datepicker_javascript) ? '|| jQuery.fn.Zebra_DatePicker=="undefined"' : '') . '){setTimeout("' . $function_name . '()",100);return}else{' . '$(document).ready(function(){' . (isset($datepicker_javascript) ? $datepicker_javascript : '') . '$("#' . $this->form_properties['name'] . '").Zebra_Form(' . ($javascript_object_properties != '' ? '{' . $javascript_object_properties . '}' : '') . ')})}}' . $function_name . '()</script>';
        // if $return argument was TRUE, return the result
        if ($return) {
            return $output;
        } else {
            echo $output;
        }
    }

Usage Example

function page_add_bookmark()
{
    $form = new Zebra_Form('form');
    $form->clientside_validation(array('close_tips' => true, 'on_ready' => false, 'disable_upload_validation' => true, 'scroll_to_error' => false, 'tips_position' => 'right', 'validate_on_the_fly' => true, 'validate_all' => true));
    $form->add('label', 'label_url', 'url', 'URL');
    $url = $form->add('text', 'url', 'http://');
    $url->set_rule(array('required' => array('url_error', 'URL musí být vyplněno.'), 'url' => array(true, 'url_error', 'Pole musí obsahovat platné URL (včetně protokolu).')));
    $form->add('label', 'label_title', 'title', 'Název stránky');
    $title = $form->add('text', 'title', '');
    $title->set_rule(array('required' => array('title_error', 'Název musí být vyplněn.')));
    $form->add('submit', 'submitbtn', 'Přidat');
    if ($form->validate()) {
        $ok = model_add($_POST['url'], $_POST['title'], array());
        if ($ok) {
            flash('info', 'Záložka byla vytvořena');
        } else {
            flash('error', 'Záložku se nepodařilo vytvořit.');
        }
        redirect_to('/');
    }
    // set('form', $form->render('views/add_form.php', true));
    set('form', $form->render('', true));
    set('title', 'Nová záložka');
    return html('add.html.php');
}
All Usage Examples Of Zebra_Form::render