form.inc 47.5 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
21
22
 * using the drupal_execute() function. Pass in the id of the form, the values to
 * submit to the form, and any parameters needed by the form's builder function.
 * For example:
23
24
 *
 * // register a new user
25
26
27
28
 * $values['name'] = 'robo-user';
 * $values['mail'] = 'robouser@example.com';
 * $values['pass'] = 'password';
 * drupal_execute('user_register', $values);
29
 *
30
31
32
33
34
35
36
37
38
 * // 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);
 *
 * Calling form_get_errors() after execution will return an array of any
 * validation errors encountered.
39
40
41
 *
 * 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
42
 * http://api.drupal.org/api/HEAD/file/developer/topics/forms_api_reference.html
43
 * and the quickstart guide at
44
 * http://api.drupal.org/api/HEAD/file/developer/topics/forms_api.html
45
46
47
 */

/**
48
49
 * Retrieves a form from a builder function, passes it on for
 * processing, and renders the form or redirects to its destination
50
51
52
 * 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.
53
54
 *
 * @param $form_id
55
56
57
58
59
60
61
62
63
64
65
66
 *   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) {
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
  // 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();
103
    $args[] = $_POST;
104
105
106
107
108
109
110
111
112
113
    $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);
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
148
149
150
/**
 * 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.
 */
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);
  }
}

151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
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;

  $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
181
182
183
184
185
186
187
188
  $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.
  $form['#parameters'] = func_get_args();
  return $form;
189
190
191
192
193
194
195
196
}

/**
 * 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.
197
198
 * @param $form
 *   An associative array containing the structure of the form.
199
200
 * @return
 *   The path to redirect the user to upon completion.
201
 */
202
function drupal_process_form($form_id, &$form) {
203
  global $form_values, $form_submitted, $user, $form_button_counter;
204
  static $saved_globals = array();
205
206
207
  // 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.
208
209
  array_push($saved_globals, array($form_values, $form_submitted, $form_button_counter));

210
  $form_values = array();
Dries's avatar
Dries committed
211
  $form_submitted = FALSE;
212
  $form_button_counter = array(0, 0);
Steven Wittens's avatar
Steven Wittens committed
213

214
  drupal_prepare_form($form_id, $form);
215
  if (($form['#programmed']) || (!empty($_POST) && (($_POST['form_id'] == $form_id) || ($_POST['form_id'] == $form['#base'])))) {
216
    drupal_validate_form($form_id, $form);
217
218
219
    // 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.
220
221
    if ((($form['#programmed']) || $form_submitted || (!$form_button_counter[0] && $form_button_counter[1])) && !form_get_errors()) {
      $redirect = drupal_submit_form($form_id, $form);
222
223
224
      if (!$form['#programmed']) {
        drupal_redirect_form($form, $redirect);
      }
225
226
227
    }
  }

228
229
  // We've finished calling functions that alter the global values, so we can
  // restore the ones that were there before this function was called.
230
  list($form_values, $form_submitted, $form_button_counter) = array_pop($saved_globals);
231
  return $redirect;
232
233
234
235
236
237
238
239
240
241
242
243
244
}

/**
 * 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.
 */
245
function drupal_prepare_form($form_id, &$form) {
246
  $form['#type'] = 'form';
247
248
249
250
251
252
253
254
255

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

256
257
258
259
260
261
262
263
264
265
266
  // 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',
    );
  }

267
268
269
270
271
272
273
  // 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'];
  }

274
  if (isset($form['#token'])) {
275
276
277
    // 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.
278
279
    if (variable_get('cache', 0) && !$user->uid && $_SERVER['REQUEST_METHOD'] == 'GET') {
      unset($form['#token']);
280
    }
281
282
283
284
285
    else {
      // Make sure that a private key is set:
      if (!variable_get('drupal_private_key', '')) {
        variable_set('drupal_private_key', mt_rand());
      }
286

287
288
      $form['form_token'] = array('#type' => 'hidden', '#default_value' => md5(session_id() . $form['#token'] . variable_get('drupal_private_key', '')));
    }
289
  }
