form.inc 70.1 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
 * 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:
 *
16
 * // Display the user registration form.
17 18 19
 * $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
 *
 * For information on the format of the structured arrays used to define forms,
23 24 25
 * and more detailed explanations of the Form API workflow, see the
 * @link http://api.drupal.org/api/HEAD/file/developer/topics/forms_api_reference.html reference @endlink
 * and the @link http://api.drupal.org/api/HEAD/file/developer/topics/forms_api.html quickstart guide. @endlink
26 27 28
 */

/**
29 30 31
 * Retrieves a form from a constructor function, or from the cache if
 * the form was built in a previous page-load. The form is then passesed
 * on for processing, after and rendered for display if necessary.
32 33
 *
 * @param $form_id
34 35 36 37
 *   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
38
 *   different $form_id values to the proper form constructor function. Examples
39 40
 *   may be found in node_forms(), search_forms(), and user_forms().
 * @param ...
41
 *   Any additional arguments needed by the form constructor function.
42 43 44 45
 * @return
 *   The rendered form.
 */
function drupal_get_form($form_id) {
46 47 48 49 50 51 52 53 54 55 56
  $form_state = array('storage' => NULL, 'submitted' => FALSE);
  $expire = max(ini_get('session.cookie_lifetime'), 86400);

  $args = func_get_args();

  if (isset($_SESSION['batch_form_state'])) {
    // We've been redirected here after a batch processing : the form has
    // already been processed, so we grab the post-process $form_state value
    // and move on to form display. See _batch_finished() function.
    $form_state = $_SESSION['batch_form_state'];
    unset($_SESSION['batch_form_state']);
57 58
  }
  else {
59 60 61 62 63 64 65 66 67 68 69 70
    // If the incoming $_POST contains a form_build_id, we'll check the
    // cache for a copy of the form in question. If it's there, we don't
    // have to rebuild the form to proceed. In addition, if there is stored
    // form_state data from a previous step, we'll retrieve it so it can
    // be passed on to the form processing code.
    if (isset($_POST['form_id']) && $_POST['form_id'] == $form_id && !empty($_POST['form_build_id'])) {
      if ($cached = cache_get('form_'. $_POST['form_build_id'], 'cache_form')) {
        $form = $cached->data;
        if ($cached = cache_get('storage_'. $_POST['form_build_id'], 'cache_form')) {
          $form_state['storage'] = $cached->data;
        }
      }
71 72
    }

73 74 75 76
    // If the previous bit of code didn't result in a populated $form
    // object, we're hitting the form for the first time and we need
    // to build it from scratch.
    if (!isset($form)) {
77
      $form_state['post'] = $_POST;
78 79 80 81
      array_shift($args);
      array_unshift($args, $form_state);
      array_unshift($args, $form_id);

82
      $form = call_user_func_array('drupal_retrieve_form', $args);
83
      $form_build_id = 'form-'. md5(mt_rand());
84
      $form['#build_id'] = $form_build_id;
85 86 87 88
      drupal_prepare_form($form_id, $form, $form_state);
      if (!empty($form['#cache'])) {
        cache_set('form_'. $form_build_id, $form, 'cache_form', $expire);
      }
89
      unset($form_state['post']);
90
    }
91
    $form['#post'] = $_POST;
92

93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
    // Now that we know we have a form, we'll process it (validating,
    // submitting, and handling the results returned by its submission
    // handlers. Submit handlers accumulate data in the form_state by
    // altering the $form_state variable, which is passed into them by
    // reference.
    drupal_process_form($form_id, $form, $form_state);
  }

  // Most simple, single-step forms will be finished by this point --
  // drupal_process_form() usually redirects to another page (or to
  // a 'fresh' copy of the form) once processing is complete. If one
  // of the form's handlers has set $form_state['redirect'] to FALSE,
  // the form will simply be re-rendered with the values still in its
  // fields.
  //
  // If $form_state['storage'] or $form_state['rebuild'] have been
  // set by any submit or validate handlers, however, we know that
  // we're in a complex multi-part process of some sort and the form's
  // workflow is NOT complete. We need to construct a fresh copy of
  // the form, passing in the latest $form_state in addition to any
  // other variables passed into drupal_get_form().

  if (!empty($form_state['rebuild']) || !empty($form_state['storage'])) {
116 117 118
    array_shift($args);
    array_unshift($args, $form_state);
    array_unshift($args, $form_id);
119
    $form = call_user_func_array('drupal_retrieve_form', $args);
120

121
    // We need a new build_id for the new version of the form.
122
    $form_build_id = 'form-'. md5(mt_rand());
123 124 125 126 127 128 129 130 131
    $form['#build_id'] = $form_build_id;
    drupal_prepare_form($form_id, $form, $form_state);

    // Now, we cache the form structure so it can be retrieved later for
    // validation. If $form_state['storage'] is populated, we'll also cache
    // it so that it can be used to resume complex multi-step processes.
    cache_set('form_'. $form_build_id, $form, 'cache_form', $expire);
    if (!empty($form_state['storage'])) {
      cache_set('storage_'. $form_build_id, $form_state['storage'], 'cache_form', $expire);
132
    }
133 134 135 136 137 138 139

    // Clear out all post data, as we don't want the previous step's
    // data to pollute this one and trigger validate/submit handling,
    // then process the form for rendering.
    $_POST = array();
    $form['#post'] = array();
    drupal_process_form($form_id, $form, $form_state);
140 141
  }

142 143 144 145
  // If we haven't redirected to a new location by now, we want to
  // render whatever form array is currently in hand.
  return drupal_render_form($form_id, $form);
}
146

