form.inc 45.4 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
 * 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
20
 * by populating $form['#post'] with values to be submitted. For example:
21
22
23
 *
 * // register a new user
 * $form = drupal_retrieve_form('user_register');
24
25
26
 * $form['#post']['name'] = 'robo-user';
 * $form['#post']['mail'] = 'robouser@example.com';
 * $form['#post']['pass'] = 'password';
27
28
29
30
31
32
33
 * 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
 * Retrieves a form from a builder function, passes it on for
 * processing, and renders the form or redirects to its destination
42
43
44
 * as appropriate. In multi-step form scenerios, it handles properly
 * processing the values using the previous step's form definition,
 * then rendering the requested step for display.
45
46
 *
 * @param $form_id
47
48
49
50
51
52
53
54
55
56
57
58
 *   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) {
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
  // In multi-step form scenerios, the incoming $_POST values are not
  // necessarily intended for the current form. We need to build
  // a copy of the previously built form for validation and processing,
  // then go on to the one that was requested if everything works.

  $form_build_id = md5(mt_rand());
  if (isset($_POST['form_build_id']) && isset($_SESSION['form'][$_POST['form_build_id']])) {
    // There's a previously stored multi-step form. We should handle
    // IT first.
    $stored = TRUE;
    $args = $_SESSION['form'][$_POST['form_build_id']];
    $form = call_user_func_array('drupal_retrieve_form', $args);
  }
  else {
    // We're coming in fresh; build things as they would be. If the
    // form's #multistep flag is set, store the build parameters so
    // the same form can be reconstituted for validation.
    $args = func_get_args();
    $form = call_user_func_array('drupal_retrieve_form', $args);
    if (isset($form['#multistep']) && $form['#multistep']) {
      $_SESSION['form'][$form_build_id] = $args;
      $form['#build_id'] = $form_build_id;
    }
    $stored = FALSE;
  }

  // Process the form, submit it, and store any errors if necessary.
  drupal_process_form($args[0], $form);

  if ($stored && !form_get_errors()) {
    // If it's a stored form and there were no errors, we processed the
    // stored form successfully. Now we need to build the form that was
    // actually requested. We always pass in the current $_POST values
    // to the builder function, as values from one stage of a multistep
    // form can determine how subsequent steps are displayed.
    $args = func_get_args();
95
    $args[] = $_POST;
96
97
98
99
100
101
102
103
104
105
    $form = call_user_func_array('drupal_retrieve_form', $args);
    unset($_SESSION['form'][$_POST['form_build_id']]);
    if (isset($form['#multistep']) && $form['#multistep']) {
      $_SESSION['form'][$form_build_id] = $args;
      $form['#build_id'] = $form_build_id;
    }
    drupal_process_form($args[0], $form);
  }

  return drupal_render_form($args[0], $form);
106
107
}

108

109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
/**
 * 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.
148
149
 * @param $form
 *   An associative array containing the structure of the form.
150
151
 * @return
 *   The path to redirect the user to upon completion.
152
 */
153
function drupal_process_form($form_id, &$form) {
154
  global $form_values, $form_submitted, $user, $form_button_counter;
155
  static $saved_globals = array();
156
157
158
  // 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.
159
160
  array_push($saved_globals, array($form_values, $form_submitted, $form_button_counter));

161
  $form_values = array();
Dries's avatar
Dries committed
162
  $form_submitted = FALSE;
163
  $form_button_counter = array(0, 0);
Steven Wittens's avatar
Steven Wittens committed
164

165
  drupal_prepare_form($form_id, $form);
166
  if (($form['#programmed']) || (!empty($_POST) && (($_POST['form_id'] == $form_id) || ($_POST['form_id'] == $form['#base'])))) {
167
    drupal_validate_form($form_id, $form);
168
169
170
    // 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.
171
172
    if ((($form['#programmed']) || $form_submitted || (!$form_button_counter[0] && $form_button_counter[1])) && !form_get_errors()) {
      $redirect = drupal_submit_form($form_id, $form);
173
174
175
      if (!$form['#programmed']) {
        drupal_redirect_form($form, $redirect);
      }
176
177
178
    }
  }

179
180
  // We've finished calling functions that alter the global values, so we can
  // restore the ones that were there before this function was called.
181
  list($form_values, $form_submitted, $form_button_counter) = array_pop($saved_globals);
182
  return $redirect;
183
184
185
186
187
188
189
190
191
192
193
194
195
}

