form_test.module 28.2 KB
Newer Older
1 2 3 4 5 6 7 8 9
<?php
// $Id$

/**
 * @file
 * Helper module for the form API tests.
 */

/**
10
 * Implements hook_menu().
11 12
 */
function form_test_menu() {
13 14 15 16 17 18 19
  $items['form-test/validate'] = array(
    'title' => 'Form validation handlers test',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('form_test_validate_form'),
    'access arguments' => array('access content'),
    'type' => MENU_CALLBACK,
  );
20 21 22 23 24 25 26
  $items['form-test/limit-validation-errors'] = array(
    'title' => 'Form validation with some error suppression',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('form_test_limit_validation_errors_form'),
    'access arguments' => array('access content'),
    'type' => MENU_CALLBACK,
  );
27

28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
  $items['form_test/tableselect/multiple-true'] = array(
    'title' => 'Tableselect checkboxes test',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('_form_test_tableselect_multiple_true_form'),
    'access arguments' => array('access content'),
    'type' => MENU_CALLBACK,
  );
  $items['form_test/tableselect/multiple-false'] = array(
    'title' => 'Tableselect radio button test',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('_form_test_tableselect_multiple_false_form'),
    'access arguments' => array('access content'),
    'type' => MENU_CALLBACK,
  );
  $items['form_test/tableselect/empty-text'] = array(
    'title' => 'Tableselect empty text test',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('_form_test_tableselect_empty_form'),
    'access arguments' => array('access content'),
    'type' => MENU_CALLBACK,
  );
  $items['form_test/tableselect/advanced-select'] = array(
    'title' => 'Tableselect js_select tests',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('_form_test_tableselect_js_select_form'),
    'access arguments' => array('access content'),
    'type' => MENU_CALLBACK,
  );

57 58 59
  $items['form_test/form-storage'] = array(
    'title' => 'Form storage test',
    'page callback' => 'drupal_get_form',
60
    'page arguments' => array('form_test_storage_form'),
61 62 63 64
    'access arguments' => array('access content'),
    'type' => MENU_CALLBACK,
  );

65 66 67 68 69 70 71 72
  $items['form_test/wrapper-callback'] = array(
    'title' => 'Form wrapper callback test',
    'page callback' => 'form_test_wrapper_callback',
    'page arguments' => array('form_test_wrapper_callback_form'),
    'access arguments' => array('access content'),
    'type' => MENU_CALLBACK,
  );

73 74 75 76 77 78 79 80
  $items['form_test/form-state-values-clean'] = array(
    'title' => 'Form state values clearance test',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('form_test_form_state_values_clean_form'),
    'access arguments' => array('access content'),
    'type' => MENU_CALLBACK,
  );

81 82 83 84 85 86 87 88
  $items['form-test/checkbox'] = array(
    'title' => t('Form test'),
    'page callback' => 'drupal_get_form',
    'page arguments' => array('_form_test_checkbox'),
    'access callback' => TRUE,
    'type' => MENU_CALLBACK,
  );

89 90 91 92 93 94 95 96
  $items['form-test/disabled-elements'] = array(
    'title' => t('Form test'),
    'page callback' => 'drupal_get_form',
    'page arguments' => array('_form_test_disabled_elements'),
    'access callback' => TRUE,
    'type' => MENU_CALLBACK,
  );

97 98 99 100 101 102 103 104
  $items['form-test/form-rebuild-preserve-values'] = array(
    'title' => 'Form values preservation during rebuild test',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('form_test_form_rebuild_preserve_values_form'),
    'access arguments' => array('access content'),
    'type' => MENU_CALLBACK,
  );

105 106 107 108 109 110 111 112
  $items['form_test/form-labels'] = array(
    'title' => 'Form label test',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('form_label_test_form'),
    'access arguments' => array('access content'),
    'type' => MENU_CALLBACK,
  );

113 114 115 116 117 118 119 120
  $items['form-test/state-persist'] = array(
    'title' => 'Form state persistence without storage',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('form_test_state_persist'),
    'access callback' => TRUE,
    'type' => MENU_CALLBACK,
  );

121 122 123
  return $items;
}

