form.inc 51.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
 * 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
 * using the drupal_execute() function.
21
22
23
24
 *
 *
 * 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
25
 * http://api.drupal.org/api/HEAD/file/developer/topics/forms_api_reference.html
26
 * and the quickstart guide at
27
 * http://api.drupal.org/api/HEAD/file/developer/topics/forms_api.html
28
29
30
 */

/**
31
32
 * Retrieves a form from a builder function, passes it on for
 * processing, and renders the form or redirects to its destination
33
 * as appropriate. In multi-step form scenarios, it handles properly
34
35
 * processing the values using the previous step's form definition,
 * then rendering the requested step for display.
36
37
 *
 * @param $form_id
38
39
40
41
42
43
44
45
46
47
48
49
 *   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) {
50
  // In multi-step form scenarios, the incoming $_POST values are not
51
52
53
54
55
  // 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());
56
  if (isset($_POST['form_build_id']) && isset($_SESSION['form'][$_POST['form_build_id']]['args']) && $_POST['form_id'] == $form_id) {
57
58
59
    // There's a previously stored multi-step form. We should handle
    // IT first.
    $stored = TRUE;
60
    $args = $_SESSION['form'][$_POST['form_build_id']]['args'];
61
62
63
64
65
66
67
68
69
    $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']) {
70
71
72
      // Clean up old multistep form session data.
      _drupal_clean_form_sessions();
      $_SESSION['form'][$form_build_id] = array('timestamp' => time(), 'args' => $args);
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
      $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();
88
    $args[] = $_POST;
89
90
91
    $form = call_user_func_array('drupal_retrieve_form', $args);
    unset($_SESSION['form'][$_POST['form_build_id']]);
    if (isset($form['#multistep']) && $form['#multistep']) {
92
      $_SESSION['form'][$form_build_id] = array('timestamp' => time(), 'args' => $args);
93
94
      $form['#build_id'] = $form_build_id;
    }
95
    drupal_prepare_form($args[0], $form);
96
97
98
  }

  return drupal_render_form($args[0], $form);
99
100
}

101

102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
/**
 * Remove form information that's at least a day old from the
 * $_SESSION['form'] array.
 */
function _drupal_clean_form_sessions() {
  if (isset($_SESSION['form'])) {
    foreach ($_SESSION['form'] as $build_id => $data) {
      if ($data['timestamp'] < (time() - 84600)) {
        unset($_SESSION['form'][$build_id]);
      }
    }
  }
}


117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
/**
 * Retrieves a form using a form_id, populates it with $form_values,
 * processes it, and returns any validation errors encountered. This
 * function is the programmatic counterpart to drupal_get_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. Examples
 *   may be found in node_forms(), search_forms(), and user_forms().
 * @param $form_values
 *   An array of values mirroring the values returned by a given form
 *   when it is submitted by a user.
 * @param ...
 *   Any additional arguments needed by the form building function.
 * @return
 *   Any form validation errors encountered.
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
 *
 * For example:
 *
 * // register a new user
 * $values['name'] = 'robo-user';
 * $values['mail'] = 'robouser@example.com';
 * $values['pass'] = 'password';
 * drupal_execute('user_register', $values);
 *
 * // Create a new node
 * $node = array('type' => 'story');
 * $values['title'] = 'My node';
 * $values['body'] = 'This is the body text!';
 * $values['name'] = 'robo-user';
 * drupal_execute('story_node_form', $values, $node);
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
 */
function drupal_execute($form_id, $form_values) {
  $args = func_get_args();

  $form_id = array_shift($args);
  $form_values = array_shift($args);
  array_unshift($args, $form_id);

  if (isset($form_values)) {
    $form = call_user_func_array('drupal_retrieve_form', $args);
    $form['#post'] = $form_values;
    return drupal_process_form($form_id, $form);
  }
}

