form.inc 37.8 KB
Newer Older
1
<?php
2 3
// $Id$

4 5 6 7 8 9 10
/**
 * @defgroup form Form generation
 * @{
 * Functions to enable output of HTML forms and form elements.
 *
 * Drupal uses these functions to achieve consistency in its form presentation,
 * while at the same time simplifying code and reducing the amount of HTML that
11
 * must be explicitly generated by modules. See the reference at
12
 * http://api.drupal.org/api/HEAD/file/developer/topics/forms_api_reference.html
13
 * and the quickstart guide at
14
 * http://api.drupal.org/api/HEAD/file/developer/topics/forms_api.html
15 16 17 18 19 20
 */

/**
 * Check if the key is a property.
 */
function element_property($key) {
21
  return $key[0] == '#';
22 23
}

24 25 26
/**
 * Get properties of a form tree element. Properties begin with '#'.
 */
27
function element_properties($element) {
28
  return array_filter(array_keys((array) $element), 'element_property');
29 30 31 32 33 34
}

/**
 * Check if the key is a child.
 */
function element_child($key) {
35
  return $key[0] != '#';
36 37
}

38 39 40
/**
 * Get keys of a form tree element that are not properties (i.e., do not begin with '#').
 */
41
function element_children($element) {
42
  return array_filter(array_keys((array) $element), 'element_child');
43 44 45
}

/**
46
 * Processes a form array and produces the HTML output of a form.
47
 * If there is input in the $_POST['edit'] variable, this function
48 49
 * will attempt to validate it, using drupal_validate_form(),
 * and then submit the form using drupal_submit_form().
50 51
 *
 * @param $form_id
52 53 54
 *   A unique string identifying the form. Allows each form to be
 *   themed.  Pass NULL to suppress the form_id parameter (produces
 *   a shorter URL with method=get)
55 56 57 58 59 60 61
 * @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) {
62
  global $form_values, $form_submitted, $user, $form_button_counter;
63
  $form_values = array();
Dries's avatar
Dries committed
64
  $form_submitted = FALSE;
65
  $form_button_counter = array(0, 0);
66

67 68
  $form['#type'] = 'form';
  if (isset($form['#token'])) {
69 70
    if (variable_get('cache', 0) && !$user->uid && $_SERVER['REQUEST_METHOD'] == 'GET') {
      unset($form['#token']);
71
    }
72 73 74 75 76
    else {
      // Make sure that a private key is set:
      if (!variable_get('drupal_private_key', '')) {
        variable_set('drupal_private_key', mt_rand());
      }
77

78 79
      $form['form_token'] = array('#type' => 'hidden', '#default_value' => md5(session_id() . $form['#token'] . variable_get('drupal_private_key', '')));
    }
80
  }
81 82 83
  if (isset($form_id)) {
    $form['form_id'] = array('#type' => 'hidden', '#value' => $form_id);
  }
84 85 86
  if (!isset($form['#id'])) {
    $form['#id'] = $form_id;
  }
87

88
  $form += _element_info('form');
89

Dries's avatar
Dries committed
90 91
  if (!isset($form['#validate'])) {
    if (function_exists($form_id .'_validate')) {
92
      $form['#validate'] = array($form_id .'_validate' => array());
Dries's avatar
Dries committed
93 94
    }
    elseif (function_exists($callback .'_validate')) {
95
      $form['#validate'] = array($callback .'_validate' => array());
Dries's avatar
Dries committed
96 97 98
    }
  }

99 100
  if (!isset($form['#submit'])) {
    if (function_exists($form_id .'_submit')) {
101 102
      // we set submit here so that it can be altered but use reference for
      // $form_values because it will change later
103
      $form['#submit'] = array($form_id .'_submit' => array());
Dries's avatar
Dries committed
104
    }
105
    elseif (function_exists($callback .'_submit')) {
106
      $form['#submit'] = array($callback .'_submit' => array());
Dries's avatar
Dries committed
107 108 109
    }
  }

110 111
  foreach (module_implements('form_alter') as $module) {
    $function = $module .'_form_alter';
112
    $function($form_id, $form);
113 114
  }

115
  $form = form_builder($form_id, $form);
116
  if (!empty($_POST['edit']) && (($_POST['edit']['form_id'] == $form_id) || ($_POST['edit']['form_id'] == $callback))) {
117
    drupal_validate_form($form_id, $form, $callback);
118 119 120 121
    // IE does not send a button value when there is only one submit button (and no non-submit buttons)
    // and you submit by pressing enter.
    // In that case we accept a submission without button values.
    if (($form_submitted || (!$form_button_counter[0] && $form_button_counter[1])) && !form_get_errors()) {
122
      $redirect = drupal_submit_form($form_id, $form, $callback);
123
      if (isset($redirect)) {
124 125
        $goto = $redirect;
      }
126
      if (isset($form['#redirect'])) {
127 128
        $goto = $form['#redirect'];
      }
129 130
      if ($goto !== FALSE) {
        if (is_array($goto)) {
131
          call_user_func_array('drupal_goto', $goto);
132
        }
133 134 135
        elseif (!isset($goto)) {
          drupal_goto($_GET['q']);
        }
136 137 138
        else {
          drupal_goto($goto);
        }
139
      }
140
    }
141 142
  }

143
  if (theme_get_function($form_id)) {
144
    $form['#theme'] = $form_id;
145
  }
146
  elseif (theme_get_function($callback)) {
147
    $form['#theme'] = $callback;
148
  }
149 150 151 152 153 154 155 156 157

  if (isset($form['#pre_render'])) {
    foreach ($form['#pre_render'] as $function) {
      if (function_exists($function)) {
        $function($form_id, $form);
      }
    }
  }

158 159 160
  return form_render($form);
}

161
function drupal_validate_form($form_id, $form, $callback = NULL) {
162
  global $form_values;
163 164 165 166 167
  static $validated_forms = array();

  if (isset($validated_forms[$form_id])) {
    return;
  }
168

169
  if (isset($form['#token'])) {
170
    if ($form_values['form_token'] != md5(session_id() . $form['#token'] . variable_get('drupal_private_key', ''))) {
171 172 173 174 175
      // 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.'));
    }
  }

176
  _form_validate($form, $form_id);
177
  $validated_forms[$form_id] = TRUE;
178 179
}

180
function drupal_submit_form($form_id, $form, $callback = NULL) {
181 182
  global $form_values;
  $default_args = array($form_id, &$form_values);
183

184
  if (isset($form['#submit'])) {
185 186
    foreach ($form['#submit'] as $function => $args) {
      if (function_exists($function)) {
187
        $args = array_merge($default_args, (array) $args);
188 189
        // Since we can only redirect to one page, only the last redirect will work
        $redirect = call_user_func_array($function, $args);
190 191 192
        if (isset($redirect)) {
          $goto = $redirect;
        }
Dries's avatar
Dries committed
193 194
      }
    }
195
  }
196
  return $goto;
197 198
}

199
function _form_validate($elements, $form_id = NULL) {
200
  /* Validate the current input */
