Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.1250
diff -u -p -r1.1250 common.inc
--- includes/common.inc	7 Nov 2010 18:33:52 -0000	1.1250
+++ includes/common.inc	11 Nov 2010 00:12:53 -0000
@@ -5969,14 +5969,22 @@ function element_set_attributes(array &$
  *   An array of parent keys, starting with the outermost key.
  * @param $value
  *   The value to set.
+ * @param $force
+ *   (Optional) If TRUE, the value is forced into the structure even if it
+ *   requires the deletion of an already existing non-array parent value. If
+ *   FALSE, PHP throws an error if trying to add into a value that is not an
+ *   array. Defaults to FALSE.
  *
  * @see drupal_array_get_nested_value()
  */
-function drupal_array_set_nested_value(array &$array, array $parents, $value) {
+function drupal_array_set_nested_value(array &$array, array $parents, $value, $force = FALSE) {
   $ref = &$array;
   foreach ($parents as $parent) {
-    // Note that PHP is fine with referencing a not existing array key - in this
-    // case it just creates an entry with NULL as value.
+    // PHP auto-creates container arrays and NULL entries without error if $ref
+    // is NULL, but throws an error if $ref is set, but not an array.
+    if ($force && isset($ref) && !is_array($ref)) {
+      $ref = array();
+    }
     $ref = &$ref[$parent];
   }
   $ref = $value;
@@ -6040,10 +6048,7 @@ function drupal_array_set_nested_value(a
 function drupal_array_get_nested_value(array &$array, array $parents, &$key_exists = NULL) {
   $ref = &$array;
   foreach ($parents as $parent) {
-    // array_key_exists() is slower than isset() and triggers notices if the
-    // second argument is not an array, so only call it when absolutely
-    // necessary.
-    if (isset($ref[$parent]) || (is_array($ref) && array_key_exists($parent, $ref))) {
+    if (is_array($ref) && array_key_exists($parent, $ref)) {
       $ref = &$ref[$parent];
     }
     else {
Index: includes/form.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/form.inc,v
retrieving revision 1.508
diff -u -p -r1.508 form.inc
--- includes/form.inc	7 Nov 2010 21:46:09 -0000	1.508
+++ includes/form.inc	11 Nov 2010 00:12:54 -0000
@@ -1031,10 +1031,29 @@ function drupal_validate_form($form_id, 
         drupal_array_set_nested_value($values, $section, $value);
       }
     }
-    // For convenience we always make the value of the pressed button available.
+    // A button's #value does not require validation, so for convenience we
+    // allow the value of the clicked button to be retained in its normal
+    // $form_state['values'] locations, even if these locations are not included
+    // in #limit_validation_errors.
     if (isset($form_state['triggering_element']['#button_type'])) {
-      $values[$form_state['triggering_element']['#name']] = $form_state['triggering_element']['#value'];
-      drupal_array_set_nested_value($values, $form_state['triggering_element']['#parents'], $form_state['triggering_element']['#value']);
+      $button_value = $form_state['triggering_element']['#value'];
+
+      // Like all input controls, the button value may be in the location
+      // dictated by #parents. If it is, copy it to $values, but do not override
+      // what may already be in $values.
+      $parents = $form_state['triggering_element']['#parents'];
+      if (!drupal_array_nested_key_exists($values, $parents) && drupal_array_get_nested_value($form_state['values'], $parents) === $button_value) {
+        drupal_array_set_nested_value($values, $parents, $button_value);
+      }
+
+      // Additionally, form_builder() places the button value in
+      // $form_state['values'][BUTTON_NAME]. If it's still there, after
+      // validation handlers have run, copy it to $values, but do not override
+      // what may already be in $values.
+      $name = $form_state['triggering_element']['#name'];
+      if (!isset($values[$name]) && isset($form_state['values'][$name]) && $form_state['values'][$name] === $button_value) {
+        $values[$name] = $button_value;
+      }
     }
     $form_state['values'] = $values;
   }
@@ -2301,7 +2320,7 @@ function form_type_token_value($element,
  *   Form state array where the value change should be recorded.
  */
 function form_set_value($element, $value, &$form_state) {
-  drupal_array_set_nested_value($form_state['values'], $element['#parents'], $value);
+  drupal_array_set_nested_value($form_state['values'], $element['#parents'], $value, TRUE);
 }
 
 /**
Index: modules/file/file.field.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/file/file.field.inc,v
retrieving revision 1.38
diff -u -p -r1.38 file.field.inc
--- modules/file/file.field.inc	31 Oct 2010 12:12:00 -0000	1.38
+++ modules/file/file.field.inc	11 Nov 2010 00:12:56 -0000
@@ -648,8 +648,9 @@ function file_field_widget_process($elem
   // Adjust the AJAX settings so that on upload and remove of any individual
   // file, the entire group of file fields is updated together.
   if ($field['cardinality'] != 1) {
-    $new_path = preg_replace('/\/\d+\//', '/', $element['remove_button']['#ajax']['path'], 1);
-    $field_element = drupal_array_get_nested_value($form, array_slice($element['#array_parents'], 0, -1));
+    $parents = array_slice($element['#array_parents'], 0, -1);
+    $new_path = 'file/ajax/' . implode('/', $parents) . '/' . $form['form_build_id']['#value'];
+    $field_element = drupal_array_get_nested_value($form, $parents);
     $new_wrapper = $field_element['#id'] . '-ajax-wrapper';
     foreach (element_children($element) as $key) {
       if (isset($element[$key]['#ajax'])) {
Index: modules/file/file.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/file/file.module,v
retrieving revision 1.45
diff -u -p -r1.45 file.module
--- modules/file/file.module	20 Oct 2010 15:22:53 -0000	1.45
+++ modules/file/file.module	11 Nov 2010 00:12:56 -0000
@@ -360,7 +360,7 @@ function file_managed_file_process($elem
   $element['#tree'] = TRUE;
 
   $ajax_settings = array(
-    'path' => 'file/ajax/' . implode('/', $element['#parents']) . '/' . $form['form_build_id']['#value'],
+    'path' => 'file/ajax/' . implode('/', $element['#array_parents']) . '/' . $form['form_build_id']['#value'],
     'wrapper' => $element['#id'] . '-ajax-wrapper',
     'effect' => 'fade',
     'progress' => array(
Index: modules/file/tests/file.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/file/tests/file.test,v
retrieving revision 1.27
diff -u -p -r1.27 file.test
--- modules/file/tests/file.test	20 Oct 2010 15:22:53 -0000	1.27
+++ modules/file/tests/file.test	11 Nov 2010 00:12:56 -0000
@@ -13,7 +13,7 @@ class FileFieldTestCase extends DrupalWe
   protected $admin_user;
 
   function setUp() {
-    parent::setUp('file');
+    parent::setUp('file', 'file_module_test');
     $this->admin_user = $this->drupalCreateUser(array('access content', 'access administration pages', 'administer site configuration', 'administer users', 'administer permissions', 'administer content types', 'administer nodes', 'bypass node access'));
     $this->drupalLogin($this->admin_user);
   }
@@ -32,6 +32,17 @@ class FileFieldTestCase extends DrupalWe
   }
 
   /**
+   * Get the fid of the last inserted file.
+   */
+  function getLastFileId() {
+    $fid = db_query('SELECT MAX(fid) FROM {file_managed}')->fetchField();
+    if (!$fid) {
+      $fid = 0;
+    }
+    return $fid;
+  }
+
+  /**
    * Create a new file field.
    *
    * @param $name
@@ -201,6 +212,95 @@ class FileFieldTestCase extends DrupalWe
   }
 }
 
+/**
+ * Test class for testing the 'managed_file' element type on its own, not as part of a file field.
+ *
+ * @todo Create a FileTestCase base class and move FileFieldTestCase methods
+ *   that aren't related to fields into it.
+ */
+class FileManagedFileElementTestCase extends FileFieldTestCase {
+  public static function getInfo() {
+    return array(
+      'name' => 'Managed file element test',
+      'description' => 'Tests the managed_file element type.',
+      'group' => 'File',
+    );
+  }
+
+  /**
+   * Tests the managed_file element type.
+   */
+  function testManagedFile() {
+    // Perform the tests with all permutations of $form['#tree'] and
+    // $element['#extended'].
+    foreach (array(FALSE, TRUE) as $tree) {
+      foreach (array(FALSE, TRUE) as $extended) {
+        $test_file = $this->getTestFile('text');
+        $path = 'file/test/' . ($tree ? '1' : '0') . '/' . ($extended ? '1' : '0');
+        $input_base_name = $tree ? 'nested_file' : 'file';
+
+        // Submit without a file.
+        $this->drupalPost($path, array(), t('Save'));
+        $this->assertRaw(t('The file id is %fid.', array('%fid' => 0)), t('Submitted without a file.'));
+
+        // Submit a new file, without using the Upload button.
+        $last_fid_prior = $this->getLastFileId();
+        $edit = array('files[' . $input_base_name . ']' => drupal_realpath($test_file->uri));
+        $this->drupalPost($path, $edit, t('Save'));
+        $last_fid = $this->getLastFileId();
+        $this->assertTrue($last_fid > $last_fid_prior, t('New file got saved.'));
+        $this->assertRaw(t('The file id is %fid.', array('%fid' => $last_fid)), t('Submit handler has correct file info.'));
+
+        // Submit no new input, but with a default file.
+        $this->drupalPost($path . '/' . $last_fid, array(), t('Save'));
+        $this->assertRaw(t('The file id is %fid.', array('%fid' => $last_fid)), t('Empty submission did not change an existing file.'));
+
+        // Now, test the Upload and Remove buttons, with and without AJAX.
+        foreach (array(FALSE, TRUE) as $ajax) {
+          // Upload, then Submit.
+          $last_fid_prior = $this->getLastFileId();
+          $this->drupalGet($path);
+          $edit = array('files[' . $input_base_name . ']' => drupal_realpath($test_file->uri));
+          if ($ajax) {
+            $this->drupalPostAJAX(NULL, $edit, $input_base_name . '_upload_button');
+          }
+          else {
+            $this->drupalPost(NULL, $edit, t('Upload'));
+          }
+          $last_fid = $this->getLastFileId();
+          $this->assertTrue($last_fid > $last_fid_prior, t('New file got uploaded.'));
+          $this->drupalPost(NULL, array(), t('Save'));
+          $this->assertRaw(t('The file id is %fid.', array('%fid' => $last_fid)), t('Submit handler has correct file info.'));
+
+          // Remove, then Submit.
+          $this->drupalGet($path . '/' . $last_fid);
+          if ($ajax) {
+            $this->drupalPostAJAX(NULL, array(), $input_base_name . '_remove_button');
+          }
+          else {
+            $this->drupalPost(NULL, array(), t('Remove'));
+          }
+          $this->drupalPost(NULL, array(), t('Save'));
+          $this->assertRaw(t('The file id is %fid.', array('%fid' => 0)), t('Submission after file removal was successful.'));
+
+          // Upload, then Remove, then Submit.
+          $this->drupalGet($path);
+          $edit = array('files[' . $input_base_name . ']' => drupal_realpath($test_file->uri));
+          if ($ajax) {
+            $this->drupalPostAJAX(NULL, $edit, $input_base_name . '_upload_button');
+            $this->drupalPostAJAX(NULL, array(), $input_base_name . '_remove_button');
+          }
+          else {
+            $this->drupalPost(NULL, $edit, t('Upload'));
+            $this->drupalPost(NULL, array(), t('Remove'));
+          }
+          $this->drupalPost(NULL, array(), t('Save'));
+          $this->assertRaw(t('The file id is %fid.', array('%fid' => 0)), t('Submission after file upload and removal was successful.'));
+        }
+      }
+    }
+  }
+}
 
 /**
  * Test class to test file field widget, single and multi-valued, with and without AJAX, with public and private files.
Index: modules/file/tests/file_module_test.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/file/tests/file_module_test.module,v
retrieving revision 1.3
diff -u -p -r1.3 file_module_test.module
--- modules/file/tests/file_module_test.module	4 Dec 2009 16:49:46 -0000	1.3
+++ modules/file/tests/file_module_test.module	11 Nov 2010 00:12:56 -0000
@@ -22,15 +22,22 @@ function file_module_test_menu() {
   return $items;
 }
 
-function file_module_test_form($form, $form_state) {
-  $form['#tree'] = TRUE;
+/**
+ * Form builder for testing a 'managed_file' element.
+ */
+function file_module_test_form($form, &$form_state, $tree = TRUE, $extended = FALSE, $default_fid = NULL) {
+  $form['#tree'] = (bool) $tree;
 
-  $form['file'] = array(
+  $form['nested']['file'] = array(
     '#type' => 'managed_file',
     '#title' => t('Managed file'),
     '#upload_location' => 'public://test',
     '#progress_message' => t('Please wait...'),
+    '#extended' => (bool) $extended,
   );
+  if ($default_fid) {
+    $form['nested']['file']['#default_value'] = $extended ? array('fid' => $default_fid) : $default_fid;
+  }
 
   $form['textfield'] = array(
     '#type' => 'textfield',
@@ -44,3 +51,16 @@ function file_module_test_form($form, $f
 
   return $form;
 }
+
+/**
+ * Form submission handler for file_module_test_form().
+ */
+function file_module_test_form_submit($form, &$form_state) {
+  if ($form['#tree']) {
+    $fid = $form['nested']['file']['#extended'] ? $form_state['values']['nested']['file']['fid'] : $form_state['values']['nested']['file'];
+  }
+  else {
+    $fid = $form['nested']['file']['#extended'] ? $form_state['values']['file']['fid'] : $form_state['values']['file'];
+  }
+  drupal_set_message(t('The file id is %fid.', array('%fid' => $fid)));
+}
