form.inc 38 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) {
Steven Wittens's avatar
Steven Wittens committed
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) {
Steven Wittens's avatar
Steven Wittens committed
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);
Steven Wittens's avatar
Steven Wittens committed
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;
  }
Steven Wittens's avatar
Steven Wittens committed
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) {
Steven Wittens's avatar
Steven Wittens committed
412
    // don't squash an existing tree value
413
414
415
    if (!isset($form[$key]['#tree'])) {
      $form[$key]['#tree'] = $form['#tree'];
    }
Steven Wittens's avatar
Steven Wittens committed
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);
Steven Wittens's avatar
Steven Wittens committed
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;
Steven Wittens's avatar
Steven Wittens committed
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;
Steven Wittens's avatar
Steven Wittens committed
563

564
  $basic_defaults = array(
565
566
567
    '#description' => NULL,
    '#attributes' => array(),
    '#required' => FALSE,
Steven Wittens's avatar
Steven Wittens committed
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();
Steven Wittens's avatar
Steven Wittens committed
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
1075
1076
1077
}

/**
 * Format a weight selection menu.
 *
 * @param $element
 *   An associative array containing the properties of the element.
1078
 *   Properties used:  title, delta, description
1079
1080
1081
1082
 * @return
 *   A themed HTML string representing the form.
 */
function theme_weight($element) {
1083
  for ($n = (-1