diff -u b/core/includes/form.inc b/core/includes/form.inc --- b/core/includes/form.inc +++ b/core/includes/form.inc @@ -2241,44 +2241,6 @@ } /** - * Form value callback: Determines the value for a radio form element. - * - * @param $element - * The form element whose value is being populated. - * @param $input - * (optional) The incoming input to populate the form element. If FALSE, the - * element's default value is returned. Defaults to FALSE. - * - * @return - * The data that will appear in the $element_state['values'] collection for - * this element. - */ -function form_type_radios_value(&$element, $input = FALSE) { - // If there is data from a form submission, use that. - if ($input !== FALSE) { - // Check if the user submission had no value for this element. - if (!isset($input)) { - // Flag as a garbage value. If the value is set to NULL and - // #has_garbage_value is not set then the form builder will automatically - // attempt to use default value or '' (an empty string). An empty string - // will fail validation because it is not in the allowed values list. - $element['#has_garbage_value'] = TRUE; - // There was a user submission so validation is a must. This way, the - // required checker will produce an error as necessary. - $element['#needs_validation'] = TRUE; - } - // The value stays the same, but the flags above will ensure it is - // processed properly. - return $input; - } - - // When $input is FALSE, return the default if it exists. - if (isset($element['#default_value'])) { - return $element['#default_value']; - } -} - -/** * Helper function to determine the value for a checkboxes form element. * * @param $element @@ -2319,6 +2281,48 @@ } /** + * Form value callback: Determines the value for a #type radios form element. + * + * @param $element + * The form element whose value is being populated. + * @param $input + * (optional) The incoming input to populate the form element. If FALSE, the + * element's default value is returned. Defaults to FALSE. + * + * @return + * The data that will appear in the $element_state['values'] collection for + * this element. + */ +function form_type_radios_value(&$element, $input = FALSE) { + if ($input !== FALSE) { + // There may not be a submitted value for multiple radio buttons, if none of + // the options was checked by default. If there is no submitted input value + // for this element (NULL), _form_builder_handle_input_element() + // automatically attempts to use the #default_value (if set) or an empty + // string (''). However, an empty string would fail validation in + // _form_validate(), in case it is not contained in the list of allowed + // values in #options. + if (!isset($input)) { + // Signify a garbage value to disable the #default_value handling and take + // over NULL as #value. + $element['#has_garbage_value'] = TRUE; + // There was a user submission so validation is a must. If this element is + // #required, then an appropriate error message will be output. While an + // optional #type 'radios' does not necessarily make sense from a user + // interaction perspective, there may be use-cases for that and it is not + // the job of Form API to artificially limit possibilities. + $element['#needs_validation'] = TRUE; + } + // The value stays the same, but the flags above will ensure it is + // processed properly. + return $input; + } + elseif (isset($element['#default_value'])) { + return $element['#default_value']; + } +} + +/** * Helper function to determine the value for a tableselect form element. * * @param $element @@ -2762,7 +2766,7 @@ $element['#attributes']['type'] = 'radio'; element_set_attributes($element, array('id', 'name', '#return_value' => 'value')); - if (isset($element['#return_value']) && $element['#value'] !== FALSE && $element['#value'] == $element['#return_value']) { + if (isset($element['#return_value']) && $element['#value'] == $element['#return_value']) { $element['#attributes']['checked'] = 'checked'; } _form_set_class($element, array('form-radio')); @@ -2994,9 +2998,7 @@ // The key is sanitized in drupal_attributes() during output from the // theme function. '#return_value' => $key, - // Use default or FALSE. A value of FALSE means that the radio button is - // not 'checked'. - '#default_value' => isset($element['#default_value']) ? $element['#default_value'] : FALSE, + '#default_value' => isset($element['#default_value']) ? $element['#default_value'] : NULL, '#attributes' => $element['#attributes'], '#parents' => $element['#parents'], '#id' => drupal_html_id('edit-' . implode('-', $parents_for_id)), diff -u b/core/modules/field/tests/field.test b/core/modules/field/tests/field.test --- b/core/modules/field/tests/field.test +++ b/core/modules/field/tests/field.test @@ -1460,6 +1460,9 @@ // Test with several multiple fields in a form } + /** + * Tests widget handling of multiple required radios. + */ function testFieldFormMultivalueWithRequiredRadio() { // Create a multivalue test field. $this->field = $this->field_unlimited; @@ -1501,6 +1504,7 @@ $this->assertFieldByName("{$this->field_name}[$langcode][1][value]", '', 'New widget is displayed'); $this->assertNoField("{$this->field_name}[$langcode][2][value]", 'No extraneous widget is displayed'); } + function testFieldFormJSAddMore() { $this->field = $this->field_unlimited; $this->field_name = $this->field['field_name']; diff -u b/core/modules/simpletest/tests/form.test b/core/modules/simpletest/tests/form.test --- b/core/modules/simpletest/tests/form.test +++ b/core/modules/simpletest/tests/form.test @@ -139,7 +139,7 @@ // Attempt to submit the form with no required fields set. $edit = array(); - $this->drupalPost('form-test/validate-required', $edit, t('Submit')); + $this->drupalPost('form-test/validate-required', $edit, 'Submit'); // The only error messages that should appear are the relevant 'required' // messages for each field. @@ -154,7 +154,7 @@ $expected_key = array_search($error[0], $expected); // If the error message is not one of the expected messages, fail. if ($expected_key === FALSE) { - $this->fail(format_string("Invalid error message: @error", array('@error' => $error[0]))); + $this->fail(format_string("Unexpected error message: @error", array('@error' => $error[0]))); } // Remove the expected message from the list once it is found. else { @@ -164,9 +164,19 @@ // Fail if any expected messages were not found. foreach ($expected as $not_found) { - $this->fail(format_string("Error message not shown: @error", array('@error' => $not_found))); + $this->fail(format_string("Found error message: @error", array('@error' => $not_found))); } + // Verify that input elements are still empty. + $this->assertFieldByName('textfield', ''); + $this->assertNoFieldChecked('edit-checkboxes-foo'); + $this->assertNoFieldChecked('edit-checkboxes-bar'); + $this->assertOptionSelected('edit-select', ''); + $this->assertNoFieldChecked('edit-radios-foo'); + $this->assertNoFieldChecked('edit-radios-bar'); + $this->assertNoFieldChecked('edit-radios-optional-foo'); + $this->assertNoFieldChecked('edit-radios-optional-bar'); + // Submit again with required fields set and verify that there are no // error messages. $edit = array( @@ -175,7 +185,7 @@ 'select' => 'foo', 'radios' => 'bar', ); - $this->drupalPost('form-test/validate-required', $edit, t('Submit')); + $this->drupalPost(NULL, $edit, 'Submit'); $this->assertNoFieldByXpath('//div[contains(@class, "error")]', FALSE, 'No error message is displayed when all required fields are filled.'); $this->assertRaw("The form_test_validate_required_form form was submitted successfully.", 'Validation form submitted successfully.'); } diff -u b/core/modules/simpletest/tests/form_test.module b/core/modules/simpletest/tests/form_test.module --- b/core/modules/simpletest/tests/form_test.module +++ b/core/modules/simpletest/tests/form_test.module @@ -27,7 +27,7 @@ 'title' => 'Form #required validation', 'page callback' => 'drupal_get_form', 'page arguments' => array('form_test_validate_required_form'), - 'access arguments' => array('access content'), + 'access callback' => TRUE, 'type' => MENU_CALLBACK, ); $items['form-test/limit-validation-errors'] = array( @@ -353,50 +353,49 @@ } /** - * Form constructor for a form to test the #required property. + * Form constructor to test the #required property. */ function form_test_validate_required_form($form, &$form_state) { + $options = drupal_map_assoc(array('foo', 'bar')); + $form['textfield'] = array( '#type' => 'textfield', - '#title' => t('Textfield'), + '#title' => 'Textfield', '#required' => TRUE, ); $form['checkboxes'] = array( '#type' => 'checkboxes', - '#title' => t('Checkboxes'), - '#options' => drupal_map_assoc(array('foo', 'bar')), + '#title' => 'Checkboxes', + '#options' => $options, '#required' => TRUE, ); $form['select'] = array( '#type' => 'select', - '#title' => t('Select'), - '#options' => drupal_map_assoc(array('foo', 'bar')), + '#title' => 'Select', + '#options' => $options, '#required' => TRUE, ); $form['radios'] = array( '#type' => 'radios', - '#title' => t('Radios'), - '#options' => drupal_map_assoc(array('foo', 'bar')), + '#title' => 'Radios', + '#options' => $options, '#required' => TRUE, ); $form['radios_optional'] = array( '#type' => 'radios', - '#title' => t('Radios (optional)'), - '#options' => drupal_map_assoc(array('foo', 'bar')), + '#title' => 'Radios (optional)', + '#options' => $options, ); $form['actions'] = array('#type' => 'actions'); - $form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Submit')); + $form['actions']['submit'] = array('#type' => 'submit', '#value' => 'Submit'); return $form; } /** - * Form submit handler to test for proper submission. - * - * This message is checked for in the tests to confirm that this for - * successfully submitted without any errors. + * Form submission handler for form_test_validate_required_form(). */ function form_test_validate_required_form_submit($form, &$form_state) { - drupal_set_message("The form_test_validate_required_form form was submitted successfully."); + drupal_set_message('The form_test_validate_required_form form was submitted successfully.'); } /**