From 7e1527ee61bc10b3765b95b9af8faaa2254da5a8 Mon Sep 17 00:00:00 2001 From: Dries Buytaert Date: Fri, 7 Oct 2005 06:11:12 +0000 Subject: - Patch #29465: new form API by Adrian et al. TODO: + The contact.module was broken; a new patch for contact.module is needed. + Documentation is needed. + The most important modules need to be updated ASAP. --- includes/form.inc | 1078 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1078 insertions(+) create mode 100644 includes/form.inc (limited to 'includes/form.inc') diff --git a/includes/form.inc b/includes/form.inc new file mode 100644 index 000000000..0408db883 --- /dev/null +++ b/includes/form.inc @@ -0,0 +1,1078 @@ + 'Display Value' + * + * It is possible to group options together; to do this, change the format of + * $options to an associative array in which the keys are group labels, and the + * values are associative arrays in the normal $options format. + */ +define('options', '_options'); + +/** + * Keyword: extra + * Used: select boxes. + * Description: Additional HTML to inject into the select element tag. + */ +define('extra', '_extra'); + +/** + * Keyword: multiple + * Used: select boxes. + * Description: Wether the user may select more than one item. + */ +define('multiple', '_multiple'); + +/** + * Keyword: button_type + * Used: buttons. + * Description: The type of button to display (cancel or submit) + */ +define('button_type', '_button_type'); + + + +/** + * Keyword: error + * Used: All visible form elements. + * Description: Wether or not a form element has been flagged as having an error. + */ +define('error', '_error'); + +/** + * Keyword: prefix + * Used: markup element. + * Description: Text to include before the value and children properties. + */ +define('prefix', '_prefix'); + +/** + * Keyword: suffix + * Used: markup element. + * Description: Text to include after the value and children properties. + */ +define('suffix', '_suffix'); + +/** + * Keyword: error + * Used: weight form element. + * Description: Number of weights to have selectable. + */ +define('delta', '_delta'); + +/** + * Multiple elements. For use in the poll module, and for file uploads. + */ + +/** + * Keyword : process + * Used : By any element, used to modify a form element. + */ +define('process', '_process'); + +/** + * Keyword: multiple + */ +define('multiple', '_multiple'); + +/** + * Keyword: min + */ +define('minimum', '_minimum'); + +/** + * Keyword: max + */ +define('maximum', '_maximum'); + +/** + * Keyword: increment + */ +define('increment', '_increment'); + +define('spawned', '_spawned'); + +define('tree', '_tree'); + +define('token', '_token'); + +define('execute', '_execute'); + +/** + * Check if the key is a property. + */ +function element_property($key) { + return (substr($key, 0, 1) == '_'); +} + +function element_properties($element) { + return array_filter(array_keys($element), 'element_property'); +} + +/** + * Check if the key is a child. + */ +function element_child($key) { + return (substr($key, 0, 1) != '_'); +} + +function element_children($element) { + return array_filter(array_keys($element), 'element_child'); +} + +/** + * Processes a form array, and produces the HTML output of a form. + * If there is input in the $_POST['edit'] variable, this function + * will attempt to validate it, using drupal_validate_form, + * and then execute the form using drupal_execute_form. + * + * @param $form_id + * A unique string identifying the form. Allows each form to be themed. + * @param $form + * An associative array containing the structure of the form. + * @param $callback + * An optional callback that will be used in addition to the form_id. + * + */ +function drupal_get_form($form_id, &$form, $callback = NULL) { + global $form_values, $form_execute; + $form_values = array(); + $form_execute = FALSE; + $form[type] = 'form'; + $form[attributes]['class'] .= ' form-api'; + if (isset($form[token])) { + $form['form_token'] = array(type => 'hidden', value => md5($_SERVER['REMOTE_ADDR'] . $form[token] . variable_get('drupal_private_key', ''))); + } + $form = array_merge(_element_info('form'), $form); + + foreach (module_implements('form_alter') as $module) { + $function = $module .'_form_alter'; + $function($form); + } + + $function = $form_id . '_alter'; + if (function_exists($function)) { + $function($form); + } + + if (!$form[built]) { + $form = _form_builder($form); + } + if ($form_execute) { + drupal_execute_form($form_id, $form, $callback); + } + + if (function_exists('theme_' . $form_id)) { + $form[theme] = $form_id; + } + elseif (function_exists('theme_' . $callback)) { + $form[theme] = $callback; + } + return form_render($form); +} + +function drupal_validate_form($form_id, &$form, $callback = NULL) { + global $form_values; + + if (isset($form[token])) { + if ($form_values['form_token'] != md5($_SERVER['REMOTE_ADDR'] . $form[token] . variable_get('drupal_private_key', ''))) { + // setting this error will cause the form to fail validation + form_set_error('form_token', t('Validation error, please try again. If this error persists, please contact the site administrator.')); + } + } + + foreach (module_implements('form_validate_alter') as $module) { + $function = $module .'_form_validate_alter'; + $function($form_id, $form_values); + } + + _form_validate($form); + + if (function_exists($form_id . '_validate')) { + call_user_func($form_id . '_validate', $form_id, $form_values); + } + if (function_exists($callback . '_validate')) { + call_user_func($callback . '_validate', $form_id, $form_values); + } +} + +function drupal_execute_form($form_id, $form, $callback = NULL) { + global $form_values; + if (!empty($_POST['edit'])) { + drupal_validate_form($form_id, $form, $callback); + if (!form_get_errors()) { + foreach (module_implements('form_execute_alter') as $module) { + $function = $module .'_form_execute_alter'; + $function($form_id, $form_values); + } + + if (function_exists($form_id . '_execute')) { + call_user_func($form_id . '_execute', $form_id, $form_values); + } + elseif (function_exists($callback . '_execute')) { + call_user_func($callback . '_execute', $form_id, $form_values); + } + } + } +} + +function _form_validate(&$elements) { + + // Recurse through all children. + foreach (element_children($elements) as $key) { + _form_validate($elements[$key]); + } + + /* Validate the current input */ + if (!$elements[validated] && $elements[input]) { + if ($elements[required]) { + if (!$elements[value]) { + form_error($elements, t('%name field is required', array('%name' => $elements[title]))); + } + if ($elements[valid]) { + if (is_array($elements[valid])) { + foreach ($elements[valid] as $key => $valid) { + $args = is_array($elements[validation_arguments][$key]) ? $elements[validation_arguments][$key] : array(); + if (function_exists('valid_' . $valid)) { + call_user_func_array('valid_' . $valid, array_merge(array($elements), $args)); + } + } + } + else { + $args = is_array($elements[validation_arguments]) ? $elements[validation_arguments] : array(); + if (function_exists('valid_' . $elements[valid])) { + call_user_func_array('valid_' . $elements[valid], array_merge(array($elements), $args)); + } + } + + } + } + $elements[validated] = TRUE; + } +} + +/** + * Flag an element as having an error. + */ +function form_error(&$element, $message) { + $element[error] = TRUE; + $GLOBALS['form'][$element[name]] = $message; + drupal_set_message($message, 'error'); +} + + +/** + * Adds some required properties to each form element, which are used internally in the form api. + * This function also automatically assigns the value property from the $edit array, provided the + * element doesn't already have an assigned value. + */ +function _form_builder($form, $parents = array(), $multiple = FALSE) { + global $form_values; + global $form_execute; + + if ($form[built] == TRUE) { + return $form; + } + $form[built] = TRUE; + + $form[parents] = ($form[parents]) ? $form[parents] : $parents; + /* Use element defaults */ + if ((!empty($form[type])) && ($info = _element_info($form[type]))) { + $form += $info; + } + + if ($form[input]) { + if (!$form[tree]) { + $form[parents] = array(array_pop($form[parents])); + } + + $form[name] = ($form[name]) ? $form[name] : 'edit[' . implode('][', $form[parents]) . ']'; + $form[id] = ($form[id]) ? $form[id] : 'edit-' . implode('-', $form[parents]); + + $posted = isset($_POST['edit']); + $edit = $posted ? $_POST['edit'] : array(); + $ref =& $form_values; + foreach ($form[parents] as $parent) { + $edit = isset($edit[$parent]) ? $edit[$parent] : NULL; + $ref =& $ref[$parent]; + } + $default_value = $posted ? $edit : $form[default_value]; + $form[value] = $form[value] ? $form[value] : $default_value; + if (isset($form[execute])) { + if ($_POST[$form[name]] == $form[value]) { + $form_execute = $form_execute || $form[execute]; + } + } + + $ref = $form[value]; + } + + // Allow for elements to expand to multiple elements. Radios, checkboxes and files for instance. + if (function_exists($form[process]) && !$form[processed]) { + $form = call_user_func($form[process], $form); + $form[processed] = TRUE; + } + + // Recurse through all child elements. + $count = 0; + foreach (element_children($form) as $key) { + $form[$key][tree] = (isset($form[$key][tree])) ? $form[$key][tree] : $form[tree]; + # Assign a decimal placeholder weight, to preserve original array order + $form[$key][weight] = $form[$key][weight] ? $form[$key][weight] : $count/10; + $form[$key] = _form_builder($form[$key], array_merge($form[parents], array($key))); + $count++; + } + + + return $form; +} + +/** + * Renders a HTML form given an form tree. Recursively iterates over each of + * each of the form elements generating HTML code. This function is usually + * called from within a theme. To render a form from within a module, use + * drupal_get_form(). + * + * @param $elements + * The form tree describing the form. + * @return + * The rendered HTML form. + */ +function form_render(&$elements) { + $content = ''; + if (is_array($elements)) { + uasort($elements, "_form_sort"); + } + + if (!$elements[children]) { + /* render all the children using a theme function */ + if ($elements[theme] && !$elements[theme_used]) { + $elements[theme_used] = TRUE; + $previous_type = $elements[type]; + $elements[type] = 'markup'; + $content = theme($elements[theme], $elements); + $elements[type] = $previous_type; + } + /* render each of the children using form_render and concatenate them */ + if (!$content) { + foreach (element_children($elements) as $key) { + $content .= form_render($elements[$key]); + } + } + } + if ($content) { + $elements[children] = $content; + } + + /* Call the form element renderer */ + if (!$elements[printed]) { + $content = theme(($elements[type]) ? $elements[type]: 'markup', $elements); + $elements[printed] = TRUE; + } + + return $elements[prefix] . $content . $elements[suffix]; +} + +function _print_rp($var) { + echo "
";
+  print_r($var);
+  echo "
"; +} + +/** + * Function used by uasort in form render to sort form via weight. + */ +function _form_sort($a, $b) { + if ($a[weight] == $b[weight]) { + return 0; + } + return ($a[weight] < $b[weight]) ? -1 : 1; +} + +/** + * Retrieve the default properties for the defined element type. + */ +function _element_info($type, $refresh = null) { + static $cache; + $basic_defaults = array( + description => NULL, + attributes => array(), + required => FALSE, + tree => FALSE + ); + if ($refresh || !is_array($cache)) { + $cache = array(); + foreach (module_implements('elements') as $module) { + $elements = module_invoke($module, 'elements'); + if (is_array($elements)) { + $cache = array_merge($cache, $elements); + } + } + if (sizeof($cache)) { + foreach ($cache as $element_type => $info) { + $cache[$element_type] = array_merge($basic_defaults, $info); + } + } + } + + return $cache[$type]; +} + +/** + * Format a dropdown menu or scrolling selection box. + * + * @param $element + * An associative array containing the properties of the element. + * Properties used : title, value, options, description, extra, multiple, required + * @return + * A themed HTML string representing the form element. + * + * It is possible to group options together; to do this, change the format of + * $options to an associative array in which the keys are group labels, and the + * values are associative arrays in the normal $options format. + */ +function theme_select($element) { + $select = ''; + foreach ($element[options] as $key => $choice) { + if (is_array($choice)) { + $select .= ''; + foreach ($choice as $key => $choice) { + $select .= ''; + } + $select .= ''; + } + else { + $select .= ''; + } + } + return theme('form_element', $element[title], '', $element[description], $element[name], $element[required], _form_get_error($element[name])); +} + +/** + * Format a group of form items. + * + * @param $element + * An associative array containing the properties of the element. + * Properties used : attributes, title, description, children, collapsible, collapsed + * @return + * A themed HTML string representing the form item group. + */ +function theme_fieldset($element) { + if ($element[collapsible]) { + drupal_add_js('misc/collapse.js'); + + $element[attributes]['class'] .= ' collapsible'; + if ($element[collapsed]) { + $element[attributes]['class'] .= ' collapsed'; + } + } + + return '' . ($element[title] ? ''. $element[title] .'' : '') . $element[children] . $element[value] . ($element[description] ? '
'. $element[description] .'
' : '') . "\n"; + +} + + +/** + * Format a radio button. + * + * @param $element + * An associative array containing the properties of the element. + * Properties used : required, return_value, value, attributes, title, description + * @return + * A themed HTML string representing the form item group. + */ +function theme_radio($element) { + $output = ''; + if (!is_null($element[title])) { + $output = ''; + } + return theme('form_element', NULL, $output, $element[description], $element[name], $element[required], _form_get_error($element[name])); +} + +/** + * Format a set of radio buttons. + * + * @param $element + * An associative array containing the properties of the element. + * Properties used : title, value, options, description, required and attributes. + * @return + * A themed HTML string representing the radio button set. + */ +function theme_radios($element) { + if ($element[title] || $element[description]) { + return theme('form_element', $element[title], $element[children], $element[description], $element[id], $element[required], _form_get_error($element[name])); + } + else { + return $element[children]; + } +} + +/** + * Format a set of radio buttons. + * + * @param $element + * An associative array containing the properties of the element. + * Properties used : title, value, options, description, required and attributes. + * @return + * A themed HTML string representing the radio button set. + */ +function theme_date($element) { + $output = '
' . $element[children] . '
'; + return theme('form_element', $element[title], $output, $element[description], $element[id], $element[required], _form_get_error($element[name])); +} + +/** + * Roll out a single checkbox element to a list of checkboxes, using the options array as index. + */ +function expand_date($element) { + // Default to current date + if (!isset($element[value])) { + $element[value] = array('day' => format_date(time(), 'custom', 'j'), + 'month' => format_date(time(), 'custom', 'n'), + 'year' => format_date(time(), 'custom', 'Y')); + } + + // Determine the order of day, month, year in the site's chosen date format. + $format = variable_get('date_format_short', 'm/d/Y'); + $sort = array(); + $sort['day'] = max(strpos($format, 'd'), strpos($format, 'j')); + $sort['month'] = max(strpos($format, 'm'), strpos($format, 'M')); + $sort['year'] = strpos($format, 'Y'); + asort($sort); + $order = array_keys($sort); + + // Output multi-selector for date + foreach ($order as $type) { + switch ($type) { + case 'day': + $options = drupal_map_assoc(range(1, 31)); + break; + case 'month': + $options = drupal_map_assoc(range(1, 12), '_profile_map_month'); + break; + case 'year': + $options = drupal_map_assoc(range(1900, 2050)); + break; + } + $element[$type] = array(type => 'radio', value => $element[value][$type], attributes => $element[attributes], parents => $element[parents], options => $options, tree => TRUE); + } + + return $element; +} + + +/** + * Roll out a single checkbox element to a list of checkboxes, using the options array as index. + */ +function expand_radios($element) { + if (count($element[options]) > 0) { + foreach ($element[options] as $key => $choice) { + if (!$element[$key]) { + $element[$key] = array(type => 'radio', title => $choice, return_value => $key, default_value => $element[default_value], attributes => $element[attributes], parents => $element[parents], spawned => TRUE); + } + } + } + + return $element; +} + + +/** + * Format a form item. + * + * @param $element + * An associative array containing the properties of the element. + * Properties used : title, value, description, required, error + * @return + * A themed HTML string representing the form item. + */ +function theme_item($element) { + return theme('form_element', $element[title], $element[value] . $element[children], $element[description], $element[id], $element[required], $element[error]); +} + + +/** + * Format a checkbox. + * + * @param $element + * An associative array containing the properties of the element. + * Properties used : title, value, return_value, description, required + * @return + * A themed HTML string representing the checkbox. + */ +function theme_checkbox($element) { + $checkbox = ''; + + if (!is_null($element[title])) { + $checkbox = ''; + } + + return theme('form_element', NULL, $checkbox, $element[description], $element[name], $element[required], _form_get_error($element[name])); +} + +/** + * Format a set of checkboxes. + * + * @param $element + * An associative array containing the properties of the element. + * @return + * A themed HTML string representing the checkbox set. + */ +function theme_checkboxes($element) { + if ($element[title] || $element[description]) { + return theme('form_element', $element[title], $element[children], $element[description], 'edit-'. $element[name], $element[required], _form_get_error($element[name])); + } + else { + return $element[children]; + } +} + +function expand_checkboxes($element) { + $value = is_array($element[value]) ? $element[value] : array(); + if (count($element[options]) > 0) { + if (!isset($element[default_value]) || $element[default_value] == 0) { + $element[default_value] = array(); + } + foreach ($element[options] as $key => $choice) { + if (!isset($element[$key])) { + $element[$key] = array( + type => 'checkbox', processed => TRUE, title => $choice, tree => TRUE, + value => in_array($key, $value), attributes => $element[attributes] + ); + } + } + } + return $element; +} + + +function theme_submit($element) { + return theme('button', $element); +} + +function theme_button($element) { + return '\n"; +} + +/** + * Format a hidden form field. + * + * @param $element + * An associative array containing the properties of the element. + * Properties used : value, edit + * @return + * A themed HTML string representing the hidden form field. + */ +function theme_hidden($element) { + return '\n"; +} + +/** + * Format a textfield. + * + * @param $element + * An associative array containing the properties of the element. + * Properties used : title, value, description, size, maxlength, required, attributes autocomplete_path + * @return + * A themed HTML string representing the textfield. + */ +function theme_textfield($element) { + $size = $element[size] ? ' size="' . $element[size] . '"' : ''; + if ($element[autocomplete_path]) { + drupal_add_js('misc/autocomplete.js'); + $class = ' form-autocomplete'; + $extra = ''; + } + + $output = ''; + return theme('form_element', $element[title], $output, $element[description], $element[id], $element[required], _form_get_error($element[name])). $extra; +} + +/** + * Format a form. + * + * @param $element + * An associative array containing the properties of the element. + * Properties used : action, method, attributes, children + * @return + * A themed HTML string representing the form. + */ +function theme_form($element) { + // Anonymous div to satisfy XHTML compliancy. + $action = $element[action] ? 'action="' . check_url($element[action]) . '" ' : ''; + return '
\n
". $element[children] ."\n
\n"; +} + + +/** + * Format a textarea. + * + * @param $element + * An associative array containing the properties of the element. + * Properties used : title, value, description, rows, cols, required, attributes + * @return + * A themed HTML string representing the textarea. + */ +function theme_textarea($element) { + $cols = $element[cols] ? ' cols="'. $element[cols] .'"' : ''; + + return theme('form_element', $element[title], ''. check_plain($element[value]) .'', $element[description], $element[id], $element[required], _form_get_error($element[name])); +} + +/** + * Format HTML markup for use in forms. + * + * This is used in more advanced forms, such as theme selection and filter format. + * + * @param $element + * An associative array containing the properties of the element. + * Properties used : prefix, value, children and suffix. + * @return + * A themed HTML string representing the HTML markup. + */ + +function theme_markup($element) { + return $element[value] . $element[children]; +} + + + +/** +* Format a password field. +* +* @param $element +* An associative array containing the properties of the element. +* Properties used : title, value, description, size, maxlength, required, attributes +* @return +* A themed HTML string representing the form. +*/ +function theme_password($element) { + $size = $element[size] ? ' size="'. $element[size] .'" ' : ''; + + $output = ''; + + return theme('form_element', $element[title], $output, $element[description], $element[id], $element[required], _form_get_error($element[name])); +} + +/** + * Format a weight selection menu. + * + * @param $element + * An associative array containing the properties of the element. + * Properties used : title, delta, description + * @return + * A themed HTML string representing the form. + */ +function theme_weight($element) { + for ($n = (-1 * $element[delta]); $n <= $element[delta]; $n++) { + $weights[$n] = $n; + } + $element[options] = $weights; + $element[type] = 'select'; + + return form_render($element); +} + +/** + * File an error against the form element with the specified name. + */ +function form_set_error($name, $message) { + $GLOBALS['form'][$name] = $message; + drupal_set_message($message, 'error'); +} + +/** + * Return an associative array of all errors. + */ +function form_get_errors() { + if (array_key_exists('form', $GLOBALS)) { + return $GLOBALS['form']; + } +} + +/** + * Format a file upload field. + * + * @param $title + * The label for the file upload field. + * @param $name + * The internal name used to refer to the field. + * @param $size + * A measure of the visible size of the field (passed directly to HTML). + * @param $description + * Explanatory text to display after the form item. + * @param $required + * Whether the user must upload a file to the field. + * @return + * A themed HTML string representing the field. + * + * For assistance with handling the uploaded file correctly, see the API + * provided by file.inc. + */ +function theme_file($element) { + return theme('form_element', $element[title], '\n", $element[description], $element[id], $element[required], _form_get_error($element[name])); +} + +/** + * Return the error message filed against the form with the specified name. + */ +function _form_get_error($name) { + if (array_key_exists('form', $GLOBALS)) { + return $GLOBALS['form'][$name]; + } +} + +function _form_get_class($name, $required, $error) { + return $name. ($required ? ' required' : '') . ($error ? ' error' : ''); +} + +/** + * Remove invalid characters from an HTML ID attribute string + * + * @param $id + * The ID to clean + * @return + * The cleaned ID + */ +function form_clean_id($id = NULL) { + $id = str_replace('][', '-', $id); + return $id; +} + +/** + * @} End of "defgroup form". + */ + +?> -- cgit v1.2.3