147
/**
148
 * Retrieves a form using a form_id, populates it with $form_state['values'],
149 150 151 152 153 154 155 156
 * 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
157
 *   different $form_id values to the proper form constructor function. Examples
158
 *   may be found in node_forms(), search_forms(), and user_forms().
159 160 161 162 163
 * @param $form_state
 *   A keyed array containing the current state of the form. Most
 *   important is the $form_state['values'] collection, a tree of data
 *   used to simulate the incoming $_POST information from a user's
 *   form submission.
164
 * @param ...
165
 *   Any additional arguments needed by the form constructor function.
166 167 168 169
 *
 * For example:
 *
 * // register a new user
170 171 172 173 174
 * $form_state = array();
 * $form_state['values']['name'] = 'robo-user';
 * $form_state['values']['mail'] = 'robouser@example.com';
 * $form_state['values']['pass'] = 'password';
 * drupal_execute('user_register', $form_state);
175 176
 *
 * // Create a new node
177
 * $form_state = array();
178
 * $node = array('type' => 'story');
179 180 181 182
 * $form_state['values']['title'] = 'My node';
 * $form_state['values']['body'] = 'This is the body text!';
 * $form_state['values']['name'] = 'robo-user';
 * drupal_execute('story_node_form', $form_state, $node);
183
 */
184
function drupal_execute($form_id, &$form_state) {
185
  $args = func_get_args();
186 187 188 189
  $form = call_user_func_array('drupal_retrieve_form', $args);
  $form['#post'] = $form_state['values'];
  drupal_prepare_form($form_id, $form, $form_state);
  drupal_process_form($form_id, $form, $form_state);
190 191
}

192 193 194 195 196 197 198 199
/**
 * 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
200
 *   different $form_id values to the proper form constructor function.
201 202
 * @param $form_state
 *   A keyed array containing the current state of the form.
203
 * @param ...
204
 *   Any additional arguments needed by the form constructor function.
205
 */
206
function drupal_retrieve_form($form_id, &$form_state) {
207 208
  static $forms;

209
  // We save two copies of the incoming arguments: one for modules to use
210 211 212
  // when mapping form ids to constructor functions, and another to pass to
  // the constructor function itself. We shift out the first argument -- the
  // $form_id itself -- from the list to pass into the constructor function,
213
  // since it's already known.
214
  $args = func_get_args();
215
  $saved_args = $args;
216
  array_shift($args);
217 218 219
  if (isset($form_state)) {
    array_shift($args);
  }
220 221 222

  // 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.
223
  if (!function_exists($form_id)) {
224
    // In cases where many form_ids need to share a central constructor function,
225
    // such as the node editing form, modules can implement hook_forms(). It
226
    // maps one or more form_ids to the correct constructor functions.
227 228 229 230 231 232 233 234 235
    //
    // 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])) {
236
      $forms = module_invoke_all('forms', $form_id, $args);
237 238 239 240 241 242 243 244 245
    }
    $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'];
    }
  }
246 247 248

  array_unshift($args, $form_state);

249 250
  // If $callback was returned by a hook_forms() implementation, call it.
  // Otherwise, call the function named after the form id.
251 252 253 254 255 256
  $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.
257
  $form['#parameters'] = $saved_args;
258
  return $form;
259 260 261 262 263 264 265 266
}

/**
 * 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.
267 268
 * @param $form
 *   An associative array containing the structure of the form.
269 270 271 272 273 274
 * @param $form_state
 *   A keyed array containing the current state of the form. This
 *   includes the current persistant storage data for the form, and
 *   any data passed along by earlier steps when displaying a
 *   multi-step form. Additional information, like the sanitized $_POST
 *   data, is also accumulated here.
275
 */