290

291
  if (isset($form_id)) {
292
    $form['form_id'] = array('#type' => 'hidden', '#value' => $form_id, '#id' => str_replace('_', '-', "edit-$form_id"));
293
  }
294
295
296
  if (!isset($form['#id'])) {
    $form['#id'] = $form_id;
  }
Steven Wittens's avatar
Steven Wittens committed
297

298
  $form += _element_info('form');
299

Dries's avatar
Dries committed
300
301
  if (!isset($form['#validate'])) {
    if (function_exists($form_id .'_validate')) {
302
      $form['#validate'] = array($form_id .'_validate' => array());
Dries's avatar
Dries committed
303
    }
304
305
    elseif (function_exists($base .'_validate')) {
      $form['#validate'] = array($base .'_validate' => array());
Dries's avatar
Dries committed
306
307
308
    }
  }

309
310
  if (!isset($form['#submit'])) {
    if (function_exists($form_id .'_submit')) {
311
312
      // we set submit here so that it can be altered but use reference for
      // $form_values because it will change later
313
      $form['#submit'] = array($form_id .'_submit' => array());
Dries's avatar
Dries committed
314
    }
315
316
    elseif (function_exists($base .'_submit')) {
      $form['#submit'] = array($base .'_submit' => array());
Dries's avatar
Dries committed
317
318
319
    }
  }

320
321
  foreach (module_implements('form_alter') as $module) {
    $function = $module .'_form_alter';
322
    $function($form_id, $form);
323
324
  }

325
  $form = form_builder($form_id, $form);
326
327
}

328
329
330
331
332
333
334
335
336
337
338
339

/**
 * 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.
 *
 */
340
function drupal_validate_form($form_id, $form) {
341
  global $form_values;
342
343
344
345
346
  static $validated_forms = array();

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

348
  // If the session token was set by drupal_prepare_form(), ensure that it
349
  // matches the current user's session
350
  if (isset($form['#token'])) {
351
    if ($form_values['form_token'] != md5(session_id() . $form['#token'] . variable_get('drupal_private_key', ''))) {
352
      // setting this error will cause the form to fail validation
353
      form_set_error('form_token', t('Validation error, please try again. If this error persists, please contact the site administrator.'));
354
355
356
    }
  }

357
  _form_validate($form, $form_id);
358
  $validated_forms[$form_id] = TRUE;
359
360
}

361
362
363
364
365
366
367
368
369
370
371
372
373
374
/**
 * 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.
 *
 */
375
function drupal_submit_form($form_id, $form) {
376
377
  global $form_values;
  $default_args = array($form_id, &$form_values);
378

379
  if (isset($form['#submit'])) {
380
381
    foreach ($form['#submit'] as $function => $args) {
      if (function_exists($function)) {
382
        $args = array_merge($default_args, (array) $args);
383
384
        // Since we can only redirect to one page, only the last redirect will work
        $redirect = call_user_func_array($function, $args);
385
386
387
        if (isset($redirect)) {
          $goto = $redirect;
        }
Dries's avatar
Dries committed
388
389
      }
    }
390
  }
391
  return $goto;
392
393
}

394
395
396
397
398
399
400
401
402
403
404
405
406
/**
 * 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.
 *
 */
407
function drupal_render_form($form_id, &$form) {
408
  // Don't override #theme if someone already set it.
409
410
411
412
  if (isset($form['#base'])) {
    $base = $form['#base'];
  }

413
414
415
416
  if (!isset($form['#theme'])) {
    if (theme_get_function($form_id)) {
      $form['#theme'] = $form_id;
    }
417
418
    elseif (theme_get_function($base)) {
      $form['#theme'] = $base;
419
420
421
422
423
424
425
426
427
428
429
    }
  }

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

430
  $output = drupal_render($form);
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
  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);
    }
  }
}