124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
/**
 * Form builder for testing drupal_validate_form().
 *
 * Serves for testing form processing and alterations by form validation
 * handlers, especially for the case of a validation error:
 * - form_set_value() should be able to alter submitted values in
 *   $form_state['values'] without affecting the form element.
 * - #element_validate handlers should be able to alter the $element in the form
 *   structure and the alterations should be contained in the rebuilt form.
 * - #validate handlers should be able to alter the $form and the alterations
 *   should be contained in the rebuilt form.
 */
function form_test_validate_form($form, &$form_state) {
  $form['name'] = array(
    '#type' => 'textfield',
    '#title' => 'Name',
    '#default_value' => '',
    '#element_validate' => array('form_test_element_validate_name'),
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => 'Save',
  );
147 148 149 150 151

  // To simplify this test, enable form caching and use form storage to
  // remember our alteration.
  $form_state['cache'] = TRUE;

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 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
  return $form;
}

/**
 * Form element validation handler for 'name' in form_test_validate_form().
 */
function form_test_element_validate_name(&$element, &$form_state) {
  $triggered = FALSE;
  if ($form_state['values']['name'] == 'element_validate') {
    // Alter the form element.
    $element['#value'] = '#value changed by #element_validate';
    // Alter the submitted value in $form_state.
    form_set_value($element, 'value changed by form_set_value() in #element_validate', $form_state);

    $triggered = TRUE;
  }
  if ($form_state['values']['name'] == 'element_validate_access') {
    $form_state['storage']['form_test_name'] = $form_state['values']['name'];
    // Alter the form element.
    $element['#access'] = FALSE;

    $triggered = TRUE;
  }
  elseif (!empty($form_state['storage']['form_test_name'])) {
    // To simplify this test, just take over the element's value into $form_state.
    form_set_value($element, $form_state['storage']['form_test_name'], $form_state);

    $triggered = TRUE;
  }

  if ($triggered) {
    // Output the element's value from $form_state.
    drupal_set_message(t('@label value: @value', array('@label' => $element['#title'], '@value' => $form_state['values']['name'])));

    // Trigger a form validation error to see our changes.
    form_set_error('');
  }
}

/**
 * Form validation handler for form_test_validate_form().
 */
function form_test_validate_form_validate(&$form, &$form_state) {
  if ($form_state['values']['name'] == 'validate') {
    // Alter the form element.
    $form['name']['#value'] = '#value changed by #validate';
    // Alter the submitted value in $form_state.
    form_set_value($form['name'], 'value changed by form_set_value() in #validate', $form_state);
    // Output the element's value from $form_state.
    drupal_set_message(t('@label value: @value', array('@label' => $form['name']['#title'], '@value' => $form_state['values']['name'])));

    // Trigger a form validation error to see our changes.
    form_set_error('');
  }
}

208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242
/**
 * Builds a simple form with a button triggering partial validation.
 */
function form_test_limit_validation_errors_form($form, &$form_state) {
  $form['title'] = array(
    '#type' => 'textfield',
    '#title' => 'Title',
    '#required' => TRUE,
  );
  $form['test'] = array(
    '#type' => 'textfield',
    '#element_validate' => array('form_test_limit_validation_errors_element_validate_test'),
  );
  $form['actions']['partial'] = array(
    '#type' => 'submit',
    '#limit_validation_errors' => array(array('test')),
    '#submit' => array(),
    '#value' => t('Partial validate'),
  );
  $form['actions']['full'] = array(
    '#type' => 'submit',
    '#value' => t('Full validate'),
  );
  return $form;
}

/**
 * Form element validation handler for the 'test' element.
 */
function form_test_limit_validation_errors_element_validate_test(&$element, &$form_state) {
  if ($element['#value'] == 'invalid') {
    form_error($element, 'Test element is invalid');
  }
}

243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288
/**
 * Create a header and options array. Helper function for callbacks.
 */
function _form_test_tableselect_get_data() {
  $header = array(
    'one' => t('One'),
    'two' => t('Two'),
    'three' => t('Three'),
    'four' => t('Four'),
  );

  $options['row1'] = array(
    'one' => 'row1col1',
    'two' => t('row1col2'),
    'three' => t('row1col3'),
    'four' => t('row1col4'),
  );

  $options['row2'] = array(
    'one' => 'row2col1',
    'two' => t('row2col2'),
    'three' => t('row2col3'),
    'four' => t('row2col4'),
  );

  $options['row3'] = array(
    'one' => 'row3col1',
    'two' => t('row3col2'),
    'three' => t('row3col3'),
    'four' => t('row3col4'),
  );

  return array($header, $options);
}