276 277 278 279 280 281 282 283 284 285 286 287 288 289
function drupal_process_form($form_id, &$form, &$form_state) {
  $form_state['values'] = array();

  $form = form_builder($form_id, $form, $form_state);
  if ((!empty($form['#programmed'])) || (!empty($form['#post']) && (($form['#post']['form_id'] == $form_id)))) {
    drupal_validate_form($form_id, $form, $form_state);

    if ((!empty($form_state['submitted'])) && !form_get_errors() && empty($form_state['rebuild'])) {
      $form_state['redirect'] = NULL;
      form_execute_handlers('submit', $form, $form_state);

      // We'll clear out the cached copies of the form and its stored data
      // here, as we've finished with them. The in-memory copies are still
      // here, though.
290
      if (variable_get('cache', CACHE_DISABLED) == CACHE_DISABLED && !empty($form_state['values']['form_build_id'])) {
291 292 293 294 295 296
        cache_clear_all('form_'. $form_state['values']['form_build_id'], 'cache_form');
        cache_clear_all('storage_'. $form_state['values']['form_build_id'], 'cache_form');
      }

      // If batches were set in the submit handlers, we process them now,
      // possibly ending execution.
297
      if ($batch =& batch_get()) {
298 299 300 301
        // The batch uses its own copies of $form and $form_state for
        // late execution of submit handers and post-batch redirection.
        $batch['form'] = $form;
        $batch['form_state'] = $form_state;
302 303
        $batch['progressive'] = !$form['#programmed'];
        batch_process();
304 305 306 307
        // Execution continues only for programmatic forms.
        // For 'regular' forms, we get redirected to the batch processing
        // page. Form redirection will be handled in _batch_finished(),
        // after the batch is processed.
308
      }
309 310 311 312 313 314 315 316 317 318

      // If no submit handlers have populated the $form_state['storage']
      // bundle, and the $form_state['rebuild'] flag has not been set,
      // we're finished and should redirect to a new destination page
      // if one has been set (and a fresh, unpopulated copy of the form
      // if one hasn't). If the form was called by drupal_execute(),
      // however, we'll skip this and let the calling function examine
      // the resulting $form_state bundle itself.
      if (!$form['#programmed'] && empty($form_state['rebuild']) && empty($form_state['storage'])) {
         drupal_redirect_form($form, $form_state['redirect']);
319
      }
320 321 322 323 324 325 326 327 328 329 330 331 332 333
    }
  }
}

/**
 * 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.
334 335 336
 * @param $form_state
 *   A keyed array containing the current state of the form. Passed
 *   in here so that hook_form_alter() calls can use it, as well.
337
 */
338
function drupal_prepare_form($form_id, &$form, &$form_state) {
339 340
  global $user;

341
  $form['#type'] = 'form';
342
  $form['#programmed'] = isset($form['#post']);
343

344 345 346 347 348 349 350 351 352
  if (isset($form['#build_id'])) {
    $form['form_build_id'] = array(
      '#type' => 'hidden',
      '#value' => $form['#build_id'],
      '#id' => $form['#build_id'],
      '#name' => 'form_build_id',
    );
  }

353 354 355 356
  // 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.
357
  if (isset($form['#token'])) {
358
    if ($form['#token'] === FALSE || $user->uid == 0 || $form['#programmed']) {
359
      unset($form['#token']);
360
    }
361
    else {
362
      $form['form_token'] = array('#type' => 'token', '#default_value' => drupal_get_token($form['#token']));
363
    }
364
  }
365
  else if (isset($user->uid) && $user->uid && !$form['#programmed']) {
366 367
    $form['#token'] = $form_id;
    $form['form_token'] = array(
368
      '#id' => form_clean_id('edit-'. $form_id .'-form-token'),
369 370 371 372 373
      '#type' => 'token',
      '#default_value' => drupal_get_token($form['#token']),
    );
  }

374
  if (isset($form_id)) {
375 376 377 378 379
    $form['form_id'] = array(
      '#type' => 'hidden',
      '#value' => $form_id,
      '#id' => form_clean_id("edit-$form_id"),
    );
380
  }
381
  if (!isset($form['#id'])) {
382
    $form['#id'] = form_clean_id($form_id);
383
  }
384

385
  $form += _element_info('form');
386

Dries's avatar
Dries committed
387 388
  if (!isset($form['#validate'])) {
    if (function_exists($form_id .'_validate')) {
389
      $form['#validate'] = array($form_id .'_validate');
Dries's avatar
Dries committed
390 391 392
    }
  }

393 394
  if (!isset($form['#submit'])) {
    if (function_exists($form_id .'_submit')) {
395
      // We set submit here so that it can be altered.
396
      $form['#submit'] = array($form_id .'_submit');
Dries's avatar
Dries committed
397 398 399
    }
  }

400
  drupal_alter('form_'. $form_id, $form, $form_state);
401
  drupal_alter('form', $form, $form_state, $form_id);
402 403
}

404 405

/**
406
 * Validates user-submitted form data from the $form_state using
407 408 409 410 411 412 413
 * 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.
414 415 416 417 418 419 420 421 422 423
 * @param $form_state
 *   A keyed array containing the current state of the form. The current
 *   user-submitted data is stored in $form_state['values'], though
 *   form validation functions are passed an explicit copy of the
 *   values for the sake of simplicity. Validation handlers can also
 *   $form_state to pass information on to submit handlers. For example:
 *     $form_state['data_for_submision'] = $data;
 *   This technique is useful when validation requires file parsing,
 *   web service requests, or other expensive requests that should
 *   not be repeated in the submission step.
424
 */