/**
 * 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.
 */
196
function drupal_prepare_form($form_id, &$form) {
197
  $form['#type'] = 'form';
198
199
200
201
202
203
204
205
206

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

207
208
209
210
211
212
213
214
215
216
217
  // In multi-step form scenerios, this id is used to identify
  // a unique instance of a particular form for retrieval.
  if (isset($form['#build_id'])) {
    $form['form_build_id'] = array(
      '#type' => 'hidden',
      '#value' => $form['#build_id'],
      '#id' => $form['#build_id'],
      '#name' => 'form_build_id',
    );
  }

218
219
220
221
222
223
224
  // 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'];
  }

225
  if (isset($form['#token'])) {
226
227
228
    // 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.
229
230
    if (variable_get('cache', 0) && !$user->uid && $_SERVER['REQUEST_METHOD'] == 'GET') {
      unset($form['#token']);
231
    }
232
233
234
235
236
    else {
      // Make sure that a private key is set:
      if (!variable_get('drupal_private_key', '')) {
        variable_set('drupal_private_key', mt_rand());
      }
237

238
239
      $form['form_token'] = array('#type' => 'hidden', '#default_value' => md5(session_id() . $form['#token'] . variable_get('drupal_private_key', '')));
    }
240
  }
241

242
  if (isset($form_id)) {
243
    $form['form_id'] = array('#type' => 'hidden', '#value' => $form_id, '#id' => str_replace('_', '-', "edit-$form_id"));
244
  }
245
246
247
  if (!isset($form['#id'])) {
    $form['#id'] = $form_id;
  }
Steven Wittens's avatar
Steven Wittens committed
248

249
  $form += _element_info('form');
250

Dries's avatar
Dries committed
251
252
  if (!isset($form['#validate'])) {
    if (function_exists($form_id .'_validate')) {
253
      $form['#validate'] = array($form_id .'_validate' => array());
Dries's avatar
Dries committed
254
    }
255
256
    elseif (function_exists($base .'_validate')) {
      $form['#validate'] = array($base .'_validate' => array());
Dries's avatar
Dries committed
257
258
259
    }
  }

260
261
  if (!isset($form['#submit'])) {
    if (function_exists($form_id .'_submit')) {
262
263
      // we set submit here so that it can be altered but use reference for
      // $form_values because it will change later
264
      $form['#submit'] = array($form_id .'_submit' => array());
Dries's avatar
Dries committed
265
    }
266
267
    elseif (function_exists($base .'_submit')) {
      $form['#submit'] = array($base .'_submit' => array());
Dries's avatar
Dries committed
268
269
270
    }
  }

271
272
  foreach (module_implements('form_alter') as $module) {
    $function = $module .'_form_alter';
273
    $function($form_id, $form);
274
275
  }

276
  $form = form_builder($form_id, $form);
277
278
}

279
280
281
282
283
284
285
286
287
288
289
290

/**
 * 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.
 *
 */
291
function drupal_validate_form($form_id, $form) {
292
  global $form_values;
293
294
295
296
297
  static $validated_forms = array();

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

299
  // If the session token was set by drupal_prepare_form(), ensure that it
300
  // matches the current user's session
301
  if (isset($form['#token'])) {
302
    if ($form_values['form_token'] != md5(session_id() . $form['#token'] . variable_get('drupal_private_key', ''))) {
303
      // setting this error will cause the form to fail validation
304
      form_set_error('form_token', t('Validation error, please try again. If this error persists, please contact the site administrator.'));
305
306
307
    }
  }

308
  _form_validate($form, $form_id);
309
  $validated_forms[$form_id] = TRUE;
310
311
}