464
function _form_validate($elements, $form_id = NULL) {
465
466
467
468
469
470
  // Recurse through all children.
  foreach (element_children($elements) as $key) {
    if (isset($elements[$key]) && $elements[$key]) {
      _form_validate($elements[$key]);
    }
  }
471
  /* Validate the current input */
472
  if (!$elements['#validated']) {
473
    if (isset($elements['#needs_validation'])) {
474
475
476
477
      // 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') {
478
        form_error($elements, t('!name field is required.', array('!name' => $elements['#title'])));
479
      }
480
481
482
483
484
485
486
487
488
489
490
491
492
493

      // 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.'));
494
              watchdog('form', t('Illegal choice %choice in !name element.', array('%choice' => $v, '!name' => empty($elements['#title']) ? $elements['#parents'][0] : $elements['#title'])), WATCHDOG_ERROR);
495
            }
496
497
          }
        }
498
499
        elseif (!isset($options[$elements['#value']])) {
          form_error($elements, t('An illegal choice has been detected. Please contact the site administrator.'));
500
          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);
501
        }
502
503
504
505
      }
    }

    // User-applied checks.
Dries's avatar
Dries committed
506
    if (isset($elements['#validate'])) {
507
508
509
510
511
      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);
512
        }
513
514
        if (function_exists($function))  {
          call_user_func_array($function, $args);
515
516
517
        }
      }
    }
518
    $elements['#validated'] = TRUE;
519
520
521
  }
}

522
523
524
525
526
/**
 * 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.
 */
527
function form_set_error($name = NULL, $message = '') {
528
529
530
  static $form = array();
  if (isset($name) && !isset($form[$name])) {
    $form[$name] = $message;
531
532
533
    if ($message) {
      drupal_set_message($message, 'error');
    }
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
  }
  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];
  }
}

563
564
565
/**
 * Flag an element as having an error.
 */
566
function form_error(&$element, $message = '') {
567
  $element['#error'] = TRUE;
568
  form_set_error(implode('][', $element['#parents']), $message);
569
570
571
}

/**
572
573
574
575
576
577
 * 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
578
579
 *   A unique string identifying the form for validation, submission,
 *   theming, and hook_form_alter functions.
580
581
 * @param $form
 *   An associative array containing the structure of the form.
582
 */
583
function form_builder($form_id, $form) {
584
  global $form_values, $form_submitted, $form_button_counter;
585

586
587
588
  // Initialize as unprocessed.
  $form['#processed'] = FALSE;

589
  /* Use element defaults */
590
  if ((!empty($form['#type'])) && ($info = _element_info($form['#type']))) {
591
    // overlay $info onto $form, retaining preexisting keys in $form
592
593
594
    $form += $info;
  }

595
  if (isset($form['#input']) && $form['#input']) {
596
    if (!isset($form['#name'])) {
597
598
      $name = array_shift($form['#parents']);
      $form['#name'] = $name;
599
600
601
602
603
604
605
      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'])) {
606
607
608
        $form['#name'] .= '['. implode('][', $form['#parents']) .']';
      }
      array_unshift($form['#parents'], $name);
609
610
    }
    if (!isset($form['#id'])) {
611
      $form['#id'] = 'edit-'. implode('-', $form['#parents']);
612
    }
613

614
615
616
617
    if (isset($form['#disabled']) && $form['#disabled']) {
      $form['#attributes']['disabled'] = 'disabled';
    }

618
    if (!isset($form['#value']) && !array_key_exists('#value', $form)) {
619
620
      if (($form['#programmed']) || ((!isset($form['#access']) || $form['#access']) && isset($form['#post']) && ($form['#post']['form_id'] == $form_id))) {
        $edit = $form['#post'];
621
622
623
        foreach ($form['#parents'] as $parent) {
          $edit = isset($edit[$parent]) ? $edit[$parent] : NULL;
        }
624
625
626
627
628
629
630
631
632
633
634
635
636
637
        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();
                }
638
              }
639
640
              elseif (isset($edit)) {
                $form['#value'] = $edit;
641
              }
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
              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;
          }