425
function drupal_validate_form($form_id, $form, &$form_state) {
426 427 428 429 430
  static $validated_forms = array();

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

432
  // If the session token was set by drupal_prepare_form(), ensure that it
433
  // matches the current user's session.
434
  if (isset($form['#token'])) {
435
    if (!drupal_valid_token($form_state['values']['form_token'], $form['#token'])) {
436
      // Setting this error will cause the form to fail validation.
437
      form_set_error('form_token', t('Validation error, please try again. If this error persists, please contact the site administrator.'));
438 439 440
    }
  }

441
  _form_validate($form, $form_state, $form_id);
442
  $validated_forms[$form_id] = TRUE;
443 444
}

445 446 447 448 449 450 451 452 453 454 455 456
/**
 * 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.
 */
457
function drupal_render_form($form_id, &$form) {
458 459
  // Don't override #theme if someone already set it.
  if (!isset($form['#theme'])) {
460 461 462
    init_theme();
    $registry = theme_get_registry();
    if (isset($registry[$form_id])) {
463 464 465 466
      $form['#theme'] = $form_id;
    }
  }

467
  $output = drupal_render($form);
468 469 470 471 472 473 474 475 476
  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
477
 *   An optional value containing the destination path to redirect
478 479 480
 *   to if none is specified by the form.
 */
function drupal_redirect_form($form, $redirect = NULL) {
481
  $goto = NULL;
482 483 484
  if (isset($redirect)) {
    $goto = $redirect;
  }
485
  if ($goto !== FALSE && isset($form['#redirect'])) {
486 487
    $goto = $form['#redirect'];
  }
488
  drupal_redirect(isset($goto) ? $goto : NULL);
489 490
}

491 492 493 494 495 496 497
/**
 * Performs validation on form elements. First ensures required fields are
 * completed, #maxlength is not exceeded, and selected options were in the
 * list of options given to the user. Then calls user-defined validators.
 *
 * @param $elements
 *   An associative array containing the structure of the form.
498 499 500 501 502 503 504 505 506 507
 * @param $form_state
 *   A keyed array containing the current state of the form. The current
 *   user-submitted data is stored in $form_state['values'], though
 *   form validation functions are passed an explicit copy of the
 *   values for the sake of simplicity. Validation handlers can also
 *   $form_state to pass information on to submit handlers. For example:
 *     $form_state['data_for_submision'] = $data;
 *   This technique is useful when validation requires file parsing,
 *   web service requests, or other expensive requests that should
 *   not be repeated in the submission step.
508 509 510 511
 * @param $form_id
 *   A unique string identifying the form for validation, submission,
 *   theming, and hook_form_alter functions.
 */
512
function _form_validate($elements, &$form_state, $form_id = NULL) {
513 514 515
  // Recurse through all children.
  foreach (element_children($elements) as $key) {
    if (isset($elements[$key]) && $elements[$key]) {
516
      _form_validate($elements[$key], $form_state);
517 518
    }
  }
519
  /* Validate the current input */
520
  if (!isset($elements['#validated']) || !$elements['#validated']) {
521
    if (isset($elements['#needs_validation'])) {
522 523 524 525
      // 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') {
526
        form_error($elements, t('!name field is required.', array('!name' => $elements['#title'])));
527
      }
528

529 530 531 532 533
      // 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']))));
      }

534 535
       // Add legal choice check if element has #options. Can be skipped, but
       // then you must validate your own element.
536 537 538 539 540 541 542 543 544 545 546 547
      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.'));
548
              watchdog('form', t('Illegal choice %choice in !name element.', array('%choice' => $v, '!name' => empty($elements['#title']) ? $elements['#parents'][0] : $elements['#title'])), WATCHDOG_ERROR);
549
            }
550 551
          }
        }
552 553
        elseif (!isset($options[$elements['#value']])) {
          form_error($elements, t('An illegal choice has been detected. Please contact the site administrator.'));
554
          watchdog('form', t('Illegal choice %choice in %name element.', array('%choice' => $elements['#value'], '%name' => empty($elements['#title']) ? $elements['#parents'][0] : $elements['#title'])), WATCHDOG_ERROR);
555
        }
556 557 558
      }
    }

559 560 561 562 563 564 565 566
    // Call user-defined form level validators.
    if (isset($form_id)) {
      form_execute_handlers('validate', $elements, $form_state);
    }
    // Call any element-specific validators. These must act on the element
    // #value data.
    elseif (isset($elements['#element_validate'])) {
      foreach ($elements['#element_validate'] as $function) {
567
        if (function_exists($function))  {
568
          $function($elements, $form_state);
569 570 571
        }
      }
    }
572
    $elements['#validated'] = TRUE;
573 574 575
  }
}

576 577 578 579 580 581 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
/**
 * A helper function used to execute custom validation and submission
 * handlers for a given form. Button-specific handlers are checked
 * first. If none exist, the function falls back to form-level handlers.
 *
 * @param $type
 *   The type of handler to execute. 'validate' or 'submit' are the
 *   defaults used by Form API.
 * @param $form
 *   An associative array containing the structure of the form.
 * @param $form_state
 *   A keyed array containing the current state of the form. If the user
 *   submitted the form by clicking a button with custom handler functions
 *   defined, those handlers will be stored here.
 */
