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

4
5
6
/**
 * @defgroup form Form generation
 * @{
7
 * Functions to enable the processing and display of HTML forms.
8
 *
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
 * Drupal uses these functions to achieve consistency in its form processing and
 * presentation, while simplifying code and reducing the amount of HTML that
 * must be explicitly generated by modules.
 *
 * The drupal_get_form() function handles retrieving, processing, and
 * displaying a rendered HTML form for modules automatically. For example:
 *
 * // display the user registration form
 * $output = drupal_get_form('user_register');
 *
 * Forms can also be built and submitted programmatically without any user input
 * by populating $form['#post']['edit'] with values to be submitted. For example:
 *
 * // register a new user
 * $form = drupal_retrieve_form('user_register');
 * $form['#post']['edit']['name'] = 'robo-user';
 * $form['#post']['edit']['mail'] = 'robouser@example.com';
 * $form['#post']['edit']['pass'] = 'password';
 * drupal_process_form('user_register', $form);
 *
 * Calling form_get_errors() will list any validation errors that prevented the
 * form from being submitted.
 *
 * For information on the format of the structured arrays used to define forms,
 * and more detailed explanations of the Form API workflow, see the reference at
34
 * http://api.drupal.org/api/HEAD/file/developer/topics/forms_api_reference.html
35
 * and the quickstart guide at
36
 * http://api.drupal.org/api/HEAD/file/developer/topics/forms_api.html
37
38
39
 */

/**
40
41
42
 * Retrieves a form from a builder function, passes it on for
 * processing, and renders the form or redirects to its destination
 * as appropriate.
43
44
 *
 * @param $form_id
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
 *   The unique string identifying the desired form. If a function
 *   with that name exists, it is called to build the form array.
 *   Modules that need to generate the same form (or very similar forms)
 *   using different $form_ids can implement hook_forms(), which maps
 *   different $form_id values to the proper form building function. Examples
 *   may be found in node_forms(), search_forms(), and user_forms().
 * @param ...
 *   Any additional arguments needed by the form building function.
 * @return
 *   The rendered form.
 */
function drupal_get_form($form_id) {
  $args = func_get_args();
  $form = call_user_func_array('drupal_retrieve_form', $args);

  $redirect = drupal_process_form($form_id, $form);

  if (isset($redirect)) {
    drupal_redirect_form($form, $redirect);
  }
  return drupal_render_form($form_id, $form);
}

/**
 * Retrieves the structured array that defines a given form.
 *
 * @param $form_id
 *   The unique string identifying the desired form. If a function
 *   with that name exists, it is called to build the form array.
 *   Modules that need to generate the same form (or very similar forms)
 *   using different $form_ids can implement hook_forms(), which maps
 *   different $form_id values to the proper form building function.
 * @param ...
 *   Any additional arguments needed by the form building function.
 */
function drupal_retrieve_form($form_id) {
  static $forms;

  $args = func_get_args();
  array_shift($args);
  if (!function_exists($form_id)) {
    if (!isset($forms)) {
      $forms = module_invoke_all('forms');
    }
    $form_definition = $forms[$form_id];
    if (isset($form_definition['callback arguments'])) {
      $args = array_merge($form_definition['callback arguments'], $args);
    }
    if (isset($form_definition['callback'])) {
      $callback = $form_definition['callback'];
    }
  }
  // $callback comes from a hook_forms() implementation
  return call_user_func_array(isset($callback) ? $callback : $form_id, $args);
}

/**
 * This function is the heart of form API. The form gets built, validated and in
 * appropriate cases, submitted.
 *
 * @param $form_id
 *   The unique string identifying the current form.
107
108
 * @param $form
 *   An associative array containing the structure of the form.
109
110
 * @return
 *   The path to redirect the user to upon completion.
111
 */
