form.inc 39 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
 *   A unique string identifying the form. Allows each form to be
53
 *   themed. Pass NULL to suppress the form_id parameter (produces
54
 *   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 64 65 66 67
  static $saved_globals = array();

  // Save globals in case of indirect recursive call
  array_push($saved_globals, array($form_values, $form_submitted, $form_button_counter));

68
  $form_values = array();
Dries's avatar
Dries committed
69
  $form_submitted = FALSE;
70
  $form_button_counter = array(0, 0);
71

72 73
  $form['#type'] = 'form';
  if (isset($form['#token'])) {
74 75 76
    // If the page cache is on and an anonymous user issues a GET request,
    // unset the token because the token in the cached page would not match,
    // because the token is based on the session ID.
77 78
    if (variable_get('cache', 0) && !$user->uid && $_SERVER['REQUEST_METHOD'] == 'GET') {
      unset($form['#token']);
79
    }
80 81 82 83 84
    else {
      // Make sure that a private key is set:
      if (!variable_get('drupal_private_key', '')) {
        variable_set('drupal_private_key', mt_rand());
      }
85

86 87
      $form['form_token'] = array('#type' => 'hidden', '#default_value' => md5(session_id() . $form['#token'] . variable_get('drupal_private_key', '')));
    }
88
  }
89
  if (isset($form_id)) {
90
    $form['form_id'] = array('#type' => 'hidden', '#value' => $form_id, '#id' => str_replace('_', '-', "edit-$form_id"));
91
  }
92 93 94
  if (!isset($form['#id'])) {
    $form['#id'] = $form_id;
  }
95

96
  $form += _element_info('form');
97

Dries's avatar
Dries committed
98 99
  if (!isset($form['#validate'])) {
    if (function_exists($form_id .'_validate')) {
100
      $form['#validate'] = array($form_id .'_validate' => array());
Dries's avatar
Dries committed
101 102
    }
    elseif (function_exists($callback .'_validate')) {
103
      $form['#validate'] = array($callback .'_validate' => array());
Dries's avatar
Dries committed
104 105 106
    }
  }

107 108
  if (!isset($form['#submit'])) {
    if (function_exists($form_id .'_submit')) {
109 110
      // we set submit here so that it can be altered but use reference for
      // $form_values because it will change later
111
      $form['#submit'] = array($form_id .'_submit' => array());
Dries's avatar
Dries committed
112
    }
113
    elseif (function_exists($callback .'_submit')) {
114
      $form['#submit'] = array($callback .'_submit' => array());
Dries's avatar
Dries committed
115 116 117
    }
  }

118 119
  foreach (module_implements('form_alter') as $module) {
    $function = $module .'_form_alter';
120
    $function($form_id, $form);
121 122
  }

123
  $form = form_builder($form_id, $form);
124
  if (!empty($_POST['edit']) && (($_POST['edit']['form_id'] == $form_id) || ($_POST['edit']['form_id'] == $callback))) {
125
    drupal_validate_form($form_id, $form, $callback);
126 127 128 129
    // 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()) {
130
      $redirect = drupal_submit_form($form_id, $form, $callback);
131
      if (isset($redirect)) {
132 133
        $goto = $redirect;
      }
134
      if (isset($form['#redirect'])) {
135 136
        $goto = $form['#redirect'];
      }
137 138
      if ($goto !== FALSE) {
        if (is_array($goto)) {
139
          call_user_func_array('drupal_goto', $goto);
140
        }
141 142 143
        elseif (!isset($goto)) {
          drupal_goto($_GET['q']);
        }
144 145 146
        else {
          drupal_goto($goto);
        }
147
      }
148
    }
149 150
  }

151 152 153 154 155 156 157 158
  // Don't override #theme if someone already set it.
  if (!isset($form['#theme'])) {
    if (theme_get_function($form_id)) {
      $form['#theme'] = $form_id;
    }
    elseif (theme_get_function($callback)) {
      $form['#theme'] = $callback;
    }
159
  }
160 161 162 163 164 165 166 167 168

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

169 170 171 172
  $output = form_render($form);
  // Restore globals
  list($form_values, $form_submitted, $form_button_counter) = array_pop($saved_globals);
  return $output;