function form_execute_handlers($type, &$form, &$form_state) {
  $return = FALSE;
  if (isset($form_state[$type .'_handlers'])) {
    $handlers = $form_state[$type .'_handlers'];
  }
  elseif (isset($form['#'. $type])) {
    $handlers = $form['#'. $type];
  }
  else {
    $handlers = array();
  }

  foreach ($handlers as $function) {
    if (function_exists($function))  {
      if ($type == 'submit' && ($batch =& batch_get())) {
        // Some previous _submit handler has set a batch. We store the call
        // in a special 'control' batch set, for execution at the correct
        // time during the batch processing workflow.
609
        $batch['sets'][] = array('form_submit' => $function);
610 611
      }
      else {
612
        $function($form, $form_state);
613 614 615 616 617 618 619
      }
      $return = TRUE;
    }
  }
  return $return;
}

620 621 622 623 624
/**
 * 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.
 */
625
function form_set_error($name = NULL, $message = '') {
626 627 628
  static $form = array();
  if (isset($name) && !isset($form[$name])) {
    $form[$name] = $message;
629 630 631
    if ($message) {
      drupal_set_message($message, 'error');
    }
632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660
  }
  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];
  }
}

661 662 663
/**
 * Flag an element as having an error.
 */
664
function form_error(&$element, $message = '') {
665
  $element['#error'] = TRUE;
666
  form_set_error(implode('][', $element['#parents']), $message);
667 668 669
}

/**
670 671 672
 * Walk through the structured form array, adding any required
 * properties to each element and mapping the incoming $_POST
 * data to the proper elements.
673 674
 *
 * @param $form_id
675 676
 *   A unique string identifying the form for validation, submission,
 *   theming, and hook_form_alter functions.
677 678
 * @param $form
 *   An associative array containing the structure of the form.
679 680 681 682 683
 * @param $form_state
 *   A keyed array containing the current state of the form. In this
 *   context, it is used to accumulate information about which button
 *   was clicked when the form was submitted, as well as the sanitized
 *   $_POST data.
684
 */
685
function form_builder($form_id, $form, &$form_state) {
686 687 688
  // Initialize as unprocessed.
  $form['#processed'] = FALSE;

689
  /* Use element defaults */
690
  if ((!empty($form['#type'])) && ($info = _element_info($form['#type']))) {
691
    // Overlay $info onto $form, retaining preexisting keys in $form.
692 693 694
    $form += $info;
  }

695 696 697 698
  if (isset($form['#type']) && $form['#type'] == 'form' && !empty($form['#programmed'])) {
    $form_state['submitted'] = TRUE;
  }

699
  if (isset($form['#input']) && $form['#input']) {
700
    _form_builder_handle_input_element($form_id, $form, $form_state);
701
  }
702
  $form['#defaults_loaded'] = TRUE;
703

704 705 706
  // We start off assuming all form elements are in the correct order.
  $form['#sorted'] = TRUE;

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

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

722
    // Don't squash existing parents value.
723
    if (!isset($form[$key]['#parents'])) {
724 725
      // Check to see if a tree of child elements is present. If so,
      // continue down the tree if required.
726
      $form[$key]['#parents'] = $form[$key]['#tree'] && $form['#tree'] ? array_merge($form['#parents'], array($key)) : array($key);
727 728
    }

729
    // Assign a decimal placeholder weight to preserve original array order.
730 731 732
    if (!isset($form[$key]['#weight'])) {
      $form[$key]['#weight'] = $count/1000;
    }
733
    else {
734 735
      // If one of the child elements has a weight then we will need to sort
      // later.
736 737
      unset($form['#sorted']);
    }
738
    $form[$key] = form_builder($form_id, $form[$key], $form_state);
739 740 741
    $count++;
  }

742 743
  // The #after_build flag allows any piece of a form to be altered
  // after normal input parsing has been completed.
744 745
  if (isset($form['#after_build']) && !isset($form['#after_build_done'])) {
    foreach ($form['#after_build'] as $function) {
746
      $form = $function($form, $form_state);
747 748 749 750 751 752 753 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 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 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
      $form['#after_build_done'] = TRUE;
    }
  }

  // Now that we've processed everything, we can go back to handle the funky
  // Internet Explorer button-click scenerio.
  _form_builder_ie_cleanup($form, $form_state);

  return $form;
}

/**
 * Populate the #value and #name properties of input elements so they
 * can be processed and rendered. Also, execute any #process handlers
 * attached to a specific element.
 */