201
  if (!$elements['#validated']) {
202
    if (isset($elements['#needs_validation'])) {
203 204 205 206 207
      // An empty textfield returns '' so we use empty(). An empty checkbox
      // and a textfield could return '0' and 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'])));
208
      }
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224

      // Add legal choice check if element has #options. Can be skipped, but then you must validate your own element.
      if (isset($elements['#options']) && isset($elements['#value']) && !isset($elements['#DANGEROUS_SKIP_CHECK'])) {
        if ($elements['#type'] == 'select') {
          $options = form_options_flatten($elements['#options']);
        }
        else {
          $options = $elements['#options'];
        }
        if (is_array($elements['#value'])) {
          $value = $elements['#type'] == 'checkboxes' ? array_keys(array_filter($elements['#value'])) : $elements['#value'];
          foreach ($value as $v) {
            if (!isset($options[$v])) {
              form_error($elements, t('An illegal choice has been detected. Please contact the site administrator.'));
              watchdog('form', t('Illegal choice %choice in %name element.', array('%choice' => theme('placeholder', check_plain($v)), '%name' => theme_placeholder(empty($elements['#title']) ? $elements['#parents'][0] : $elements['#title'])), WATCHDOG_ERROR));
            }
225 226
          }
        }
227 228 229 230
        elseif (!isset($options[$elements['#value']])) {
          form_error($elements, t('An illegal choice has been detected. Please contact the site administrator.'));
          watchdog('form', t('Illegal choice %choice in %name element.', array('%choice' => theme_placeholder(check_plain($v)), '%name' => theme('placeholder', empty($elements['#title']) ? $elements['#parents'][0] : $elements['#title'])), WATCHDOG_ERROR));
        }
231 232 233 234
      }
    }

    // User-applied checks.
Dries's avatar
Dries committed
235
    if (isset($elements['#validate'])) {
236 237 238 239 240
      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);
241
        }
242 243
        if (function_exists($function))  {
          call_user_func_array($function, $args);
244 245 246
        }
      }
    }
247
    $elements['#validated'] = TRUE;
248
  }
249 250 251 252 253 254 255

  // Recurse through all children.
  foreach (element_children($elements) as $key) {
    if (isset($elements[$key]) && $elements[$key]) {
      _form_validate($elements[$key]);
    }
  }
256 257
}

258 259 260 261 262
/**
 * 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.
 */
263
function form_set_error($name = NULL, $message = '') {
264 265 266
  static $form = array();
  if (isset($name) && !isset($form[$name])) {
    $form[$name] = $message;
267 268 269
    if ($message) {
      drupal_set_message($message, 'error');
    }
270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298
  }
  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];
  }
}