166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
/**
 * 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;

181
182
183
184
185
  // We save two copies of the incoming arguments: one for modules to use
  // when mapping form ids to builder functions, and another to pass to
  // the builder function itself. We shift out the first argument -- the
  // $form_id itself -- from the list to pass into the builder function,
  // since it's already known.
186
  $args = func_get_args();
187
  $saved_args = $args;
188
  array_shift($args);
189
190
191

  // We first check to see if there's a function named after the $form_id.
  // If there is, we simply pass the arguments on to it to get the form.
192
  if (!function_exists($form_id)) {
193
194
195
196
197
198
199
200
201
202
203
204
205
    // In cases where many form_ids need to share a central builder function,
    // such as the node editing form, modules can implement hook_forms(). It
    // maps one or more form_ids to the correct builder functions.
    //
    // We cache the results of that hook to save time, but that only works
    // for modules that know all their form_ids in advance. (A module that
    // adds a small 'rate this comment' form to each comment in a list
    // would need a unique form_id for each one, for example.)
    //
    // So, we call the hook if $forms isn't yet populated, OR if it doesn't
    // yet have an entry for the requested form_id.
    if (!isset($forms) || !isset($forms[$form_id])) {
      $forms = module_invoke_all('forms', $saved_args);
206
207
208
209
210
211
212
213
214
    }
    $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'];
    }
  }
215
216
  // If $callback was returned by a hook_forms() implementation, call it.
  // Otherwise, call the function named after the form id.
217
218
219
220
221
222
  $form = call_user_func_array(isset($callback) ? $callback : $form_id, $args);

  // We store the original function arguments, rather than the final $arg
  // value, so that form_alter functions can see what was originally
  // passed to drupal_retrieve_form(). This allows the contents of #parameters
  // to be saved and passed in at a later date to recreate the form.
223
  $form['#parameters'] = $saved_args;
224
  return $form;
225
226
227
228
229
230
231
232
}

/**
 * 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.
233
234
 * @param $form
 *   An associative array containing the structure of the form.
235
236
 * @return
 *   The path to redirect the user to upon completion.
237
 */
238
function drupal_process_form($form_id, &$form) {
239
  global $form_values, $form_submitted, $user, $form_button_counter;
240
  static $saved_globals = array();
241
  // In some scenarios, this function can be called recursively. Pushing any pre-existing
242
243
  // $form_values and form submission data lets us start fresh without clobbering work done
  // in earlier recursive calls.
244
245
  array_push($saved_globals, array($form_values, $form_submitted, $form_button_counter));

246
  $form_values = array();
Dries's avatar
Dries committed
247
  $form_submitted = FALSE;
248
  $form_button_counter = array(0, 0);
Steven Wittens's avatar
Steven Wittens committed
249

250
  drupal_prepare_form($form_id, $form);
251
  if (($form['#programmed']) || (!empty($_POST) && (($_POST['form_id'] == $form_id) || ($_POST['form_id'] == $form['#base'])))) {
252
    drupal_validate_form($form_id, $form);
253
254
255
    // 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.
256
257
    if ((($form['#programmed']) || $form_submitted || (!$form_button_counter[0] && $form_button_counter[1])) && !form_get_errors()) {
      $redirect = drupal_submit_form($form_id, $form);
258
259
260
      if (!$form['#programmed']) {
        drupal_redirect_form($form, $redirect);
      }
261
262
263
    }
  }

264
265
  // We've finished calling functions that alter the global values, so we can
  // restore the ones that were there before this function was called.
266
  list($form_values, $form_submitted, $form_button_counter) = array_pop($saved_globals);
267
  return $redirect;
268
269
270
271
272
273
274
275
276
277
278
279
280
}

/**
 * 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.
 */
281
function drupal_prepare_form($form_id, &$form) {
282
283
  global $user;

284
  $form['#type'] = 'form';
285
286
287
288
289
290
291
292
293

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

294
  // In multi-step form scenarios, this id is used to identify
295
296
297
298
299
300
301
302
303
304
  // 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',
    );
  }

305
306
307
308
309
310
311
  // 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'];
  }

312
313
314
315
  // Add a token, based on either #token or form_id, to any form displayed to authenticated users.
  // This ensures that any submitted form was actually requested previously by the user and protects against
  // cross site request forgeries.

316
  if (isset($form['#token'])) {
317
    if ($form['#token'] === FALSE || $user->uid == 0 || $form['#programmed']) {
318
      unset($form['#token']);
319
    }
320
    else {
321
      $form['form_token'] = array('#type' => 'token', '#default_value' => drupal_get_token($form['#token']));
322
    }
323
  }