function _form_builder_handle_input_element($form_id, &$form, &$form_state) {
  if (!isset($form['#name'])) {
    $name = array_shift($form['#parents']);
    $form['#name'] = $name;
    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'])) {
      $form['#name'] .= '['. implode('][', $form['#parents']) .']';
    }
    array_unshift($form['#parents'], $name);
  }
  if (!isset($form['#id'])) {
    $form['#id'] = form_clean_id('edit-'. implode('-', $form['#parents']));
  }

  if (!empty($form['#disabled'])) {
    $form['#attributes']['disabled'] = 'disabled';
  }

  if (!isset($form['#value']) && !array_key_exists('#value', $form)) {
    if (($form['#programmed']) || ((!isset($form['#access']) || $form['#access']) && isset($form['#post']) && (isset($form['#post']['form_id']) && $form['#post']['form_id'] == $form_id))) {
      $edit = $form['#post'];
      foreach ($form['#parents'] as $parent) {
        $edit = isset($edit[$parent]) ? $edit[$parent] : NULL;
      }
      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();
              }
            }
            elseif (isset($edit)) {
              $form['#value'] = $edit;
            }
            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;

          case 'token':
            $form['#value'] = (string)$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;
        }
      }
    }
    if (!isset($form['#value'])) {
      $function = 'form_'. $form['#type'] .'_value';
838
      if (function_exists($function)) {
839 840 841 842
        $function($form);
      }
      else {
        $form['#value'] = isset($form['#default_value']) ? $form['#default_value'] : '';
843 844
      }
    }
845
  }
846

847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909
  // Determine which button (if any) was clicked to submit the form.
  // We compare the incoming values with the buttons defined in the form,
  // and flag the one that matches. We have to do some funky tricks to
  // deal with Internet Explorer's handling of single-button forms, though.
  if (!empty($form['#post']) && isset($form['#executes_submit_callback'])) {
    // First, accumulate a collection of buttons, divided into two bins:
    // those that execute full submit callbacks and those that only validate.
    $button_type = $form['#executes_submit_callback'] ? 'submit' : 'button';
    $form_state['buttons'][$button_type][] = $form;

    // See if a submit button was clicked. In Internet Explorer, if ONLY
    // one submit button is present, AND the enter key is used to submit
    // the form, no form value is sent for it and we'll never detect a
    // match. In most cases, though, the following code will properly handle
    // finding the clicked button and storing any custom validate and
    // submit handlers it has defined.
    if (isset($form['#post'][$form['#name']]) && $form['#post'][$form['#name']] == $form['#value']) {
      $form_state['submitted'] = $form_state['submitted'] || $form['#executes_submit_callback'];

      // 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_state['values'][$form['#name']] = $form['#value'];

      if (isset($form['#validate'])) {
        $form_state['validate_handlers'] = $form['#validate'];
      }
      if (isset($form['#submit'])) {
        $form_state['submit_handlers'] = $form['#submit'];
      }
    }
  }
  // Allow for elements to expand to multiple elements, e.g., radios,
  // checkboxes and files.
  if (isset($form['#process']) && !$form['#processed']) {
    foreach ($form['#process'] as $process) {
      if (function_exists($process)) {
        $args = array_merge(array($form), array(isset($edit) ? $edit : NULL), array($form_state));
        $form = call_user_func_array($process, $args);
      }
    }
    $form['#processed'] = TRUE;
  }
  form_set_value($form, $form['#value'], $form_state);
}

/**
 * Handle the special Internet Explorer one-button-form hit-enter-
 * instead-of-clicking scenerio.
 */
function _form_builder_ie_cleanup($form, &$form_state) {
  if (!empty($form['#type']) && $form['#type'] == 'form') {
    // If the 'submitted' flag isn't tripped, but there is only one submit button...
    if (empty($form_state['submitted']) && !empty($form_state['buttons']['submit']) && empty($form_state['buttons']['button'])) {
      $button = $form_state['buttons']['submit'][0];
      $form_state['submitted'] = TRUE;
      $form_state['submit_handlers'] = empty($button['#submit']) ? NULL : $button['#submit'];
      $form_state['validate_handlers'] = empty($button['#validate']) ? NULL : $button['#validate'];
      $form_state['values'][$button['#name']] = $button['#value'];
    }
    // After handling the special IE case, we no longer need the buttons collection.
    unset($form_state['buttons']);
  }
910 911
}

912
/**
Dries's avatar
Dries committed
913
 * Use this function to make changes to form values in the form validate
914
 * phase, so they will be available in the submit phase in $form_state.
915 916 917
 *
 * Specifically, if $form['#parents'] is array('foo', 'bar')
 * and $value is 'baz' then this function will make
918
 * $form_state['values']['foo']['bar'] to be 'baz'.
919 920 921 922 923 924
 *
 * @param $form
 *   The form item. Keys used: #parents, #value
 * @param $value
 *   The value for the form item.
 */
925 926
function form_set_value($form, $value, &$form_state) {
  _form_set_value($form_state['values'], $form, $form['#parents'], $value);
927 928 929 930 931
}

/**
 * Helper function for form_set_value().
 *
932
 * We iterate over $parents and create nested arrays for them
933
 * in $form_state['values'] if needed. Then we insert the value into
934 935 936 937 938 939 940 941 942 943 944 945 946 947 948
 * 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);
  }
}

949 950 951
/**
 * Retrieve the default properties for the defined element type.
 */