312
313
314
315
316
317
318
319
320
321
322
323
324
325
/**
 * 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.
 *
 */
326
function drupal_submit_form($form_id, $form) {
327
328
  global $form_values;
  $default_args = array($form_id, &$form_values);
329

330
  if (isset($form['#submit'])) {
331
332
    foreach ($form['#submit'] as $function => $args) {
      if (function_exists($function)) {
333
        $args = array_merge($default_args, (array) $args);
334
335
        // Since we can only redirect to one page, only the last redirect will work
        $redirect = call_user_func_array($function, $args);
336
337
338
        if (isset($redirect)) {
          $goto = $redirect;
        }
Dries's avatar
Dries committed
339
340
      }
    }
341
  }
342
  return $goto;
343
344
}

345
346
347
348
349
350
351
352
353
354
355
356
357
/**
 * 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.
 *
 */
358
function drupal_render_form($form_id, &$form) {
359
  // Don't override #theme if someone already set it.
360
361
362
363
  if (isset($form['#base'])) {
    $base = $form['#base'];
  }

364
365
366
367
  if (!isset($form['#theme'])) {
    if (theme_get_function($form_id)) {
      $form['#theme'] = $form_id;
    }
368
369
    elseif (theme_get_function($base)) {
      $form['#theme'] = $base;
370
371
372
373
374
375
376
377
378
379
380
    }
  }

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

381
  $output = drupal_render($form);
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
  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);
    }
  }
}

415
function _form_validate($elements, $form_id = NULL) {
416
417
418
419
420
421
  // Recurse through all children.
  foreach (element_children($elements) as $key) {
    if (isset($elements[$key]) && $elements[$key]) {
      _form_validate($elements[$key]);
    }
  }
422
  /* Validate the current input */
423
  if (!$elements['#validated']) {
424
    if (isset($elements['#needs_validation'])) {
425
426
427
428
      // 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') {
429
        form_error($elements, t('!name field is required.', array('!name' => $elements['#title'])));
430
      }
431
432
433
434
435
436
437
438
439
440
441
442
443
444

      // 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.'));
445
              watchdog('form', t('Illegal choice %choice in !name element.', array('%choice' => $v, '!name' => empty($elements['#title']) ? $elements['#parents'][0] : $elements['#title'])), WATCHDOG_ERROR);
446
            }
447
448
          }
        }
449
450
        elseif (!isset($options[$elements['#value']])) {
          form_error($elements, t('An illegal choice has been detected. Please contact the site administrator.'));
451
          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);
452
        }
453
454
455
456
      }
    }

    // User-applied checks.
Dries's avatar
Dries committed
457
    if (isset($elements['#validate'])) {
458
459
460
461
462
      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);
463
        }
464
465
        if (function_exists($function))  {
          call_user_func_array($function, $args);
466
467
468
        }
      }
    }
469
    $elements['#validated'] = TRUE;
470
471
472
  }
}

473
474
475
476
477
/**
 * 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.
 */
478
function form_set_error($name = NULL, $message = '') {
479
480
481
  static $form = array();
  if (isset($name) && !isset($form[$name])) {
    $form[$name] = $message;
482
483
484
    if ($message) {
      drupal_set_message($message, 'error');
    }
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
  }
  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];
  }
}

514
515
516
/**
 * Flag an element as having an error.
 */
517
function form_error(&$element, $message = '') {
518
  $element['#error'] = TRUE;
519
  form_set_error(implode('][', $element['#parents']), $message);
520
521
522
}

/**
523
524
525
526
527
528
 * 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
529
530
 *   A unique string identifying the form for validation, submission,
 *   theming, and hook_form_alter functions.
531
532
 * @param $form
 *   An associative array containing the structure of the form.
533
 */
