diff --git a/modules/file/file.field.inc b/modules/file/file.field.inc index 5508b9b..9016e8b 100644 --- a/modules/file/file.field.inc +++ b/modules/file/file.field.inc @@ -447,32 +447,18 @@ function file_field_widget_form(&$form, &$form_state, $field, $instance, $langco 'description' => '', ); - // Retrieve any values set in $form_state, as will be the case during Ajax - // rebuilds of this form. - if (isset($form_state['values'])) { - $path = array_merge($element['#field_parents'], array($field['field_name'], $langcode)); - $path_exists = FALSE; - $values = drupal_array_get_nested_value($form_state['values'], $path, $path_exists); - if ($path_exists) { - $items = $values; - drupal_array_set_nested_value($form_state['values'], $path, NULL); - } - } + // Determine the number of widgets to display. + switch ($field['cardinality']) { + case FIELD_CARDINALITY_UNLIMITED: + $field_state = field_form_get_state($element['#field_parents'], $field['field_name'], $langcode, $form_state); + $max = $field_state['items_count']; + break; - foreach ($items as $delta => $item) { - $items[$delta] = array_merge($defaults, $items[$delta]); - // Remove any items from being displayed that are not needed. - if ($items[$delta]['fid'] == 0) { - unset($items[$delta]); - } + default: + $max = $field['cardinality'] - 1; + break; } - // Re-index deltas after removing empty items. - $items = array_values($items); - - // Update order according to weight. - $items = _field_sort_items($field, $items); - // Essentially we use the managed_file type, extended with some enhancements. $element_info = element_info('managed_file'); $element += array( @@ -495,18 +481,18 @@ function file_field_widget_form(&$form, &$form_state, $field, $instance, $langco } else { // If there are multiple values, add an element for each existing one. - $delta = -1; - foreach ($items as $delta => $item) { + for ($delta = 0; $delta < $max; $delta++) { $elements[$delta] = $element; - $elements[$delta]['#default_value'] = $item; + $elements[$delta]['#default_value'] = isset($items[$delta]) ? $items[$delta] : $defaults; $elements[$delta]['#weight'] = $delta; + $elements[$delta]['#delta'] = $delta; } // And then add one more empty row for new uploads. - $delta++; - if ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED || $delta < $field['cardinality']) { + if (($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED || $delta < $field['cardinality']) && empty($form_state['programmed'])) { $elements[$delta] = $element; $elements[$delta]['#default_value'] = $defaults; $elements[$delta]['#weight'] = $delta; + $elements[$delta]['#delta'] = $delta; $elements[$delta]['#required'] = ($element['#required'] && $delta == 0); } // The group of elements all-together need some extra functionality @@ -754,6 +740,38 @@ function file_field_widget_submit($form, &$form_state) { // so nothing is lost in doing this. $parents = array_slice($form_state['triggering_element']['#parents'], 0, -2); drupal_array_set_nested_value($form_state['input'], $parents, NULL); + + + $button = $form_state['triggering_element']; + + // Go one level up in the form, to the widgets container. + $element = drupal_array_get_nested_value($form, array_slice($button['#array_parents'], 0, -1)); + $field_name = $element['#field_name']; + $langcode = $element['#language']; + $parents = $element['#field_parents']; + + $submitted_values = drupal_array_get_nested_value($form_state['values'], array_slice($button['#array_parents'], 0, -2)); + $count = 0; + foreach ($submitted_values as $delta => $submitted_value) { + if ($submitted_value['fid']) { + $count++; + } + else { + unset($submitted_values[$delta]); + } + } + + // Re-index deltas after removing empty items. + $submitted_values = array_values($submitted_values); + + // Update form_state values and input. + form_set_value($element, $submitted_values, $form_state); + drupal_array_set_nested_value($form_state['values'], array_slice($button['#array_parents'], 0, -2), $submitted_values); + + // Update items count. + $field_state = field_form_get_state($parents, $field_name, $langcode, $form_state); + $field_state['items_count'] = $count; + field_form_set_state($parents, $field_name, $langcode, $form_state, $field_state); } /** diff --git a/modules/file/tests/file.test b/modules/file/tests/file.test index 99f95b4..d7a5378 100644 --- a/modules/file/tests/file.test +++ b/modules/file/tests/file.test @@ -370,7 +370,7 @@ class FileFieldWidgetTestCase extends FileFieldTestCase { } /** - * Tests upload and remove buttons, with and without Ajax, for a multi-valued File field. + * Tests upload and remove buttons, with and without Ajax, for multiple multi-valued File field. */ function testMultiValuedWidget() { // Use 'page' instead of 'article', so that the 'article' image field does @@ -379,10 +379,16 @@ class FileFieldWidgetTestCase extends FileFieldTestCase { // using a custom node type. $type_name = 'page'; $field_name = strtolower($this->randomName()); + $field_name2 = strtolower($this->randomName()); $this->createFileField($field_name, $type_name, array('cardinality' => 3)); + $this->createFileField($field_name2, $type_name, array('cardinality' => 3)); + $field = field_info_field($field_name); $instance = field_info_instance('node', $field_name, $type_name); + $field2 = field_info_field($field_name2); + $instance2 = field_info_instance('node', $field_name2, $type_name); + $test_file = $this->getTestFile('text'); foreach (array('nojs', 'js') as $type) { @@ -392,13 +398,15 @@ class FileFieldWidgetTestCase extends FileFieldTestCase { // @todo This is only testing a non-Ajax upload, because drupalPostAJAX() // does not yet emulate jQuery's file upload. $this->drupalGet("node/add/$type_name"); - for ($delta = 0; $delta < 3; $delta++) { - $edit = array('files[' . $field_name . '_' . LANGUAGE_NONE . '_' . $delta . ']' => drupal_realpath($test_file->uri)); - // If the Upload button doesn't exist, drupalPost() will automatically - // fail with an assertion message. - $this->drupalPost(NULL, $edit, t('Upload')); + foreach (array($field_name, $field_name2) as $each_field_name) { + for ($delta = 0; $delta < 3; $delta++) { + $edit = array('files[' . $each_field_name . '_' . LANGUAGE_NONE . '_' . $delta . ']' => drupal_realpath($test_file->uri)); + // If the Upload button doesn't exist, drupalPost() will automatically + // fail with an assertion message. + $this->drupalPost(NULL, $edit, t('Upload')); + } } - $this->assertNoFieldByXpath('//input[@type="submit"]', t('Upload'), t('After uploading 3 files, the "Upload" button is no longer displayed.')); + $this->assertNoFieldByXpath('//input[@type="submit"]', t('Upload'), t('After uploading 3 files for each field, the "Upload" button is no longer displayed.')); // Test clicking each "Remove" button. For extra robustness, test them out // of sequential order. They are 0-indexed, and get renumbered after each @@ -406,14 +414,14 @@ class FileFieldWidgetTestCase extends FileFieldTestCase { // - First remove the 2nd file. // - Then remove what is then the 2nd file (was originally the 3rd file). // - Then remove the first file. - $num_expected_remove_buttons = 3; + $num_expected_remove_buttons = 6; foreach (array(1, 1, 0) as $delta) { // Ensure we have the expected number of Remove buttons, and that they // are numbered sequentially. $buttons = $this->xpath('//input[@type="submit" and @value="Remove"]'); $this->assertTrue(is_array($buttons) && count($buttons) === $num_expected_remove_buttons, t('There are %n "Remove" buttons displayed (JSMode=%type).', array('%n' => $num_expected_remove_buttons, '%type' => $type))); foreach ($buttons as $i => $button) { - $this->assertIdentical((string) $button['name'], $field_name . '_' . LANGUAGE_NONE . '_' . $i . '_remove_button'); + $this->assertIdentical((string) $button['name'], ($i > 2 ? $field_name2 : $field_name) . '_' . LANGUAGE_NONE . '_' . $i % 3 . '_remove_button'); } // "Click" the remove button (emulating either a nojs or js submission). @@ -443,9 +451,9 @@ class FileFieldWidgetTestCase extends FileFieldTestCase { $num_expected_remove_buttons--; // Ensure we have a single Upload button, and that it is numbered - // sequentially after the Remove buttons. + // according to the deleted element. $buttons = $this->xpath('//input[@type="submit" and @value="Upload"]'); - $this->assertTrue(is_array($buttons) && count($buttons) == 1 && ((string) $buttons[0]['name'] === ($field_name . '_' . LANGUAGE_NONE . '_' . $num_expected_remove_buttons . '_upload_button')), t('After removing a file, an "Upload" button is displayed (JSMode=%type).')); + $this->assertTrue(is_array($buttons) && count($buttons) == 1 && ((string) $buttons[0]['name'] === ($field_name . '_' . LANGUAGE_NONE . '_' . ($num_expected_remove_buttons - 3) . '_upload_button')), t('After removing a file, an "Upload" button is displayed (JSMode=%type).', array('%type' => $type))); } // Ensure the page now has no Remove buttons. diff --git a/modules/simpletest/drupal_web_test_case.php b/modules/simpletest/drupal_web_test_case.php index a0cd114..19e11d0 100644 --- a/modules/simpletest/drupal_web_test_case.php +++ b/modules/simpletest/drupal_web_test_case.php @@ -2237,7 +2237,7 @@ class DrupalWebTestCase extends DrupalTestCase { break; case 'submit': case 'image': - if (isset($submit) && $submit == $value) { + if (isset($submit) && $submit == $value && !$submit_matches) { $post[$name] = $value; $submit_matches = TRUE; }