Index: includes/form.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/form.inc,v retrieving revision 1.507 diff -u -p -r1.507 form.inc --- includes/form.inc 28 Oct 2010 02:20:14 -0000 1.507 +++ includes/form.inc 7 Nov 2010 18:24:56 -0000 @@ -2786,22 +2786,28 @@ function weight_value(&$form) { */ function form_process_radios($element) { if (count($element['#options']) > 0) { + $weight = 0; foreach ($element['#options'] as $key => $choice) { - if (!isset($element[$key])) { - // Generate the parents as the autogenerator does, so we will have a - // unique id for each radio button. - $parents_for_id = array_merge($element['#parents'], array($key)); - $element[$key] = array( - '#type' => 'radio', - '#title' => $choice, - '#return_value' => check_plain($key), - '#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)), - '#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL, - ); - } + // Maintain order of options as defined in #options, in case the element + // defines custom option sub-elements, but does not define all option + // sub-elements. + $weight += 0.001; + + $element += array($key => array()); + // Generate the parents as the autogenerator does, so we will have a + // unique id for each radio button. + $parents_for_id = array_merge($element['#parents'], array($key)); + $element[$key] += array( + '#type' => 'radio', + '#title' => $choice, + '#return_value' => check_plain($key), + '#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)), + '#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL, + '#weight' => $weight, + ); } } return $element; @@ -2911,25 +2917,30 @@ function form_process_checkboxes($elemen if (!isset($element['#default_value']) || $element['#default_value'] == 0) { $element['#default_value'] = array(); } + $weight = 0; foreach ($element['#options'] as $key => $choice) { - if (!isset($element[$key])) { - // Integer 0 is not a valid #return_value, so use '0' instead. - // @see form_type_checkbox_value(). - // @todo For Drupal 8, cast all integer keys to strings for consistency - // with form_process_radios(). - if ($key === 0) { - $key = '0'; - } - $element[$key] = array( - '#type' => 'checkbox', - '#processed' => TRUE, - '#title' => $choice, - '#return_value' => $key, - '#default_value' => isset($value[$key]) ? $key : NULL, - '#attributes' => $element['#attributes'], - '#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL, - ); + // Integer 0 is not a valid #return_value, so use '0' instead. + // @see form_type_checkbox_value(). + // @todo For Drupal 8, cast all integer keys to strings for consistency + // with form_process_radios(). + if ($key === 0) { + $key = '0'; } + // Maintain order of options as defined in #options, in case the element + // defines custom option sub-elements, but does not define all option + // sub-elements. + $weight += 0.001; + + $element += array($key => array()); + $element[$key] += array( + '#type' => 'checkbox', + '#title' => $choice, + '#return_value' => $key, + '#default_value' => isset($value[$key]) ? $key : NULL, + '#attributes' => $element['#attributes'], + '#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL, + '#weight' => $weight, + ); } } return $element; Index: modules/comment/comment.module =================================================================== RCS file: /cvs/drupal/drupal/modules/comment/comment.module,v retrieving revision 1.912 diff -u -p -r1.912 comment.module --- modules/comment/comment.module 6 Nov 2010 23:24:33 -0000 1.912 +++ modules/comment/comment.module 7 Nov 2010 18:26:16 -0000 @@ -1190,28 +1190,13 @@ function comment_form_node_form_alter(&$ COMMENT_NODE_HIDDEN => t('Hidden'), ), COMMENT_NODE_OPEN => array( - '#type' => 'radio', - '#title' => t('Open'), '#description' => t('Users with the "Post comments" permission can post comments.'), - '#return_value' => COMMENT_NODE_OPEN, - '#default_value' => $comment_settings, - '#parents' => array('comment'), ), COMMENT_NODE_CLOSED => array( - '#type' => 'radio', - '#title' => t('Closed'), '#description' => t('Users cannot post comments, but existing comments will be displayed.'), - '#return_value' => COMMENT_NODE_CLOSED, - '#default_value' => $comment_settings, - '#parents' => array('comment'), ), COMMENT_NODE_HIDDEN => array( - '#type' => 'radio', - '#title' => t('Hidden'), '#description' => t('Comments are hidden from view.'), - '#return_value' => COMMENT_NODE_HIDDEN, - '#default_value' => $comment_settings, - '#parents' => array('comment'), ), ); // If the node doesn't have any comments, the "hidden" option makes no Index: modules/simpletest/tests/form.test =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/tests/form.test,v retrieving revision 1.73 diff -u -p -r1.73 form.test --- modules/simpletest/tests/form.test 28 Oct 2010 02:20:14 -0000 1.73 +++ modules/simpletest/tests/form.test 7 Nov 2010 18:24:56 -0000 @@ -370,6 +370,66 @@ class FormsTestCase extends DrupalWebTes } /** + * Tests building and processing of core form elements. + */ +class FormElementTestCase extends DrupalWebTestCase { + protected $profile = 'testing'; + + public static function getInfo() { + return array( + 'name' => 'Element processing', + 'description' => 'Tests building and processing of core form elements.', + 'group' => 'Form API', + ); + } + + function setUp() { + parent::setUp(array('form_test')); + } + + /** + * Tests expansion of #options for #type checkboxes and radios. + */ + function testOptions() { + $this->drupalGet('form-test/checkboxes-radios'); + + // Verify that all options appear in their defined order. + foreach (array('checkbox', 'radio') as $type) { + $elements = $this->xpath('//input[@type=:type]', array(':type' => $type)); + $expected_values = array('0', 'foo', '1', 'bar'); + foreach ($elements as $element) { + $expected = array_shift($expected_values); + $this->assertIdentical((string) $element['value'], $expected); + } + } + + // Enable customized option sub-elements. + $this->drupalGet('form-test/checkboxes-radios/customize'); + + // Verify that all options appear in their defined order, taking a custom + // #weight into account. + foreach (array('checkbox', 'radio') as $type) { + $elements = $this->xpath('//input[@type=:type]', array(':type' => $type)); + $expected_values = array('0', 'foo', 'bar', '1'); + foreach ($elements as $element) { + $expected = array_shift($expected_values); + $this->assertIdentical((string) $element['value'], $expected); + } + } + // Verify that custom #description properties are output. + foreach (array('checkboxes', 'radios') as $type) { + $elements = $this->xpath('//input[@name=:name]/following-sibling::div[@class=:class]', array( + ':name' => $type . '[foo]', + ':class' => 'description', + )); + $this->assertTrue(count($elements), t('Custom %type option description found.', array( + '%type' => $type, + ))); + } + } +} + +/** * Test form alter hooks. */ class FormAlterTestCase extends DrupalWebTestCase { Index: modules/simpletest/tests/form_test.module =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/tests/form_test.module,v retrieving revision 1.53 diff -u -p -r1.53 form_test.module --- modules/simpletest/tests/form_test.module 28 Oct 2010 02:20:14 -0000 1.53 +++ modules/simpletest/tests/form_test.module 7 Nov 2010 18:24:56 -0000 @@ -98,6 +98,12 @@ function form_test_menu() { 'page arguments' => array('form_test_select'), 'access callback' => TRUE, ); + $items['form-test/checkboxes-radios'] = array( + 'title' => t('Checkboxes, Radios'), + 'page callback' => 'drupal_get_form', + 'page arguments' => array('form_test_checkboxes_radios'), + 'access callback' => TRUE, + ); $items['form-test/disabled-elements'] = array( 'title' => t('Form test'), @@ -187,6 +193,14 @@ function form_test_menu() { } /** + * Form submit handler to return form values as JSON. + */ +function _form_test_submit_values_json($form, &$form_state) { + drupal_json_output($form_state['values']); + drupal_exit(); +} + +/** * Form builder for testing hook_form_alter() and hook_form_FORM_ID_alter(). */ function form_test_alter_form($form, &$form_state) { @@ -893,6 +907,63 @@ function form_test_select_submit($form, } /** + * Form constructor to test expansion of #type checkboxes and radios. + */ +function form_test_checkboxes_radios($form, &$form_state, $customize = FALSE) { + $form['#submit'] = array('_form_test_submit_values_json'); + + // Expand #type checkboxes, setting custom element properties for some but not + // all options. + $form['checkboxes'] = array( + '#type' => 'checkboxes', + '#title' => 'Checkboxes', + '#options' => array( + 0 => 'Zero', + 'foo' => 'Foo', + 1 => 'One', + 'bar' => 'Bar', + ), + ); + if ($customize) { + $form['checkboxes'] += array( + 'foo' => array( + '#description' => 'Enable to foo.', + ), + 1 => array( + '#weight' => 10, + ), + ); + } + + // Expand #type radios, setting custom element properties for some but not + // all options. + $form['radios'] = array( + '#type' => 'radios', + '#title' => 'Radios', + '#options' => array( + 0 => 'Zero', + 'foo' => 'Foo', + 1 => 'One', + 'bar' => 'Bar', + ), + ); + if ($customize) { + $form['radios'] += array( + 'foo' => array( + '#description' => 'Enable to foo.', + ), + 1 => array( + '#weight' => 10, + ), + ); + } + + $form['submit'] = array('#type' => 'submit', '#value' => 'Submit'); + + return $form; +} + +/** * Build a form to test disabled elements. */ function _form_test_disabled_elements($form, &$form_state) {