299 300 301
/**
 * Flag an element as having an error.
 */
302
function form_error(&$element, $message = '') {
303
  $element['#error'] = TRUE;
304
  form_set_error(implode('][', $element['#parents']), $message);
305 306 307
}

/**
308 309 310 311 312 313 314 315 316
 * 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.
 *
 * @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.
317
 */
318
function form_builder($form_id, $form) {
319
  global $form_values, $form_submitted, $form_button_counter;
320

321
  /* Use element defaults */
322
  if ((!empty($form['#type'])) && ($info = _element_info($form['#type']))) {
323
    // overlay $info onto $form, retaining preexisting keys in $form
324 325 326
    $form += $info;
  }

327
  if (isset($form['#input']) && $form['#input']) {
328 329 330 331 332 333
    if (!isset($form['#name'])) {
      $form['#name'] = 'edit[' . implode('][', $form['#parents']) . ']';
    }
    if (!isset($form['#id'])) {
      $form['#id'] =  'edit-' . implode('-', $form['#parents']);
    }
334

335
    $posted = (isset($_POST['edit']) && ($_POST['edit']['form_id'] == $form_id));
336
    $edit = $posted ? $_POST['edit'] : array();
337
    foreach ($form['#parents'] as $parent) {
338 339
      $edit = isset($edit[$parent]) ? $edit[$parent] : NULL;
    }
340
    if (!isset($form['#value']) && !array_key_exists('#value', $form)) {
341
      if ($posted) {
342 343
        switch ($form['#type']) {
          case 'checkbox':
344
            $form['#value'] = !empty($edit) ? $form['#return_value'] : 0;
345 346
            break;
          case 'select':
347 348 349 350 351 352
            if (isset($edit)) {
              $form['#value'] = $edit;
            }
            elseif (isset($form['#multiple']) && $form['#multiple']) {
              $form['#value'] = array();
            }
353 354 355
            break;
          case 'textfield':
            if (isset($edit)) {
356 357 358
              // Equate $edit to the form value to ensure it's marked for validation
              $edit = str_replace(array("\r", "\n"), '', $edit);
              $form['#value'] = $edit;
359 360 361 362 363 364
            }
            break;
          default:
            if (isset($edit)) {
              $form['#value'] = $edit;
            }
365
        }
366
        // Mark all posted values for validation
367
        if ((isset($form['#value']) && $form['#value'] === $edit) || (isset($form['#required']) && $form['#required'])) {
368
          $form['#needs_validation'] = TRUE;
369 370 371
        }
      }
      if (!isset($form['#value'])) {
372 373 374 375 376 377 378
        $function = $form['#type'] . '_value';
        if (function_exists($function)) {
          $function($form);
        }
        else {
          $form['#value'] = $form['#default_value'];
        }
379
      }
380
    }
381 382 383 384 385
    if (isset($form['#executes_submit_callback'])) {
      // Count submit and non-submit buttons
      $form_button_counter[$form['#executes_submit_callback']]++;
      // See if a submit button was pressed
      if (isset($_POST[$form['#name']]) && $_POST[$form['#name']] == $form['#value']) {
386
        $form_submitted = $form_submitted || $form['#executes_submit_callback'];
387 388 389 390
      }
    }
  }

391
  // Allow for elements to expand to multiple elements, e.g. radios, checkboxes and files.
392
  if (isset($form['#process']) && !$form['#processed']) {
393 394
    foreach ($form['#process'] as $process => $args) {
      if (function_exists($process)) {
395 396
        $args = array_merge(array($form), $args);
        $form = call_user_func_array($process, $args);
397 398
      }
    }
399
    $form['#processed'] = TRUE;
400 401
  }

402 403 404
  // Set the $form_values key that gets passed to validate and submit.
  // We call this after #process gets called so that #process has a
  // chance to update #value if desired.
405
  if (isset($form['#input']) && $form['#input']) {
406
    form_set_value($form, $form['#value']);
407 408
  }

409 410 411
  // Recurse through all child elements.
  $count  = 0;
  foreach (element_children($form) as $key) {
412
    // don't squash an existing tree value
413 414 415
    if (!isset($form[$key]['#tree'])) {
      $form[$key]['#tree'] = $form['#tree'];
    }
416

417 418
    // don't squash existing parents value
    if (!isset($form[$key]['#parents'])) {
419 420
      // 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);
421 422
    }

423
    # Assign a decimal placeholder weight to preserve original array order
424 425 426
    if (!isset($form[$key]['#weight'])) {
      $form[$key]['#weight'] = $count/1000;
    }
427
    $form[$key] = form_builder($form_id, $form[$key]);
428 429 430
    $count++;
  }

431 432 433 434 435 436
  if (isset($form['#after_build']) && !isset($form['#after_build_done'])) {
    foreach ($form['#after_build'] as $function) {
      if (function_exists($function)) {
        $form = $function($form, $form_values);
      }
    }
437
    $form['#after_build_done'] = TRUE;
438
  }
439 440

  return $form;
441 442
}

