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;
}
}