/**
 * Build a form to test the tableselect element.
 *
 * @param $form_state
 *   The form_state
 * @param $element_properties
 *   An array of element properties for the tableselect element.
 *
 * @return
 *   A form with a tableselect element and a submit button.
 */
289
function _form_test_tableselect_form_builder($form, $form_state, $element_properties) {
290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312
  list($header, $options) = _form_test_tableselect_get_data();

  $form['tableselect'] = $element_properties;

  $form['tableselect'] += array(
    '#type' => 'tableselect',
    '#header' => $header,
    '#options' => $options,
    '#multiple' => FALSE,
    '#empty' => t('Empty text.'),
  );

  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Submit'),
  );

  return $form;
}

/**
 * Test the tableselect #multiple = TRUE functionality.
 */
313 314
function _form_test_tableselect_multiple_true_form($form, $form_state) {
  return _form_test_tableselect_form_builder($form, $form_state, array('#multiple' => TRUE));
315 316 317 318 319 320 321 322 323 324 325 326 327 328 329
}

/**
 * Process the tableselect #multiple = TRUE submitted values.
 */
function _form_test_tableselect_multiple_true_form_submit($form, &$form_state) {
  $selected = $form_state['values']['tableselect'];
  foreach ($selected as $key => $value) {
    drupal_set_message(t('Submitted: @key = @value', array('@key' => $key, '@value' => $value)));
  }
}

/**
 * Test the tableselect #multiple = FALSE functionality.
 */
330 331
function _form_test_tableselect_multiple_false_form($form, $form_state) {
  return _form_test_tableselect_form_builder($form, $form_state, array('#multiple' => FALSE));
332 333 334 335 336 337 338 339 340 341 342 343
}

/**
 * Process the tableselect #multiple = FALSE submitted values.
 */
function _form_test_tableselect_multiple_false_form_submit($form, &$form_state) {
  drupal_set_message(t('Submitted: @value', array('@value' => $form_state['values']['tableselect'])));
}

/**
 * Test functionality of the tableselect #empty property.
 */
344 345
function _form_test_tableselect_empty_form($form, $form_state) {
  return _form_test_tableselect_form_builder($form, $form_state, array('#options' => array()));
346 347 348 349 350
}

/**
 * Test functionality of the tableselect #js_select property.
 */
351
function _form_test_tableselect_js_select_form($form, $form_state, $action) {
352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369
  switch ($action) {
    case 'multiple-true-default':
      $options = array('#multiple' => TRUE);
      break;

    case 'multiple-false-default':
      $options = array('#multiple' => FALSE);
      break;

    case 'multiple-true-no-advanced-select':
      $options = array('#multiple' => TRUE, '#js_select' => FALSE);
      break;

    case 'multiple-false-advanced-select':
      $options = array('#multiple' => FALSE, '#js_select' => TRUE);
      break;
  }

370
  return _form_test_tableselect_form_builder($form, $form_state, $options);
371
}
372

373 374 375 376 377 378
/**
 * A multistep form for testing the form storage.
 *
 * It uses two steps for editing a virtual "thing". Any changes to it are saved
 * in the form storage and have to be present during any step. By setting the
 * request parameter "cache" the form can be tested with caching enabled, as
379
 * it would be the case, if the form would contain some #ajax callbacks.
380
 *
381
 * @see form_test_storage_form_submit()
382
 */