661
662
663
        }
      }
      if (!isset($form['#value'])) {
664
665
666
667
668
669
670
        $function = $form['#type'] . '_value';
        if (function_exists($function)) {
          $function($form);
        }
        else {
          $form['#value'] = $form['#default_value'];
        }
671
      }
672
    }
673
674
675
676
    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
677
      if (isset($form['#post'][$form['#name']]) && $form['#post'][$form['#name']] == $form['#value']) {
678
        $form_submitted = $form_submitted || $form['#executes_submit_callback'];
679
680
681
682
683

        // 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'];
684
685
686
687
      }
    }
  }

688
  // Allow for elements to expand to multiple elements, e.g. radios, checkboxes and files.
689
  if (isset($form['#process']) && !$form['#processed']) {
690
691
    foreach ($form['#process'] as $process => $args) {
      if (function_exists($process)) {
692
        $args = array_merge(array($form), array($edit), $args);
693
        $form = call_user_func_array($process, $args);
694
695
      }
    }
696
    $form['#processed'] = TRUE;
697
698
  }

699
700
701
  // 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.
702
  if (isset($form['#input']) && $form['#input']) {
703
    form_set_value($form, $form['#value']);
704
705
  }

706
707
708
  // Recurse through all child elements.
  $count  = 0;
  foreach (element_children($form) as $key) {
709
710
    $form[$key]['#post'] = $form['#post'];
    $form[$key]['#programmed'] = $form['#programmed'];
Steven Wittens's avatar
Steven Wittens committed
711
    // don't squash an existing tree value
712
713
714
    if (!isset($form[$key]['#tree'])) {
      $form[$key]['#tree'] = $form['#tree'];
    }
Steven Wittens's avatar
Steven Wittens committed
715

716
717
718
719
720
    // deny access to child elements if parent is denied
    if (isset($form['#access']) && !$form['#access']) {
      $form[$key]['#access'] = FALSE;
    }

721
722
    // don't squash existing parents value
    if (!isset($form[$key]['#parents'])) {
723
724
      // 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
725
726
    }

727
    // Assign a decimal placeholder weight to preserve original array order
728
729
730
    if (!isset($form[$key]['#weight'])) {
      $form[$key]['#weight'] = $count/1000;
    }
731
    $form[$key] = form_builder($form_id, $form[$key]);
732
733
734
    $count++;
  }

735
736
737
738
739
740
  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);
      }
    }
741
    $form['#after_build_done'] = TRUE;
Steven Wittens's avatar
Steven Wittens committed
742
  }
743
744

  return $form;
745
746
}

747
/**
Dries's avatar
Dries committed
748
 * Use this function to make changes to form values in the form validate
749
750
751
752
 * 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
753
 * $form_values['foo']['bar'] to be 'baz'.
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
 *
 * @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;
}

786
787
788
/**
 * Retrieve the default properties for the defined element type.
 */
789
function _element_info($type, $refresh = NULL) {
790
  static $cache;
Steven Wittens's avatar
Steven Wittens committed
791

792
  $basic_defaults = array(
793
794
795
    '#description' => NULL,
    '#attributes' => array(),
    '#required' => FALSE,
Steven Wittens's avatar
Steven Wittens committed
796
    '#tree' => FALSE,
797
    '#parents' => array()
798
  );
799
  if (!isset($cache) || $refresh) {
800
801
802
    $cache = array();
    foreach (module_implements('elements') as $module) {
      $elements = module_invoke($module, 'elements');
803
      if (isset($elements) && is_array($elements)) {
804
        $cache = array_merge_recursive($cache, $elements);
805
806
807
808
      }
    }
    if (sizeof($cache)) {
      foreach ($cache as $element_type => $info) {
809
        $cache[$element_type] = array_merge_recursive($basic_defaults, $info);
810
811
812
813
814
815
816
      }
    }
  }

  return $cache[$type];
}