443
/**
Dries's avatar
Dries committed
444
 * Use this function to make changes to form values in the form validate
445 446 447 448
 * phase, so they will be available in the submit phase in $form_values.
 *
 * Specifically, if $form['#parents'] is array('foo', 'bar')
 * and $value is 'baz' then this function will make
Dries's avatar
Dries committed
449
 * $form_values['foo']['bar'] to be 'baz'.
450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481
 *
 * @param $form
 *   The form item. Keys used: #parents, #value
 * @param $value
 *   The value for the form item.
 */
function form_set_value($form, $value) {
  global $form_values;
  _form_set_value($form_values, $form, $form['#parents'], $value);
}

/**
 * Helper function for form_set_value().
 *
 * We iterate of $parents and create nested arrays for them
 * in $form_values if needed. Then we insert the value in
 * the right array.
 */
function _form_set_value(&$form_values, $form, $parents, $value) {
  $parent = array_shift($parents);
  if (empty($parents)) {
    $form_values[$parent] = $value;
  }
  else {
    if (!isset($form_values[$parent])) {
      $form_values[$parent] = array();
    }
    _form_set_value($form_values[$parent], $form, $parents, $value);
  }
  return $form;
}

482
/**
483 484
 * Renders a HTML form given a form tree. Recursively iterates over each of
 * the form elements, generating HTML code. This function is usually
485
 * called from within a theme.  To render a form from within a module, use
486
 * drupal_get_form().
487 488 489 490 491 492 493
 *
 * @param $elements
 *   The form tree describing the form.
 * @return
 *   The rendered HTML form.
 */
function form_render(&$elements) {
494 495
  if (!isset($elements)) {
    return NULL;
496
  }
497 498
  $content = '';
  uasort($elements, "_form_sort");
499
  if (!isset($elements['#children'])) {
500
    $children = element_children($elements);
501
    /* Render all the children that use a theme function */
502
    if (isset($elements['#theme']) && !$elements['#theme_used']) {
503
      $elements['#theme_used'] = TRUE;
504 505

      $previous_value = $elements['#value'];
506
      $previous_type = $elements['#type'];
drumm's avatar
drumm committed
507
      // If we rendered a single element, then we will skip the renderer.
drumm's avatar
drumm committed
508 509 510 511 512 513
      if (empty($children)) {
        $elements['#printed'] = TRUE;
      }
      else {
        $elements['#value'] = '';
      }
514
      $elements['#type'] = 'markup';
515

516
      $content = theme($elements['#theme'], $elements);
517 518

      $elements['#value'] = $previous_value;
519
      $elements['#type'] = $previous_type;
520
      unset($elements['#prefix'], $elements['#suffix']);
521 522 523
    }
    /* render each of the children using form_render and concatenate them */
    if (!$content) {
524
      foreach ($children as $key) {
525 526 527 528 529
        $content .= form_render($elements[$key]);
      }
    }
  }
  if ($content) {
530
    $elements['#children'] = $content;
531 532
  }

533
  // Until now, we rendered the children, here we render the element itself
534
  if (!isset($elements['#printed'])) {
535 536
    $content = theme(($elements['#type']) ? $elements['#type']: 'markup', $elements);
    $elements['#printed'] = TRUE;
537 538
  }

539
  if ($content) {
540 541 542
    $prefix = isset($elements['#prefix']) ? $elements['#prefix'] : '';
    $suffix = isset($elements['#suffix']) ? $elements['#suffix'] : '';
    return $prefix . $content . $suffix;
543
  }
544 545 546
}

/**
547
 * Function used by uasort in form_render() to sort form by weight.
548 549
 */
function _form_sort($a, $b) {
550 551 552
  $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) {
Dries's avatar
Dries committed
553 554
    return 0;
  }
555
  return ($a_weight < $b_weight) ? -1 : 1;
556 557 558 559 560 561 562
}

/**
 * Retrieve the default properties for the defined element type.
 */
function _element_info($type, $refresh = null) {
  static $cache;
563

564
  $basic_defaults = array(
565 566 567
    '#description' => NULL,
    '#attributes' => array(),
    '#required' => FALSE,
568
    '#tree' => FALSE,
569
    '#parents' => array()
570
  );
571
  if (!isset($cache) || $refresh) {
572 573 574
    $cache = array();
    foreach (module_implements('elements') as $module) {
      $elements = module_invoke($module, 'elements');
575
      if (isset($elements) && is_array($elements)) {
576
        $cache = array_merge_recursive($cache, $elements);
577 578 579 580
      }
    }
    if (sizeof($cache)) {
      foreach ($cache as $element_type => $info) {
581
        $cache[$element_type] = array_merge_recursive($basic_defaults, $info);
582 583 584 585 586 587 588
      }
    }
  }

  return $cache[$type];
}