952
function _element_info($type, $refresh = NULL) {
953
  static $cache;
954

955
  $basic_defaults = array(
956 957 958
    '#description' => NULL,
    '#attributes' => array(),
    '#required' => FALSE,
959
    '#tree' => FALSE,
960
    '#parents' => array()
961
  );
962
  if (!isset($cache) || $refresh) {
963 964 965
    $cache = array();
    foreach (module_implements('elements') as $module) {
      $elements = module_invoke($module, 'elements');
966
      if (isset($elements) && is_array($elements)) {
967
        $cache = array_merge_recursive($cache, $elements);
968 969 970 971
      }
    }
    if (sizeof($cache)) {
      foreach ($cache as $element_type => $info) {
972
        $cache[$element_type] = array_merge_recursive($basic_defaults, $info);
973 974 975 976 977 978 979
      }
    }
  }

  return $cache[$type];
}

980 981 982 983 984 985 986 987
function form_options_flatten($array, $reset = TRUE) {
  static $return;

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

  foreach ($array as $key => $value) {
988 989 990 991
    if (is_object($value)) {
      form_options_flatten($value->option, FALSE);
    }
    else if (is_array($value)) {
992 993 994 995 996 997 998 999 1000 1001
      form_options_flatten($value, FALSE);
    }
    else {
      $return[$key] = 1;
    }
  }

  return $return;
}

1002 1003 1004 1005 1006
/**
 * Format a dropdown menu or scrolling selection box.
 *
 * @param $element
 *   An associative array containing the properties of the element.
1007
 *   Properties used: title, value, options, description, extra, multiple, required
1008 1009 1010 1011 1012 1013 1014 1015 1016
 * @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 = '';
1017
  $size = $element['#size'] ? ' size="'. $element['#size'] .'"' : '';
1018
  _form_set_class($element, array('form-select'));
1019
  $multiple = $element['#multiple'];
1020
  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>');
1021 1022 1023 1024 1025 1026
}

function form_select_options($element, $choices = NULL) {
  if (!isset($choices)) {
    $choices = $element['#options'];
  }
1027
  // array_key_exists() accommodates the rare event where $element['#value'] is NULL.
1028 1029 1030
  // isset() fails in this situation.
  $value_valid = isset($element['#value']) || array_key_exists('#value', $element);
  $value_is_array = is_array($element['#value']);
1031 1032
  $options = '';
  foreach ($choices as $key => $choice) {
1033
    if (is_array($choice)) {
1034 1035 1036
      $options .= '<optgroup label="'. $key .'">';
      $options .= form_select_options($element, $choice);
      $options .= '</optgroup>';
1037
    }
1038 1039 1040
    elseif (is_object($choice)) {
      $options .= form_select_options($element, $choice->option);
    }
1041
    else {
1042
      $key = (string)$key;
1043
      if ($value_valid && (!$value_is_array && (string)$element['#value'] === $key || ($value_is_array && in_array($key, $element['#value'])))) {
1044 1045 1046 1047 1048
        $selected = ' selected="selected"';
      }
      else {
        $selected = '';
      }
1049
      $options .= '<option value="'. $key .'"'. $selected .'>'. check_plain($choice) .'</option>';
1050 1051
    }
  }
1052
  return $options;
1053 1054
}

1055
/**
1056 1057 1058 1059
 * Traverses a select element's #option array looking for any values
 * that hold the given key. Returns an array of indexes that match.
 *
 * This function is useful if you need to modify the options that are
1060
 * already in a form element; for example, to remove choices which are
1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077
 * not valid because of additional filters imposed by another module.
 * One example might be altering the choices in a taxonomy selector.
 * To correctly handle the case of a multiple hierarchy taxonomy,
 * #options arrays can now hold an array of objects, instead of a
 * direct mapping of keys to labels, so that multiple choices in the
 * selector can have the same key (and label). This makes it difficult
 * to manipulate directly, which is why this helper function exists.
 *
 * This function does not support optgroups (when the elements of the
 * #options array are themselves arrays), and will return FALSE if
 * arrays are found. The caller must either flatten/restore or
 * manually do their manipulations in this case, since returning the
 * index is not sufficient, and supporting this would make the
 * "helper" too complicated and cumbersome to be of any help.
 *
 * As usual with functions that can return array() or FALSE, do not
 * forget to use === and !== if needed.
1078 1079
 *
 * @param $element
1080
 *   The select element to search.
1081 1082 1083
 * @param $key
 *   The key to look for.
 * @return
1084 1085
 *   An array of indexes that match the given $key. Array will be
 *   empty if no elements were found. FALSE if optgroups were found.
1086
 */
1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099
function form_get_options($element, $key) {
  $keys = array();
  foreach ($element['#options'] as $index => $choice) {
    if (is_array($choice)) {
      return FALSE;
    }
    else if (is_object($choice)) {
      if (isset($choice->option[$key])) {
        $keys[] = $index;
      }
    }
    else if ($index == $key) {
      $keys[] = $index;
1100 1101
    }
  }
1102
  return $keys;
1103 1104
}

1105 1106 1107 1108 1109
/**
 * Format a group of form items.
 *
 * @param $element
 *   An associative array containing the properties of the element.
1110
 *   Properties used: attributes, title, value, description, children, collapsible, collapsed
1111 1112 1113 1114
 * @return
 *   A themed HTML string representing the form item group.
 */