112
function drupal_process_form($form_id, &$form) {
113
  global $form_values, $form_submitted, $user, $form_button_counter;
114
  static $saved_globals = array();
115
116
117
  // In some scenerios, this function can be called recursively. Pushing any pre-existing
  // $form_values and form submission data lets us start fresh without clobbering work done
  // in earlier recursive calls.
118
119
  array_push($saved_globals, array($form_values, $form_submitted, $form_button_counter));

120
  $form_values = array();
Dries's avatar
Dries committed
121
  $form_submitted = FALSE;
122
  $form_button_counter = array(0, 0);
Steven Wittens's avatar
Steven Wittens committed
123

124
125
126
  drupal_prepare_form($form_id, $form);
  if (($form['#programmed']) || (!empty($_POST['edit']) && (($_POST['edit']['form_id'] == $form_id) || ($_POST['edit']['form_id'] == $form['#base'])))) {
    drupal_validate_form($form_id, $form);
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.
130
131
    if ((($form['#programmed']) || $form_submitted || (!$form_button_counter[0] && $form_button_counter[1])) && !form_get_errors()) {
      $redirect = drupal_submit_form($form_id, $form);
132
133
134
    }
  }

135
136
  // We've finished calling functions that alter the global values, so we can
  // restore the ones that were there before this function was called.
137
  list($form_values, $form_submitted, $form_button_counter) = array_pop($saved_globals);
138
  return $redirect;
139
140
141
142
143
144
145
146
147
148
149
150
151
}

/**
 * Prepares a structured form array by adding required elements,
 * executing any hook_form_alter functions, and optionally inserting
 * a validation token to prevent tampering.
 *
 * @param $form_id
 *   A unique string identifying the form for validation, submission,
 *   theming, and hook_form_alter functions.
 * @param $form
 *   An associative array containing the structure of the form.
 */
152
function drupal_prepare_form($form_id, &$form) {
153
  $form['#type'] = 'form';
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169

  if (!isset($form['#post'])) {
    $form['#post'] = $_POST;
    $form['#programmed'] = FALSE;
  }
  else {
    $form['#programmed'] = TRUE;
  }

  // If $base is set, it is used in place of $form_id when constructing validation,
  // submission, and theming functions. Useful for mapping many similar or duplicate
  // forms with different $form_ids to the same processing functions.
  if (isset($form['#base'])) {
    $base = $form['#base'];
  }

170
  if (isset($form['#token'])) {
171
172
173
    // 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.
174
175
    if (variable_get('cache', 0) && !$user->uid && $_SERVER['REQUEST_METHOD'] == 'GET') {
      unset($form['#token']);
176
    }
177
178
179
180
181
    else {
      // Make sure that a private key is set:
      if (!variable_get('drupal_private_key', '')) {
        variable_set('drupal_private_key', mt_rand());
      }
182

183
184
      $form['form_token'] = array('#type' => 'hidden', '#default_value' => md5(session_id() . $form['#token'] . variable_get('drupal_private_key', '')));
    }
185
  }
186

187
  if (isset($form_id)) {
188
    $form['form_id'] = array('#type' => 'hidden', '#value' => $form_id, '#id' => str_replace('_', '-', "edit-$form_id"));
189
  }
190
191
192
  if (!isset($form['#id'])) {
    $form['#id'] = $form_id;
  }
Steven Wittens's avatar
Steven Wittens committed
193

194
  $form += _element_info('form');
195

Dries's avatar
Dries committed
196
197
  if (!isset($form['#validate'])) {
    if (function_exists($form_id .'_validate')) {
198
      $form['#validate'] = array($form_id .'_validate' => array());
Dries's avatar
Dries committed
199
    }
200
201
    elseif (function_exists($base .'_validate')) {
      $form['#validate'] = array($base .'_validate' => array());
Dries's avatar
Dries committed
202
203
204
    }
  }

205
206
  if (!isset($form['#submit'])) {
    if (function_exists($form_id .'_submit')) {
207
208
      // we set submit here so that it can be altered but use reference for
      // $form_values because it will change later
209
      $form['#submit'] = array($form_id .'_submit' => array());
Dries's avatar
Dries committed
210
    }
211
212
    elseif (function_exists($base .'_submit')) {
      $form['#submit'] = array($base .'_submit' => array());
Dries's avatar
Dries committed
213
214
215
    }
  }

216
217
  foreach (module_implements('form_alter') as $module) {
    $function = $module .'_form_alter';
218
    $function($form_id, $form);
219
220
  }

221
  $form = form_builder($form_id, $form);
222
223
}

224
225
226
227
228
229
230
231
232
233
234
235

/**
 * Validates user-submitted form data from a global variable using
 * the validate functions defined in a structured form array.
 *
 * @param $form_id
 *   A unique string identifying the form for validation, submission,
 *   theming, and hook_form_alter functions.
 * @param $form
 *   An associative array containing the structure of the form.
 *
 */