534
function form_builder($form_id, $form) {
535
  global $form_values, $form_submitted, $form_button_counter;
536

537
538
539
  // Initialize as unprocessed.
  $form['#processed'] = FALSE;

540
  /* Use element defaults */
541
  if ((!empty($form['#type'])) && ($info = _element_info($form['#type']))) {
542
    // overlay $info onto $form, retaining preexisting keys in $form
543
544
545
    $form += $info;
  }

546
  if (isset($form['#input']) && $form['#input']) {
547
    if (!isset($form['#name'])) {
548
549
550
551
552
553
      $name = array_shift($form['#parents']);
      $form['#name'] = $name;
      if (count($form['#parents'])) {
        $form['#name'] .= '['. implode('][', $form['#parents']) .']';
      }
      array_unshift($form['#parents'], $name);
554
555
    }
    if (!isset($form['#id'])) {
556
      $form['#id'] = 'edit-'. implode('-', $form['#parents']);
557
    }
558

559
560
561
562
    if (isset($form['#disabled']) && $form['#disabled']) {
      $form['#attributes']['disabled'] = 'disabled';
    }

563
    if (!isset($form['#value']) && !array_key_exists('#value', $form)) {
564
565
      if (($form['#programmed']) || ((!isset($form['#access']) || $form['#access']) && isset($form['#post']) && ($form['#post']['form_id'] == $form_id))) {
        $edit = $form['#post'];
566
567
568
        foreach ($form['#parents'] as $parent) {
          $edit = isset($edit[$parent]) ? $edit[$parent] : NULL;
        }
569
570
571
572
573
574
575
576
577
578
579
580
581
582
        if (!$form['#programmed'] || isset($edit)) {
          switch ($form['#type']) {
            case 'checkbox':
              $form['#value'] = !empty($edit) ? $form['#return_value'] : 0;
              break;

            case 'select':
              if (isset($form['#multiple']) && $form['#multiple']) {
                if (isset($edit) && is_array($edit)) {
                  $form['#value'] = drupal_map_assoc($edit);
                }
                else {
                  $form['#value'] = array();
                }
583
              }
584
585
              elseif (isset($edit)) {
                $form['#value'] = $edit;
586
              }
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
              break;

            case 'textfield':
              if (isset($edit)) {
                // Equate $edit to the form value to ensure it's marked for validation
                $edit = str_replace(array("\r", "\n"), '', $edit);
                $form['#value'] = $edit;
              }
              break;

            default:
              if (isset($edit)) {
                $form['#value'] = $edit;
              }
          }
          // Mark all posted values for validation
          if ((isset($form['#value']) && $form['#value'] === $edit) || (isset($form['#required']) && $form['#required'])) {
            $form['#needs_validation'] = TRUE;
          }
606
607
608
        }
      }
      if (!isset($form['#value'])) {
609
610
611
612
613
614
615
        $function = $form['#type'] . '_value';
        if (function_exists($function)) {
          $function($form);
        }
        else {
          $form['#value'] = $form['#default_value'];
        }
616
      }
617
    }
618
619
620
621
    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
622
      if (isset($form['#post'][$form['#name']]) && $form['#post'][$form['#name']] == $form['#value']) {
623
        $form_submitted = $form_submitted || $form['#executes_submit_callback'];
624
625
626
627
628

        // In most cases, we want to use form_set_value() to manipulate the global variables.
        // In this special case, we want to make sure that the value of this element is listed
        // in $form_variables under 'op'.
        $form_values[$form['#name']] = $form['#value'];
629
630
631
632
      }
    }
  }

633
  // Allow for elements to expand to multiple elements, e.g. radios, checkboxes and files.
634
  if (isset($form['#process']) && !$form['#processed']) {
635
636
    foreach ($form['#process'] as $process => $args) {
      if (function_exists($process)) {
637
        $args = array_merge(array($form), array($edit), $args);
638
        $form = call_user_func_array($process, $args);
639
640
      }
    }
641
    $form['#processed'] = TRUE;
642
643
  }