589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607
function form_options_flatten($array, $reset = TRUE) {
  static $return;

  if ($reset) {
    $return = array();
  }

  foreach ($array as $key => $value) {
    if (is_array($value)) {
      form_options_flatten($value, FALSE);
    }
    else {
      $return[$key] = 1;
    }
  }

  return $return;
}

608 609 610 611 612
/**
 * Format a dropdown menu or scrolling selection box.
 *
 * @param $element
 *   An associative array containing the properties of the element.
613
 *   Properties used: title, value, options, description, extra, multiple, required
614 615 616 617 618 619 620 621 622
 * @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 = '';
623
  $size = $element['#size'] ? ' size="' . $element['#size'] . '"' : '';
624
  _form_set_class($element, array('form-select'));
625 626
  $multiple = isset($element['#multiple']) && $element['#multiple'];
  return theme('form_element', $element, '<select name="'. $element['#name'] .''. ($multiple ? '[]' : '') .'"'. ($multiple ? ' multiple="multiple" ' : '') . drupal_attributes($element['#attributes']) .' id="'. $element['#id'] .'" '. $size .'>'. form_select_options($element) .'</select>');
627 628 629 630 631 632
}

function form_select_options($element, $choices = NULL) {
  if (!isset($choices)) {
    $choices = $element['#options'];
  }
633
  // array_key_exists() accommodates the rare event where $element['#value'] is NULL.
634 635 636
  // isset() fails in this situation.
  $value_valid = isset($element['#value']) || array_key_exists('#value', $element);
  $value_is_array = is_array($element['#value']);
637 638
  $options = '';
  foreach ($choices as $key => $choice) {
639
    if (is_array($choice)) {
640 641 642
      $options .= '<optgroup label="'. $key .'">';
      $options .= form_select_options($element, $choice);
      $options .= '</optgroup>';
643 644
    }
    else {
645 646
      $key = (string)$key;
      if ($value_valid && ($element['#value'] == $key || ($value_is_array && in_array($key, $element['#value'])))) {
647 648 649 650 651
        $selected = ' selected="selected"';
      }
      else {
        $selected = '';
      }
652
      $options .= '<option value="'. $key .'"'. $selected .'>'. check_plain($choice) .'</option>';
653 654
    }
  }
655
  return $options;
656 657 658 659 660 661 662
}

/**
 * Format a group of form items.
 *
 * @param $element
 *   An associative array containing the properties of the element.
663
 *   Properties used: attributes, title, value, description, children, collapsible, collapsed
664 665 666 667
 * @return
 *   A themed HTML string representing the form item group.
 */
function theme_fieldset($element) {
668
  if ($element['#collapsible']) {
669 670
    drupal_add_js('misc/collapse.js');

671 672 673
    $element['#attributes']['class'] .= ' collapsible';
    if ($element['#collapsed']) {
     $element['#attributes']['class'] .= ' collapsed';
674 675 676
    }
  }

677
  return '<fieldset' . drupal_attributes($element['#attributes']) .'>' . ($element['#title'] ? '<legend>'. $element['#title'] .'</legend>' : '') . ($element['#description'] ? '<div class="description">'. $element['#description'] .'</div>' : '') . $element['#children'] . $element['#value'] . "</fieldset>\n";
678 679 680 681 682 683 684
}

/**
 * Format a radio button.
 *
 * @param $element
 *   An associative array containing the properties of the element.
685
 *   Properties used: required, return_value, value, attributes, title, description
686 687 688 689
 * @return
 *   A themed HTML string representing the form item group.
 */
function theme_radio($element) {
690
  _form_set_class($element, array('form-radio'));
691
  $output = '<input type="radio" ';
692 693 694 695 696 697
  $output .= 'name="' . $element['#name'] .'" ';
  $output .= 'value="'. $element['#return_value'] .'" ';
  $output .= ($element['#value'] == $element['#return_value']) ? ' checked="checked" ' : ' ';
  $output .= drupal_attributes($element['#attributes']) .' />';
  if (!is_null($element['#title'])) {
    $output = '<label class="option">'. $output .' '. $element['#title'] .'</label>';
698
  }
699 700 701

  unset($element['#title']);
  return theme('form_element', $element, $output);
702 703 704 705 706 707 708
}

/**
 * Format a set of radio buttons.
 *
 * @param $element
 *   An associative array containing the properties of the element.
709
 *   Properties used: title, value, options, description, required and attributes.
710 711 712 713
 * @return
 *   A themed HTML string representing the radio button set.
 */