236
function drupal_validate_form($form_id, $form) {
237
  global $form_values;
238
239
240
241
242
  static $validated_forms = array();

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

244
  // If the session token was set by drupal_prepare_form(), ensure that it
245
  // matches the current user's session
246
  if (isset($form['#token'])) {
247
    if ($form_values['form_token'] != md5(session_id() . $form['#token'] . variable_get('drupal_private_key', ''))) {
248
      // setting this error will cause the form to fail validation
249
      form_set_error('form_token', t('Validation error, please try again. If this error persists, please contact the site administrator.'));
250
251
252
    }
  }

253
  _form_validate($form, $form_id);
254
  $validated_forms[$form_id] = TRUE;
255
256
}

257
258
259
260
261
262
263
264
265
266
267
268
269
270
/**
 * Processes user-submitted form data from a global variable using
 * the submit functions defined in a structured form array.
 *
 * @param $form_id
 *   A unique string identifying the form for validation, submission,
 *   theming, and hook_form_alter functions.
 * @param $form
 *   An associative array containing the structure of the form.
 * @return
 *   A string containing the path of the page to display when processing
 *   is complete.
 *
 */
271
function drupal_submit_form($form_id, $form) {
272
273
  global $form_values;
  $default_args = array($form_id, &$form_values);
274

275
  if (isset($form['#submit'])) {
276
277
    foreach ($form['#submit'] as $function => $args) {
      if (function_exists($function)) {
278
        $args = array_merge($default_args, (array) $args);
279
280
        // Since we can only redirect to one page, only the last redirect will work
        $redirect = call_user_func_array($function, $args);
281
282
283
        if (isset($redirect)) {
          $goto = $redirect;
        }
Dries's avatar
Dries committed
284
285
      }
    }
286
  }
287
  return $goto;
288
289
}

290
291
292
293
294
295
296
297
298
299
300
301
302
/**
 * Renders a structured form array into themed HTML.
 *
 * @param $form_id
 *   A unique string identifying the form for validation, submission,
 *   theming, and hook_form_alter functions.
 * @param $form
 *   An associative array containing the structure of the form.
 * @return
 *   A string containing the path of the page to display when processing
 *   is complete.
 *
 */
303
function drupal_render_form($form_id, &$form) {
304
  // Don't override #theme if someone already set it.
305
306
307
308
  if (isset($form['#base'])) {
    $base = $form['#base'];
  }

309
310
311
312
  if (!isset($form['#theme'])) {
    if (theme_get_function($form_id)) {
      $form['#theme'] = $form_id;
    }
313
314
    elseif (theme_get_function($base)) {
      $form['#theme'] = $base;
315
316
317
318
319
320
321
322
323
324
325
    }
  }

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

326
  $output = drupal_render($form);
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
  return $output;
}

/**
 * Redirect the user to a URL after a form has been processed.
 *
 * @param $form
 *   An associative array containing the structure of the form.
 * @param $redirect
 *   An optional string containing the destination path to redirect
 *   to if none is specified by the form.
 *
 */
function drupal_redirect_form($form, $redirect = NULL) {
  if (isset($redirect)) {
    $goto = $redirect;
  }
  if (isset($form['#redirect'])) {
    $goto = $form['#redirect'];
  }
  if ($goto !== FALSE) {
    if (is_array($goto)) {
      call_user_func_array('drupal_goto', $goto);
    }
    elseif (!isset($goto)) {
      drupal_goto($_GET['q']);
    }
    else {
      drupal_goto($goto);
    }
  }
}

360
function _form_validate($elements, $form_id = NULL) {
361
362
363
364
365
366
  // Recurse through all children.
  foreach (element_children($elements) as $key) {
    if (isset($elements[$key]) && $elements[$key]) {
      _form_validate($elements[$key]);
    }
  }
367
  /* Validate the current input */
368
  if (!$elements['#validated']) {
369
    if (isset($elements['#needs_validation'])) {
370
371
372
373
      // 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') {
374
        form_error($elements, t('!name field is required.', array('!name' => $elements['#title'])));
375
      }
376
377
378
379
380
381
382
383
384
385
386
387
388
389

      // 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.'));
390
              watchdog('form', t('Illegal choice %choice in !name element.', array('%choice' => $v, '!name' => empty($elements['#title']) ? $elements['#parents'][0] : $elements['#title'])), WATCHDOG_ERROR);
391
            }
392
393
          }
        }