817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
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;
}

836
837
838
839
840
/**
 * Format a dropdown menu or scrolling selection box.
 *
 * @param $element
 *   An associative array containing the properties of the element.
841
 *   Properties used: title, value, options, description, extra, multiple, required
842
843
844
845
846
847
848
849
850
 * @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 = '';
851
  $size = $element['#size'] ? ' size="' . $element['#size'] . '"' : '';
852
  _form_set_class($element, array('form-select'));
853
854
  $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>');
855
856
857
858
859
860
}

function form_select_options($element, $choices = NULL) {
  if (!isset($choices)) {
    $choices = $element['#options'];
  }
861
  // array_key_exists() accommodates the rare event where $element['#value'] is NULL.
862
863
864
  // isset() fails in this situation.
  $value_valid = isset($element['#value']) || array_key_exists('#value', $element);
  $value_is_array = is_array($element['#value']);
865
866
  $options = '';
  foreach ($choices as $key => $choice) {
867
    if (is_array($choice)) {
868
869
870
      $options .= '<optgroup label="'. $key .'">';
      $options .= form_select_options($element, $choice);
      $options .= '</optgroup>';
871
872
    }
    else {
873
874
      $key = (string)$key;
      if ($value_valid && ($element['#value'] == $key || ($value_is_array && in_array($key, $element['#value'])))) {
875
876
877
878
879
        $selected = ' selected="selected"';
      }
      else {
        $selected = '';
      }
880
      $options .= '<option value="'. $key .'"'. $selected .'>'. check_plain($choice) .'</option>';
881
882
    }
  }
883
  return $options;
884
885
886
887
888
889
890
}

/**
 * Format a group of form items.
 *
 * @param $element
 *   An associative array containing the properties of the element.
891
 *   Properties used: attributes, title, value, description, children, collapsible, collapsed
892
893
894
895
 * @return
 *   A themed HTML string representing the form item group.
 */
function theme_fieldset($element) {
896
  if ($element['#collapsible']) {
897
898
    drupal_add_js('misc/collapse.js');

899
900
901
902
    if (!isset($element['#attributes']['class'])) {
      $element['#attributes']['class'] = '';
    }

903
904
905
    $element['#attributes']['class'] .= ' collapsible';
    if ($element['#collapsed']) {
     $element['#attributes']['class'] .= ' collapsed';
906
907
908
    }
  }

909
  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";
910
911
912
913
914
915
916
}

/**
 * Format a radio button.
 *
 * @param $element
 *   An associative array containing the properties of the element.
917
 *   Properties used: required, return_value, value, attributes, title, description
918
919
920
921
 * @return
 *   A themed HTML string representing the form item group.
 */
function theme_radio($element) {
922
  _form_set_class($element, array('form-radio'));
923
  $output = '<input type="radio" ';
924
925
926
927
928
929
  $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>';
930
  }
931
932
933

  unset($element['#title']);
  return theme('form_element', $element, $output);
934
935
936
937
938
939
940
}

/**
 * Format a set of radio buttons.
 *
 * @param $element
 *   An associative array containing the properties of the element.
941
 *   Properties used: title, value, options, description, required and attributes.
942
943
944
945
 * @return
 *   A themed HTML string representing the radio button set.
 */
function theme_radios($element) {
946
  if ($element['#title'] || $element['#description']) {
947
948
    unset($element['#id']);
    return theme('form_element', $element, $element['#children']);
949
950
  }
  else {
951
    return $element['#children'];
952
953
954
  }
}