173 174
}

175
function drupal_validate_form($form_id, $form, $callback = NULL) {
176
  global $form_values;
177 178 179 180 181
  static $validated_forms = array();

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

183
  if (isset($form['#token'])) {
184
    if ($form_values['form_token'] != md5(session_id() . $form['#token'] . variable_get('drupal_private_key', ''))) {
185
      // setting this error will cause the form to fail validation
186
      form_set_error('form_token', t('Validation error, please try again. If this error persists, please contact the site administrator.'));
187 188 189
    }
  }

190
  _form_validate($form, $form_id);
191
  $validated_forms[$form_id] = TRUE;
192 193
}

194
function drupal_submit_form($form_id, $form, $callback = NULL) {
195 196
  global $form_values;
  $default_args = array($form_id, &$form_values);
197

198
  if (isset($form['#submit'])) {
199 200
    foreach ($form['#submit'] as $function => $args) {
      if (function_exists($function)) {
201
        $args = array_merge($default_args, (array) $args);
202 203
        // Since we can only redirect to one page, only the last redirect will work
        $redirect = call_user_func_array($function, $args);
204 205 206
        if (isset($redirect)) {
          $goto = $redirect;
        }
Dries's avatar
Dries committed
207 208
      }
    }
209
  }
210
  return $goto;
211 212
}

213
function _form_validate($elements, $form_id = NULL) {
214 215 216 217 218 219
  // Recurse through all children.
  foreach (element_children($elements) as $key) {
    if (isset($elements[$key]) && $elements[$key]) {
      _form_validate($elements[$key]);
    }
  }
220
  /* Validate the current input */
221
  if (!$elements['#validated']) {
222
    if (isset($elements['#needs_validation'])) {
223 224 225 226 227
      // 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'])));
228
      }
229 230 231 232 233 234 235 236 237 238 239 240 241 242

      // 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.'));
243
              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);
244
            }
245 246
          }
        }
247 248
        elseif (!isset($options[$elements['#value']])) {
          form_error($elements, t('An illegal choice has been detected. Please contact the site administrator.'));
249
          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);
250
        }
251 252 253 254
      }
    }

    // User-applied checks.
Dries's avatar
Dries committed
255
    if (isset($elements['#validate'])) {
256 257 258 259 260
      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);
261
        }
262 263
        if (function_exists($function))  {
          call_user_func_array($function, $args);
264 265 266
        }
      }
    }
267
    $elements['#validated'] = TRUE;
268 269 270
  }
}

271 272 273 274 275
/**
 * 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.
 */
276
function form_set_error($name = NULL, $message = '') {
277 278 279
  static $form = array();
  if (isset($name) && !isset($form[$name])) {
    $form[$name] = $message;
280 281 282
    if ($message) {
      drupal_set_message($message, 'error');
    }
283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311
  }
  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];
  }
}

312 313 314
/**
 * Flag an element as having an error.
 */
315
function form_error(&$element, $message = '') {
316
  $element['#error'] = TRUE;
317
  form_set_error(implode('][', $element['#parents']), $message);
318 319 320
}

/**
321 322 323 324 325 326 327 328 329
 * 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.
330
 */