394
395
        elseif (!isset($options[$elements['#value']])) {
          form_error($elements, t('An illegal choice has been detected. Please contact the site administrator.'));
396
          watchdog('form', t('Illegal choice %choice in %name element.', array('%choice' => theme_placeholder(check_plain($elements['#value'])), '%name' => theme('placeholder', empty($elements['#title']) ? $elements['#parents'][0] : $elements['#title']))), WATCHDOG_ERROR);
397
        }
398
399
400
401
      }
    }

    // User-applied checks.
Dries's avatar
Dries committed
402
    if (isset($elements['#validate'])) {
403
404
405
406
407
      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);
408
        }
409
410
        if (function_exists($function))  {
          call_user_func_array($function, $args);
411
412
413
        }
      }
    }
414
    $elements['#validated'] = TRUE;
415
416
417
  }
}

418
419
420
421
422
/**
 * 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.
 */
423
function form_set_error($name = NULL, $message = '') {
424
425
426
  static $form = array();
  if (isset($name) && !isset($form[$name])) {
    $form[$name] = $message;
427
428
429
    if ($message) {
      drupal_set_message($message, 'error');
    }
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
  }
  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];
  }
}

459
460
461
/**
 * Flag an element as having an error.
 */
462
function form_error(&$element, $message = '') {
463
  $element['#error'] = TRUE;
464
  form_set_error(implode('][', $element['#parents']), $message);
465
466
467
}

/**
468
469
470
471
472
473
 * 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
474
475
 *   A unique string identifying the form for validation, submission,
 *   theming, and hook_form_alter functions.
476
477
 * @param $form
 *   An associative array containing the structure of the form.
478
 */
479
function form_builder($form_id, $form) {
480
  global $form_values, $form_submitted, $form_button_counter;
481

482
483
484
  // Initialize as unprocessed.
  $form['#processed'] = FALSE;

485
  /* Use element defaults */
486
  if ((!empty($form['#type'])) && ($info = _element_info($form['#type']))) {
487
    // overlay $info onto $form, retaining preexisting keys in $form
488
489
490
    $form += $info;
  }

491
  if (isset($form['#input']) && $form['#input']) {
492
493
494
495
496
497
    if (!isset($form['#name'])) {
      $form['#name'] = 'edit[' . implode('][', $form['#parents']) . ']';
    }
    if (!isset($form['#id'])) {
      $form['#id'] =  'edit-' . implode('-', $form['#parents']);
    }
498

499
    $posted = (($form['#programmed']) || ((!isset($form['#access']) || $form['#access']) && isset($_POST['edit']) && ($_POST['edit']['form_id'] == $form_id)));
500
    $edit = $posted ? $form['#post']['edit'] : array();
501
    foreach ($form['#parents'] as $parent) {
502
503
      $edit = isset($edit[$parent]) ? $edit[$parent] : NULL;
    }
504
    if (!isset($form['#value']) && !array_key_exists('#value', $form)) {
505
      if ($posted) {
506
507
        switch ($form['#type']) {
          case 'checkbox':
508
            $form['#value'] = !empty($edit) ? $form['#return_value'] : 0;
509
510
            break;
          case 'select':
511
512
513
514
515
516
517
            if (isset($form['#multiple']) && $form['#multiple']) {
              if (isset($edit) && is_array($edit)) {
                $form['#value'] = drupal_map_assoc($edit);
              }
              else {
                $form['#value'] = array();
              }
518
            }
519
520
            elseif (isset($edit)) {
              $form['#value'] = $edit;
521
            }
522
523
524
            break;
          case 'textfield':
            if (isset($edit)) {
525
526
527
              // Equate $edit to the form value to ensure it's marked for validation
              $edit = str_replace(array("\r", "\n"), '', $edit);
              $form['#value'] = $edit;
528
529
530
531
532
533
            }
            break;
          default:
            if (isset($edit)) {
              $form['#value'] = $edit;
            }
534
        }
535
        // Mark all posted values for validation
536
        if ((isset($form['#value']) && $form['#value'] === $edit) || (isset($form['#required']) && $form['#required'])) {
537
          $form['#needs_validation'] = TRUE;
538
539
540
        }
      }
      if (!isset($form['#value'])) {
541
542
543
544
545
546
547
        $function = $form['#type'] . '_value';
        if (function_exists($function)) {
          $function($form);
        }
        else {
          $form['#value'] = $form['#default_value'];
        }
548
      }
549
    }
550
551
552
553
554
    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']) {
555
        $form_submitted = $form_submitted || $form['#executes_submit_callback'];
556
557
558
559
      }
    }
  }