955
956
957
958
959
960
961
962
963
964
/**
 * 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
965
  return theme('form_element', $element, $element['#children']);
966
967
}

968
969
970
971
/*
 * Expand a password_confirm field into two text boxes.
 */
function expand_password_confirm($element) {
Dries's avatar
Dries committed
972
973
974
975
976
977
978
979
980
981
  $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'],
  );
982
983
984
985
986
987
  $element['#validate'] = array('password_confirm_validate' => array());
  $element['#tree'] = TRUE;

  return $element;
}

988
/**
989
 * Validate password_confirm element.
990
 */
991
function password_confirm_validate($form) {
992
993
  $pass1 = trim($form['pass1']['#value']);
  if (!empty($pass1)) {
994
    $pass2 = trim($form['pass2']['#value']);
995
    if ($pass1 != $pass2) {
996
      form_error($form, t('The specified passwords do not match.'));
997
    }
998
  }
999
  elseif ($form['#required'] && !empty($form['#post'])) {
1000
    form_error($form, t('Password field is required.'));
1001
  }
1002

1003
1004
1005
1006
1007
1008
  // 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);

1009
1010
1011
  return $form;
}

1012
/**
1013
 * Format a date selection element.
1014
1015
1016
 *
 * @param $element
 *   An associative array containing the properties of the element.
1017
 *   Properties used: title, value, options, description, required and attributes.
1018
 * @return
1019
 *   A themed HTML string representing the date selection boxes.
1020
1021
 */
function theme_date($element) {
1022
  return theme('form_element', $element, '<div class="container-inline">'. $element['#children'] .'</div>');
1023
1024
1025
}

/**
1026
 * Roll out a single date element.
1027
1028
1029
 */
function expand_date($element) {
  // Default to current date
1030
1031
  if (!isset($element['#value'])) {
    $element['#value'] = array('day' => format_date(time(), 'custom', 'j'),
1032
1033
1034
1035
                            'month' => format_date(time(), 'custom', 'n'),
                            'year' => format_date(time(), 'custom', 'Y'));
  }

1036
1037
  $element['#tree'] = TRUE;

1038
  // Determine the order of day, month, year in the site's chosen date format.
1039
  $format = variable_get('date_format_short', 'm/d/Y - H:i');
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
  $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':
1054
        $options = drupal_map_assoc(range(1, 12), 'map_month');
1055
1056
1057
1058
1059
        break;
      case 'year':
        $options = drupal_map_assoc(range(1900, 2050));
        break;
    }
1060
1061
1062
1063
1064
1065
1066
1067
    $parents = $element['#parents'];
    $parents[] = $type;
    $element[$type] = array(
      '#type' => 'select',
      '#value' => $element['#value'][$type],
      '#attributes' => $element['#attributes'],
      '#options' => $options,
    );
1068
1069
1070
1071
1072
  }

  return $element;
}

1073
1074
1075
1076
1077
1078
1079
1080
1081
/**
 * 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.'));
  }
}

1082
1083
1084
1085
1086
1087
/**
 * 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);
}
1088

1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
/**
 * 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;
}

1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
/**
 * 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;
  }
}

1112
/**
1113
1114
 * Roll out a single radios element to a list of radios,
 * using the options array as index.
1115
1116
 */
function expand_radios($element) {
1117
1118
  if (count($element['#options']) > 0) {
    foreach ($element['#options'] as $key => $choice) {
1119
      if (!isset($element[$key])) {
1120
        $element[$key] = array('#type' => 'radio', '#title' => $choice, '#return_value' => $key, '#default_value' => $element['#default_value'], '#attributes' => $element['#attributes'], '#parents' => $element['#parents'], '#spawned' => TRUE);
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
      }
    }
  }
  return $element;
}

/**
 * Format a form item.
 *
 * @param $element
 *   An associative array containing the properties of the element.
1132
 *   Properties used:  title, value, description, required, error
1133
1134
1135
1136
 * @return
 *   A themed HTML string representing the form item.
 */