331
function form_builder($form_id, $form) {
332
  global $form_values, $form_submitted, $form_button_counter;
333

334 335 336
  // Initialize as unprocessed.
  $form['#processed'] = FALSE;

337
  /* Use element defaults */
338
  if ((!empty($form['#type'])) && ($info = _element_info($form['#type']))) {
339
    // overlay $info onto $form, retaining preexisting keys in $form
340 341 342
    $form += $info;
  }

343
  if (isset($form['#input']) && $form['#input']) {
344 345 346 347 348 349
    if (!isset($form['#name'])) {
      $form['#name'] = 'edit[' . implode('][', $form['#parents']) . ']';
    }
    if (!isset($form['#id'])) {
      $form['#id'] =  'edit-' . implode('-', $form['#parents']);
    }
350

351
    $posted = (isset($_POST['edit']) && ($_POST['edit']['form_id'] == $form_id));
352
    $edit = $posted ? $_POST['edit'] : array();
353
    foreach ($form['#parents'] as $parent) {
354 355
      $edit = isset($edit[$parent]) ? $edit[$parent] : NULL;
    }
356
    if (!isset($form['#value']) && !array_key_exists('#value', $form)) {
357
      if ($posted) {
358 359
        switch ($form['#type']) {
          case 'checkbox':
360
            $form['#value'] = !empty($edit) ? $form['#return_value'] : 0;
361 362
            break;
          case 'select':
363 364 365 366 367 368 369
            if (isset($form['#multiple']) && $form['#multiple']) {
              if (isset($edit) && is_array($edit)) {
                $form['#value'] = drupal_map_assoc($edit);
              }
              else {
                $form['#value'] = array();
              }
370
            }
371 372
            elseif (isset($edit)) {
              $form['#value'] = $edit;
373
            }
374 375 376
            break;
          case 'textfield':
            if (isset($edit)) {
377 378 379
              // Equate $edit to the form value to ensure it's marked for validation
              $edit = str_replace(array("\r", "\n"), '', $edit);
              $form['#value'] = $edit;
380 381 382 383 384 385
            }
            break;
          default:
            if (isset($edit)) {
              $form['#value'] = $edit;
            }
386
        }
387
        // Mark all posted values for validation
388
        if ((isset($form['#value']) && $form['#value'] === $edit) || (isset($form['#required']) && $form['#required'])) {
389
          $form['#needs_validation'] = TRUE;
390 391 392
        }
      }
      if (!isset($form['#value'])) {
393 394 395 396 397 398 399
        $function = $form['#type'] . '_value';
        if (function_exists($function)) {
          $function($form);
        }
        else {
          $form['#value'] = $form['#default_value'];
        }
400
      }
401
    }
402 403 404 405 406
    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']) {
407
        $form_submitted = $form_submitted || $form['#executes_submit_callback'];
408 409 410 411
      }
    }
  }

412
  // Allow for elements to expand to multiple elements, e.g. radios, checkboxes and files.
413
  if (isset($form['#process']) && !$form['#processed']) {
414 415
    foreach ($form['#process'] as $process => $args) {
      if (function_exists($process)) {
416 417
        $args = array_merge(array($form), $args);
        $form = call_user_func_array($process, $args);
418 419
      }
    }
420
    $form['#processed'] = TRUE;
421 422
  }

423 424 425
  // 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.
426
  if (isset($form['#input']) && $form['#input']) {
427
    form_set_value($form, $form['#value']);
428 429
  }

430 431 432
  // Recurse through all child elements.
  $count  = 0;
  foreach (element_children($form) as $key) {
433
    // don't squash an existing tree value
434 435 436
    if (!isset($form[$key]['#tree'])) {
      $form[$key]['#tree'] = $form['#tree'];
    }
437

438 439
    // don't squash existing parents value
    if (!isset($form[$key]['#parents'])) {
440 441
      // 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);
442 443
    }

444
    # Assign a decimal placeholder weight to preserve original array order
445 446 447
    if (!isset($form[$key]['#weight'])) {
      $form[$key]['#weight'] = $count/1000;
    }
448
    $form[$key] = form_builder($form_id, $form[$key]);
449 450 451
    $count++;
  }

452 453 454 455 456 457
  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);
      }
    }
458
    $form['#after_build_done'] = TRUE;
459
  }
460 461

  return $form;
462 463
}

464
/**
Dries's avatar
Dries committed
465
 * Use this function to make changes to form values in the form validate
466 467 468 469
 * 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
470
 * $form_values['foo']['bar'] to be 'baz'.
471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502
 *
 * @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;
}

503
/**
504 505
 * Renders a HTML form given a form tree. Recursively iterates over each of
 * the form elements, generating HTML code. This function is usually
506
 * called from within a theme. To render a form from within a module, use
507
 * drupal_get_form().
508 509 510 511 512 513 514
 *
 * @param $elements
 *   The form tree describing the form.
 * @return
 *   The rendered HTML form.
 */