560
  // Allow for elements to expand to multiple elements, e.g. radios, checkboxes and files.
561
  if (isset($form['#process']) && !$form['#processed']) {
562
563
    foreach ($form['#process'] as $process => $args) {
      if (function_exists($process)) {
564
565
        $args = array_merge(array($form), $args);
        $form = call_user_func_array($process, $args);
566
567
      }
    }
568
    $form['#processed'] = TRUE;
569
570
  }

571
572
573
  // 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.
574
  if (isset($form['#input']) && $form['#input']) {
575
    form_set_value($form, $form['#value']);
576
577
  }

578
579
580
  // Recurse through all child elements.
  $count  = 0;
  foreach (element_children($form) as $key) {
581
582
    $form[$key]['#post'] = $form['#post'];
    $form[$key]['#programmed'] = $form['#programmed'];
Steven Wittens's avatar
Steven Wittens committed
583
    // don't squash an existing tree value
584
585
586
    if (!isset($form[$key]['#tree'])) {
      $form[$key]['#tree'] = $form['#tree'];
    }
Steven Wittens's avatar
Steven Wittens committed
587

588
589
590
591
592
    // deny access to child elements if parent is denied
    if (isset($form['#access']) && !$form['#access']) {
      $form[$key]['#access'] = FALSE;
    }

593
594
    // don't squash existing parents value
    if (!isset($form[$key]['#parents'])) {
595
596
      // 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
597
598
    }

599
    // Assign a decimal placeholder weight to preserve original array order
600
601
602
    if (!isset($form[$key]['#weight'])) {
      $form[$key]['#weight'] = $count/1000;
    }
603
    $form[$key] = form_builder($form_id, $form[$key]);
604
605
606
    $count++;
  }

607
608
609
610
611
612
  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);
      }
    }
613
    $form['#after_build_done'] = TRUE;
Steven Wittens's avatar
Steven Wittens committed
614
  }
615
616

  return $form;
617
618
}

619
/**
Dries's avatar
Dries committed
620
 * Use this function to make changes to form values in the form validate
621
622
623
624
 * 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
625
 * $form_values['foo']['bar'] to be 'baz'.
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
 *
 * @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;
}

658
659
660
/**
 * Retrieve the default properties for the defined element type.
 */
661
function _element_info($type, $refresh = NULL) {
662
  static $cache;
Steven Wittens's avatar
Steven Wittens committed
663

664
  $basic_defaults = array(
665
666
667
    '#description' => NULL,
    '#attributes' => array(),
    '#required' => FALSE,
Steven Wittens's avatar
Steven Wittens committed
668
    '#tree' => FALSE,
669
    '#parents' => array()
670
  );
671
  if (!isset($cache) || $refresh) {
672
673
674
    $cache = array();
    foreach (module_implements('elements') as $module) {
      $elements = module_invoke($module, 'elements');
675
      if (isset($elements) && is_array($elements)) {
676
        $cache = array_merge_recursive($cache, $elements);
677
678
679
680
      }
    }
    if (sizeof($cache)) {
      foreach ($cache as $element_type => $info) {
681
        $cache[$element_type] = array_merge_recursive($basic_defaults, $info);
682
683
684
685
686
687
688
      }
    }
  }

  return $cache[$type];
}

689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
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;
}