function theme_item($element) {
1137
  return theme('form_element', $element, $element['#value'] . $element['#children']);
1138
1139
1140
1141
1142
1143
1144
}

/**
 * Format a checkbox.
 *
 * @param $element
 *   An associative array containing the properties of the element.
1145
 *   Properties used:  title, value, return_value, description, required
1146
1147
1148
1149
 * @return
 *   A themed HTML string representing the checkbox.
 */
function theme_checkbox($element) {
1150
  _form_set_class($element, array('form-checkbox'));
1151
1152
  $checkbox = '<input ';
  $checkbox .= 'type="checkbox" ';
1153
1154
1155
  $checkbox .= 'name="'. $element['#name'] .'" ';
  $checkbox .= 'id="'. $element['#id'].'" ' ;
  $checkbox .= 'value="'. $element['#return_value'] .'" ';
1156
  $checkbox .= $element['#value'] ? ' checked="checked" ' : ' ';
1157
1158
1159
1160
  $checkbox .= drupal_attributes($element['#attributes']) . ' />';

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

1163
1164
  unset($element['#title']);
  return theme('form_element', $element, $checkbox);
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
}

/**
 * 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) {
1176
  if ($element['#title'] || $element['#description']) {
1177
1178
    unset($element['#id']);
    return theme('form_element', $element, $element['#children']);
1179
1180
  }
  else {
1181
    return $element['#children'];
1182
1183
1184
1185
  }
}

function expand_checkboxes($element) {
1186
  $value = is_array($element['#value']) ? $element['#value'] : array();
Steven Wittens's avatar
Steven Wittens committed
1187
  $element['#tree'] = TRUE;
1188
1189
1190
  if (count($element['#options']) > 0) {
    if (!isset($element['#default_value']) || $element['#default_value'] == 0) {
      $element['#default_value'] = array();
1191
    }
1192
    foreach ($element['#options'] as $key => $choice) {
1193
      if (!isset($element[$key])) {
1194
        $element[$key] = array('#type' => 'checkbox', '#processed' => TRUE, '#title' => $choice, '#return_value' => $key, '#default_value' => isset($value[$key]), '#attributes' => $element['#attributes']);
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
      }
    }
  }
  return $element;
}

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

function theme_button($element) {
1206
1207
1208
1209
1210
1211
1212
  //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'];
  }
1213

1214
  return '<input type="submit" '. (empty($element['#name']) ? '' : 'name="'. $element['#name'] .'" ') .'value="'. check_plain($element['#value']) .'" '. drupal_attributes($element['#attributes']) ." />\n";
1215
1216
1217
1218
1219
1220
1221
}

/**
 * Format a hidden form field.
 *
 * @param $element
 *   An associative array containing the properties of the element.
1222
 *   Properties used:  value, edit
1223
1224
1225
1226
 * @return
 *   A themed HTML string representing the hidden form field.
 */
function theme_hidden($element) {
1227
  return '<input type="hidden" name="'. $element['#name'] . '" id="'. $element['#id'] . '" value="'. check_plain($element['#value']) ."\" " . drupal_attributes($element['#attributes']) ." />\n";
1228
1229
1230
1231
1232
1233
1234
}

/**
 * Format a textfield.
 *
 * @param $element
 *   An associative array containing the properties of the element.
1235
 *   Properties used:  title, value, description, size, maxlength, required, attributes autocomplete_path
1236
1237
1238
1239
 * @return
 *   A themed HTML string representing the textfield.
 */
function theme_textfield($element) {
1240
  $size = $element['#size'] ? ' size="' . $element['#size'] . '"' : '';
1241
  $class = array('form-text');
1242
  $extra = '';
1243
1244
  $output = '';

1245
  if ($element['#autocomplete_path']) {
1246
    drupal_add_js('misc/autocomplete.js');
1247
    $class[] = 'form-autocomplete';