383 384 385 386
function form_test_storage_form($form, &$form_state) {
  if ($form_state['rebuild']) {
    $form_state['input'] = array();
  }
387
  // Initialize
388
  if (empty($form_state['storage'])) {
389 390 391 392 393 394 395 396 397 398 399
    if (empty($form_state['input'])) {
      $_SESSION['constructions'] = 0;
    }
    // Put the initial thing into the storage
    $form_state['storage'] = array(
      'thing' => array(
        'title' => 'none',
        'value' => '',
      ),
    );
  }
400
  // Count how often the form is constructed.
401
  $_SESSION['constructions']++;
402
  drupal_set_message("Form constructions: ". $_SESSION['constructions']);
403

404 405 406 407 408 409 410 411 412 413 414 415
  $form['title'] = array(
    '#type' => 'textfield',
    '#title' => 'Title',
    '#default_value' => $form_state['storage']['thing']['title'],
    '#required' => TRUE,
  );
  $form['value'] = array(
    '#type' => 'textfield',
    '#title' => 'Value',
    '#default_value' => $form_state['storage']['thing']['value'],
    '#element_validate' => array('form_test_storage_element_validate_value_cached'),
  );
416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435
  $form['button'] = array(
    '#type' => 'button',
    '#value' => 'Reload',
    // Reload the form (don't rebuild), thus we start at the initial step again.
  );
  $form['continue_button'] = array(
    '#type' => 'button',
    '#value' => 'Reset',
    // Rebuilds the form without keeping the values.
    '#validate' => array('form_storage_test_form_continue_validate'),
  );
  $form['continue_submit'] = array(
    '#type' => 'submit',
    '#value' => 'Continue submit',
    '#submit' => array('form_storage_test_form_continue_submit'),
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => 'Save',
  );
436 437 438 439

  if (isset($_REQUEST['cache'])) {
    // Manually activate caching, so we can test that the storage keeps working
    // when it's enabled.
440
    $form_state['cache'] = TRUE;
441 442 443 444 445 446
  }

  return $form;
}

/**
447 448 449
 * Form element validation handler for 'value' element in form_test_storage_form().
 *
 * Tests updating of cached form storage during validation.
450
 */
451
function form_test_storage_element_validate_value_cached($element, &$form_state) {
452 453 454 455
  // If caching is enabled and we receive a certain value, change the storage.
  // This presumes that another submitted form value triggers a validation error
  // elsewhere in the form. Form API should still update the cached form storage
  // though.
456
  if (isset($_REQUEST['cache']) && $form_state['values']['value'] == 'change_title') {
457
    $form_state['storage']['thing']['changed'] = TRUE;
458 459 460 461
  }
}

/**
462
 * Form submit handler to continue multi-step form.
463
 */
464 465 466 467 468 469 470 471 472 473 474 475
function form_storage_test_form_continue_submit($form, &$form_state) {
  $form_state['storage']['thing']['title'] = $form_state['values']['title'];
  $form_state['storage']['thing']['value'] = $form_state['values']['value'];
  $form_state['rebuild'] = TRUE;
}

/**
 * Form validation handler, which doesn't preserve the values but rebuilds the
 * form. We cannot use a submit handler here, as buttons of type button don't
 * submit the form.
 */
function form_storage_test_form_continue_validate($form, &$form_state) {
476
  $form_state['rebuild'] = TRUE;
477 478 479 480 481 482 483
}

/**
 * Form submit handler to finish multi-step form.
 */
function form_test_storage_form_submit($form, &$form_state) {
  drupal_set_message("Title: ". check_plain($form_state['values']['title']));
484
  drupal_set_message("Form constructions: ". $_SESSION['constructions']);
485 486 487 488
  if (isset($form_state['storage']['thing']['changed'])) {
    drupal_set_message("The thing has been changed.");
  }
  $form_state['redirect'] = 'node';
489
}
490

491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546
 /**
 * A form for testing form labels and required marks.
 */
function form_label_test_form(&$form_state) {
  $form['form_checkboxes_test'] = array(
    '#type' => 'checkboxes',
    '#title' => t('Checkboxes test'),
    '#options' => array(
      'first-checkbox' => t('First checkbox'),
      'second-checkbox' => t('Second checkbox'),
      'third-checkbox' => t('Third checkbox'),
    ),
  );
  $form['form_radios_test'] = array(
    '#type' => 'radios',
    '#title' => t('Radios test'),
    '#options' => array(
      'first-radio' => t('First radio'),
      'second-radio' => t('Second radio'),
      'third-radio' => t('Third radio'),
    ),
  );
  $form['form_checkbox_test'] = array(
    '#type' => 'checkbox',
    '#title' => t('Checkbox test'),
  );
  $form['form_textfield_test_title_and_required'] = array(
    '#type' => 'textfield',
    '#title' => t('Textfield test for required with title'),
    '#required' => TRUE,
  );
  $form['form_textfield_test_no_title_required'] = array(
    '#type' => 'textfield',
    // We use an empty title, since not setting #title supresses the label
    // and required marker.
    '#title' => '',
    '#required' => TRUE,
  );
  $form['form_textfield_test_title'] = array(
    '#type' => 'textfield',
    '#title' => t('Textfield test for title only'),
    // Not required.
  );
  $form['form_textfield_test_title_after'] = array(
    '#type' => 'textfield',
    '#title' => t('Textfield test for title after element'),
    '#title_display' => 'after',
  );
  // Textfield test for title set not to display
  $form['form_textfield_test_title_no_show'] = array(
    '#type' => 'textfield',
  );

  return $form;
}