function theme_radios($element) {
714
  if ($element['#title'] || $element['#description']) {
715 716
    unset($element['#id']);
    return theme('form_element', $element, $element['#children']);
717 718
  }
  else {
719
    return $element['#children'];
720 721 722
  }
}

723 724 725 726 727 728 729 730 731 732
/**
 * Format a password_confirm item.
 *
 * @param $element
 *   An associative array containing the properties of the element.
 *   Properties used: title, value, id, required, error.
 * @return
 *   A themed HTML string representing the form item.
 */
function theme_password_confirm($element) {
733
  return theme('form_element', $element, '<div class="container-inline">'. $element['#children'] .'</div>');
734 735
}

736 737 738 739 740 741 742 743 744 745 746 747
/*
 * Expand a password_confirm field into two text boxes.
 */
function expand_password_confirm($element) {
  $element['pass1'] =  array('#type' => 'password', '#size' => 12, '#value' => $element['#value']['pass1']);
  $element['pass2'] =  array('#type' => 'password', '#size' => 12, '#value' => $element['#value']['pass2']);
  $element['#validate'] = array('password_confirm_validate' => array());
  $element['#tree'] = TRUE;

  return $element;
}

748
/**
749
 * Validate password_confirm element.
750
 */
751
function password_confirm_validate($form) {
752 753
  $pass1 = trim($form['pass1']['#value']);
  if (!empty($pass1)) {
754
    $pass2 = trim($form['pass2']['#value']);
755
    if ($pass1 != $pass2) {
756
      form_error($form, t('The specified passwords do not match.'));
757
    }
758 759
  }
  elseif ($form['#required'] && !empty($_POST['edit'])) {
760
    form_error($form, t('Password field is required.'));
761
  }
762

763 764 765 766 767 768
  // Password field must be converted from a two-element array into a single
  // string regardless of validation results.
  form_set_value($form['pass1'], NULL);
  form_set_value($form['pass2'], NULL);
  form_set_value($form, $pass1);

769 770 771
  return $form;
}

772
/**
773
 * Format a date selection element.
774 775 776
 *
 * @param $element
 *   An associative array containing the properties of the element.
777
 *   Properties used: title, value, options, description, required and attributes.
778
 * @return
779
 *   A themed HTML string representing the date selection boxes.
780 781
 */
function theme_date($element) {
782
  return theme('form_element', $element, '<div class="container-inline">'. $element['#children'] .'</div>');
783 784 785
}

/**
786
 * Roll out a single date element.
787 788 789
 */
function expand_date($element) {
  // Default to current date
790 791
  if (!isset($element['#value'])) {
    $element['#value'] = array('day' => format_date(time(), 'custom', 'j'),
792 793 794 795
                            'month' => format_date(time(), 'custom', 'n'),
                            'year' => format_date(time(), 'custom', 'Y'));
  }

796 797
  $element['#tree'] = TRUE;

798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813
  // 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':
814
        $options = drupal_map_assoc(range(1, 12), 'map_month');
815 816 817 818 819
        break;
      case 'year':
        $options = drupal_map_assoc(range(1900, 2050));
        break;
    }
820 821 822 823 824 825 826 827
    $parents = $element['#parents'];
    $parents[] = $type;
    $element[$type] = array(
      '#type' => 'select',
      '#value' => $element['#value'][$type],
      '#attributes' => $element['#attributes'],
      '#options' => $options,
    );
828 829 830 831 832
  }

  return $element;
}

833 834 835 836 837 838
/**
 * Helper function for usage with drupal_map_assoc to display month names.
 */
function map_month($month) {
  return format_date(gmmktime(0, 0, 0, $month, 2, 1970), 'custom', 'M', 0);
}
839

840 841 842 843 844 845 846 847 848 849 850
/**
 * Helper function to load value from default value for checkboxes
 */
function checkboxes_value(&$form) {
  $value = array();
  foreach ((array)$form['#default_value'] as $key) {
    $value[$key] = 1;
  }
  $form['#value'] = $value;
}

851 852 853 854 855 856 857 858 859 860 861 862
/**
 * If no default value is set for weight select boxes, use 0.
 */
function weight_value(&$form) {
  if (isset($form['#default_value'])) {
    $form['#value'] = $form['#default_value'];
  }
  else {
    $form['#value'] = 0;
  }
}

863
/**
864 865
 * Roll out a single radios element to a list of radios,
 * using the options array as index.
866 867
 */
function expand_radios($element) {
868 869
  if (count($element['#options']) > 0) {
    foreach ($element['#options'] as $key => $choice) {
870
      if (!$element[$key]) {
871
        $element[$key] = array('#type' => 'radio', '#title' => $choice, '#return_value' => $key, '#default_value' => $element['#default_value'], '#attributes' => $element['#attributes'], '#parents' => $element['#parents'], '#spawned' => TRUE);
872 873 874 875 876 877 878 879 880 881 882
      }
    }
  }
  return $element;
}