function form_render(&$elements) {
515 516
  if (!isset($elements)) {
    return NULL;
517
  }
518 519
  $content = '';
  uasort($elements, "_form_sort");
520
  if (!isset($elements['#children'])) {
521
    $children = element_children($elements);
522
    /* Render all the children that use a theme function */
523
    if (isset($elements['#theme']) && !$elements['#theme_used']) {
524
      $elements['#theme_used'] = TRUE;
525 526

      $previous_value = $elements['#value'];
527
      $previous_type = $elements['#type'];
drumm's avatar
drumm committed
528
      // If we rendered a single element, then we will skip the renderer.
drumm's avatar
drumm committed
529 530 531 532 533 534
      if (empty($children)) {
        $elements['#printed'] = TRUE;
      }
      else {
        $elements['#value'] = '';
      }
535
      $elements['#type'] = 'markup';
536

537
      $content = theme($elements['#theme'], $elements);
538 539

      $elements['#value'] = $previous_value;
540
      $elements['#type'] = $previous_type;
541
      unset($elements['#prefix'], $elements['#suffix']);
542 543
    }
    /* render each of the children using form_render and concatenate them */
544
    if (!isset($content) || $content === '') {
545
      foreach ($children as $key) {
546 547 548 549
        $content .= form_render($elements[$key]);
      }
    }
  }
550
  if (isset($content) && $content !== '') {
551
    $elements['#children'] = $content;
552 553
  }

554
  // Until now, we rendered the children, here we render the element itself
555
  if (!isset($elements['#printed'])) {
556 557
    $content = theme(($elements['#type']) ? $elements['#type']: 'markup', $elements);
    $elements['#printed'] = TRUE;
558 559
  }

560
  if (isset($content) && $content !== '') {
561 562 563
    $prefix = isset($elements['#prefix']) ? $elements['#prefix'] : '';
    $suffix = isset($elements['#suffix']) ? $elements['#suffix'] : '';
    return $prefix . $content . $suffix;
564
  }
565 566 567
}

/**
568
 * Function used by uasort in form_render() to sort form by weight.
569 570
 */
function _form_sort($a, $b) {
571 572 573
  $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
574 575
    return 0;
  }
576
  return ($a_weight < $b_weight) ? -1 : 1;
577 578 579 580 581
}

/**
 * Retrieve the default properties for the defined element type.
 */
582
function _element_info($type, $refresh = NULL) {
583
  static $cache;
584

585
  $basic_defaults = array(
586 587 588
    '#description' => NULL,
    '#attributes' => array(),
    '#required' => FALSE,
589
    '#tree' => FALSE,
590
    '#parents' => array()
591
  );
592
  if (!isset($cache) || $refresh) {
593 594 595
    $cache = array();
    foreach (module_implements('elements') as $module) {
      $elements = module_invoke($module, 'elements');
596
      if (isset($elements) && is_array($elements)) {
597
        $cache = array_merge_recursive($cache, $elements);
598 599 600 601
      }
    }
    if (sizeof($cache)) {
      foreach ($cache as $element_type => $info) {
602
        $cache[$element_type] = array_merge_recursive($basic_defaults, $info);
603 604 605 606 607 608 609
      }
    }
  }

  return $cache[$type];
}

610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628
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;
}

629 630 631 632 633
/**
 * Format a dropdown menu or scrolling selection box.
 *
 * @param $element
 *   An associative array containing the properties of the element.
634
 *   Properties used: title, value, options, description, extra, multiple, required
635 636 637 638 639 640 641 642 643
 * @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 = '';
644
  $size = $element['#size'] ? ' size="' . $element['#size'] . '"' : '';
645
  _form_set_class($element, array('form-select'));
646 647
  $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>');
648 649 650 651 652 653
}

function form_select_options($element, $choices = NULL) {
  if (!isset($choices)) {
    $choices = $element['#options'];
  }
654
  // array_key_exists() accommodates the rare event where $element['#value'] is NULL.
655 656 657
  // isset() fails in this situation.
  $value_valid = isset($element['#value']) || array_key_exists('#value', $element);
  $value_is_array = is_array($element['#value']);
658 659
  $options = '';
  foreach ($choices as $key => $choice) {
660
    if (is_array($choice)) {
661 662 663
      $options .= '<optgroup label="'. $key .'">';
      $options .= form_select_options($element, $choice);
      $options .= '</optgroup>';
664 665
    }
    else {
666 667
      $key = (string)$key;
      if ($value_valid && ($element['#value'] == $key || ($value_is_array && in_array($key, $element['#value'])))) {
668 669 670 671 672
        $selected = ' selected="selected"';
      }
      else {
        $selected = '';
      }
673
      $options .= '<option value="'. $key .'"'. $selected .'>'. check_plain($choice) .'</option>';
674 675
    }
  }
676
  return $options;
677 678 679 680 681 682 683
}

/**
 * Format a group of form items.
 *
 * @param $element
 *   An associative array containing the properties of the element.
684
 *   Properties used: attributes, title, value, description, children, collapsible, collapsed
685 686 687 688
 * @return
 *   A themed HTML string representing the form item group.
 */