708
709
710
711
712
/**
 * Format a dropdown menu or scrolling selection box.
 *
 * @param $element
 *   An associative array containing the properties of the element.
713
 *   Properties used: title, value, options, description, extra, multiple, required
714
715
716
717
718
719
720
721
722
 * @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 = '';
723
  $size = $element['#size'] ? ' size="' . $element['#size'] . '"' : '';
724
  _form_set_class($element, array('form-select'));
725
726
  $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>');
727
728
729
730
731
732
}

function form_select_options($element, $choices = NULL) {
  if (!isset($choices)) {
    $choices = $element['#options'];
  }
733
  // array_key_exists() accommodates the rare event where $element['#value'] is NULL.
734
735
736
  // isset() fails in this situation.
  $value_valid = isset($element['#value']) || array_key_exists('#value', $element);
  $value_is_array = is_array($element['#value']);
737
738
  $options = '';
  foreach ($choices as $key => $choice) {
739
    if (is_array($choice)) {
740
741
742
      $options .= '<optgroup label="'. $key .'">';
      $options .= form_select_options($element, $choice);
      $options .= '</optgroup>';
743
744
    }
    else {
745
746
      $key = (string)$key;
      if ($value_valid && ($element['#value'] == $key || ($value_is_array && in_array($key, $element['#value'])))) {
747
748
749
750
751
        $selected = ' selected="selected"';
      }
      else {
        $selected = '';
      }
752
      $options .= '<option value="'. $key .'"'. $selected .'>'. check_plain($choice) .'</option>';
753
754
    }
  }
755
  return $options;
756
757
758
759
760
761
762
}

/**
 * Format a group of form items.
 *
 * @param $element
 *   An associative array containing the properties of the element.
763
 *   Properties used: attributes, title, value, description, children, collapsible, collapsed
764
765
766
767
 * @return
 *   A themed HTML string representing the form item group.
 */
function theme_fieldset($element) {
768
  if ($element['#collapsible']) {
769
770
    drupal_add_js('misc/collapse.js');

771
772
773
774
    if (!isset($element['#attributes']['class'])) {
      $element['#attributes']['class'] = '';
    }

775
776
777
    $element['#attributes']['class'] .= ' collapsible';
    if ($element['#collapsed']) {
     $element['#attributes']['class'] .= ' collapsed';
778
779
780
    }
  }

781
  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";
782
783
784
785
786
787
788
}

/**
 * Format a radio button.
 *
 * @param $element
 *   An associative array containing the properties of the element.
789
 *   Properties used: required, return_value, value, attributes, title, description
790
791
792
793
 * @return
 *   A themed HTML string representing the form item group.
 */
function theme_radio($element) {
794
  _form_set_class($element, array('form-radio'));
795
  $output = '<input type="radio" ';
796
797
798
799
800
801
  $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>';
802
  }
803
804
805

  unset($element['#title']);
  return theme('form_element', $element, $output);
806
807
808
809
810
811
812
}

/**
 * Format a set of radio buttons.
 *
 * @param $element
 *   An associative array containing the properties of the element.
813
 *   Properties used: title, value, options, description, required and attributes.
814
815
816
817
 * @return
 *   A themed HTML string representing the radio button set.
 */
function theme_radios($element) {
818
  if ($element['#title'] || $element['#description']) {
819
820
    unset($element['#id']);
    return theme('form_element', $element, $element['#children']);
821
822
  }
  else {
823
    return $element['#children'];
824
825
826
  }
}

827
828
829
830
831
832
833
834
835
836
/**
 * 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) {
Dries's avatar
Dries committed
837
  return theme('form_element', $element, $element['#children']);
838
839
}

840
841
842
843
/*
 * Expand a password_confirm field into two text boxes.
 */
function expand_password_confirm($element) {
Dries's avatar
Dries committed
844
845
846
847
848
849
850
851
852
853
  $element['pass1'] =  array(
    '#type' => 'password',
    '#title' => t('Password'),
    '#value' => $element['#value']['pass1'],
  );
  $element['pass2'] =  array(
    '#type' => 'password',
    '#title' => t('Confirm password'),
    '#value' => $element['#value']['pass2'],
  );
854
855
856
857
858
859
  $element['#validate'] = array('password_confirm_validate' => array());
  $element['#tree'] = TRUE;

  return $element;
}

860
/**
861
 * Validate password_confirm element.
862
 */
863
function password_confirm_validate($form) {
864
865
  $pass1 = trim($form['pass1']['#value']);
  if (!empty($pass1)) {
866
    $pass2 = trim($form['pass2']['#value']);
867
    if ($pass1 != $pass2) {
868
      form_error($form, t('The specified passwords do not match.'));
869
    }
870
  }
871
  elseif ($form['#required'] && !empty($form['#post']['edit'])) {
872
    form_error($form, t('Password field is required.'));
873
  }
874

875
876
877
878
879
880
  // 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);

881
882
883
  return $form;
}

884
/**
885
 * Format a date selection element.
886
887
888
 *
 * @param $element
 *   An associative array containing the properties of the element.
889
 *   Properties used: title, value, options, description, required and attributes.
890
 * @return
891
 *   A themed HTML string representing the date selection boxes.
892
893
 */
function theme_date($element) {
894
  return theme('form_element', $element, '<div class="container-inline">'. $element['#children'] .'</div>');
895
896
897
}