/**
 * Format a form item.
 *
 * @param $element
 *   An associative array containing the properties of the element.
883
 *   Properties used:  title, value, description, required, error
884 885 886 887
 * @return
 *   A themed HTML string representing the form item.
 */
function theme_item($element) {
888
  return theme('form_element', $element, $element['#value'] . $element['#children']);
889 890 891 892 893 894 895
}

/**
 * Format a checkbox.
 *
 * @param $element
 *   An associative array containing the properties of the element.
896
 *   Properties used:  title, value, return_value, description, required
897 898 899 900
 * @return
 *   A themed HTML string representing the checkbox.
 */
function theme_checkbox($element) {
901
  _form_set_class($element, array('form-checkbox'));
902 903
  $checkbox = '<input ';
  $checkbox .= 'type="checkbox" ';
904 905 906
  $checkbox .= 'name="'. $element['#name'] .'" ';
  $checkbox .= 'id="'. $element['#id'].'" ' ;
  $checkbox .= 'value="'. $element['#return_value'] .'" ';
907
  $checkbox .= $element['#value'] ? ' checked="checked" ' : ' ';
908 909 910 911
  $checkbox .= drupal_attributes($element['#attributes']) . ' />';

  if (!is_null($element['#title'])) {
    $checkbox = '<label class="option">'. $checkbox .' '. $element['#title'] .'</label>';
912 913
  }

914 915
  unset($element['#title']);
  return theme('form_element', $element, $checkbox);
916 917 918 919 920 921 922 923 924 925 926
}

/**
 * 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) {
927
  if ($element['#title'] || $element['#description']) {
928 929
    unset($element['#id']);
    return theme('form_element', $element, $element['#children']);
930 931
  }
  else {
932
    return $element['#children'];
933 934 935 936
  }
}

function expand_checkboxes($element) {
937
  $value = is_array($element['#value']) ? $element['#value'] : array();
938
  $element['#tree'] = TRUE;
939 940 941
  if (count($element['#options']) > 0) {
    if (!isset($element['#default_value']) || $element['#default_value'] == 0) {
      $element['#default_value'] = array();
942
    }
943
    foreach ($element['#options'] as $key => $choice) {
944
      if (!isset($element[$key])) {
945
        $element[$key] = array('#type' => 'checkbox', '#processed' => TRUE, '#title' => $choice, '#return_value' => $key, '#default_value' => isset($value[$key]), '#attributes' => $element['#attributes']);
946 947 948 949 950 951 952 953 954 955 956
      }
    }
  }
  return $element;
}

function theme_submit($element) {
  return theme('button', $element);
}

function theme_button($element) {
957 958 959 960 961 962 963
  //Make sure not to overwrite classes
  if (isset($element['#attributes']['class'])) {
    $element['#attributes']['class'] = 'form-'. $element['#button_type'] .' '. $element['#attributes']['class'];
  }
  else {
    $element['#attributes']['class'] = 'form-'. $element['#button_type'];
  }
964

965
  return '<input type="submit" '. (empty($element['#name']) ? '' : 'name="'. $element['#name'] .'" ') .'value="'. check_plain($element['#value']) .'" '. drupal_attributes($element['#attributes']) ." />\n";
966 967 968 969 970 971 972
}

/**
 * Format a hidden form field.
 *
 * @param $element
 *   An associative array containing the properties of the element.
973
 *   Properties used:  value, edit
974 975 976 977
 * @return
 *   A themed HTML string representing the hidden form field.
 */
function theme_hidden($element) {
978
  return '<input type="hidden" name="'. $element['#name'] . '" id="'. $element['#id'] . '" value="'. check_plain($element['#value']) ."\" " . drupal_attributes($element['#attributes']) ." />\n";
979 980 981 982 983 984 985
}

/**
 * Format a textfield.
 *
 * @param $element
 *   An associative array containing the properties of the element.
986
 *   Properties used:  title, value, description, size, maxlength, required, attributes autocomplete_path
987 988 989 990
 * @return
 *   A themed HTML string representing the textfield.
 */
function theme_textfield($element) {
991
  $size = $element['#size'] ? ' size="' . $element['#size'] . '"' : '';
992
  $class = array('form-text');
993
  $extra = '';
994
  if ($element['#autocomplete_path']) {
995
    drupal_add_js('misc/autocomplete.js');
996
    $class[] = 'form-autocomplete';
997
    $extra =  '<input class="autocomplete" type="hidden" id="'. $element['#id'] .'-autocomplete" value="'. check_url(url($element['#autocomplete_path'], NULL, NULL, TRUE)) .'" disabled="disabled" />';
998
  }
999 1000
  _form_set_class($element, $class);
  $output = '<input type="text" maxlength="'. $element['#maxlength'] .'" name="'. $element['#name'] .'" id="'. $element['#id'] .'" '. $size .' value="'. check_plain($element['#value']) .'"'. drupal_attributes($element['#attributes']) .' />';
1001
  return theme('form_element', $element, $output). $extra;
1002 1003 1004 1005 1006 1007 1008
}