function theme_fieldset($element) {
689
  if ($element['#collapsible']) {
690 691
    drupal_add_js('misc/collapse.js');

692 693 694
    $element['#attributes']['class'] .= ' collapsible';
    if ($element['#collapsed']) {
     $element['#attributes']['class'] .= ' collapsed';
695 696 697
    }
  }

698
  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";
699 700 701 702 703 704 705
}

/**
 * Format a radio button.
 *
 * @param $element
 *   An associative array containing the properties of the element.
706
 *   Properties used: required, return_value, value, attributes, title, description
707 708 709 710
 * @return
 *   A themed HTML string representing the form item group.
 */
function theme_radio($element) {
711
  _form_set_class($element, array('form-radio'));
712
  $output = '<input type="radio" ';
713 714 715 716 717 718
  $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>';
719
  }
720 721 722

  unset($element['#title']);
  return theme('form_element', $element, $output);
723 724 725 726 727 728 729
}

/**
 * Format a set of radio buttons.
 *
 * @param $element
 *   An associative array containing the properties of the element.
730
 *   Properties used: title, value, options, description, required and attributes.
731 732 733 734
 * @return
 *   A themed HTML string representing the radio button set.
 */
function theme_radios($element) {
735
  if ($element['#title'] || $element['#description']) {
736 737
    unset($element['#id']);
    return theme('form_element', $element, $element['#children']);
738 739
  }
  else {
740
    return $element['#children'];
741 742 743
  }
}

744 745 746 747 748 749 750 751 752 753
/**
 * 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) {
754
  return theme('form_element', $element, '<div class="container-inline">'. $element['#children'] .'</div>');
755 756
}

757 758 759 760 761 762 763 764 765 766 767 768
/*
 * 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;
}

769
/**
770
 * Validate password_confirm element.
771
 */
772
function password_confirm_validate($form) {
773 774
  $pass1 = trim($form['pass1']['#value']);
  if (!empty($pass1)) {
775
    $pass2 = trim($form['pass2']['#value']);
776
    if ($pass1 != $pass2) {
777
      form_error($form, t('The specified passwords do not match.'));
778
    }
779 780
  }
  elseif ($form['#required'] && !empty($_POST['edit'])) {
781
    form_error($form, t('Password field is required.'));
782
  }
783

784 785 786 787 788 789
  // 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);

790 791 792
  return $form;
}

793
/**
794
 * Format a date selection element.
795 796 797
 *
 * @param $element
 *   An associative array containing the properties of the element.
798
 *   Properties used: title, value, options, description, required and attributes.
799
 * @return
800
 *   A themed HTML string representing the date selection boxes.
801 802
 */
function theme_date($element) {
803
  return theme('form_element', $element, '<div class="container-inline">'. $element['#children'] .'</div>');
804 805 806
}

/**
807
 * Roll out a single date element.
808 809 810
 */
function expand_date($element) {
  // Default to current date
811 812
  if (!isset($element['#value'])) {
    $element['#value'] = array('day' => format_date(time(), 'custom', 'j'),
813 814 815 816
                            'month' => format_date(time(), 'custom', 'n'),
                            'year' => format_date(time(), 'custom', 'Y'));
  }

817 818
  $element['#tree'] = TRUE;

819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834
  // 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':
835
        $options = drupal_map_assoc(range(1, 12), 'map_month');
836 837 838 839 840
        break;
      case 'year':
        $options = drupal_map_assoc(range(1900, 2050));
        break;
    }