324
325
326
  else if ($user->uid && !$form['#programmed']) {
    $form['#token'] = $form_id;
    $form['form_token'] = array(
327
      '#id' => form_clean_id('edit-'. $form_id .'-form-token'),
328
329
330
331
332
      '#type' => 'token',
      '#default_value' => drupal_get_token($form['#token']),
    );
  }

333

334
  if (isset($form_id)) {
335
    $form['form_id'] = array('#type' => 'hidden', '#value' => $form_id, '#id' => form_clean_id("edit-$form_id"));
336
  }
337
  if (!isset($form['#id'])) {
338
    $form['#id'] = form_clean_id($form_id);
339
  }
Steven Wittens's avatar
Steven Wittens committed
340

341
  $form += _element_info('form');
342

Dries's avatar
Dries committed
343
344
  if (!isset($form['#validate'])) {
    if (function_exists($form_id .'_validate')) {
345
      $form['#validate'] = array($form_id .'_validate' => array());
Dries's avatar
Dries committed
346
    }
347
348
    elseif (function_exists($base .'_validate')) {
      $form['#validate'] = array($base .'_validate' => array());
Dries's avatar
Dries committed
349
350
351
    }
  }

352
353
  if (!isset($form['#submit'])) {
    if (function_exists($form_id .'_submit')) {
354
355
      // we set submit here so that it can be altered but use reference for
      // $form_values because it will change later
356
      $form['#submit'] = array($form_id .'_submit' => array());
Dries's avatar
Dries committed
357
    }
358
359
    elseif (function_exists($base .'_submit')) {
      $form['#submit'] = array($base .'_submit' => array());
Dries's avatar
Dries committed
360
361
362
    }
  }

363
364
  foreach (module_implements('form_alter') as $module) {
    $function = $module .'_form_alter';
365
    $function($form_id, $form);
366
367
  }

368
  $form = form_builder($form_id, $form);
369
370
}

371
372
373
374
375
376
377
378
379
380
381
382

/**
 * 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.
 *
 */
383
function drupal_validate_form($form_id, $form) {
384
  global $form_values;
385
386
387
388
389
  static $validated_forms = array();

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

391
  // If the session token was set by drupal_prepare_form(), ensure that it
392
  // matches the current user's session
393
  if (isset($form['#token'])) {
394
    if (!drupal_valid_token($form_values['form_token'], $form['#token'])) {
395
      // setting this error will cause the form to fail validation
396
      form_set_error('form_token', t('Validation error, please try again. If this error persists, please contact the site administrator.'));
397
398
399
    }
  }

400
  _form_validate($form, $form_id);
401
  $validated_forms[$form_id] = TRUE;
402
403
}

404
405
406
407
408
409
410
411
412
413
414
415
416
417
/**
 * 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.
 *
 */
418
function drupal_submit_form($form_id, $form) {
419
420
  global $form_values;
  $default_args = array($form_id, &$form_values);
421

422
  if (isset($form['#submit'])) {
423
424
    foreach ($form['#submit'] as $function => $args) {
      if (function_exists($function)) {
425
        $args = array_merge($default_args, (array) $args);
426
427
        // Since we can only redirect to one page, only the last redirect will work
        $redirect = call_user_func_array($function, $args);
428
429
430
        if (isset($redirect)) {
          $goto = $redirect;
        }
Dries's avatar
Dries committed
431
432
      }
    }
433
  }
434
  return $goto;
435
436
}

437
438
439
440
441
442
443
444
445
446
447
448
449
/**
 * 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.
 *
 */
450
function drupal_render_form($form_id, &$form) {
451
  // Don't override #theme if someone already set it.
452
453
454
455
  if (isset($form['#base'])) {
    $base = $form['#base'];
  }

456
457
458
459
  if (!isset($form['#theme'])) {
    if (theme_get_function($form_id)) {
      $form['#theme'] = $form_id;
    }
460
461
    elseif (theme_get_function($base)) {
      $form['#theme'] = $base;
462
463
464
465
466
467
468
469
470
471
472
    }
  }

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

473
  $output = drupal_render($form);
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
  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);
    }
  }
}