/**
 * Format a form.
 *
 * @param $element
 *   An associative array containing the properties of the element.
1009
 *   Properties used: action, method, attributes, children
1010 1011 1012 1013
 * @return
 *   A themed HTML string representing the form.
 */
function theme_form($element) {
1014
  // Anonymous div to satisfy XHTML compliance.
1015
  $action = $element['#action'] ? 'action="' . check_url($element['#action']) . '" ' : '';
1016
  return '<form '. $action . ' method="'. $element['#method'] .'" '. 'id="'. $element['#id'] .'"'.  drupal_attributes($element['#attributes']) .">\n<div>". $element['#children'] ."\n</div></form>\n";
1017 1018 1019 1020 1021 1022 1023
}

/**
 * Format a textarea.
 *
 * @param $element
 *   An associative array containing the properties of the element.
1024
 *   Properties used: title, value, description, rows, cols, required, attributes
1025 1026 1027 1028
 * @return
 *   A themed HTML string representing the textarea.
 */
function theme_textarea($element) {
1029
  $class = array('form-textarea');
1030 1031
  if ($element['#resizable'] !== false) {
    drupal_add_js('misc/textarea.js');
1032
    $class[] = 'resizable';
1033 1034
  }

1035
  $cols = $element['#cols'] ? ' cols="'. $element['#cols'] .'"' : '';
1036
  _form_set_class($element, $class);
1037
  return theme('form_element', $element, '<textarea'. $cols .' rows="'. $element['#rows'] .'" name="'. $element['#name'] .'" id="'. $element['#id'] .'" '. drupal_attributes($element['#attributes']) .'>'. check_plain($element['#value']) .'</textarea>');
1038 1039 1040 1041 1042 1043 1044 1045 1046
}

/**
 * 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.
1047
 *   Properties used: value, children.
1048 1049 1050 1051 1052
 * @return
 *   A themed HTML string representing the HTML markup.
 */

function theme_markup($element) {
1053
  return $element['#value'] . $element['#children'];
1054 1055 1056 1057 1058 1059 1060
}

/**
* Format a password field.
*
* @param $element
*   An associative array containing the properties of the element.
1061
*   Properties used:  title, value, description, size, maxlength, required, attributes
1062 1063 1064 1065
* @return
*   A themed HTML string representing the form.
*/
function theme_password($element) {
1066
  $size = $element['#size'] ? ' size="'. $element['#size'] .'" ' : '';
1067

1068 1069
  _form_set_class($element, array('form-text'));
  $output = '<input type="password" maxlength="'. $element['#maxlength'] .'" name="'. $element['#name'] .'" id="'. $element['#id'] .'" '. $size . drupal_attributes($element['#attributes']) .' />';
1070
  return theme('form_element', $element, $output);
1071 1072 1073
}

/**
1074
 * Expand weight elements into selects.
1075
 */
1076
function process_weight($element) {
1077
  for ($n = (-1 * $element['#delta']); $n <= $element['#delta']; $n++) {
1078 1079
    $weights[$n] = $n;
  }
1080 1081
  $element['#options'] = $weights;
  $element['#type'] = 'select';
1082 1083
  $element['#is_weight'] = TRUE;
  return $element;
1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105
}

/**
 * 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) {
1106
  _form_set_class($element, array('form-file'));
1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143
  return theme('form_element', $element, '<input type="file" name="'. $element['#name'] .'"'. ($element['#attributes'] ? ' '. drupal_attributes($element['#attributes']) : '') .' id="'. form_clean_id($element['#id']) .'" size="'. $element['#size'] ."\" />\n");
}

/**
 * Return a themed form element.
 *
 * @param element
 *   An associative array containing the properties of the element.
 *   Properties used: title, description, id, required
 * @param $value
 *   the form element's data
 * @return
 *   a string representing the form element
 */
function theme_form_element($element, $value) {
  $output  = '<div class="form-item">'."\n";
  $required = !empty($element['#required']) ? '<span class="form-required" title="'. t('This field is required.') .'">*</span>' : '';

  if (!empty($element['#title'])) {
    $title = $element['#title'];
    if (!empty($element['#id'])) {
      $output .= ' <label for="'. form_clean_id($element['#id']) .'">'. t('%title: %required', array('%title' => $title, '%required' => $required)) ."</label>\n";
    }
    else {
      $output .= ' <label>'. t('%title: %required', array('%title' => $title, '%required' => $required)) ."</label>\n";
    }
  }