function theme_fieldset($element) {
1115
  if ($element['#collapsible']) {
1116 1117
    drupal_add_js('misc/collapse.js');

1118 1119 1120 1121
    if (!isset($element['#attributes']['class'])) {
      $element['#attributes']['class'] = '';
    }

1122 1123 1124
    $element['#attributes']['class'] .= ' collapsible';
    if ($element['#collapsed']) {
     $element['#attributes']['class'] .= ' collapsed';
1125 1126 1127
    }
  }

1128
  return '<fieldset'. drupal_attributes($element['#attributes']) .'>'. ($element['#title'] ? '<legend>'. $element['#title'] .'</legend>' : '') . (isset($element['#description']) && $element['#description'] ? '<div class="description">'. $element['#description'] .'</div>' : '') . (!empty($element['#children']) ? $element['#children'] : '') . $element['#value'] ."</fieldset>\n";
1129 1130 1131 1132 1133 1134 1135
}

/**
 * Format a radio button.
 *
 * @param $element
 *   An associative array containing the properties of the element.
1136
 *   Properties used: required, return_value, value, attributes, title, description
1137 1138 1139 1140
 * @return
 *   A themed HTML string representing the form item group.
 */
function theme_radio($element) {
1141
  _form_set_class($element, array('form-radio'));
1142
  $output = '<input type="radio" ';
1143
  $output .= 'name="'. $element['#name'] .'" ';
1144 1145 1146 1147 1148
  $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>';
1149
  }
1150 1151 1152

  unset($element['#title']);
  return theme('form_element', $element, $output);
1153 1154 1155 1156 1157 1158 1159
}

/**
 * Format a set of radio buttons.
 *
 * @param $element
 *   An associative array containing the properties of the element.
1160
 *   Properties used: title, value, options, description, required and attributes.
1161 1162 1163 1164
 * @return
 *   A themed HTML string representing the radio button set.
 */
function theme_radios($element) {
1165 1166 1167 1168
  $class = 'form-radios';
  if (isset($element['#attributes']['class'])) {
    $class .= ' '. $element['#attributes']['class'];
  }
1169
  $element['#children'] = '<div class="'. $class .'">'. (!empty($element['#children']) ? $element['#children'] : '') .'</div>';
1170
  if ($element['#title'] || $element['#description']) {
1171 1172
    unset($element['#id']);
    return theme('form_element', $element, $element['#children']);
1173 1174
  }
  else {
1175
    return $element['#children'];
1176 1177 1178
  }
}

1179 1180 1181 1182 1183 1184 1185 1186 1187 1188
/**
 * 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
1189
  return theme('form_element', $element, $element['#children']);
1190 1191
}

1192 1193 1194 1195
/*
 * Expand a password_confirm field into two text boxes.
 */
function expand_password_confirm($element) {
Dries's avatar
Dries committed
1196 1197 1198
  $element['pass1'] =  array(
    '#type' => 'password',
    '#title' => t('Password'),
1199
    '#value' => empty($element['#value']) ? NULL : $element['#value']['pass1'],
1200
    '#required' => $element['#required'],
1201
    '#attributes' => array('class' => 'password-field'),
Dries's avatar
Dries committed
1202 1203 1204 1205
  );
  $element['pass2'] =  array(
    '#type' => 'password',
    '#title' => t('Confirm password'),
1206
    '#value' => empty($element['#value']) ? NULL : $element['#value']['pass2'],
1207
    '#required' => $element['#required'],
1208
    '#attributes' => array('class' => 'password-confirm'),
Dries's avatar
Dries committed
1209
  );
1210
  $element['#element_validate'] = array('password_confirm_validate');
1211 1212
  $element['#tree'] = TRUE;

1213 1214 1215 1216
  if (isset($element['#size'])) {
    $element['pass1']['#size'] = $element['pass2']['#size'] = $element['#size'];
  }

1217 1218 1219
  return $element;
}

1220
/**
1221
 * Validate password_confirm element.
1222
 */
1223
function password_confirm_validate($form, &$form_state) {
1224 1225
  $pass1 = trim($form['pass1']['#value']);
  if (!empty($pass1)) {
1226
    $pass2 = trim($form['pass2']['#value']);
1227
    if ($pass1 != $pass2) {
1228
      form_error($form, t('The specified passwords do not match.'));
1229
    }
1230
  }
1231
  elseif ($form['#required'] && !empty($form['#post'])) {
1232
    form_error($form, t('Password field is required.'));
1233
  }
1234

1235 1236
  // Password field must be converted from a two-element array into a single
  // string regardless of validation results.
1237 1238 1239
  form_set_value($form['pass1'], NULL, $form_state);
  form_set_value($form['pass2'], NULL, $form_state);
  form_set_value($form, $pass1, $form_state);
1240

1241
  return $form;
1242

1243 1244
}

1245
/**
1246
 * Format a date selection element.
1247 1248 1249
 *
 * @param $element
 *   An associative array containing the properties of the element.
1250
 *   Properties used: title, value, options, description, required and attributes.
1251
 * @return
1252
 *   A themed HTML string representing the date selection boxes.
1253 1254
 */
function theme_date($element) {