547 548 549 550 551
/**
 * Menu callback; Invokes a form builder function with a wrapper callback.
 */
function form_test_wrapper_callback($form_id) {
  $form_state = array(
552
    'build_info' => array('args' => array()),
553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573
    'wrapper_callback' => 'form_test_wrapper_callback_wrapper',
  );
  return drupal_build_form($form_id, $form_state);
}

/**
 * Form wrapper for form_test_wrapper_callback_form().
 */
function form_test_wrapper_callback_wrapper($form, &$form_state) {
  $form['wrapper'] = array('#markup' => 'Form wrapper callback element output.');
  return $form;
}

/**
 * Form builder for form wrapper callback test.
 */
function form_test_wrapper_callback_form($form, &$form_state) {
  $form['builder'] = array('#markup' => 'Form builder element output.');
  return $form;
}

574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597
/**
 * Form builder for form_state_values_clean() test.
 */
function form_test_form_state_values_clean_form($form, &$form_state) {
  // Build an example form containing multiple submit and button elements; not
  // only on the top-level.
  $form = array('#tree' => TRUE);
  $form['foo'] = array('#type' => 'submit', '#value' => t('Submit'));
  $form['bar'] = array('#type' => 'submit', '#value' => t('Submit'));
  $form['beer'] = array('#type' => 'value', '#value' => 1000);
  $form['baz']['foo'] = array('#type' => 'button', '#value' => t('Submit'));
  $form['baz']['baz'] = array('#type' => 'submit', '#value' => t('Submit'));
  $form['baz']['beer'] = array('#type' => 'value', '#value' => 2000);
  return $form;
}

/**
 * Form submit handler for form_state_values_clean() test form.
 */
function form_test_form_state_values_clean_form_submit($form, &$form_state) {
  form_state_values_clean($form_state);
  drupal_json_output($form_state['values']);
  exit;
}
598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672

/**
 * Build a form to test a checkbox.
 */
function _form_test_checkbox($form, &$form_state) {
  // A required checkbox.
  $form['required_checkbox'] = array(
    '#type' => 'checkbox',
    '#required' => TRUE,
    '#title' => 'required_checkbox',
  );

  // A disabled checkbox should get its default value back.
  $form['disabled_checkbox_on'] = array(
    '#type' => 'checkbox',
    '#disabled' => TRUE,
    '#return_value' => 'disabled_checkbox_on',
    '#default_value' => 'disabled_checkbox_on',
    '#title' => 'disabled_checkbox_on',
  );
  $form['disabled_checkbox_off'] = array(
    '#type' => 'checkbox',
    '#disabled' => TRUE,
    '#return_value' => 'disabled_checkbox_off',
    '#default_value' => NULL,
    '#title' => 'disabled_checkbox_off',
  );

  // A checkbox is active when #default_value == #return_value.
  $form['checkbox_on'] = array(
    '#type' => 'checkbox',
    '#return_value' => 'checkbox_on',
    '#default_value' => 'checkbox_on',
    '#title' => 'checkbox_on',
  );

  // But inactive in any other case.
  $form['checkbox_off'] = array(
    '#type' => 'checkbox',
    '#return_value' => 'checkbox_off',
    '#default_value' => 'checkbox_on',
    '#title' => 'checkbox_off',
  );

  // Checkboxes with a #return_value of '0' are supported.
  $form['zero_checkbox_on'] = array(
    '#type' => 'checkbox',
    '#return_value' => '0',
    '#default_value' => '0',
    '#title' => 'zero_checkbox_on',
  );

  // In that case, passing a #default_value != '0' means that the checkbox is off.
  $form['zero_checkbox_off'] = array(
    '#type' => 'checkbox',
    '#return_value' => '0',
    '#default_value' => '1',
    '#title' => 'zero_checkbox_off',
  );

  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Submit')
  );

  return $form;
}