507
function _form_validate($elements, $form_id = NULL) {
508
509
510
511
512
513
  // Recurse through all children.
  foreach (element_children($elements) as $key) {
    if (isset($elements[$key]) && $elements[$key]) {
      _form_validate($elements[$key]);
    }
  }
514
  /* Validate the current input */
drumm's avatar
drumm committed
515
  if (!isset($elements['#validated']) || !$elements['#validated']) {
516
    if (isset($elements['#needs_validation'])) {
517
518
519
520
      // 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') {
521
        form_error($elements, t('!name field is required.', array('!name' => $elements['#title'])));
522
      }
523

524
525
526
527
528
      // Verify that the value is not longer than #maxlength.
      if (isset($elements['#maxlength']) && drupal_strlen($elements['#value']) > $elements['#maxlength']) {
        form_error($elements, t('!name cannot be longer than %max characters but is currently %length characters long.', array('!name' => empty($elements['#title']) ? $elements['#parents'][0] : $elements['#title'], '%max' => $elements['#maxlength'], '%length' => drupal_strlen($elements['#value']))));
      }

529
530
531
532
533
534
535
536
537
538
539
540
541
      // 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.'));
542
              watchdog('form', t('Illegal choice %choice in !name element.', array('%choice' => $v, '!name' => empty($elements['#title']) ? $elements['#parents'][0] : $elements['#title'])), WATCHDOG_ERROR);
543
            }
544
545
          }
        }
546
547
        elseif (!isset($options[$elements['#value']])) {
          form_error($elements, t('An illegal choice has been detected. Please contact the site administrator.'));
548
          watchdog('form', t('Illegal choice %choice in %name element.', array('%choice' => $elements['#value'], '%name' => empty($elements['#title']) ? $elements['#parents'][0] : $elements['#title'])), WATCHDOG_ERROR);
549
        }
550
551
552
553
      }
    }

    // User-applied checks.
Dries's avatar
Dries committed
554
    if (isset($elements['#validate'])) {
555
556
557
558
559
      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);
560
        }
561
562
        if (function_exists($function))  {
          call_user_func_array($function, $args);
563
564
565
        }
      }
    }
566
    $elements['#validated'] = TRUE;
567
568
569
  }
}

570
571
572
573
574
/**
 * 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.
 */
575
function form_set_error($name = NULL, $message = '') {
576
577
578
  static $form = array();
  if (isset($name) && !isset($form[$name])) {
    $form[$name] = $message;
579
580
581
    if ($message) {
      drupal_set_message($message, 'error');
    }
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
  }
  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];
  }
}

611
612
613
/**
 * Flag an element as having an error.
 */
614
function form_error(&$element, $message = '') {
615
  $element['#error'] = TRUE;
616
  form_set_error(implode('][', $element['#parents']), $message);
617
618
619
}

/**
620
621
622
623
624
625
 * 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
626
627
 *   A unique string identifying the form for validation, submission,
 *   theming, and hook_form_alter functions.
628
629
 * @param $form
 *   An associative array containing the structure of the form.
630
 */
631
function form_builder($form_id, $form) {
632
  global $form_values, $form_submitted, $form_button_counter;
633

634
635
636
  // Initialize as unprocessed.
  $form['#processed'] = FALSE;

637
  /* Use element defaults */
638
  if ((!empty($form['#type'])) && ($info = _element_info($form['#type']))) {
639
    // overlay $info onto $form, retaining preexisting keys in $form
640
641
642
    $form += $info;
  }

643
  if (isset($form['#input']) && $form['#input']) {
644
    if (!isset($form['#name'])) {
645
646
      $name = array_shift($form['#parents']);
      $form['#name'] = $name;
647
648
649
650
651
652
653
      if ($form['#type'] == 'file') {
        // to make it easier to handle $_FILES in file.inc, we place all
        // file fields in the 'files' array. Also, we do not support
        // nested file names
        $form['#name'] = 'files['. $form['#name'] .']';
      }
      elseif (count($form['#parents'])) {
654
655
656
        $form['#name'] .= '['. implode('][', $form['#parents']) .']';
      }
      array_unshift($form['#parents'], $name);
657
658
    }
    if (!isset($form['#id'])) {
659
      $form['#id'] = form_clean_id('edit-'. implode('-', $form['#parents']));
660
    }
661

662
663
664
665
    if (isset($form['#disabled']) && $form['#disabled']) {
      $form['#attributes']['disabled'] = 'disabled';
    }

666
    if (!isset($form['#value']) && !array_key_exists('#value', $form)) {
drumm's avatar
drumm committed
667
      if (($form['#programmed']) || ((!isset($form['#access']) || $form['#access']) && isset($form['#post']) && (isset($form['#post']['form_id']) && $form['#post']['form_id'] == $form_id))) {
668
        $edit = $form['#post'];
669
670
671
        foreach ($form['#parents'] as $parent) {
          $edit = isset($edit[$parent]) ? $edit[$parent] : NULL;
        }
672
673
674
675
676
677
678
679
680
681
682
683
684
685
        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();
                }
686
              }
687
688
              elseif (isset($edit)) {
                $form['#value'] = $edit;
689
              }
690
691
692
693
694
695
696
697
698
699
              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;

700
701
702
703
            case 'token':
              $form['#value'] = (string)$edit;
              break;

704
705
706
707
708
709
710
711
712
            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;
          }
713
714
715
        }
      }
      if (!isset($form['#value'])) {
716
717
718
719
720
        $function = $form['#type'] . '_value';
        if (function_exists($function)) {
          $function($form);
        }
        else {
drumm's avatar
drumm committed
721
          $form['#value'] = isset($form['#default_value']) ? $form['#default_value'] : '';
722
        }
723
      }
