diff -u b/core/modules/file/file.module b/core/modules/file/file.module --- b/core/modules/file/file.module +++ b/core/modules/file/file.module @@ -1112,13 +1112,6 @@ form_error($element['upload'], t('!name field is required.', array('!name' => $element['#title']))); } - // Save entire values to storage. - $values = NestedArray::getValue($form_state['values'], $element['#array_parents']); - if (!isset($form_state['storage']['managed_file_values'])) { - $form_state['storage']['managed_file_values'] = array(); - } - NestedArray::setValue($form_state['storage']['managed_file_values'], $element['#array_parents'], $values); - // Consolidate the array value of this field to array of FIDs. if (!$element['#extended']) { form_set_value($element, $element['fids']['#value'], $form_state); @@ -1142,27 +1135,22 @@ // button was clicked. Action is needed here for the remove button, because we // only remove a file in response to its remove button being clicked. if ($button_key == 'remove_button') { - // Get files that need to be removed from list. - $fids = array(); - $values = NestedArray::getValue($form_state['storage']['managed_file_values'], $parents); - if (!is_array($values)) { - $values = array('fids' => array()); - } - + $fids = array_keys($element['#files']); + // Get files that will be removed. if ($element['#multiple']) { - foreach ($values as $name => $value) { - if (strpos($name, 'file_') === 0 && $value['selected']) { - $fids[] = (int) substr($name, 5); + $remove_fids = array(); + foreach (element_children($element) as $name) { + if (strpos($name, 'file_') === 0 && $element[$name]['selected']['#value']) { + $remove_fids[] = (int) substr($name, 5); } } + $fids = array_diff($fids, $remove_fids); } else { - $fids = $values['fids']; + $remove_fids = $fids; } - $values['fids'] = array_diff($values['fids'], $fids); - - foreach ($fids as $fid) { + foreach ($remove_fids as $fid) { // If it's a temporary file we can safely remove it immediately, otherwise // it's up to the implementing module to remove usages of files to have them // removed. @@ -1177,9 +1165,8 @@ // when the managed_file element is part of a field widget. // $form_state['input'] must be updated so that file_managed_file_value() // has correct information during the rebuild. - $values_element = $element['#extended'] ? $element['fids'] : $element['fids']; - form_set_value($values_element, $values['fids'], $form_state); - NestedArray::setValue($form_state['input'], $values_element['#parents'], implode(' ', $values['fids'])); + form_set_value($element['fids'], implode(' ', $fids), $form_state); + NestedArray::setValue($form_state['input'], $element['fids']['#parents'], implode(' ', $fids)); } // Set the form to rebuild so that $form is correctly updated in response to diff -u b/core/modules/file/lib/Drupal/file/Tests/FileManagedFileElementTest.php b/core/modules/file/lib/Drupal/file/Tests/FileManagedFileElementTest.php --- b/core/modules/file/lib/Drupal/file/Tests/FileManagedFileElementTest.php +++ b/core/modules/file/lib/Drupal/file/Tests/FileManagedFileElementTest.php @@ -31,72 +31,122 @@ $this->assertFieldByXpath('//input[@name="files[nested_file]" and @size="13"]', NULL, 'The custom #size attribute is passed to the child upload element.'); - // Perform the tests with all permutations of $form['#tree'] and - // $element['#extended']. + // Perform the tests with all permutations of $form['#tree'], + // $element['#extended'], and $element['#multiple']. + $test_file = $this->getTestFile('text'); foreach (array(0, 1) as $tree) { foreach (array(0, 1) as $extended) { - $test_file = $this->getTestFile('text'); - $path = 'file/test/' . $tree . '/' . $extended; - $input_base_name = $tree ? 'nested_file' : 'file'; - - // Submit without a file. - $this->drupalPost($path, array(), t('Save')); - $this->assertRaw(t('The file ids are %fids.', array('%fids' => implode(',', array()))), 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 ids are %fids.', array('%fids' => implode(',', array($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 ids are %fids.', array('%fids' => implode(',', array($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. + foreach (array(0, 1) as $multiple) { + $path = 'file/test/' . $tree . '/' . $extended . '/' . $multiple; + $input_base_name = $tree ? 'nested_file' : 'file'; + $file_field_name = $multiple ? 'files[' . $input_base_name . '][]' : 'files[' . $input_base_name . ']'; + + // Submit without a file. + $this->drupalPost($path, array(), t('Save')); + $this->assertRaw(t('The file ids are %fids.', array('%fids' => implode(',', array()))), t('Submitted without a file.')); + + // Submit a new file, without using the Upload button. $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')); - } + $edit = array($file_field_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 uploaded.')); - $this->drupalPost(NULL, array(), t('Save')); + $this->assertTrue($last_fid > $last_fid_prior, t('New file got saved.')); $this->assertRaw(t('The file ids are %fids.', array('%fids' => implode(',', array($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 ids are %fids.', array('%fids' => implode(',', array()))), t('Submission after file removal was successful.')); + // Submit no new input, but with a default file. + $this->drupalPost($path . '/' . $last_fid, array(), t('Save')); + $this->assertRaw(t('The file ids are %fids.', array('%fids' => implode(',', array($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($file_field_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 ids are %fids.', array('%fids' => implode(',', array($last_fid)))), t('Submit handler has correct file info.')); + + // Remove, then Submit. + $remove_button_title = $multiple ? t('Remove selected') : t('Remove'); + $remove_edit = array(); + if ($multiple) { + $selected_checkbox = ($tree ? 'nested[file]' : 'file') . '[file_' . $last_fid . '][selected]'; + $remove_edit = array($selected_checkbox => '1'); + } + $this->drupalGet($path . '/' . $last_fid); + if ($ajax) { + $this->drupalPostAJAX(NULL, $remove_edit, $input_base_name . '_remove_button'); + } + else { + $this->drupalPost(NULL, $remove_edit, $remove_button_title); + } + $this->drupalPost(NULL, array(), t('Save')); + $this->assertRaw(t('The file ids are %fids.', array('%fids' => '')), t('Submission after file removal was successful.')); + + // Upload, then Remove, then Submit. + $this->drupalGet($path); + $edit = array($file_field_name => drupal_realpath($test_file->uri)); + if ($ajax) { + $this->drupalPostAJAX(NULL, $edit, $input_base_name . '_upload_button'); + } + else { + $this->drupalPost(NULL, $edit, t('Upload')); + } + $remove_edit = array(); + if ($multiple) { + $selected_checkbox = ($tree ? 'nested[file]' : 'file') . '[file_' . $this->getLastFileId() . '][selected]'; + $remove_edit = array($selected_checkbox => '1'); + } + if ($ajax) { + $this->drupalPostAJAX(NULL, $remove_edit, $input_base_name . '_remove_button'); + } + else { + $this->drupalPost(NULL, $remove_edit, $remove_button_title); + } - // 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 ids are %fids.', array('%fids' => '')), t('Submission after file upload and removal was successful.')); } - $this->drupalPost(NULL, array(), t('Save')); - $this->assertRaw(t('The file ids are %fids.', array('%fids' => implode(',', array()))), t('Submission after file upload and removal was successful.')); } } } + + // The multiple file upload has additional conditions that need checking. + $path = 'file/test/1/1/1'; + $edit = array('files[nested_file][]' => drupal_realpath($test_file->uri)); + $fid_list = array(); + + $this->drupalGet($path); + + // Add a single file to the upload field. + $this->drupalPost(NULL, $edit, t('Upload')); + $fid_list[] = $this->getLastFileId(); + $this->assertFieldByXpath('//input[@name="nested[file][file_' . $fid_list[0] . '][selected]"]', NULL, 'First file successfully uploaded to multiple file element.'); + + // Add another file to the same upload field. + $this->drupalPost(NULL, $edit, t('Upload')); + $fid_list[] = $this->getLastFileId(); + $this->assertFieldByXpath('//input[@name="nested[file][file_' . $fid_list[1] . '][selected]"]', NULL, 'Second file successfully uploaded to multiple file element.'); + + // Save the entire form. + $this->drupalPost(NULL, array(), t('Save')); + $this->assertRaw(t('The file ids are %fids.', array('%fids' => implode(',', $fid_list))), t('Two files saved into a single multiple file element.')); + + // Delete only the first file. + $edit = array( + 'nested[file][file_' . $fid_list[0] . '][selected]' => '1', + ); + $this->drupalPost($path . '/' . implode(',', $fid_list), $edit, t('Remove selected')); + + // Check that the first file has been deleted but not the second. + $this->assertNoFieldByXpath('//input[@name="nested[file][file_' . $fid_list[0] . '][selected]"]', NULL, 'An individual file can be deleted from a multiple file element.'); + $this->assertFieldByXpath('//input[@name="nested[file][file_' . $fid_list[1] . '][selected]"]', NULL, 'Second individual file not deleted when the first file is deleted from a multiple file element.'); } } diff -u b/core/modules/file/tests/file_module_test.module b/core/modules/file/tests/file_module_test.module --- b/core/modules/file/tests/file_module_test.module +++ b/core/modules/file/tests/file_module_test.module @@ -31,7 +31,7 @@ * @see file_module_test_form_submit() * @ingroup forms */ -function file_module_test_form($form, &$form_state, $tree = TRUE, $extended = TRUE, $default_fids = NULL, $multiple = NULL) { +function file_module_test_form($form, &$form_state, $tree = TRUE, $extended = TRUE, $multiple = FALSE, $default_fids = NULL) { $form['#tree'] = (bool) $tree; $form['nested']['file'] = array(