644
645
646
  // 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.
647
  if (isset($form['#input']) && $form['#input']) {
648
    form_set_value($form, $form['#value']);
649
650
  }

651
652
653
  // Recurse through all child elements.
  $count  = 0;
  foreach (element_children($form) as $key) {
654
655
    $form[$key]['#post'] = $form['#post'];
    $form[$key]['#programmed'] = $form['#programmed'];
Steven Wittens's avatar
Steven Wittens committed
656
    // don't squash an existing tree value
657
658
659
    if (!isset($form[$key]['#tree'])) {
      $form[$key]['#tree'] = $form['#tree'];
    }
Steven Wittens's avatar
Steven Wittens committed
660

661
662
663
664
665
    // deny access to child elements if parent is denied
    if (isset($form['#access']) && !$form['#access']) {
      $form[$key]['#access'] = FALSE;
    }

666
667
    // don't squash existing parents value
    if (!isset($form[$key]['#parents'])) {
668
669
      // 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
670
671
    }

672
    // Assign a decimal placeholder weight to preserve original array order
673
674
675
    if (!isset($form[$key]['#weight'])) {
      $form[$key]['#weight'] = $count/1000;
    }
676
    $form[$key] = form_builder($form_id, $form[$key]);
677
678
679
    $count++;
  }

680
681
682
683
684
685
  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);
      }
    }
686
    $form['#after_build_done'] = TRUE;
Steven Wittens's avatar
Steven Wittens committed
687
  }
688
689

  return $form;
690
691
}

692
/**
Dries's avatar
Dries committed
693
 * Use this function to make changes to form values in the form validate
694
695
696
697
 * 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
698
 * $form_values['foo']['bar'] to be 'baz'.
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
 *
 * @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;
}

731
732
733
/**
 * Retrieve the default properties for the defined element type.
 */
734
function _element_info($type, $refresh = NULL) {
735
  static $cache;
Steven Wittens's avatar
Steven Wittens committed
736

737
  $basic_defaults = array(
738
739
740
    '#description' => NULL,
    '#attributes' => array(),
    '#required' => FALSE,
Steven Wittens's avatar
Steven Wittens committed
741
    '#tree' => FALSE,
742
    '#parents' => array()
743
  );
744
  if (!isset($cache) || $refresh) {
745
746
747
    $cache = array();
    foreach (module_implements('elements') as $module) {
      $elements = module_invoke($module, 'elements');
748
      if (isset($elements) && is_array($elements)) {
749
        $cache = array_merge_recursive($cache, $elements);
750
751
752
753
      }
    }
    if (sizeof($cache)) {
      foreach ($cache as $element_type => $info) {
754
        $cache[$element_type] = array_merge_recursive($basic_defaults, $info);
755
756
757
758
759
760
761
      }
    }
  }

  return $cache[$type];
}

762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
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;
}

781
782
783
784
785
/**
 * Format a dropdown menu or scrolling selection box.
 *
 * @param $element
 *   An associative array containing the properties of the element.
786
 *   Properties used: title, value, options, description, extra, multiple, required
787
788
789
790
791
792
793
794
795
 * @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 = '';
796
  $size = $element['#size'] ? ' size="' . $element['#size'] . '"' : '';
797
  _form_set_class($element, array('form-select'));
798
799
  $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>');
800
801
802
803
804
805
}

function form_select_options($element, $choices = NULL) {
  if (!isset($choices)) {
    $choices = $element['#options'];
  }
806
  // array_key_exists() accommodates the rare event where $element['#value'] is NULL.
807
808
809
  // isset() fails in this situation.
  $value_valid = isset($element['#value']) || array_key_exists('#value', $element);
  $value_is_array = is_array($element['#value']);
810
811
  $options = '';
  foreach ($choices as $key => $choice) {
812
    if (is_array($choice)) {
813
814
815
      $options .= '<optgroup label="'. $key .'">';
      $options .= form_select_options($element, $choice);
      $options .= '</optgroup>';
816
817
    }
    else {
818
819
      $key = (string)$key;
      if ($value_valid && ($element['#value'] == $key || ($value_is_array && in_array($key, $element['#value'])))) {
820
821
822
823
824
        $selected = ' selected="selected"';
      }
      else {
        $selected = '';
      }
825
      $options .= '<option value="'. $key .'"'. $selected .'>'. check_plain($choice) .'</option>';
826
827
    }
  }
828
  return $options;
829
830
831
832
833
834
835
}

/**
 * Format a group of form items.
 *
 * @param $element
 *   An associative array containing the properties of the element.
836
 *   Properties used: attributes, title, value, description, children, collapsible, collapsed
837
838
839
840
 * @return
 *   A themed HTML string representing the form item group.
 */