724
    }
725
726
727
728
    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
729
      if (isset($form['#post'][$form['#name']]) && $form['#post'][$form['#name']] == $form['#value']) {
730
        $form_submitted = $form_submitted || $form['#executes_submit_callback'];
731
732
733
734
735

        // 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'];
736
737
738
739
      }
    }
  }

740
  // Allow for elements to expand to multiple elements, e.g. radios, checkboxes and files.
741
  if (isset($form['#process']) && !$form['#processed']) {
742
743
    foreach ($form['#process'] as $process => $args) {
      if (function_exists($process)) {
744
        $args = array_merge(array($form), array($edit), $args);
745
        $form = call_user_func_array($process, $args);
746
747
      }
    }
748
    $form['#processed'] = TRUE;
749
750
  }

751
752
753
  // 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.
754
  if (isset($form['#input']) && $form['#input']) {
755
    form_set_value($form, $form['#value']);
756
757
  }

758
759
760
  // Recurse through all child elements.
  $count  = 0;
  foreach (element_children($form) as $key) {
761
762
    $form[$key]['#post'] = $form['#post'];
    $form[$key]['#programmed'] = $form['#programmed'];
Steven Wittens's avatar
Steven Wittens committed
763
    // don't squash an existing tree value
764
765
766
    if (!isset($form[$key]['#tree'])) {
      $form[$key]['#tree'] = $form['#tree'];
    }
Steven Wittens's avatar
Steven Wittens committed
767

768
769
770
771
772
    // deny access to child elements if parent is denied
    if (isset($form['#access']) && !$form['#access']) {
      $form[$key]['#access'] = FALSE;
    }

773
774
    // don't squash existing parents value
    if (!isset($form[$key]['#parents'])) {
775
776
      // 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
777
778
    }

779
    // Assign a decimal placeholder weight to preserve original array order
780
781
782
    if (!isset($form[$key]['#weight'])) {
      $form[$key]['#weight'] = $count/1000;
    }
783
    $form[$key] = form_builder($form_id, $form[$key]);
784
785
786
    $count++;
  }

787
788
789
790
791
792
  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);
      }
    }
793
    $form['#after_build_done'] = TRUE;
Steven Wittens's avatar
Steven Wittens committed
794
  }
795
796

  return $form;
797
798
}

799
/**
Dries's avatar
Dries committed
800
 * Use this function to make changes to form values in the form validate
801
802
803
804
 * 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
805
 * $form_values['foo']['bar'] to be 'baz'.
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
 *
 * @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;
}

838
839
840
/**
 * Retrieve the default properties for the defined element type.
 */
841
function _element_info($type, $refresh = NULL) {
842
  static $cache;
Steven Wittens's avatar
Steven Wittens committed
843

844
  $basic_defaults = array(
845
846
847
    '#description' => NULL,
    '#attributes' => array(),
    '#required' => FALSE,
Steven Wittens's avatar
Steven Wittens committed
848
    '#tree' => FALSE,
849
    '#parents' => array()
850
  );
851
  if (!isset($cache) || $refresh) {
852
853
854
    $cache = array();
    foreach (module_implements('elements') as $module) {
      $elements = module_invoke($module, 'elements');
855
      if (isset($elements) && is_array($elements)) {
856
        $cache = array_merge_recursive($cache, $elements);
857
858
859
860
      }
    }
    if (sizeof($cache)) {
      foreach ($cache as $element_type => $info) {
861
        $cache[$element_type] = array_merge_recursive($basic_defaults, $info);
862
863
864
865
866
867
868
      }
    }
  }

  return $cache[$type];
}

