=== modified file 'includes/form.inc'
--- includes/form.inc	2010-10-21 20:46:58 +0000
+++ includes/form.inc	2010-10-27 07:49:14 +0000
@@ -163,7 +163,7 @@
  *   Any additional arguments are passed on to the functions called by
  *   drupal_get_form(), including the unique form constructor function. For
  *   example, the node_edit form requires that a node object is passed in here
- *   when it is called. These are available to implementations of 
+ *   when it is called. These are available to implementations of
  *   hook_form_alter() and hook_form_FORM_ID_alter() as the array
  *   $form_state['build_info']['args'].
  *
@@ -2073,11 +2073,17 @@ 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 ($input === FALSE) {
+    // For non-existing or NULL default_values return FALSE as it means
+    // unchecked and NULL is changed to empty string later in
+    // _form_builder_handle_input_element().
+    return isset($element['#default_value']) ? $element['#default_value'] : 0;
+  }
+  else {
     // 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 integer 0, so we can explicitly
-    // test for a value different than string '0'.
+    // For an unchecked checkbox, we return 0, so we can explicitly test for a
+    // value different than string '0'.
     return isset($input) ? $element['#return_value'] : 0;
   }
 }
@@ -2109,7 +2115,8 @@ function form_type_checkboxes_value($ele
     // NULL elements from the array before constructing the return value, to
     // simulate the behavior of web browsers (which do not send unchecked
     // checkboxes to the server at all). This will not affect non-programmatic
-    // form submissions, since a checkbox can never legitimately be NULL.
+    // form submissions, since all values in $_POST are strings so they can
+    // never legitimately be NULL.
     foreach ($input as $key => $value) {
       if (!isset($value)) {
         unset($input[$key]);
@@ -2807,7 +2814,7 @@ function theme_checkbox($variables) {
   element_set_attributes($element, array('id', 'name', '#return_value' => 'value'));
 
   // Unchecked checkbox has #value of integer 0.
-  if (isset($element['#return_value']) && isset($element['#value']) && $element['#value'] !== 0 && $element['#value'] == $element['#return_value']) {
+  if (!empty($element['#checked'])) {
     $element['#attributes']['checked'] = 'checked';
   }
   _form_set_class($element, array('form-checkbox'));
@@ -2859,6 +2866,34 @@ function form_pre_render_conditional_for
   return $element;
 }
 
+/**
+ * Set the #checked attribute of a checkbox element.
+ */
+function form_process_checkbox($element, $form_state) {
+  // On form submission, the #value of a checked checkbox element is
+  // #return_value. On form submission, the #value of an unchecked checkbox
+  // element is 0. On not submitted forms, the #value of a checkbox is
+  // #default_value.
+  $value = $element['#value'];
+  $return_value = $element['#return_value'];
+  // Most of the time, a relaxed equals check is adequate so that a #value of
+  // '1' matches #return_value 1. The isset in form_process_checkboxes() is
+  // type agnostic too so that won't interfere with this. There are a few
+  // exceptions: TRUE means checked, FALSE simply means unchecked. The value 0
+  // needs special handling because the integer 0 is equal to almost every
+  // string.
+  if ($value === TRUE || $value === FALSE || $value === 0) {
+    $element['#checked'] = (bool) $value;
+  }
+  else {
+    // cast both sides to string to avoid PHP casting them to int and causing
+    // 15 to be equal to '15monkeys'. This cast does not mean that either
+    // #value or #return_value is expected to be a string.
+    $element['#checked'] = (string)$value === (string)$return_value;
+  }
+  return $element;
+}
+
 function form_process_checkboxes($element) {
   $value = is_array($element['#value']) ? $element['#value'] : array();
   $element['#tree'] = TRUE;
@@ -2868,6 +2903,11 @@ function form_process_checkboxes($elemen
     }
     foreach ($element['#options'] as $key => $choice) {
       if (!isset($element[$key])) {
+        // As 0 means unchecked checkbox, it needs to be cast into string to
+        // differentiate.
+        if ($key === 0) {
+          $key = '0';
+        }
         $element[$key] = array(
           '#type' => 'checkbox',
           '#processed' => TRUE,

=== modified file 'modules/simpletest/tests/form.test'
--- modules/simpletest/tests/form.test	2010-10-04 18:00:45 +0000
+++ modules/simpletest/tests/form.test	2010-10-27 07:04:18 +0000
@@ -1352,3 +1352,78 @@ class FormsFileInclusionTestCase extends
     $this->assertText('Submit callback called.');
   }
 }
+
+/**
+ * Test checkbox element.
+ */
+class FormCheckboxTestCase extends DrupalWebTestCase {
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Form API checkbox',
+      'description' => 'Tests form API checkbox handling especially 0, empty string etc.',
+      'group' => 'Form API',
+    );
+  }
+
+  function setUp() {
+    parent::setUp('form_test');
+  }
+
+  function testFormCheckbox() {
+    $default_value_array = array(FALSE, NULL, TRUE, 0, '0', '', 1, '1', 'foobar', '1foobar');
+    $return_value_array = array('0', '', 1, '1', 'foobar', '1foobar');
+    foreach ($default_value_array as $default_value_key => $default_value) {
+      foreach ($return_value_array as $return_value) {
+        $form_array = drupal_get_form('form_test_checkbox_type_juggling', $default_value, $return_value);
+        $form = drupal_render($form_array);
+        if ($default_value === TRUE) {
+          $checked = TRUE;
+        }
+        elseif ($return_value === '0') {
+          $checked = ($default_value === '0');
+        }
+        elseif ($return_value === '') {
+          $checked = ($default_value === '');
+        }
+        elseif ($return_value === 1 || $return_value === '1') {
+          $checked = ($default_value === 1 || $default_value === '1');
+        }
+        elseif ($return_value === 'foobar') {
+          $checked = ($default_value === 'foobar');
+        }
+        elseif ($return_value === '1foobar') {
+          $checked = ($default_value === '1foobar');
+        }
+        $checked_in_html = strpos($form, 'checked') !== FALSE;
+        $message = t('#default_value is !default_value #return_value is !return_value.', array('!default_value' => var_export($default_value, TRUE), '!return_value' => var_export($return_value, TRUE)));
+        $this->assertEqual($checked, $checked_in_html, $message);
+      }
+    }
+    // Check form_state['values']
+    $results = json_decode($this->drupalPost('form-test/checkboxes-zero', array(), 'Save'));
+    $this->assertIdentical($results->checkbox_off, array(0, 0, 0), t('All three in checkbox_off are zeroes: off.'));
+    $this->assertIdentical($results->checkbox_zero_default, array('0', 0, 0), t('The first choice is on in checkbox_zero_default'));
+    $this->assertIdentical($results->checkbox_string_zero_default, array('0', 0, 0), t('The first choice is on in checkbox_string_zero_default'));
+    $edit = array('checkbox_off[0]' => '0');
+    $results = json_decode($this->drupalPost('form-test/checkboxes-zero', $edit, 'Save'));
+    $this->assertIdentical($results->checkbox_off, array('0', 0, 0), t('The first choice is on in checkbox_off but the rest is not'));
+
+    // Now check the actual HTML.
+    $this->drupalPost('form-test/checkboxes-zero/0', array(), 'Save');
+    $checkboxes = $this->xpath('//input[@type="checkbox"]');
+    foreach ($checkboxes as $checkbox) {
+      $checked = isset($checkbox['checked']);
+      $name = $checkbox['name'];
+      $this->assertEqual($checked, $name == 'checkbox_zero_default[0]' || $name == 'checkbox_string_zero_default[0]', t('Checkbox %name correctly checked', array('%name' => $name)));
+    }
+    $edit = array('checkbox_off[0]' => '0');
+    $this->drupalPost('form-test/checkboxes-zero/0', $edit, 'Save');
+    $checkboxes = $this->xpath('//input[@type="checkbox"]');
+    foreach ($checkboxes as $checkbox) {
+      $checked = isset($checkbox['checked']);
+      $name = $checkbox['name'];
+      $this->assertEqual($checked, $name == 'checkbox_off[0]' || $name == 'checkbox_zero_default[0]' || $name == 'checkbox_string_zero_default[0]', t('Checkbox %name correctly checked', array('%name' => $name)));
+    }
+  }
+}

=== modified file 'modules/simpletest/tests/form_test.module'
--- modules/simpletest/tests/form_test.module	2010-10-20 01:15:58 +0000
+++ modules/simpletest/tests/form_test.module	2010-10-27 07:47:35 +0000
@@ -175,6 +175,13 @@ function form_test_menu() {
     'access callback' => TRUE,
     'type' => MENU_CALLBACK,
   );
+  $items['form-test/checkboxes-zero'] = array(
+    'title' => 'FAPI test involving checkboxes and zero',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('form_test_checkboxes_zero'),
+    'access callback' => TRUE,
+    'type' => MENU_CALLBACK,
+  );
 
   return $items;
 }
@@ -1395,3 +1402,44 @@ function form_test_load_include_custom($
   $form_state['cache'] = TRUE;
   return $form;
 }
+
+function form_test_checkbox_type_juggling($form, $form_state, $default_value, $return_value) {
+  $form['checkbox'] = array(
+    '#type' => 'checkbox',
+    '#return_value' => $return_value,
+    '#default_value' => $default_value,
+  );
+  return $form;
+}
+
+function form_test_checkboxes_zero($form, &$form_state, $json = TRUE) {
+  $form['checkbox_off'] = array(
+    '#type' => 'checkboxes',
+    '#options' => array('foo', 'bar', 'baz'),
+  );
+  $form['checkbox_zero_default'] = array(
+    '#type' => 'checkboxes',
+    '#options' => array('foo', 'bar', 'baz'),
+    '#default_value' => array(0),
+  );
+  $form['checkbox_string_zero_default'] = array(
+    '#type' => 'checkboxes',
+    '#options' => array('foo', 'bar', 'baz'),
+    '#default_value' => array('0'),
+  );
+  $form['submit'] = array(
+    '#type' => 'submit',
+    '#value' => 'Save',
+  );
+  if ($json) {
+    $form['#submit'][] = '_form_test_checkbox_submit';
+  }
+  else {
+    $form['#submit'][] = '_form_test_checkboxes_zero_no_redirect';
+  }
+  return $form;
+}
+
+function _form_test_checkboxes_zero_no_redirect($form, &$form_state) {
+  $form_state['redirect'] = FALSE;
+}

=== modified file 'modules/system/system.module'
--- modules/system/system.module	2010-10-21 12:09:41 +0000
+++ modules/system/system.module	2010-10-26 06:25:30 +0000
@@ -410,6 +410,7 @@ function system_element_info() {
     '#return_value' => 1,
     '#process' => array('ajax_process_form'),
     '#theme' => 'checkbox',
+    '#process' => array('form_process_checkbox'),
     '#theme_wrappers' => array('form_element'),
     '#title_display' => 'after',
   );
@@ -2883,7 +2884,7 @@ function system_get_module_admin_tasks($
           }
           if ($parent = menu_link_load($admin_tasks[$duplicate_path]['plid'])) {
             // Append the parent item's title to the duplicated task's title.
-            // We use $links[$duplicate_path] in case there are triplicates. 
+            // We use $links[$duplicate_path] in case there are triplicates.
             $admin_tasks[$duplicate_path]['title'] = t('@original_title (@parent_title)', array('@original_title' => $links[$duplicate_path]['title'], '@parent_title' => $parent['title']));
           }
         }