841 842 843 844 845 846 847 848
    $parents = $element['#parents'];
    $parents[] = $type;
    $element[$type] = array(
      '#type' => 'select',
      '#value' => $element['#value'][$type],
      '#attributes' => $element['#attributes'],
      '#options' => $options,
    );
849 850 851 852 853
  }

  return $element;
}

854 855 856 857 858 859 860 861 862
/**
 * Validates the date type to stop dates like February 30, 2006.
 */
function date_validate($form) {
  if (!checkdate($form['#value']['month'], $form['#value']['day'], $form['#value']['year'])) {
    form_error($form, t('The specified date is invalid.'));
  }
}

863 864 865 866 867 868
/**
 * 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);
}
869

870 871 872 873 874 875 876 877 878 879 880
/**
 * 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;
}

881 882 883 884 885 886 887 888 889 890 891 892
/**
 * 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;
  }
}

893
/**
894 895
 * Roll out a single radios element to a list of radios,
 * using the options array as index.
896 897
 */
function expand_radios($element) {
898 899
  if (count($element['#options']) > 0) {
    foreach ($element['#options'] as $key => $choice) {
900
      if (!isset($element[$key])) {
901
        $element[$key] = array('#type' => 'radio', '#title' => $choice, '#return_value' => $key, '#default_value' => $element['#default_value'], '#attributes' => $element['#attributes'], '#parents' => $element['#parents'], '#spawned' => TRUE);
902 903 904 905 906 907 908 909 910 911 912
      }
    }
  }
  return $element;
}

/**
 * Format a form item.
 *
 * @param $element
 *   An associative array containing the properties of the element.
913
 *   Properties used:  title, value, description, required, error
914 915 916 917
 * @return
 *   A themed HTML string representing the form item.
 */
function theme_item($element) {
918
  return theme('form_element', $element, $element['#value'] . $element['#children']);
919 920 921 922 923 924 925
}

/**
 * Format a checkbox.
 *
 * @param $element
 *   An associative array containing the properties of the element.
926
 *   Properties used:  title, value, return_value, description, required
927 928 929 930
 * @return
 *   A themed HTML string representing the checkbox.
 */
function theme_checkbox($element) {
931
  _form_set_class($element, array('form-checkbox'));
932 933
  $checkbox = '<input ';
  $checkbox .= 'type="checkbox" ';
934 935 936
  $checkbox .= 'name="'. $element['#name'] .'" ';
  $checkbox .= 'id="'. $element['#id'].'" ' ;
  $checkbox .= 'value="'. $element['#return_value'] .'" ';
937
  $checkbox .= $element['#value'] ? ' checked="checked" ' : ' ';
938 939 940 941
  $checkbox .= drupal_attributes($element['#attributes']) . ' />';

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

944 945
  unset($element['#title']);
  return theme('form_element', $element, $checkbox);
946 947 948 949 950 951 952 953 954 955 956
}

/**
 * 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) {
957
  if ($element['#title'] || $element['#description']) {
958 959
    unset($element['#id']);
    return theme('form_element', $element, $element['#children']);
960 961
  }
  else {
962
    return $element['#children'];
963 964 965 966
  }
}

function expand_checkboxes($element) {
967
  $value = is_array($element['#value']) ? $element['#value'] : array();
968
  $element['#tree'] = TRUE;
969 970 971
  if (count($element['#options']) > 0) {
    if (!isset($element['#default_value']) || $element['#default_value'] == 0) {
      $element['#default_value'] = array();
972
    }
973
    foreach ($element['#options'] as $key => $choice) {
974
      if (!isset($element[$key])) {
975
        $element[$key] = array('#type' => 'checkbox', '#processed' => TRUE, '#title' => $choice, '#return_value' => $key, '#default_value' => isset($value[$key]), '#attributes' => $element['#attributes']);
976 977 978 979 980 981 982 983 984 985 986
      }
    }
  }
  return $element;
}

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

function theme_button($element) {
987 988 989 990 991 992 993
  //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'];
  }
994

995
  return '<input type="submit" '. (empty($element['#name']) ? '' : 'name="'. $element['#name'] .'" ') .'value="'. check_plain($element['#value']) .'" '. drupal_attributes($element['#attributes']) ." />\n";
996 997 998 999 1000 1001 1002
}

/**
 * Format a hidden form field.
 *
 * @param $element
 *   An associative array containing the properties of the element.
1003
 *   Properties used:  value, edit
1004 1005 1006 1007
 * @return
 *   A themed HTML string representing the hidden form field.
 */