869
870
871
872
873
874
875
876
function form_options_flatten($array, $reset = TRUE) {
  static $return;

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

  foreach ($array as $key => $value) {
877
878
879
880
    if (is_object($value)) {
      form_options_flatten($value->option, FALSE);
    }
    else if (is_array($value)) {
881
882
883
884
885
886
887
888
889
890
      form_options_flatten($value, FALSE);
    }
    else {
      $return[$key] = 1;
    }
  }

  return $return;
}

891
892
893
894
895
/**
 * Format a dropdown menu or scrolling selection box.
 *
 * @param $element
 *   An associative array containing the properties of the element.
896
 *   Properties used: title, value, options, description, extra, multiple, required
897
898
899
900
901
902
903
904
905
 * @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 = '';
906
  $size = $element['#size'] ? ' size="' . $element['#size'] . '"' : '';
907
  _form_set_class($element, array('form-select'));
908
909
  $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>');
910
911
912
913
914
915
}

function form_select_options($element, $choices = NULL) {
  if (!isset($choices)) {
    $choices = $element['#options'];
  }
916
  // array_key_exists() accommodates the rare event where $element['#value'] is NULL.
917
918
919
  // isset() fails in this situation.
  $value_valid = isset($element['#value']) || array_key_exists('#value', $element);
  $value_is_array = is_array($element['#value']);
920
921
  $options = '';
  foreach ($choices as $key => $choice) {
922
    if (is_array($choice)) {
923
924
925
      $options .= '<optgroup label="'. $key .'">';
      $options .= form_select_options($element, $choice);
      $options .= '</optgroup>';
926
    }
927
928
929
    elseif (is_object($choice)) {
      $options .= form_select_options($element, $choice->option);
    }
930
    else {
931
932
      $key = (string)$key;
      if ($value_valid && ($element['#value'] == $key || ($value_is_array && in_array($key, $element['#value'])))) {
933
934
935
936
937
        $selected = ' selected="selected"';
      }
      else {
        $selected = '';
      }
938
      $options .= '<option value="'. $key .'"'. $selected .'>'. check_plain($choice) .'</option>';
939
940
    }
  }
941
  return $options;
942
943
}

944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
/**
 * Traverses a select element's #option array looking for the object that 
 * holds the given key. Returns FALSE if not found. As usual with functions
 * that can return 0 or FALSE do not forget to use === and !== if needed. 
 *
 * @param $element
 *   The select element.
 * @param $key
 *   The key to look for.
 * @return
 *   The index of the object that held the $key with some value, or FALSE.
 */
function form_get_option_key($element, $key) {
  foreach ($element['#options'] as $index => $object) {
    if (isset($object->option[$key])) {
      return $index;
    }
  }
  return FALSE;
}

965
966
967
968
969
/**
 * Format a group of form items.
 *
 * @param $element
 *   An associative array containing the properties of the element.
970
 *   Properties used: attributes, title, value, description, children, collapsible, collapsed
971
972
973
974
 * @return
 *   A themed HTML string representing the form item group.
 */
function theme_fieldset($element) {
975
  if ($element['#collapsible']) {
976
977
    drupal_add_js('misc/collapse.js');

978
979
980
981
    if (!isset($element['#attributes']['class'])) {
      $element['#attributes']['class'] = '';
    }

982
983
984
    $element['#attributes']['class'] .= ' collapsible';
    if ($element['#collapsed']) {
     $element['#attributes']['class'] .= ' collapsed';
985
986
987
    }
  }

988
  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";
989
990
991
992
993
994
995
}

/**
 * Format a radio button.
 *
 * @param $element
 *   An associative array containing the properties of the element.
996
 *   Properties used: required, return_value, value, attributes, title, description
997
998
999
1000
 * @return
 *   A themed HTML string representing the form item group.
 */