function theme_fieldset($element) {
841
  if ($element['#collapsible']) {
842
843
    drupal_add_js('misc/collapse.js');

844
845
846
847
    if (!isset($element['#attributes']['class'])) {
      $element['#attributes']['class'] = '';
    }

848
849
850
    $element['#attributes']['class'] .= ' collapsible';
    if ($element['#collapsed']) {
     $element['#attributes']['class'] .= ' collapsed';
851
852
853
    }
  }

854
  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";
855
856
857
858
859
860
861
}

/**
 * Format a radio button.
 *
 * @param $element
 *   An associative array containing the properties of the element.
862
 *   Properties used: required, return_value, value, attributes, title, description
863
864
865
866
 * @return
 *   A themed HTML string representing the form item group.
 */
function theme_radio($element) {
867
  _form_set_class($element, array('form-radio'));
868
  $output = '<input type="radio" ';
869
870
871
872
873
874
  $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>';
875
  }
876
877
878

  unset($element['#title']);
  return theme('form_element', $element, $output);
879
880
881
882
883
884
885
}

/**
 * Format a set of radio buttons.
 *
 * @param $element
 *   An associative array containing the properties of the element.
886
 *   Properties used: title, value, options, description, required and attributes.
887
888
889
890
 * @return
 *   A themed HTML string representing the radio button set.
 */
function theme_radios($element) {
891
  if ($element['#title'] || $element['#description']) {
892
893
    unset($element['#id']);
    return theme('form_element', $element, $element['#children']);
894
895
  }
  else {
896
    return $element['#children'];
897
898
899
  }
}

900
901
902
903
904
905
906
907
908
909
/**
 * 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
910
  return theme('form_element', $element, $element['#children']);
911
912
}

913
914
915
916
/*
 * Expand a password_confirm field into two text boxes.
 */
function expand_password_confirm($element) {
Dries's avatar
Dries committed
917
918
919
920
921
922
923
924
925
926
  $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'],
  );
927
928
929
930
931
932
  $element['#validate'] = array('password_confirm_validate' => array());
  $element['#tree'] = TRUE;

  return $element;
}

933
/**
934
 * Validate password_confirm element.
935
 */
936
function password_confirm_validate($form) {
937
938
  $pass1 = trim($form['pass1']['#value']);
  if (!empty($pass1)) {
939
    $pass2 = trim($form['pass2']['#value']);
940
    if ($pass1 != $pass2) {
941
      form_error($form, t('The specified passwords do not match.'));
942
    }
943
  }
944
  elseif ($form['#required'] && !empty($form['#post'])) {
945
    form_error($form, t('Password field is required.'));
946
  }
947

948
949
950
951
952
953
  // 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);

954
955
956
  return $form;
}

957
/**
958
 * Format a date selection element.
959
960
961
 *
 * @param $element
 *   An associative array containing the properties of the element.
962
 *   Properties used: title, value, options, description, required and attributes.
963
 * @return
964
 *   A themed HTML string representing the date selection boxes.
965
966
 */