/**
 * Return the form values via JSON.
 */
function _form_test_checkbox_submit($form, &$form_state) {
  drupal_json_output($form_state['values']);
  exit();
}
673

674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 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
/**
 * Build a form to test disabled elements.
 */
function _form_test_disabled_elements($form, &$form_state) {
  // Elements that take a simple default value.
  foreach (array('textfield', 'textarea', 'hidden') as $type) {
    $form[$type] = array(
      '#type' => $type,
      '#title' => $type,
      '#default_value' => $type,
      '#disabled' => TRUE,
    );
  }

  // Multiple values option elements.
  foreach (array('checkboxes', 'select') as $type) {
    $form[$type . '_multiple'] = array(
      '#type' => $type,
      '#title' => $type . ' (multiple)',
      '#options' => array(
        'test_1' => 'Test 1',
        'test_2' => 'Test 2',
      ),
      '#multiple' => TRUE,
      '#default_value' => array('test_2' => 'test_2'),
      '#disabled' => TRUE,
    );
  }

  // Single values option elements.
  foreach (array('radios', 'select') as $type) {
    $form[$type . '_single'] = array(
      '#type' => $type,
      '#title' => $type . ' (single)',
      '#options' => array(
        'test_1' => 'Test 1',
        'test_2' => 'Test 2',
      ),
      '#multiple' => FALSE,
      '#default_value' => 'test_2',
      '#disabled' => TRUE,
    );
  }

  // Checkbox and radio.
  foreach (array('checkbox', 'radio') as $type) {
    $form[$type . '_unchecked'] = array(
      '#type' => $type,
      '#title' => $type . ' (unchecked)',
      '#return_value' => 1,
      '#default_value' => 0,
      '#disabled' => TRUE,
    );
    $form[$type . '_checked'] = array(
      '#type' => $type,
      '#title' => $type . ' (checked)',
      '#return_value' => 1,
      '#default_value' => 1,
      '#disabled' => TRUE,
    );
  }

  // Weight.
  $form['weight'] = array(
    '#type' => 'weight',
    '#title' => 'weight',
    '#default_value' => 10,
    '#disabled' => TRUE,
  );

  // Date.
  $form['date'] = array(
    '#type' => 'date',
    '#title' => 'date',
    '#disabled' => TRUE,
    '#default_value' => array(
      'day' => 19,
      'month' => 11,
      'year' => 1978,
    ),
  );

  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Submit'),
  );

  return $form;
}

/**
 * Return the form values via JSON.
 */
function _form_test_disabled_elements_submit($form, &$form_state) {
  drupal_json_output($form_state['values']);
  exit();
}

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 838 839 840 841 842 843 844 845 846 847 848
/**
 * Form builder for testing preservation of values during a rebuild.
 */
function form_test_form_rebuild_preserve_values_form($form, &$form_state) {
  // Start the form with two checkboxes, to test different defaults, and a
  // textfield, to test more than one element type.
  $form = array(
    'checkbox_1_default_off' => array(
      '#type' => 'checkbox',
      '#title' => t('This checkbox defaults to unchecked.'),
      '#default_value' => FALSE,
    ),
    'checkbox_1_default_on' => array(
      '#type' => 'checkbox',
      '#title' => t('This checkbox defaults to checked.'),
      '#default_value' => TRUE,
    ),
    'text_1' => array(
      '#type' => 'textfield',
      '#title' => t('This textfield has a non-empty default value.'),
      '#default_value' => 'DEFAULT 1',
    ),
  );
  // Provide an 'add more' button that rebuilds the form with an additional two
  // checkboxes and a textfield. The test is to make sure that the rebuild
  // triggered by this button preserves the user input values for the initial
  // elements and initializes the new elements with the correct default values.
  if (empty($form_state['storage']['add_more'])) {
    $form['add_more'] = array(
      '#type' => 'submit',
      '#value' => 'Add more',
      '#submit' => array('form_test_form_rebuild_preserve_values_form_add_more'),
    );
  }
  else {
    $form += array(
      'checkbox_2_default_off' => array(
        '#type' => 'checkbox',
        '#title' => t('This checkbox defaults to unchecked.'),
        '#default_value' => FALSE,
      ),
      'checkbox_2_default_on' => array(
        '#type' => 'checkbox',
        '#title' => t('This checkbox defaults to checked.'),
        '#default_value' => TRUE,
      ),
      'text_2' => array(
        '#type' => 'textfield',
        '#title' => t('This textfield has a non-empty default value.'),
        '#default_value' => 'DEFAULT 2',
      ),
    );
  }
  // A submit button that finishes the form workflow (does not rebuild).
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => 'Submit',
  );
  return $form;
}