function theme_radio($element) {
1001
  _form_set_class($element, array('form-radio'));
1002
  $output = '<input type="radio" ';
1003
1004
1005
1006
1007
1008
  $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>';
1009
  }
1010
1011
1012

  unset($element['#title']);
  return theme('form_element', $element, $output);
1013
1014
1015
1016
1017
1018
1019
}

/**
 * Format a set of radio buttons.
 *
 * @param $element
 *   An associative array containing the properties of the element.
1020
 *   Properties used: title, value, options, description, required and attributes.
1021
1022
1023
1024
 * @return
 *   A themed HTML string representing the radio button set.
 */
function theme_radios($element) {
1025
1026
1027
1028
1029
  $class = 'form-radios';
  if (isset($element['#attributes']['class'])) {
    $class .= ' '. $element['#attributes']['class'];
  }
  $element['#children'] = '<div class="'. $class .'">'. $element['#children'] .'</div>';
1030
  if ($element['#title'] || $element['#description']) {
1031
1032
    unset($element['#id']);
    return theme('form_element', $element, $element['#children']);
1033
1034
  }
  else {
1035
    return $element['#children'];
1036
1037
1038
  }
}

1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
/**
 * 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
1049
  return theme('form_element', $element, $element['#children']);
1050
1051
}

1052
1053
1054
1055
/*
 * Expand a password_confirm field into two text boxes.
 */
function expand_password_confirm($element) {
Dries's avatar
Dries committed
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
  $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'],
  );
1066
1067
1068
1069
1070
1071
  $element['#validate'] = array('password_confirm_validate' => array());
  $element['#tree'] = TRUE;

  return $element;
}

1072
/**
1073
 * Validate password_confirm element.
1074
 */
1075
function password_confirm_validate($form) {
1076
1077
  $pass1 = trim($form['pass1']['#value']);
  if (!empty($pass1)) {
1078
    $pass2 = trim($form['pass2']['#value']);
1079
    if ($pass1 != $pass2) {
1080
      form_error($form, t('The specified passwords do not match.'));
1081
    }
1082
  }
1083
  elseif ($form['#required'] && !empty($form['#post'])) {
1084
    form_error($form, t('Password field is required.'));
1085
  }
1086

1087
1088
1089
1090
1091
1092
  // 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);

1093
1094
1095
  return $form;
}

1096
/**
1097
 * Format a date selection element.
1098
1099
1100
 *
 * @param $element
 *   An associative array containing the properties of the element.
1101
 *   Properties used: title, value, options, description, required and attributes.
1102
 * @return
1103
 *   A themed HTML string representing the date selection boxes.
1104
1105
 */
function theme_date($element) {
1106
  return theme('form_element', $element, '<div class="container-inline">'. $element['#children'] .'</div>');
1107
1108
1109
}

/**
1110
 * Roll out a single date element.
1111
1112
1113
 */
function expand_date($element) {
  // Default to current date
1114
1115
  if (!isset($element['#value'])) {
    $element['#value'] = array('day' => format_date(time(), 'custom', 'j'),
1116
1117
1118
1119
                            'month' => format_date(time(), 'custom', 'n'),
                            'year' => format_date(time(), 'custom', 'Y'));
  }

1120
1121
  $element['#tree'] = TRUE;

1122
  // Determine the order of day, month, year in the site's chosen date format.
1123
  $format = variable_get('date_format_short', 'm/d/Y - H:i');
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
  $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':
1138
        $options = drupal_map_assoc(range(1, 12), 'map_month');
1139
1140
1141
1142
1143
        break;
      case 'year':
        $options = drupal_map_assoc(range(1900, 2050));
        break;
    }
1144
1145
1146
1147
1148
1149
1150
1151
    $parents = $element['#parents'];
    $parents[] = $type;
    $element[$type] = array(
      '#type' => 'select',
      '#value' => $element['#value'][$type],
      '#attributes' => $element['#attributes'],
      '#options' => $options,
    );
1152
1153
1154
1155
1156
  }

  return $element;
}

1157
1158
1159
1160
1161
1162
1163
1164
1165
/**
 * 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.'));
  }
}

1166
1167
1168
1169
1170
1171
/**
 * 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);
}
1172

1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
/**
 * 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;
}

1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
/**
 * 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;
  }
}

1196
/**