function theme_date($element) {
967
  return theme('form_element', $element, '<div class="container-inline">'. $element['#children'] .'</div>');
968
969
970
}

/**
971
 * Roll out a single date element.
972
973
974
 */
function expand_date($element) {
  // Default to current date
975
976
  if (!isset($element['#value'])) {
    $element['#value'] = array('day' => format_date(time(), 'custom', 'j'),
977
978
979
980
                            'month' => format_date(time(), 'custom', 'n'),
                            'year' => format_date(time(), 'custom', 'Y'));
  }

981
982
  $element['#tree'] = TRUE;

983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
  // 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':
999
        $options = drupal_map_assoc(range(1, 12), 'map_month');
1000
1001
1002
1003
1004
        break;
      case 'year':
        $options = drupal_map_assoc(range(1900, 2050));
        break;
    }
1005
1006
1007
1008
1009
1010
1011
1012
    $parents = $element['#parents'];
    $parents[] = $type;
    $element[$type] = array(
      '#type' => 'select',
      '#value' => $element['#value'][$type],
      '#attributes' => $element['#attributes'],
      '#options' => $options,
    );
1013
1014
1015
1016
1017
  }

  return $element;
}

1018
1019
1020
1021
1022
1023
1024
1025
1026
/**
 * 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.'));
  }
}

1027
1028
1029
1030
1031
1032
/**
 * 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);
}
1033

1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
/**
 * 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;
}

1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
/**
 * 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;
  }
}

1057
/**
1058
1059
 * Roll out a single radios element to a list of radios,
 * using the options array as index.
1060
1061
 */
function expand_radios($element) {
1062
1063
  if (count($element['#options']) > 0) {
    foreach ($element['#options'] as $key => $choice) {
1064
      if (!isset($element[$key])) {
1065
        $element[$key] = array('#type' => 'radio', '#title' => $choice, '#return_value' => $key, '#default_value' => $element['#default_value'], '#attributes' => $element['#attributes'], '#parents' => $element['#parents'], '#spawned' => TRUE);
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
      }
    }
  }
  return $element;
}

/**
 * Format a form item.
 *
 * @param $element
 *   An associative array containing the properties of the element.
1077
 *   Properties used:  title, value, description, required, error
1078
1079
1080
1081
 * @return
 *   A themed HTML string representing the form item.
 */
function theme_item($element) {
1082
  return theme('form_element', $element, $element['#value'] . $element['#children']);
1083
1084
1085
1086
1087
1088
1089
}

/**
 * Format a checkbox.
 *
 * @param $element
 *   An associative array containing the properties of the element.
1090
 *   Properties used:  title, value, return_value, description, required
1091
1092
1093
1094
 * @return
 *   A themed HTML string representing the checkbox.
 */
function theme_checkbox($element) {
1095
  _form_set_class($element, array('form-checkbox'));
1096
1097
  $checkbox = '<input ';
  $checkbox .= 'type="checkbox" ';
1098
1099
1100
  $checkbox .= 'name="'. $element['#name'] .'" ';
  $checkbox .= 'id="'. $element['#id'].'" ' ;
  $checkbox .= 'value="'. $element['#return_value'] .'" ';
1101
  $checkbox .= $element['#value'] ? ' checked="checked" ' : ' ';
1102
1103
1104
1105
  $checkbox .= drupal_attributes($element['#attributes']) . ' />';

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

1108
1109
  unset($element['#title']);
  return theme('form_element', $element, $checkbox);
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
}