/**
898
 * Roll out a single date element.
899
900
901
 */
function expand_date($element) {
  // Default to current date
902
903
  if (!isset($element['#value'])) {
    $element['#value'] = array('day' => format_date(time(), 'custom', 'j'),
904
905
906
907
                            'month' => format_date(time(), 'custom', 'n'),
                            'year' => format_date(time(), 'custom', 'Y'));
  }

908
909
  $element['#tree'] = TRUE;

910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
  // 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':
926
        $options = drupal_map_assoc(range(1, 12), 'map_month');
927
928
929
930
931
        break;
      case 'year':
        $options = drupal_map_assoc(range(1900, 2050));
        break;
    }
932
933
934
935
936
937
938
939
    $parents = $element['#parents'];
    $parents[] = $type;
    $element[$type] = array(
      '#type' => 'select',
      '#value' => $element['#value'][$type],
      '#attributes' => $element['#attributes'],
      '#options' => $options,
    );
940
941
942
943
944
  }

  return $element;
}

945
946
947
948
949
950
951
952
953
/**
 * 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.'));
  }
}

954
955
956
957
958
959
/**
 * 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);
}
960

961
962
963
964
965
966
967
968
969
970
971
/**
 * 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;
}

972
973
974
975
976
977
978
979
980
981
982
983
/**
 * 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;
  }
}

984
/**
985
986
 * Roll out a single radios element to a list of radios,
 * using the options array as index.
987
988
 */
function expand_radios($element) {
989
990
  if (count($element['#options']) > 0) {
    foreach ($element['#options'] as $key => $choice) {
991
      if (!isset($element[$key])) {
992
        $element[$key] = array('#type' => 'radio', '#title' => $choice, '#return_value' => $key, '#default_value' => $element['#default_value'], '#attributes' => $element['#attributes'], '#parents' => $element['#parents'], '#spawned' => TRUE);
993
994
995
996
997
998
999
1000
1001
1002
1003
      }
    }
  }
  return $element;
}

/**
 * Format a form item.
 *
 * @param $element
 *   An associative array containing the properties of the element.
1004
 *   Properties used:  title, value, description, required, error
1005
1006
1007
1008
 * @return
 *   A themed HTML string representing the form item.
 */
function theme_item($element) {
1009
  return theme('form_element', $element, $element['#value'] . $element['#children']);
1010
1011
1012
1013
1014
1015
1016
}

/**
 * Format a checkbox.
 *
 * @param $element
 *   An associative array containing the properties of the element.
1017
 *   Properties used:  title, value, return_value, description, required
1018
1019
1020
1021
 * @return
 *   A themed HTML string representing the checkbox.
 */
function theme_checkbox($element) {
1022
  _form_set_class($element, array('form-checkbox'));
1023
1024
  $checkbox = '<input ';
  $checkbox .= 'type="checkbox" ';
1025
1026
1027
  $checkbox .= 'name="'. $element['#name'] .'" ';
  $checkbox .= 'id="'. $element['#id'].'" ' ;
  $checkbox .= 'value="'. $element['#return_value'] .'" ';
1028
  $checkbox .= $element['#value'] ? ' checked="checked" ' : ' ';
1029
1030
1031
1032
  $checkbox .= drupal_attributes($element['#attributes']) . ' />';

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

1035
1036
  unset($element['#title']);
  return theme('form_element', $element, $checkbox);
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
}

/**
 * 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) {
1048
  if ($element['#title'] || $element['#description']) {
1049
1050
    unset($element['#id']);
    return theme('form_element', $element, $element['#children']);
1051
1052
  }
  else {
1053
    return $element['#children'];
1054
1055
1056
1057
  }
}

function expand_checkboxes($element) {
1058
  $value = is_array($element['#value']) ? $element['#value'] : array();
Steven Wittens's avatar
Steven Wittens committed
1059
  $element['#tree'] = TRUE;
1060
1061
1062
  if (count($element['#options']) > 0) {
    if (!isset($element['#default_value']) || $element['#default_value'] == 0) {
      $element['#default_value'] = array();
1063
    }
1064
    foreach ($element['#options'] as $key => $choice) {
1065
      if (!isset($element[$key])) {
1066
        $element[$key] = array('#type' => 'checkbox', '#processed' => TRUE, '#title' => $choice, '#return_value' => $key, '#default_value' => isset($value[$key]), '#attributes' => $element['#attributes']);
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
      }
    }
  }
  return $element;
}

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

function theme_button($element) {
1078
1079
1080
1081
1082
1083
1084
  //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'];
  }
1085

1086
  return '<input type="submit" '. (empty($element['#name']) ? '' : 'name="'. $element['#name'] .'" ') .'value="'. check_plain($element['#value']) .'" '. drupal_attributes($element['#attributes']) ." />\n";
1087
1088
1089
1090
1091
1092
1093
}

/**
 * Format a hidden form field.
 *
 * @param $element
 *   An associative array containing the properties of the element.
1094
 *   Properties used:  value, edit
1095
1096
1097
1098
 * @return
 *   A themed HTML string representing the hidden form field.
 */
