reference
* and the
* quickstart
* guide for more.
*/
/**
* Check if the key is a property.
*/
function element_property($key) {
return $key{0} == '#';
}
function element_properties($element) {
return array_filter(array_keys((array) $element), 'element_property');
}
/**
* Check if the key is a child.
*/
function element_child($key) {
return $key{0} != '#';
}
function element_children($element) {
return array_filter(array_keys((array) $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 submit the form using drupal_submit_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_submitted;
$form_values = array();
$form_submitted = FALSE;
$form['#type'] = 'form';
if (isset($form['#token'])) {
// Make sure that a private key is set:
if (!variable_get('drupal_private_key', '')) {
variable_set('drupal_private_key', mt_rand());
}
$form['form_token'] = array('#type' => 'hidden', '#value' => md5($_SERVER['REMOTE_ADDR'] . $form['#token'] . variable_get('drupal_private_key', '')));
}
$form['form_id'] = array('#type' => 'hidden', '#default_value' => $form_id);
$form = array_merge(_element_info('form'), $form);
if (!isset($form['#validate'])) {
if (function_exists($form_id .'_validate')) {
$form['#validate'] = array($form_id .'_validate' => array());
}
elseif (function_exists($callback .'_validate')) {
$form['#validate'] = array($callback .'_validate' => array());
}
}
if (!isset($form['#submit'])) {
if (function_exists($form_id .'_submit')) {
// we set submit here so that it can be altered but use reference for
// $form_values because it will change later
$form['#submit'] = array($form_id .'_submit' => array($form_id, &$form_values));
}
elseif (function_exists($callback .'_submit')) {
$form['#submit'] = array($callback .'_submit' => array($form_id, &$form_values));
}
}
foreach (module_implements('form_alter') as $module) {
$function = $module .'_form_alter';
$function($form_id, $form);
}
$form = _form_builder($form_id, $form);
if (!empty($_POST['edit']) && (($_POST['edit']['form_id'] == $form_id) || ($_POST['edit']['form_id'] == $callback))) {
drupal_validate_form($form_id, $form, $callback);
if ($form_submitted && !form_get_errors()) {
drupal_submit_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.'));
}
}
_form_validate($form, $form_id);
}
function drupal_submit_form($form_id, $form, $callback = NULL) {
global $form_values;
if (isset($form['#submit'])) {
foreach ($form['#submit'] as $function => $args) {
if (function_exists($function)) {
call_user_func_array($function, $args);
}
}
}
}
function _form_validate($elements, $form_id = NULL) {
/* Validate the current input */
if (!$elements['#validated'] && ($elements['#input'] || isset($form_id))) {
// An empty checkbox returns 0, an empty textfield returns '' so we use empty().
// Unfortunately, empty('0') returns TRUE so we need a special check for the '0' string.
if ($elements['#required'] && empty($elements['#value']) && $elements['#value'] !== '0') {
form_error($elements, t('%name field is required', array('%name' => $elements['#title'])));
}
if (isset($elements['#validate'])) {
foreach ($elements['#validate'] as $function => $args) {
$args = array_merge(array($elements), $args);
// for the full form we hand over a copy of $form_values
if (isset($form_id)) {
$args = array_merge(array($form_id, $GLOBALS['form_values']), $args);
}
if (function_exists($function)) {
call_user_func_array($function, $args);
}
}
}
$elements['#validated'] = TRUE;
}
// Recurse through all children.
foreach (element_children($elements) as $key) {
if (isset($elements[$key]) && $elements[$key]) {
_form_validate($elements[$key]);
}
}
}
/**
* File an error against a form element. If the name of the element is
* edit[foo][bar] then you may pass either foo or foo][bar as $name
* foo will set an error for all its children.
*/
function form_set_error($name = NULL, $message = NULL) {
static $form = array();
if (isset($name) && !isset($form[$name])) {
$form[$name] = $message;
drupal_set_message($message, 'error');
}
return $form;
}
/**
* Return an associative array of all errors.
*/
function form_get_errors() {
$form = form_set_error();
if (!empty($form)) {
return $form;
}
}
/**
* Return the error message filed against the form with the specified name.
*/
function form_get_error($element) {
$form = form_set_error();
$key = $element['#parents'][0];
if (isset($form[$key])) {
return $form[$key];
}
$key = implode('][', $element['#parents']);
if (isset($form[$key])) {
return $form[$key];
}
}
/**
* Flag an element as having an error.
*/
function form_error(&$element, $message) {
$element['#error'] = TRUE;
form_set_error(implode('][', $element['#parents']), $message);
}
/**
* 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_id, $form) {
global $form_values;
global $form_submitted;
/* Use element defaults */
if ((!empty($form['#type'])) && ($info = _element_info($form['#type']))) {
$form += $info;
}
if ($form['#input']) {
if (!isset($form['#name'])) {
$form['#name'] = 'edit[' . implode('][', $form['#parents']) . ']';
}
if (!isset($form['#id'])) {
$form['#id'] = 'edit-' . implode('-', $form['#parents']);
}
$posted = (isset($_POST['edit']) && ($_POST['edit']['form_id'] == $form_id));
$edit = $posted ? $_POST['edit'] : array();
$ref =& $form_values;
foreach ($form['#parents'] as $parent) {
$edit = isset($edit[$parent]) ? $edit[$parent] : NULL;
$ref =& $ref[$parent];
}
if (!isset($form['#value'])) {
if ($posted) {
if (isset($edit)) {
$form['#value'] = $edit; // normal element
}
elseif (isset($form['#return_value'])) {
$form['#value'] = 0; // checkbox unchecked
}
}
if (!isset($form['#value'])) {
$form['#value'] = $form['#default_value'];
}
}
if (isset($form['#form_submitted'])) {
if ($_POST[$form['#name']] == $form['#value']) {
$form_submitted = $form_submitted || $form['#form_submitted'];
}
}
$ref = $form['#value'];
}
// Allow for elements to expand to multiple elements. Radios, checkboxes and files for instance.
if (isset($form['#process']) && !$form['#processed']) {
foreach ($form['#process'] as $process => $args) {
if (function_exists($process)) {
$args = array_merge(array($form), $args);
$form = call_user_func_array($process, $args);
}
}
$form['#processed'] = TRUE;
}
// Recurse through all child elements.
$count = 0;
foreach (element_children($form) as $key) {
// don't squash an existing tree value
if (!isset($form[$key]['#tree'])) {
$form[$key]['#tree'] = $form['#tree'];
}
// don't squash existing parents value
if (!isset($form[$key]['#parents'])) {
// Check to see if a tree of child elements is present. If so, continue down the tree if required.
$form[$key]['#parents'] = $form[$key]['#tree'] && $form['#tree'] ? array_merge($form['#parents'], array($key)) : array($key);
}
# Assign a decimal placeholder weight, to preserve original array order
if (!isset($form[$key]['#weight'])) {
$form[$key]['#weight'] = $count/1000;
}
$form[$key] = _form_builder($form_id, $form[$key]);
$count++;
}
if (function_exists($form['#after_build']) && !isset($form['#after_build_done'])) {
$form = call_user_func($form['#after_build'], $form, $form_values);
$form['#after_build_done'] = TRUE;
}
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) {
if (!isset($elements)) {
return NULL;
}
$content = '';
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;
}
if ($content) {
return $elements['#prefix'] . $content . $elements['#suffix'];
}
}
/**
* Function used by uasort in form render to sort form via weight.
*/
function _form_sort($a, $b) {
$a_weight = (is_array($a) && isset($a['#weight'])) ? $a['#weight'] : 0;
$b_weight = (is_array($b) && isset($b['#weight'])) ? $b['#weight'] : 0;
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;
$parents = array();
$basic_defaults = array(
'#description' => NULL,
'#attributes' => array(),
'#required' => FALSE,
'#tree' => FALSE,
'#parents' => $parents
);
if ($refresh || !isset($cache)) {
$cache = array();
foreach (module_implements('elements') as $module) {
$elements = module_invoke($module, 'elements');
if (isset($elements) && is_array($elements)) {
$cache = array_merge_recursive($cache, $elements);
}
}
if (sizeof($cache)) {
foreach ($cache as $element_type => $info) {
$cache[$element_type] = array_merge_recursive($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 .= '';
}
else {
$select .= '';
}
}
return theme('form_element', $element['#title'], '', $element['#description'], $element['#id'], $element['#required'], form_get_error($element));
}
/**
* 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 '