Index: includes/form.inc =================================================================== RCS file: /cvs/drupal/drupal/includes/form.inc,v retrieving revision 1.426 diff -u -9 -p -r1.426 form.inc --- includes/form.inc 8 Jan 2010 06:36:34 -0000 1.426 +++ includes/form.inc 10 Jan 2010 13:09:31 -0000 @@ -1620,24 +1620,24 @@ function form_type_image_button_value($f * for this element. Return nothing to use the default. */ function form_type_checkbox_value($element, $input = FALSE) { if ($input !== FALSE) { if (empty($element['#disabled'])) { // Successful (checked) checkboxes are present with a value (possibly '0'). // http://www.w3.org/TR/html401/interact/forms.html#successful-controls // For an unchecked checkbox, we return numeric 0, so we can explicitly // test for a value different than string '0'. - return isset($input) ? $element['#return_value'] : 0; + return isset($input) ? (string)$element['#return_value'] : 0; } else { // Disabled form controls are not submitted by the browser. Ignore any // submitted value and always return default. - return $element['#default_value']; + return (string)$element['#default_value']; } } } /** * Helper function to determine the value for a checkboxes form element. * * @param $element * The form element whose value is being populated. @@ -1964,18 +1964,28 @@ function theme_fieldset($variables) { $output .= $element['#children']; if (isset($element['#value'])) { $output .= $element['#value']; } $output .= "\n"; return $output; } /** + * Initialize #checked on radio and checkbox elements. + */ +function form_process_checked($element) { + // Cast operands to strings, because 0 == 'any string'. An unchecked element + // has #value of numeric 0. + $element['#checked'] = $element['#value'] !== 0 && (string)$element['#value'] == (string)$element['#return_value']; + return $element; +} + +/** * Theme a radio button form element. * * @param $variables * An associative array containing: * - element: An associative array containing the properties of the element. * Properties used: #required, #return_value, #value, #attributes, #title, * #description * * @return @@ -1983,20 +1993,22 @@ function theme_fieldset($variables) { * * @ingroup themeable */ function theme_radio($variables) { $element = $variables['element']; _form_set_class($element, array('form-radio')); $output = ''; return $output; } /** * Theme a set of radio button form elements. * * @param $variables @@ -2190,19 +2202,19 @@ function form_process_radios($element) { if (count($element['#options']) > 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), + '#return_value' => (string)$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, ); } } } @@ -2329,21 +2341,22 @@ function theme_text_format_wrapper($vari */ function theme_checkbox($variables) { $element = $variables['element']; $t = get_t(); _form_set_class($element, array('form-checkbox')); $checkbox = ''; return $checkbox; } /** * Theme a set of checkbox form elements. @@ -2398,20 +2411,21 @@ function form_process_checkboxes($elemen if (!isset($element['#default_value']) || $element['#default_value'] == 0) { $element['#default_value'] = array(); } foreach ($element['#options'] as $key => $choice) { if (!isset($element[$key])) { $element[$key] = array( '#type' => 'checkbox', '#processed' => TRUE, '#title' => $choice, - '#return_value' => $key, - '#default_value' => isset($value[$key]) ? $key : NULL, + '#return_value' => (string)$key, + '#name' => $element['#name'] . '[' . check_plain($key) . ']', + '#default_value' => isset($value[$key]) ? (string)$key : NULL, '#attributes' => $element['#attributes'], '#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL, ); } } } return $element; } Index: modules/field/modules/options/options.module =================================================================== RCS file: /cvs/drupal/drupal/modules/field/modules/options/options.module,v retrieving revision 1.23 diff -u -9 -p -r1.23 options.module --- modules/field/modules/options/options.module 21 Dec 2009 13:47:32 -0000 1.23 +++ modules/field/modules/options/options.module 10 Jan 2010 13:09:31 -0000 @@ -142,19 +142,18 @@ function options_field_widget_validate($ $items = _options_form_to_storage($element); form_set_value($element, $items, $form_state); } /** * Describes the preparation steps required by each widget. */ function _options_properties($type, $multiple, $required) { $base = array( - 'zero_placeholder' => FALSE, 'filter_xss' => FALSE, 'strip_tags' => FALSE, 'empty_value' => FALSE, 'optgroups' => FALSE, ); switch ($type) { case 'select': $properties = array( @@ -162,21 +161,18 @@ function _options_properties($type, $mul 'strip_tags' => TRUE, 'empty_value' => !$required, 'optgroups' => TRUE, ); break; case 'buttons': $properties = array( 'filter_xss' => TRUE, - // Form API 'checkboxes' do not suport 0 as an option, so we replace it with - // a placeholder within the form workflow. - 'zero_placeholder' => $multiple, // Checkboxes do not need a 'none' choice. 'empty_value' => !$required && !$multiple, ); break; case 'onoff': $properties = array( 'filter_xss' => TRUE, ); @@ -207,30 +203,18 @@ function _options_get_options($field, $i return $options; } /** * Sanitizes the options. * * The function is recursive to support optgroups. */ function _options_prepare_options(&$options, $properties) { - // Substitute the '_0' placeholder. - if ($properties['zero_placeholder']) { - $values = array_keys($options); - $labels = array_values($options); - // Use a strict comparison, because 0 == 'any string'. - $index = array_search(0, $values, TRUE); - if ($index !== FALSE && !is_array($options[$index])) { - $values[$index] = '_0'; - $options = array_combine($values, $labels); - } - } - foreach ($options as $value => $label) { // Recurse for optgroups. if (is_array($label)) { _options_prepare_options($options[$value], $properties); } else { if ($properties['strip_tags']) { $options[$value] = strip_tags($label); } @@ -242,26 +226,18 @@ function _options_prepare_options(&$opti } /** * Transforms stored field values into the format the widgets need. */ function _options_storage_to_form($items, $options, $column, $properties) { $items_transposed = options_array_transpose($items); $values = (isset($items_transposed[$column]) && is_array($items_transposed[$column])) ? $items_transposed[$column] : array(); - // Substitute the '_0' placeholder. - if ($properties['zero_placeholder']) { - $index = array_search('0', $values); - if ($index !== FALSE) { - $values[$index] = '_0'; - } - } - // Discard values that are not in the current list of options. Flatten the // array if needed. if ($properties['optgroups']) { $options = options_array_flatten($options); } $values = array_values(array_intersect($values, array_keys($options))); return $values; } @@ -271,26 +247,18 @@ function _options_storage_to_form($items function _options_form_to_storage($element) { $values = array_values((array) $element['#value']); $properties = $element['#properties']; // On/off checkbox: transform '0 / 1' into the 'on / off' values. if ($element['#type'] == 'checkbox') { $values = array($values[0] ? $element['#on_value'] : $element['#off_value']); } - // Substitute the '_0' placeholder. - if ($properties['zero_placeholder']) { - $index = array_search('_0', $values); - if ($index !== FALSE) { - $values[$index] = 0; - } - } - // Filter out the 'none' option. Use a strict comparison, because // 0 == 'any string'. if ($properties['empty_value']) { $index = array_search('_none', $values, TRUE); if ($index !== FALSE) { unset($values[$index]); } } Index: modules/field/modules/options/options.test =================================================================== RCS file: /cvs/drupal/drupal/modules/field/modules/options/options.test,v retrieving revision 1.10 diff -u -9 -p -r1.10 options.test --- modules/field/modules/options/options.test 14 Dec 2009 20:18:55 -0000 1.10 +++ modules/field/modules/options/options.test 10 Jan 2010 13:09:31 -0000 @@ -129,65 +129,65 @@ class OptionsWidgetsTestCase extends Fie // Create an entity. $entity_init = field_test_create_stub_entity(); $entity = clone $entity_init; $entity->is_new = TRUE; field_test_entity_save($entity); // Display form: with no field data, nothing is checked. $this->drupalGet('test-entity/' . $entity->ftid .'/edit'); - $this->assertNoFieldChecked("edit-card-2-$langcode--0"); + $this->assertNoFieldChecked("edit-card-2-$langcode-0"); $this->assertNoFieldChecked("edit-card-2-$langcode-1"); $this->assertNoFieldChecked("edit-card-2-$langcode-2"); $this->assertRaw('Some dangerous & unescaped markup', t('Option text was properly filtered.')); // Submit form: select first and third options. $edit = array( - "card_2[$langcode][_0]" => TRUE, + "card_2[$langcode][0]" => TRUE, "card_2[$langcode][1]" => FALSE, "card_2[$langcode][2]" => TRUE, ); $this->drupalPost(NULL, $edit, t('Save')); $this->assertFieldValues($entity_init, 'card_2', $langcode, array(0, 2)); // Display form: check that the right options are selected. $this->drupalGet('test-entity/' . $entity->ftid .'/edit'); - $this->assertFieldChecked("edit-card-2-$langcode--0"); + $this->assertFieldChecked("edit-card-2-$langcode-0"); $this->assertNoFieldChecked("edit-card-2-$langcode-1"); $this->assertFieldChecked("edit-card-2-$langcode-2"); // Submit form: select only first option. $edit = array( - "card_2[$langcode][_0]" => TRUE, + "card_2[$langcode][0]" => TRUE, "card_2[$langcode][1]" => FALSE, "card_2[$langcode][2]" => FALSE, ); $this->drupalPost(NULL, $edit, t('Save')); $this->assertFieldValues($entity_init, 'card_2', $langcode, array(0)); // Display form: check that the right options are selected. $this->drupalGet('test-entity/' . $entity->ftid .'/edit'); - $this->assertFieldChecked("edit-card-2-$langcode--0"); + $this->assertFieldChecked("edit-card-2-$langcode-0"); $this->assertNoFieldChecked("edit-card-2-$langcode-1"); $this->assertNoFieldChecked("edit-card-2-$langcode-2"); // Submit form: select the three options while the field accepts only 2. $edit = array( - "card_2[$langcode][_0]" => TRUE, + "card_2[$langcode][0]" => TRUE, "card_2[$langcode][1]" => TRUE, "card_2[$langcode][2]" => TRUE, ); $this->drupalPost(NULL, $edit, t('Save')); $this->assertText('this field cannot hold more than 2 values', t('Validation error was displayed.')); // Submit form: uncheck all options. $edit = array( - "card_2[$langcode][_0]" => FALSE, + "card_2[$langcode][0]" => FALSE, "card_2[$langcode][1]" => FALSE, "card_2[$langcode][2]" => FALSE, ); $this->drupalPost(NULL, $edit, t('Save')); // Check that the value was saved. $this->assertFieldValues($entity_init, 'card_2', $langcode, array()); // Required checkbox with one option is auto-selected. $this->card_2['settings']['allowed_values'] = '99|Only allowed value'; Index: modules/simpletest/simpletest.pages.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/simpletest.pages.inc,v retrieving revision 1.21 diff -u -9 -p -r1.21 simpletest.pages.inc --- modules/simpletest/simpletest.pages.inc 30 Dec 2009 11:41:52 -0000 1.21 +++ modules/simpletest/simpletest.pages.inc 10 Jan 2010 13:09:31 -0000 @@ -179,19 +179,19 @@ function _simpletest_sort_by_title($a, $ } /** * Run selected tests. */ function simpletest_test_form_submit($form, &$form_state) { // Get list of tests. $tests_list = array(); foreach ($form_state['values'] as $class_name => $value) { - if (class_exists($class_name) && $value === 1) { + if (class_exists($class_name) && $value) { $tests_list[] = $class_name; } } if (count($tests_list) > 0 ) { simpletest_run_tests($tests_list, 'drupal'); } else { drupal_set_message(t('No test(s) selected.'), 'error'); } @@ -349,19 +349,19 @@ function simpletest_result_form_submit($ } if (!$classes) { $form_state['redirect'] = 'admin/config/development/testing'; return; } $form_state_execute = array('values' => array()); foreach ($classes as $class) { - $form_state_execute['values'][$class] = 1; + $form_state_execute['values'][$class] = '1'; } simpletest_test_form_submit(array(), $form_state_execute); } /** * Add wrapper div with class based on summary status. * * @return HTML output. Index: modules/simpletest/tests/form.test =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/tests/form.test,v retrieving revision 1.33 diff -u -9 -p -r1.33 form.test --- modules/simpletest/tests/form.test 8 Jan 2010 06:36:34 -0000 1.33 +++ modules/simpletest/tests/form.test 10 Jan 2010 13:09:31 -0000 @@ -538,60 +538,68 @@ class FormsFormStorageTestCase extends D } /** * Tests using the form in a usual way. */ function testForm() { $this->drupalGet('form_test/form-storage'); $this->assertText('Form constructions: 1'); - $edit = array('title' => 'new', 'value' => 'value_is_set'); + $edit = array('title' => 'new', 'value' => 'value_is_set', 'checkbox1' => TRUE); // Reload the form, but don't rebuild. $this->drupalPost(NULL, $edit, 'Reload'); $this->assertText('Form constructions: 2'); // Now use form rebuilding triggered by a submit button. $this->drupalPost(NULL, $edit, 'Continue submit'); $this->assertText('Form constructions: 3'); $this->assertText('Form constructions: 4'); + $this->assertFieldChecked('edit-checkbox1', t('Checkbox checked state persisted.')); + $this->assertNoFieldChecked('edit-checkbox2', t('Checkbox checked state persisted.')); // Reset the form to the values of the storage, using a form rebuild // triggered by button of type button. - $this->drupalPost(NULL, array('title' => 'changed'), 'Reset'); - $this->assertFieldByName('title', 'new', 'Values have been resetted.'); + $this->drupalPost(NULL, array('title' => 'changed', 'checkbox1' => FALSE, 'checkbox2' => TRUE), 'Reset'); + $this->assertFieldByName('title', 'new', 'Values have been reset.'); + $this->assertFieldChecked('edit-checkbox1', t('Checkbox checked state was reset.')); + $this->assertNoFieldChecked('edit-checkbox2', t('Checkbox checked state was reset.')); // After rebuilding, the form has been cached. $this->assertText('Form constructions: 5'); $this->drupalPost(NULL, $edit, 'Save'); $this->assertText('Form constructions: 5'); $this->assertText('Title: new', t('The form storage has stored the values.')); } /** * Tests using the form with an activated $form_state['cache'] property. */ function testFormCached() { $this->drupalGet('form_test/form-storage', array('query' => array('cache' => 1))); $this->assertText('Form constructions: 1'); - $edit = array('title' => 'new', 'value' => 'value_is_set'); + $edit = array('title' => 'new', 'value' => 'value_is_set', 'checkbox1' => TRUE); // Reload the form, but don't rebuild. $this->drupalPost(NULL, $edit, 'Reload'); $this->assertNoText('Form constructions'); // Now use form rebuilding triggered by a submit button. $this->drupalPost(NULL, $edit, 'Continue submit'); $this->assertText('Form constructions: 2'); + $this->assertFieldChecked('edit-checkbox1', t('Checkbox checked state persisted.')); + $this->assertNoFieldChecked('edit-checkbox2', t('Checkbox checked state persisted.')); // Reset the form to the values of the storage, using a form rebuild // triggered by button of type button. - $this->drupalPost(NULL, array('title' => 'changed'), 'Reset'); - $this->assertFieldByName('title', 'new', 'Values have been resetted.'); + $this->drupalPost(NULL, array('title' => 'changed', 'checkbox1' => FALSE, 'checkbox2' => TRUE), 'Reset'); + $this->assertFieldByName('title', 'new', 'Values have been reset.'); + $this->assertFieldChecked('edit-checkbox1', t('Checkbox checked state was reset.')); + $this->assertNoFieldChecked('edit-checkbox2', t('Checkbox checked state was reset.')); $this->assertText('Form constructions: 3'); $this->drupalPost(NULL, $edit, 'Save'); $this->assertText('Form constructions: 3'); $this->assertText('Title: new', t('The form storage has stored the values.')); } /** * Tests validation when form storage is used. Index: modules/simpletest/tests/form_test.module =================================================================== RCS file: /cvs/drupal/drupal/modules/simpletest/tests/form_test.module,v retrieving revision 1.26 diff -u -9 -p -r1.26 form_test.module --- modules/simpletest/tests/form_test.module 8 Jan 2010 06:36:34 -0000 1.26 +++ modules/simpletest/tests/form_test.module 10 Jan 2010 13:09:32 -0000 @@ -386,18 +386,20 @@ function form_test_storage_form($form, & if (empty($form_state['storage'])) { if (empty($form_state['input'])) { $_SESSION['constructions'] = 0; } // Put the initial thing into the storage $form_state['storage'] = array( 'thing' => array( 'title' => 'none', 'value' => '', + 'checkbox1' => 0, + 'checkbox2' => 0, ), ); } // Count how often the form is constructed. $_SESSION['constructions']++; drupal_set_message("Form constructions: ". $_SESSION['constructions']); $form['title'] = array( '#type' => 'textfield', @@ -405,18 +407,30 @@ function form_test_storage_form($form, & '#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'), ); + + $form['checkbox1'] = array( + '#type' => 'checkbox', + '#title' => 'One', + '#default_value' => $form_state['storage']['thing']['checkbox1'], + ); + $form['checkbox2'] = array( + '#type' => 'checkbox', + '#title' => 'Two', + '#default_value' => $form_state['storage']['thing']['checkbox2'], + ); + $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. @@ -459,18 +473,20 @@ function form_test_storage_element_valid } } /** * Form submit handler to continue multi-step form. */ 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['storage']['thing']['checkbox1'] = $form_state['values']['checkbox1']; + $form_state['storage']['thing']['checkbox2'] = $form_state['values']['checkbox2']; $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) { Index: modules/system/system.module =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.module,v retrieving revision 1.873 diff -u -9 -p -r1.873 system.module --- modules/system/system.module 10 Jan 2010 00:09:37 -0000 1.873 +++ modules/system/system.module 10 Jan 2010 13:09:32 -0000 @@ -382,34 +382,36 @@ function system_element_info() { $types['radios'] = array( '#input' => TRUE, '#process' => array('form_process_radios'), '#theme_wrappers' => array('radios'), '#pre_render' => array('form_pre_render_conditional_form_element'), ); $types['radio'] = array( '#input' => TRUE, '#default_value' => NULL, - '#process' => array('ajax_process_form'), + '#process' => array('ajax_process_form', 'form_process_checked'), '#theme' => 'radio', '#theme_wrappers' => array('form_element'), '#title_display' => 'after', ); $types['checkboxes'] = array( '#input' => TRUE, '#tree' => TRUE, '#process' => array('form_process_checkboxes'), '#theme_wrappers' => array('checkboxes'), '#pre_render' => array('form_pre_render_conditional_form_element'), ); $types['checkbox'] = array( '#input' => TRUE, - '#return_value' => 1, - '#process' => array('ajax_process_form'), + // #return_value is a string, because that is what is received from the + // browser when the form is submitted. + '#return_value' => '1', + '#process' => array('ajax_process_form', 'form_process_checked'), '#theme' => 'checkbox', '#theme_wrappers' => array('form_element'), '#title_display' => 'after', ); $types['select'] = array( '#input' => TRUE, '#size' => 0, '#multiple' => FALSE, '#process' => array('ajax_process_form'),