function theme_hidden($element) {
1099
  return '<input type="hidden" name="'. $element['#name'] . '" id="'. $element['#id'] . '" value="'. check_plain($element['#value']) ."\" " . drupal_attributes($element['#attributes']) ." />\n";
1100
1101
1102
1103
1104
1105
1106
}

/**
 * Format a textfield.
 *
 * @param $element
 *   An associative array containing the properties of the element.
1107
 *   Properties used:  title, value, description, size, maxlength, required, attributes autocomplete_path
1108
1109
1110
1111
 * @return
 *   A themed HTML string representing the textfield.
 */
function theme_textfield($element) {
1112
  $size = $element['#size'] ? ' size="' . $element['#size'] . '"' : '';
1113
  $class = array('form-text');
1114
  $extra = '';
1115
  if ($element['#autocomplete_path']) {
1116
    drupal_add_js('misc/autocomplete.js');
1117
    $class[] = 'form-autocomplete';
1118
    $extra =  '<input class="autocomplete" type="hidden" id="'. $element['#id'] .'-autocomplete" value="'. check_url(url($element['#autocomplete_path'], NULL, NULL, TRUE)) .'" disabled="disabled" />';
1119
  }
1120
1121
  _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']) .' />';
1122
  return theme('form_element', $element, $output). $extra;
1123
1124
1125
1126
1127
1128
1129
}

/**
 * Format a form.
 *
 * @param $element
 *   An associative array containing the properties of the element.
1130
 *   Properties used: action, method, attributes, children
1131
1132
1133
1134
 * @return
 *   A themed HTML string representing the form.
 */
function theme_form($element) {
1135
  // Anonymous div to satisfy XHTML compliance.
1136
  $action = $element['#action'] ? 'action="' . check_url($element['#action']) . '" ' : '';
1137
  return '<form '. $action . ' method="'. $element['#method'] .'" '. 'id="'. $element['#id'] .'"'. drupal_attributes($element['#attributes']) .">\n<div>". $element['#children'] ."\n</div></form>\n";
1138
1139
1140
1141
1142
1143
1144
}

/**
 * Format a textarea.
 *
 * @param $element
 *   An associative array containing the properties of the element.
1145
 *   Properties used: title, value, description, rows, cols, required, attributes
1146
1147
1148
1149
 * @return
 *   A themed HTML string representing the textarea.
 */
function theme_textarea($element) {
1150
  $class = array('form-textarea');
1151
  if ($element['#resizable'] !== FALSE) {
1152
    drupal_add_js('misc/textarea.js');
1153
    $class[] = 'resizable';
1154
1155
  }

1156
  $cols = $element['#cols'] ? ' cols="'. $element['#cols'] .'"' : '';
1157
  _form_set_class($element, $class);
1158
  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>');
1159
1160
1161
1162
1163
1164
1165
1166
1167
}

/**
 * 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.
1168
 *   Properties used: value, children.
1169
1170
1171
1172
1173
 * @return
 *   A themed HTML string representing the HTML markup.
 */

function theme_markup($element) {
1174
  return $element['#value'] . $element['#children'];
1175
1176
1177
1178
1179
1180
1181
}

/**
* Format a password field.
*
* @param $element
*   An associative array containing the properties of the element.
1182
*   Properties used:  title, value, description, size, maxlength, required, attributes
1183
1184
1185
1186
* @return
*   A themed HTML string representing the form.
*/
function theme_password($element) {
1187
  $size = $element['#size'] ? ' size="'. $element['#size'] .'" ' : '';
1188

1189
1190
  _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']) .' />';
1191
  return theme('form_element', $element, $output);