function theme_hidden($element) {
1008
  return '<input type="hidden" name="'. $element['#name'] . '" id="'. $element['#id'] . '" value="'. check_plain($element['#value']) ."\" " . drupal_attributes($element['#attributes']) ." />\n";
1009 1010 1011 1012 1013 1014 1015
}

/**
 * Format a textfield.
 *
 * @param $element
 *   An associative array containing the properties of the element.
1016
 *   Properties used:  title, value, description, size, maxlength, required, attributes autocomplete_path
1017 1018 1019 1020
 * @return
 *   A themed HTML string representing the textfield.
 */
function theme_textfield($element) {
1021
  $size = $element['#size'] ? ' size="' . $element['#size'] . '"' : '';
1022
  $class = array('form-text');
1023
  $extra = '';
1024
  if ($element['#autocomplete_path']) {
1025
    drupal_add_js('misc/autocomplete.js');
1026
    $class[] = 'form-autocomplete';
1027
    $extra =  '<input class="autocomplete" type="hidden" id="'. $element['#id'] .'-autocomplete" value="'. check_url(url($element['#autocomplete_path'], NULL, NULL, TRUE)) .'" disabled="disabled" />';
1028
  }
1029 1030
  _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']) .' />';
1031
  return theme('form_element', $element, $output). $extra;
1032 1033 1034 1035 1036 1037 1038
}

/**
 * Format a form.
 *
 * @param $element
 *   An associative array containing the properties of the element.
1039
 *   Properties used: action, method, attributes, children
1040 1041 1042 1043
 * @return
 *   A themed HTML string representing the form.
 */
function theme_form($element) {
1044
  // Anonymous div to satisfy XHTML compliance.
1045
  $action = $element['#action'] ? 'action="' . check_url($element['#action']) . '" ' : '';
1046
  return '<form '. $action . ' method="'. $element['#method'] .'" '. 'id="'. $element['#id'] .'"'. drupal_attributes($element['#attributes']) .">\n<div>". $element['#children'] ."\n</div></form>\n";
1047 1048 1049 1050 1051 1052 1053
}

/**
 * Format a textarea.
 *
 * @param $element
 *   An associative array containing the properties of the element.
1054
 *   Properties used: title, value, description, rows, cols, required, attributes
1055 1056 1057 1058
 * @return
 *   A themed HTML string representing the textarea.
 */
function theme_textarea($element) {
1059
  $class = array('form-textarea');
1060
  if ($element['#resizable'] !== FALSE) {
1061
    drupal_add_js('misc/textarea.js');
1062
    $class[] = 'resizable';
1063 1064
  }

1065
  $cols = $element['#cols'] ? ' cols="'. $element['#cols'] .'"' : '';
1066
  _form_set_class($element, $class);
1067
  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>');
1068 1069 1070 1071 1072 1073 1074 1075 1076
}

/**
 * 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.
1077
 *   Properties used: value, children.
1078 1079 1080 1081 1082
 * @return
 *   A themed HTML string representing the HTML markup.
 */

function theme_markup($element) {
1083
  return $element['#value'] . $element['#children'];
1084 1085 1086 1087 1088 1089 1090
}

/**
* Format a password field.
*
* @param $element
*   An associative array containing the properties of the element.
1091
*   Properties used:  title, value, description, size, maxlength, required, attributes
1092 1093 1094 1095
* @return
*   A themed HTML string representing the form.
*/
function theme_password($element) {
1096
  $size = $element['#size'] ? ' size="'. $element['#size'] .'" ' : '';
1097

1098 1099
  _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']) .' />';
1100
  return theme('form_element', $element, $output);
1101 1102 1103
}

/**