/**
 * Button submit handler for form_test_form_rebuild_preserve_values_form().
 */
function form_test_form_rebuild_preserve_values_form_add_more($form, &$form_state) {
  // Rebuild, to test preservation of input values.
  $form_state['storage']['add_more'] = TRUE;
  $form_state['rebuild'] = TRUE;
}

/**
 * Form submit handler for form_test_form_rebuild_preserve_values_form().
 */
function form_test_form_rebuild_preserve_values_form_submit($form, &$form_state) {
  // Finish the workflow. Do not rebuild.
  drupal_set_message(t('Form values: %values', array('%values' => var_export($form_state['values'], TRUE))));
}
849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870

/**
 * Form constructor for testing form state persistence.
 */
function form_test_state_persist($form, &$form_state) {
  $form['title'] = array(
    '#type' => 'textfield',
    '#title' => 'title',
    '#default_value' => 'DEFAULT',
    '#required' => TRUE,
  );
  $form_state['value'] = 'State persisted.';

  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Submit'),
  );
  return $form;
}

/**
 * Submit handler.
871
 *
872 873 874 875 876 877 878 879 880
 * @see form_test_state_persist()
 */
function form_test_state_persist_submit($form, &$form_state) {
  drupal_set_message($form_state['value']);
  $form_state['rebuild'] = TRUE;
}

/**
 * Implements hook_form_FORM_ID_alter().
881
 *
882 883 884 885 886 887 888 889 890
 * @see form_test_state_persist()
 */
function form_test_form_form_test_state_persist_alter(&$form, &$form_state) {
  // Simulate a form alter implementation inserting form elements that enable
  // caching of the form, e.g. elements having #ajax.
  if (!empty($_REQUEST['cache'])) {
    $form_state['cache'] = TRUE;
  }
}
891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924

/**
 * Form builder to test programmatic form submissions.
 */
function form_test_programmatic_form($form, &$form_state) {
  $form['submitted_field'] = array(
    '#title' => 'Submitted',
    '#type' => 'textfield',
  );

  return $form;
}

/**
 * Form validation handler for programmatic form submissions.
 *
 * To test that the validation handler is correctly executed, the field value is
 * explicitly required here.
 */
function form_test_programmatic_form_validate($form, &$form_state) {
  if (empty($form_state['values']['submitted_field'])) {
    form_set_error('submitted_field', t('Submitted field is required.'));
  }
}

/**
 * Form submit handler for programmatic form submissions.
 *
 * To test that the submission handler is correctly executed, we store the
 * submitted value in a place we can access from the caller context.
 */
function form_test_programmatic_form_submit($form, &$form_state) {
  $form_state['storage']['programmatic_form_submit'] = $form_state['values']['submitted_field'];
}
925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951


/**
 * Implements hook_form_FORM_ID_alter() for the registration form.
 */
function form_test_form_user_register_form_alter(&$form, &$form_state) {
  $form['test_rebuild'] = array(
    '#type' => 'submit',
    '#value' => t('Rebuild'),
    '#submit' => array('form_test_user_register_form_rebuild'),
  );
  // If requested, add the test field by attaching the node page form.
  if (!empty($_REQUEST['field'])) {
    $node = (object)array('type' => 'page');
    field_attach_form('node', $node, $form, $form_state);
    // The form API requires the builder function to set rebuilding, so do so.
    $form['#builder_function'] = 'form_test_user_register_form_rebuild';
  }
}

/**
 * Submit callback that just lets the form rebuild.
 */
function form_test_user_register_form_rebuild($form, &$form_state) {
  drupal_set_message('Form rebuilt.');
  $form_state['rebuild'] = TRUE;
}