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 .= "</fieldset>\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 = '<input type="radio" ';
   $output .= 'id="' . $element['#id'] . '" ';
   $output .= 'name="' . $element['#name'] . '" ';
-  $output .= 'value="' . $element['#return_value'] . '" ';
-  $output .= (check_plain($element['#value']) == $element['#return_value']) ? ' checked="checked" ' : ' ';
+  $output .= 'value="' . check_plain($element['#return_value']) . '" ';
+  if ($element['#checked']) {
+    $output .= 'checked="checked" ';
+  }
   $output .= drupal_attributes($element['#attributes']) . ' />';
 
   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 = '<input ';
   $checkbox .= 'type="checkbox" ';
   $checkbox .= 'name="' . $element['#name'] . '" ';
   $checkbox .= 'id="' . $element['#id'] . '" ' ;
-  $checkbox .= 'value="' . $element['#return_value'] . '" ';
-  // Unchecked checkbox has #value of numeric 0.
-  if ($element['#value'] !== 0 && $element['#value'] == $element['#return_value']) {
+  $checkbox .= 'value="' . check_plain($element['#return_value']) . '" ';
+  // Cast operands to strings, because 0 == 'any string'. An unchecked checkbox
+  // has #value of numeric 0.
+  if ($element['#checked']) {
     $checkbox .= 'checked="checked" ';
   }
   $checkbox .= drupal_attributes($element['#attributes']) . ' />';
 
   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 &amp; unescaped <strong>markup</strong>', 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'),