/**
 * 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) {
1121
  if ($element['#title'] || $element['#description']) {
1122
1123
    unset($element['#id']);
    return theme('form_element', $element, $element['#children']);
1124
1125
  }
  else {
1126
    return $element['#children'];
1127
1128
1129
1130
  }
}

function expand_checkboxes($element) {
1131
  $value = is_array($element['#value']) ? $element['#value'] : array();
Steven Wittens's avatar
Steven Wittens committed
1132
  $element['#tree'] = TRUE;
1133
1134
1135
  if (count($element['#options']) > 0) {
    if (!isset($element['#default_value']) || $element['#default_value'] == 0) {
      $element['#default_value'] = array();
1136
    }
1137
    foreach ($element['#options'] as $key => $choice) {
1138
      if (!isset($element[$key])) {
1139
        $element[$key] = array('#type' => 'checkbox', '#processed' => TRUE, '#title' => $choice, '#return_value' => $key, '#default_value' => isset($value[$key]), '#attributes' => $element['#attributes']);
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
      }
    }
  }
  return $element;
}

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

function theme_button($element) {
1151
1152
1153
1154
1155
1156
1157
  //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'];
  }
1158

1159
  return '<input type="submit" '. (empty($element['#name']) ? '' : 'name="'. $element['#name'] .'" ') .'value="'. check_plain($element['#value']) .'" '. drupal_attributes($element['#attributes']) ." />\n";
1160
1161
1162
1163
1164
1165
1166
}

/**
 * Format a hidden form field.
 *
 * @param $element
 *   An associative array containing the properties of the element.
1167
 *   Properties used:  value, edit
1168
1169
1170
1171
 * @return
 *   A themed HTML string representing the hidden form field.
 */
function theme_hidden($element) {
1172
  return '<input type="hidden" name="'. $element['#name'] . '" id="'. $element['#id'] . '" value="'. check_plain($element['#value']) ."\" " . drupal_attributes($element['#attributes']) ." />\n";
1173
1174
1175
1176
1177
1178
1179
}

/**
 * Format a textfield.
 *
 * @param $element
 *   An associative array containing the properties of the element.
1180
 *   Properties used:  title, value, description, size, maxlength, required, attributes autocomplete_path
1181
1182
1183
1184
 * @return
 *   A themed HTML string representing the textfield.
 */
function theme_textfield($element) {
1185
  $size = $element['#size'] ? ' size="' . $element['#size'] . '"' : '';
1186
  $class = array('form-text');
1187
  $extra = '';
1188
1189
  $output = '';

1190
  if ($element['#autocomplete_path']) {
1191
    drupal_add_js('misc/autocomplete.js');
1192
    $class[] = 'form-autocomplete';
1193
    $extra =  '<input class="autocomplete" type="hidden" id="'. $element['#id'] .'-autocomplete" value="'. check_url(url($element['#autocomplete_path'], NULL, NULL, TRUE)) .'" disabled="disabled" />';
1194
  }
1195
  _form_set_class($element, $class);
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206

  if (isset($element['#field_prefix'])) {
    $output .= '<span class="field-prefix">'. $element['#field_prefix'] .'</span> ';
  }

  $output .= '<input type="text" maxlength="'. $element['#maxlength'] .'" name="'. $element['#name'] .'" id="'. $element['#id'] .'" '. $size .' value="'. check_plain($element['#value']) .'"'. drupal_attributes($element['#attributes']) .' />';

  if (isset($element['#field_suffix'])) {
    $output .= ' <span class="field-suffix">'. $element['#field_suffix'] .'</span>';
  }

1207
  return theme('form_element', $element, $output). $extra;
1208
1209
1210
1211
1212
1213
1214
}

/**
 * Format a form.
 *
 * @param $element
 *   An associative array containing the properties of the element.
1215
 *   Properties used: action, method, attributes, children
1216
1217
1218
1219
 * @return
 *   A themed HTML string representing the form.
 */
function theme_form($element) {
1220
  // Anonymous div to satisfy XHTML compliance.
1221
  $action = $element['#action'] ? 'action="' . check_url($element['#action']) . '" ' : '';
1222
  return '<form '. $action . ' method="'. $element['#method'] .'" '. 'id="'. $element['#id'] .'"'. drupal_attributes($element['#attributes']) .">\n<div>". $element['#children'] ."\n</div></form>\n";