diff --git a/core/includes/file.inc b/core/includes/file.inc index 14c2850..91a9b4c 100644 --- a/core/includes/file.inc +++ b/core/includes/file.inc @@ -1020,14 +1020,14 @@ function file_unmanaged_delete_recursive($path, $callback = NULL) { } /** - * Saves a file upload to a new location. + * Saves file uploads to a new location. * - * The file will be added to the {file_managed} table as a temporary file. + * The files will be added to the {file_managed} table as temporary files. * Temporary files are periodically cleaned. Use file_usage()->add() to register * the usage of the file which will automatically mark it as permanent. * * @param $source - * A string specifying the filepath or URI of the uploaded file to save. + * A string specifying the filepath or URI of the uploaded files to save. * @param $validators * An optional, associative array of callback functions used to validate the * file. See file_validate() for a full discussion of the array format. @@ -1041,6 +1041,8 @@ function file_unmanaged_delete_recursive($path, $callback = NULL) { * A string containing the URI $source should be copied to. * This must be a stream wrapper URI. If this value is omitted, Drupal's * temporary files scheme will be used ("temporary://"). + * @param $delta + * Delta of the file to save or NULL to save all files. Defaults to NULL. * @param $replace * Replace behavior when the destination file already exists: * - FILE_EXISTS_REPLACE: Replace the existing file. @@ -1049,181 +1051,201 @@ function file_unmanaged_delete_recursive($path, $callback = NULL) { * - FILE_EXISTS_ERROR: Do nothing and return FALSE. * * @return - * An object containing the file information if the upload succeeded, FALSE - * in the event of an error, or NULL if no file was uploaded. The - * documentation for the "File interface" group, which you can find under + * Function returns array of files or a single file object if $delta + * != NULL. Each file object contains the file information if the + * upload succeeded or FALSE in the event of an error. Function + * returns NULL if no file was uploaded. + * + * The documentation for the "File interface" group, which you can find under * Related topics, or the header at the top of this file, documents the * components of a file entity. In addition to the standard components, * this function adds: * - source: Path to the file before it is moved. * - destination: Path to the file after it is moved (same as 'uri'). */ -function file_save_upload($source, $validators = array(), $destination = FALSE, $replace = FILE_EXISTS_RENAME) { +function file_save_upload($source, $validators = array(), $destination = FALSE, $delta = NULL, $replace = FILE_EXISTS_RENAME) { global $user; static $upload_cache; // Return cached objects without processing since the file will have - // already been processed and the paths in _FILES will be invalid. + // already been processed and the paths in $_FILES will be invalid. if (isset($upload_cache[$source])) { + if (isset($delta)) { + return $upload_cache[$source][$delta]; + } return $upload_cache[$source]; } // Make sure there's an upload to process. - if (empty($_FILES['files']['name'][$source])) { + if (empty($_FILES['files']['name'][$source][0])) { return NULL; } - // Check for file upload errors and return FALSE if a lower level system - // error occurred. For a complete list of errors: - // See http://php.net/manual/features.file-upload.errors.php. - switch ($_FILES['files']['error'][$source]) { - case UPLOAD_ERR_INI_SIZE: - case UPLOAD_ERR_FORM_SIZE: - drupal_set_message(t('The file %file could not be saved because it exceeds %maxsize, the maximum allowed size for uploads.', array('%file' => $_FILES['files']['name'][$source], '%maxsize' => format_size(file_upload_max_size()))), 'error'); - return FALSE; + $files = array(); + foreach ($_FILES['files']['name'][$source] as $i => $name) { + // Check for file upload errors and return FALSE for this file if a lower + // level system error occurred. For a complete list of errors: + // See http://php.net/manual/features.file-upload.errors.php. + switch ($_FILES['files']['error'][$source][$i]) { + case UPLOAD_ERR_INI_SIZE: + case UPLOAD_ERR_FORM_SIZE: + drupal_set_message(t('The file %file could not be saved because it exceeds %maxsize, the maximum allowed size for uploads.', array('%file' => $name, '%maxsize' => format_size(file_upload_max_size()))), 'error'); + $files[$i] = FALSE; + continue; - case UPLOAD_ERR_PARTIAL: - case UPLOAD_ERR_NO_FILE: - drupal_set_message(t('The file %file could not be saved because the upload did not complete.', array('%file' => $_FILES['files']['name'][$source])), 'error'); - return FALSE; + case UPLOAD_ERR_PARTIAL: + case UPLOAD_ERR_NO_FILE: + drupal_set_message(t('The file %file could not be saved because the upload did not complete.', array('%file' => $name)), 'error'); + $files[$i] = FALSE; + continue; - case UPLOAD_ERR_OK: - // Final check that this is a valid upload, if it isn't, use the - // default error handler. - if (is_uploaded_file($_FILES['files']['tmp_name'][$source])) { - break; - } + case UPLOAD_ERR_OK: + // Final check that this is a valid upload, if it isn't, use the + // default error handler. + if (is_uploaded_file($_FILES['files']['tmp_name'][$source][$i])) { + break; + } - // Unknown error - default: - drupal_set_message(t('The file %file could not be saved. An unknown error has occurred.', array('%file' => $_FILES['files']['name'][$source])), 'error'); - return FALSE; - } - // Begin building file entity. - $values = array( - 'uid' => $user->uid, - 'status' => 0, - 'filename' => trim(drupal_basename($_FILES['files']['name'][$source]), '.'), - 'uri' => $_FILES['files']['tmp_name'][$source], - 'filesize' => $_FILES['files']['size'][$source], - ); - $values['filemime'] = file_get_mimetype($values['filename']); - $file = entity_create('file', $values); - - $extensions = ''; - if (isset($validators['file_validate_extensions'])) { - if (isset($validators['file_validate_extensions'][0])) { - // Build the list of non-munged extensions if the caller provided them. - $extensions = $validators['file_validate_extensions'][0]; + // Unknown error + default: + drupal_set_message(t('The file %file could not be saved. An unknown error has occurred.', array('%file' => $name)), 'error'); + $files[$i] = FALSE; + continue; + + } + // Begin building file entity. + $values = array( + 'uid' => $user->uid, + 'status' => 0, + 'filename' => trim(drupal_basename($name, '.')), + 'uri' => $_FILES['files']['tmp_name'][$source][$i], + 'filesize' => $_FILES['files']['size'][$source][$i], + ); + $values['filemime'] = file_get_mimetype($values['filename']); + $file = entity_create('file', $values); + + $extensions = ''; + if (isset($validators['file_validate_extensions'])) { + if (isset($validators['file_validate_extensions'][0])) { + // Build the list of non-munged extensions if the caller provided them. + $extensions = $validators['file_validate_extensions'][0]; + } + else { + // If 'file_validate_extensions' is set and the list is empty then the + // caller wants to allow any extension. In this case we have to remove the + // validator or else it will reject all extensions. + unset($validators['file_validate_extensions']); + } } else { - // If 'file_validate_extensions' is set and the list is empty then the - // caller wants to allow any extension. In this case we have to remove the - // validator or else it will reject all extensions. - unset($validators['file_validate_extensions']); + // No validator was provided, so add one using the default list. + // Build a default non-munged safe list for file_munge_filename(). + $extensions = 'jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp'; + $validators['file_validate_extensions'] = array(); + $validators['file_validate_extensions'][0] = $extensions; } - } - else { - // No validator was provided, so add one using the default list. - // Build a default non-munged safe list for file_munge_filename(). - $extensions = 'jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp'; - $validators['file_validate_extensions'] = array(); - $validators['file_validate_extensions'][0] = $extensions; - } - - if (!empty($extensions)) { - // Munge the filename to protect against possible malicious extension hiding - // within an unknown file type (ie: filename.html.foo). - $file->filename = file_munge_filename($file->filename, $extensions); - } - - // Rename potentially executable files, to help prevent exploits (i.e. will - // rename filename.php.foo and filename.php to filename.php.foo.txt and - // filename.php.txt, respectively). Don't rename if 'allow_insecure_uploads' - // evaluates to TRUE. - if (!config('system.file')->get('allow_insecure_uploads') && preg_match('/\.(php|pl|py|cgi|asp|js)(\.|$)/i', $file->filename) && (substr($file->filename, -4) != '.txt')) { - $file->filemime = 'text/plain'; - $file->uri .= '.txt'; - $file->filename .= '.txt'; - // The .txt extension may not be in the allowed list of extensions. We have - // to add it here or else the file upload will fail. + if (!empty($extensions)) { - $validators['file_validate_extensions'][0] .= ' txt'; - drupal_set_message(t('For security reasons, your upload has been renamed to %filename.', array('%filename' => $file->filename))); + // Munge the filename to protect against possible malicious extension + // hiding within an unknown file type (ie: filename.html.foo). + $file->filename = file_munge_filename($file->filename, $extensions); } - } - // If the destination is not provided, use the temporary directory. - if (empty($destination)) { - $destination = 'temporary://'; - } + // Rename potentially executable files, to help prevent exploits (i.e. will + // rename filename.php.foo and filename.php to filename.php.foo.txt and + // filename.php.txt, respectively). Don't rename if 'allow_insecure_uploads' + // evaluates to TRUE. + if (!config('system.file')->get('allow_insecure_uploads') && preg_match('/\.(php|pl|py|cgi|asp|js)(\.|$)/i', $file->filename) && (substr($file->filename, -4) != '.txt')) { + $file->filemime = 'text/plain'; + $file->uri .= '.txt'; + $file->filename .= '.txt'; + // The .txt extension may not be in the allowed list of extensions. We have + // to add it here or else the file upload will fail. + if (!empty($extensions)) { + $validators['file_validate_extensions'][0] .= ' txt'; + drupal_set_message(t('For security reasons, your upload has been renamed to %filename.', array('%filename' => $file->filename))); + } + } - // Assert that the destination contains a valid stream. - $destination_scheme = file_uri_scheme($destination); - if (!file_stream_wrapper_valid_scheme($destination_scheme)) { - drupal_set_message(t('The file could not be uploaded because the destination %destination is invalid.', array('%destination' => $destination)), 'error'); - return FALSE; - } + // If the destination is not provided, use the temporary directory. + if (empty($destination)) { + $destination = 'temporary://'; + } - $file->source = $source; - // A URI may already have a trailing slash or look like "public://". - if (substr($destination, -1) != '/') { - $destination .= '/'; - } - $file->destination = file_destination($destination . $file->filename, $replace); - // If file_destination() returns FALSE then $replace == FILE_EXISTS_ERROR and - // there's an existing file so we need to bail. - if ($file->destination === FALSE) { - drupal_set_message(t('The file %source could not be uploaded because a file by that name already exists in the destination %directory.', array('%source' => $source, '%directory' => $destination)), 'error'); - return FALSE; - } + // Assert that the destination contains a valid stream. + $destination_scheme = file_uri_scheme($destination); + if (!file_stream_wrapper_valid_scheme($destination_scheme)) { + drupal_set_message(t('The file could not be uploaded because the destination %destination is invalid.', array('%destination' => $destination)), 'error'); + $files[$i] = FALSE; + continue; + } + + $file->source = $source; + // A URI may already have a trailing slash or look like "public://". + if (substr($destination, -1) != '/') { + $destination .= '/'; + } + $file->destination = file_destination($destination . $file->filename, $replace); + // If file_destination() returns FALSE then $replace == FILE_EXISTS_ERROR and + // there's an existing file so we need to bail. + if ($file->destination === FALSE) { + drupal_set_message(t('The file %source could not be uploaded because a file by that name already exists in the destination %directory.', array('%source' => $source, '%directory' => $destination)), 'error'); + $files[$i] = FALSE; + continue; + } - // Add in our check of the the file name length. - $validators['file_validate_name_length'] = array(); + // Add in our check of the the file name length. + $validators['file_validate_name_length'] = array(); - // Call the validation functions specified by this function's caller. - $errors = file_validate($file, $validators); + // Call the validation functions specified by this function's caller. + $errors = file_validate($file, $validators); - // Check for errors. - if (!empty($errors)) { - $message = t('The specified file %name could not be uploaded.', array('%name' => $file->filename)); - if (count($errors) > 1) { - $message .= theme('item_list', array('items' => $errors)); + // Check for errors. + if (!empty($errors)) { + $message = t('The specified file %name could not be uploaded.', array('%name' => $file->filename)); + if (count($errors) > 1) { + $message .= theme('item_list', array('items' => $errors)); + } + else { + $message .= ' ' . array_pop($errors); + } + form_set_error($source, $message); + $files[$i] = FALSE; + continue; } - else { - $message .= ' ' . array_pop($errors); + + // Move uploaded files from PHP's upload_tmp_dir to Drupal's temporary + // directory. This overcomes open_basedir restrictions for future file + // operations. + $file->uri = $file->destination; + if (!drupal_move_uploaded_file($_FILES['files']['tmp_name'][$source][$i], $file->uri)) { + form_set_error($source, t('File upload error. Could not move uploaded file.')); + watchdog('file', 'Upload error. Could not move uploaded file %file to destination %destination.', array('%file' => $file->filename, '%destination' => $file->uri)); + $files[$i] = FALSE; + continue; } - form_set_error($source, $message); - return FALSE; - } - // Move uploaded files from PHP's upload_tmp_dir to Drupal's temporary - // directory. This overcomes open_basedir restrictions for future file - // operations. - $file->uri = $file->destination; - if (!drupal_move_uploaded_file($_FILES['files']['tmp_name'][$source], $file->uri)) { - form_set_error($source, t('File upload error. Could not move uploaded file.')); - watchdog('file', 'Upload error. Could not move uploaded file %file to destination %destination.', array('%file' => $file->filename, '%destination' => $file->uri)); - return FALSE; - } + // Set the permissions on the new file. + drupal_chmod($file->uri); - // Set the permissions on the new file. - drupal_chmod($file->uri); - - // If we are replacing an existing file re-use its database record. - if ($replace == FILE_EXISTS_REPLACE) { - $existing_files = entity_load_multiple_by_properties('file', array('uri' => $file->uri)); - if (count($existing_files)) { - $existing = reset($existing_files); - $file->fid = $existing->fid; + // If we are replacing an existing file re-use its database record. + if ($replace == FILE_EXISTS_REPLACE) { + $existing_files = entity_load_multiple_by_properties('file', array('uri' => $file->uri)); + if (count($existing_files)) { + $existing = reset($existing_files); + $file->fid = $existing->fid; + } } + + // If we made it this far it's safe to record this file in the database. + $file->save(); + $files[$i] = $file; } - // If we made it this far it's safe to record this file in the database. - $file->save(); - // Add file to the cache. - $upload_cache[$source] = $file; - return $file; + // Add files to the cache. + $upload_cache[$source] = $files; + + return isset($delta) ? $files[$delta] : $files; } /** diff --git a/core/includes/form.inc b/core/includes/form.inc index 27f450e..68fb0d9 100644 --- a/core/includes/form.inc +++ b/core/includes/form.inc @@ -4621,6 +4621,17 @@ function form_pre_render_file($element) { } /** + * Processes a file upload element, make use of #multiple if present. + */ +function form_process_file($element) { + if ($element['#multiple'] == TRUE) { + $element['#attributes'] = array('multiple' => 'multiple'); + } + $element['#name'] .= '[]'; + return $element; +} + +/** * Returns HTML for a form element. * * Each form element is wrapped in a DIV container having the following CSS diff --git a/core/modules/aggregator/aggregator.admin.inc b/core/modules/aggregator/aggregator.admin.inc index 632f1ec..055ed19 100644 --- a/core/modules/aggregator/aggregator.admin.inc +++ b/core/modules/aggregator/aggregator.admin.inc @@ -217,7 +217,7 @@ function aggregator_form_opml_validate($form, &$form_state) { function aggregator_form_opml_submit($form, &$form_state) { $data = ''; $validators = array('file_validate_extensions' => array('opml xml')); - if ($file = file_save_upload('upload', $validators)) { + if ($file = file_save_upload('upload', $validators, FALSE, 0)) { $data = file_get_contents($file->uri); } else { diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Tests/ImportOpmlTest.php b/core/modules/aggregator/lib/Drupal/aggregator/Tests/ImportOpmlTest.php index 7a9f463..631bc6e 100644 --- a/core/modules/aggregator/lib/Drupal/aggregator/Tests/ImportOpmlTest.php +++ b/core/modules/aggregator/lib/Drupal/aggregator/Tests/ImportOpmlTest.php @@ -53,7 +53,7 @@ function openImportForm() { $this->drupalGet('admin/config/services/aggregator/add/opml'); $this->assertText('A single OPML document may contain a collection of many feeds.', 'Found OPML help text.'); - $this->assertField('files[upload]', 'Found file upload field.'); + $this->assertField('files[upload][]', 'Found file upload field.'); $this->assertField('remote', 'Found Remote URL field.'); $this->assertField('refresh', '', 'Found Refresh field.'); $this->assertFieldByName("category[$cid]", $cid, 'Found category field.'); @@ -71,7 +71,7 @@ function validateImportFormFields() { $path = $this->getEmptyOpml(); $edit = array( - 'files[upload]' => $path, + 'files[upload][]' => $path, 'remote' => file_create_url($path), ); $this->drupalPost('admin/config/services/aggregator/add/opml', $edit, t('Import')); @@ -91,7 +91,7 @@ function validateImportFormFields() { function submitImportForm() { $before = db_query('SELECT COUNT(*) FROM {aggregator_feed}')->fetchField(); - $form['files[upload]'] = $this->getInvalidOpml(); + $form['files[upload][]'] = $this->getInvalidOpml(); $this->drupalPost('admin/config/services/aggregator/add/opml', $form, t('Import')); $this->assertText(t('No new feed has been added.'), 'Attempting to upload invalid XML.'); @@ -119,7 +119,7 @@ function submitImportForm() { $feeds[1] = $this->getFeedEditArray(); $feeds[2] = $this->getFeedEditArray(); $edit = array( - 'files[upload]' => $this->getValidOpml($feeds), + 'files[upload][]' => $this->getValidOpml($feeds), 'refresh' => '900', 'category[1]' => $category, ); diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentPreviewTest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentPreviewTest.php index f898ab2..5e4885b 100644 --- a/core/modules/comment/lib/Drupal/comment/Tests/CommentPreviewTest.php +++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentPreviewTest.php @@ -49,7 +49,7 @@ function testCommentPreview() { $test_signature = $this->randomName(); $edit['signature[value]'] = '' . $test_signature. ''; $image = current($this->drupalGetTestFiles('image')); - $edit['files[user_picture_und_0]'] = drupal_realpath($image->uri); + $edit['files[user_picture_und_0][]'] = drupal_realpath($image->uri); $this->drupalPost('user/' . $this->web_user->uid . '/edit', $edit, t('Save')); // As the web user, fill in the comment form and preview the comment. diff --git a/core/modules/file/file.field.inc b/core/modules/file/file.field.inc index e3a7d7c..0854b9f 100644 --- a/core/modules/file/file.field.inc +++ b/core/modules/file/file.field.inc @@ -388,7 +388,7 @@ function file_field_widget_value($element, $input = FALSE, $form_state) { // Ensure that all the required properties are returned even if empty. $return += array( - 'fid' => 0, + 'fids' => array(), 'display' => 1, 'description' => '', ); @@ -397,6 +397,47 @@ function file_field_widget_value($element, $input = FALSE, $form_state) { } /** + * Validation callback for upload element on file widget. Checks if + * user has uploaded more files than allowed. + * + * This validator is used only when cardinality not set to 1 or unlimited. + */ +function file_field_widget_multiple_count_validate($element, &$form_state, $form) { + $parents = $element['#array_parents']; + $values = NestedArray::getValue($form_state['values'], $parents); + + array_pop($parents); + $current = count(element_children(NestedArray::getValue($form, $parents))) - 1; + + $field = field_info_field($element['#field_name']); + $uploaded = count($values['fids']); + $count = $uploaded + $current; + if ($count > $field['cardinality']) { + $keep = $uploaded - $count + $field['cardinality']; + $removed_files = array_slice($values['fids'], $keep); + $removed_names = array(); + foreach ($removed_files as $fid) { + $file = file_load($fid); + $removed_names[] = $file->filename; + } + drupal_set_message( + t( + 'Field %field can only hold @max values but there were @count uploaded. The following files have been omitted as a result: %list.', + array( + '%field' => $field['field_name'], + '@max' => $field['cardinality'], + '@count' => $keep, + '%list' => implode(', ', $removed_names), + ) + ), + 'warning' + ); + $values['fids'] = array_slice($values['fids'], 0, $keep); + NestedArray::setValue($form_state['values'], $element['#array_parents'], $values); + } +} + +/** * Render API callback: Processes a file_generic field element. * * Expands the file_generic type to include the description and display fields. @@ -405,7 +446,7 @@ function file_field_widget_value($element, $input = FALSE, $form_state) { */ function file_field_widget_process($element, &$form_state, $form) { $item = $element['#value']; - $item['fid'] = $element['fid']['#value']; + $item['fids'] = $element['fids']['#value']; $field = field_widget_field($element, $form_state); $instance = field_widget_instance($element, $form_state); @@ -414,9 +455,9 @@ function file_field_widget_process($element, &$form_state, $form) { $element['#theme'] = 'file_widget'; // Add the display field if enabled. - if (!empty($field['settings']['display_field']) && $item['fid']) { + if (!empty($field['settings']['display_field']) && $item['fids']) { $element['display'] = array( - '#type' => empty($item['fid']) ? 'hidden' : 'checkbox', + '#type' => empty($item['fids']) ? 'hidden' : 'checkbox', '#title' => t('Include file in display'), '#value' => isset($item['display']) ? $item['display'] : $field['settings']['display_default'], '#attributes' => array('class' => array('file-display')), @@ -430,7 +471,7 @@ function file_field_widget_process($element, &$form_state, $form) { } // Add the description field if enabled. - if (!empty($instance['settings']['description_field']) && $item['fid']) { + if (!empty($instance['settings']['description_field']) && $item['fids']) { $config = config('file.settings'); $element['description'] = array( '#type' => $config->get('description.type'), @@ -562,13 +603,25 @@ function file_field_widget_submit($form, &$form_state) { $submitted_values = NestedArray::getValue($form_state['values'], array_slice($button['#array_parents'], 0, -2)); foreach ($submitted_values as $delta => $submitted_value) { - if (!$submitted_value['fid']) { + if (empty($submitted_value['fids'])) { unset($submitted_values[$delta]); } } + // If there are more files uploaded via the same widget, we + // have to separate them, as we display each file in it's + // own widget. + $new_values = array(); + foreach ($submitted_values as $delta => $submitted_value) { + foreach ($submitted_value['fids'] as $fid) { + $new_value = $submitted_value; + $new_value['fids'] = array($fid); + $new_values[] = $new_value; + } + } + // Re-index deltas after removing empty items. - $submitted_values = array_values($submitted_values); + $submitted_values = array_values($new_values); // Update form_state values. NestedArray::setValue($form_state['values'], array_slice($button['#array_parents'], 0, -2), $submitted_values); @@ -594,9 +647,10 @@ function theme_file_widget($variables) { // The "form-managed-file" class is required for proper Ajax functionality. $output .= '
'; - if ($element['fid']['#value'] != 0) { + if (!empty($element['fids']['#value'])) { // Add the file size after the file name. - $element['filename']['#markup'] .= ' (' . format_size($element['#file']->filesize) . ') '; + $file = reset($element['#files']); + $element['file_' . $file->fid]['filename']['#markup'] .= ' (' . format_size($file->filesize) . ') '; } $output .= drupal_render_children($element); $output .= '
'; @@ -643,7 +697,7 @@ function theme_file_widget_multiple($variables) { $rows = array(); foreach ($widgets as $key => &$widget) { // Save the uploading row for last. - if ($widget['#file'] == FALSE) { + if (empty($widget['#files'])) { $widget['#title'] = $element['#file_upload_title']; $widget['#description'] = $element['#file_upload_description']; continue; @@ -725,6 +779,7 @@ function theme_file_widget_multiple($variables) { function theme_file_upload_help($variables) { $description = $variables['description']; $upload_validators = $variables['upload_validators']; + $cardinality = $variables['cardinality']; $descriptions = array(); @@ -737,6 +792,14 @@ function theme_file_upload_help($variables) { if (isset($upload_validators['file_validate_extensions'])) { $descriptions[] = t('Allowed file types: !extensions.', array('!extensions' => '' . check_plain($upload_validators['file_validate_extensions'][0]) . '')); } + if (isset($cardinality)) { + if ($cardinality == -1) { + $descriptions[] = t('Unlimited number of files can be uploaded to this field.'); + } + else { + $descriptions[] = format_plural($cardinality, 'This field can store only one file.', 'This field can store at most @count files.'); + } + } if (isset($upload_validators['file_validate_image_resolution'])) { $max = $upload_validators['file_validate_image_resolution'][0]; $min = $upload_validators['file_validate_image_resolution'][1]; diff --git a/core/modules/file/file.install b/core/modules/file/file.install index 27a2438..19c2bd9 100644 --- a/core/modules/file/file.install +++ b/core/modules/file/file.install @@ -245,3 +245,32 @@ function file_update_8000() { 'file_icon_directory'=>'icon.directory', )); } + +/** +* Comvert image field's default image configuration to the new format. +*/ +function file_update_8001() { + if (module_exists('field_sql_storage')) { + $fields = _update_7000_field_read_fields(array('type' => 'image')); + foreach ($fields as $field) { + if (!empty($field['settings']['default_image'])) { + $field['settings']['default_image'] = array($field['settings']['default_image']); + } + else { + $field['settings']['default_image'] = array(); + } + field_update_field($field); + + $instances = field_read_instances(array('field_name' => $field['field_name'])); + foreach ($instances as $instance) { + if (!empty($instance['settings']['default_image'])) { + $instance['settings']['default_image'] = array($instance['settings']['default_image']); + } + else { + $instance['settings']['default_image'] = array(); + } + field_update_instance($instance); + } + } + } +} diff --git a/core/modules/file/file.module b/core/modules/file/file.module index 69fcbf3..51f215e 100644 --- a/core/modules/file/file.module +++ b/core/modules/file/file.module @@ -81,6 +81,7 @@ function file_element_info() { '#upload_validators' => array(), '#upload_location' => NULL, '#size' => 22, + '#multiple' => FALSE, '#extended' => FALSE, '#attached' => array( 'library' => array(array('file','drupal.file')), @@ -610,7 +611,7 @@ function file_theme() { 'variables' => array('items' => NULL), ), 'file_upload_help' => array( - 'variables' => array('description' => NULL, 'upload_validators' => NULL), + 'variables' => array('description' => NULL, 'upload_validators' => NULL, 'cardinality' => NULL), ), ); } @@ -870,11 +871,15 @@ function file_managed_file_process($element, &$form_state, $form) { // Append the '-upload' to the #id so the field label's 'for' attribute // corresponds with the file element. $element['#id'] .= '-upload'; - $fid = isset($element['#value']['fid']) ? $element['#value']['fid'] : 0; + + // This is used some times so let's implode it just once. + $parents_prefix = implode('_', $element['#parents']); + + $fids = isset($element['#value']['fids']) ? $element['#value']['fids'] : array(); // Set some default element properties. $element['#progress_indicator'] = empty($element['#progress_indicator']) ? 'none' : $element['#progress_indicator']; - $element['#file'] = $fid ? file_load($fid) : FALSE; + $element['#files'] = !empty($fids) ? file_load_multiple($fids) : FALSE; $element['#tree'] = TRUE; $ajax_settings = array( @@ -889,7 +894,7 @@ function file_managed_file_process($element, &$form_state, $form) { // Set up the buttons first since we need to check if they were clicked. $element['upload_button'] = array( - '#name' => implode('_', $element['#parents']) . '_upload_button', + '#name' => $parents_prefix . '_upload_button', '#type' => 'submit', '#value' => t('Upload'), '#validate' => array(), @@ -898,26 +903,26 @@ function file_managed_file_process($element, &$form_state, $form) { '#ajax' => $ajax_settings, '#weight' => -5, ); - - // Force the progress indicator for the remove button to be either 'none' or - // 'throbber', even if the upload button is using something else. - $ajax_settings['progress']['type'] = ($element['#progress_indicator'] == 'none') ? 'none' : 'throbber'; - $ajax_settings['progress']['message'] = NULL; - $ajax_settings['effect'] = 'none'; $element['remove_button'] = array( - '#name' => implode('_', $element['#parents']) . '_remove_button', + '#name' => $parents_prefix . '_remove_button', '#type' => 'submit', - '#value' => t('Remove'), + '#value' => $element['#multiple'] ? t('Remove selected') : t('Remove'), '#validate' => array(), '#submit' => array('file_managed_file_submit'), '#limit_validation_errors' => array($element['#parents']), '#ajax' => $ajax_settings, - '#weight' => -5, + '#weight' => 1, ); - $element['fid'] = array( + // Force the progress indicator for the remove button to be either 'none' or + // 'throbber', even if the upload button is using something else. + $ajax_settings['progress']['type'] = ($element['#progress_indicator'] == 'none') ? 'none' : 'throbber'; + $ajax_settings['progress']['message'] = NULL; + $ajax_settings['effect'] = 'none'; + + $element['fids'] = array( '#type' => 'hidden', - '#value' => $fid, + '#value' => $fids, ); // Add progress bar support to the upload if possible. @@ -951,21 +956,32 @@ function file_managed_file_process($element, &$form_state, $form) { // The file upload field itself. $element['upload'] = array( - '#name' => 'files[' . implode('_', $element['#parents']) . ']', + '#name' => 'files[' . $parents_prefix . ']', '#type' => 'file', '#title' => t('Choose a file'), '#title_display' => 'invisible', '#size' => $element['#size'], + '#multiple' => $element['#multiple'], '#theme_wrappers' => array(), '#weight' => -10, ); - if ($fid && $element['#file']) { - $element['filename'] = array( - '#type' => 'markup', - '#markup' => theme('file_link', array('file' => $element['#file'])) . ' ', - '#weight' => -10, - ); + if (!empty($fids) && $element['#files']) { + foreach ($element['#files'] as $delta => $file) { + if ($element['#multiple']) { + $element['file_' . $delta]['selected'] = array( + '#type' => 'checkbox', + '#title' => theme('file_link', array('file' => $file)) . ' ', + ); + } + else { + $element['file_' . $delta]['filename'] = array( + '#type' => 'markup', + '#markup' => theme('file_link', array('file' => $file)) . ' ', + '#weight' => -10, + ); + } + } } // Add the extension list to the page as JavaScript settings. @@ -992,28 +1008,30 @@ function file_managed_file_process($element, &$form_state, $form) { * This function is assigned as a #value_callback in file_element_info(). */ function file_managed_file_value(&$element, $input = FALSE, $form_state = NULL) { - $fid = 0; - - // Find the current value of this field from the form state. - $form_state_fid = $form_state['values']; - foreach ($element['#parents'] as $parent) { - $form_state_fid = isset($form_state_fid[$parent]) ? $form_state_fid[$parent] : 0; - } - - if ($element['#extended'] && isset($form_state_fid['fid'])) { - $fid = $form_state_fid['fid']; - } - elseif (is_numeric($form_state_fid)) { - $fid = $form_state_fid; - } + $fids = array(); + + // Find the current value of this field. + $fids = !empty($input['fids']) ? explode(' ', $input['fids']) : array(); + $fids = array_map( + function($item) { + return (int) $item; + }, + $fids + ); // Process any input and save new uploads. if ($input !== FALSE) { + $input['fids'] = $fids; $return = $input; // Uploads take priority over all other values. - if ($file = file_managed_file_save_upload($element)) { - $fid = $file->fid; + if ($files = file_managed_file_save_upload($element)) { + if ($element['#multiple']) { + $fids = array_merge($fids, array_keys($files)); + } + else { + $fids = array_keys($files); + } } else { // Check for #filefield_value_callback values. @@ -1025,9 +1043,15 @@ function file_managed_file_value(&$element, $input = FALSE, $form_state = NULL) $callback($element, $input, $form_state); } } - // Load file if the FID has changed to confirm it exists. - if (isset($input['fid']) && $file = file_load($input['fid'])) { - $fid = $file->fid; + + // Load files if the FIDs have changed to confirm they exist. + if (!empty($input['fids'])) { + $fids = array(); + foreach ($input['fids'] as $key => $fid) { + if ($file = file_load($fid)) { + $fids[] = $file->fid; + } + } } } } @@ -1035,22 +1059,26 @@ function file_managed_file_value(&$element, $input = FALSE, $form_state = NULL) // If there is no input, set the default value. else { if ($element['#extended']) { - $default_fid = isset($element['#default_value']['fid']) ? $element['#default_value']['fid'] : 0; - $return = isset($element['#default_value']) ? $element['#default_value'] : array('fid' => 0); + $default_fids = isset($element['#default_value']['fids']) ? $element['#default_value']['fids'] : array(); + $return = isset($element['#default_value']) ? $element['#default_value'] : array('fids' => array()); } else { - $default_fid = isset($element['#default_value']) ? $element['#default_value'] : 0; - $return = array('fid' => 0); + $default_fids = isset($element['#default_value']) ? $element['#default_value'] : array(); + $return = array('fids' => array()); } // Confirm that the file exists when used as a default value. - if ($default_fid && $file = file_load($default_fid)) { - $fid = $file->fid; + if (!empty($default_fids)) { + $fids = array(); + foreach ($default_fids as $key => $fid) { + if ($file = file_load($fid)) { + $fids[] = $file->fid; + } + } } } - $return['fid'] = $fid; - + $return['fids'] = $fids; return $return; } @@ -1065,28 +1093,38 @@ function file_managed_file_validate(&$element, &$form_state) { // references. This prevents unmanaged files from being deleted if this // item were to be deleted. $clicked_button = end($form_state['triggering_element']['#parents']); - if ($clicked_button != 'remove_button' && !empty($element['fid']['#value'])) { - if ($file = file_load($element['fid']['#value'])) { - if ($file->status == FILE_STATUS_PERMANENT) { - $references = file_usage()->listUsage($file); - if (empty($references)) { - form_error($element, t('The file used in the !name field may not be referenced.', array('!name' => $element['#title']))); + if ($clicked_button != 'remove_button' && !empty($element['fids']['#value'])) { + $fids = $element['fids']['#value']; + foreach ($fids as $fid) { + if ($file = file_load($fid)) { + if ($file->status == FILE_STATUS_PERMANENT) { + $references = file_usage()->listUsage($file); + if (empty($references)) { + form_error($element, t('The file used in the !name field may not be referenced.', array('!name' => $element['#title']))); + } } } - } - else { - form_error($element, t('The file referenced by the !name field does not exist.', array('!name' => $element['#title']))); + else { + form_error($element, t('The file referenced by the !name field does not exist.', array('!name' => $element['#title']))); + } } } // Check required property based on the FID. - if ($element['#required'] && empty($element['fid']['#value']) && !in_array($clicked_button, array('upload_button', 'remove_button'))) { + if ($element['#required'] && empty($element['fids']['#value']) && !in_array($clicked_button, array('upload_button', 'remove_button'))) { form_error($element['upload'], t('!name field is required.', array('!name' => $element['#title']))); } - // Consolidate the array value of this field to a single FID. + // 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['fid']['#value'], $form_state); + form_set_value($element, $element['fids']['#value'], $form_state); } } @@ -1107,11 +1145,33 @@ function file_managed_file_submit($form, &$form_state) { // 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') { - // 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. - if ($element['#file'] && $element['#file']->status == 0) { - file_delete($element['#file']->fid); + // 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()); + } + + if ($element['#multiple']) { + foreach ($values as $name => $value) { + if (strpos($name, 'file_') === 0 && $value['selected']) { + $fids[] = (int) substr($name, 5); + } + } + } + else { + $fids = $values['fids']; + } + + $values['fids'] = array_diff($values['fids'], $fids); + + foreach ($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. + if ($element['#files'][$fid] && $element['#files'][$fid]->status == 0) { + file_delete($element['#files'][$fid]->fid); + } } // Update both $form_state['values'] and $form_state['input'] to reflect // that the file has been removed, so that the form is rebuilt correctly. @@ -1120,9 +1180,9 @@ function file_managed_file_submit($form, &$form_state) { // 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['fid'] : $element; - form_set_value($values_element, NULL, $form_state); - NestedArray::setValue($form_state['input'], $values_element['#parents'], NULL); + $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'])); } // Set the form to rebuild so that $form is correctly updated in response to @@ -1157,13 +1217,22 @@ function file_managed_file_save_upload($element) { return FALSE; } - if (!$file = file_save_upload($upload_name, $element['#upload_validators'], $destination)) { - watchdog('file', 'The file upload failed. %upload', array('%upload' => $upload_name)); - form_set_error($upload_name, t('The file in the !name field was unable to be uploaded.', array('!name' => $element['#title']))); - return FALSE; + // Save attached files to the database. + if(count(array_filter($_FILES['files']['name'][$upload_name])) > 0) { + if (!$files = file_save_upload($upload_name, $element['#upload_validators'], $destination)) { + watchdog('file', 'The file upload failed. %upload', array('%upload' => $upload_name)); + form_set_error($upload_name, t('Files in the !name field were unable to be uploaded.', array('!name' => $element['#title']))); + return array(); + } + + // Value callback expects FIDs to be keys. + $files = array_filter($files); + $fids = array_map(function($file) {return $file->fid;}, $files); + + return empty($files) ? array() : array_combine($fids, $files); } - return $file; + return array(); } /** @@ -1218,9 +1287,11 @@ function theme_file_managed_file($variables) { */ function file_managed_file_pre_render($element) { // If we already have a file, we don't want to show the upload controls. - if (!empty($element['#value']['fid'])) { - $element['upload']['#access'] = FALSE; - $element['upload_button']['#access'] = FALSE; + if (!empty($element['#value']['fids'])) { + if (!$element['#multiple']) { + $element['upload']['#access'] = FALSE; + $element['upload_button']['#access'] = FALSE; + } } // If we don't already have a file, there is nothing to remove. else { diff --git a/core/modules/file/lib/Drupal/file/Plugin/field/widget/FileWidget.php b/core/modules/file/lib/Drupal/file/Plugin/field/widget/FileWidget.php index 94031b2..f35f605 100644 --- a/core/modules/file/lib/Drupal/file/Plugin/field/widget/FileWidget.php +++ b/core/modules/file/lib/Drupal/file/Plugin/field/widget/FileWidget.php @@ -156,7 +156,7 @@ protected function formMultipleElements(EntityInterface $entity, array $items, $ // field. These are added here so that they may be referenced easily // through a hook_form_alter(). $elements['#file_upload_title'] = t('Add a new file'); - $elements['#file_upload_description'] = theme('file_upload_help', array('description' => '', 'upload_validators' => $elements[0]['#upload_validators'])); + $elements['#file_upload_description'] = theme('file_upload_help', array('description' => '', 'upload_validators' => $elements[0]['#upload_validators'], 'cardinality' => $this->field['cardinality'])); } return $elements; @@ -167,7 +167,7 @@ protected function formMultipleElements(EntityInterface $entity, array $items, $ */ public function formElement(array $items, $delta, array $element, $langcode, array &$form, array &$form_state) { $defaults = array( - 'fid' => 0, + 'fids' => array(), 'display' => !empty($this->field['settings']['display_default']), 'description' => '', ); @@ -187,13 +187,45 @@ public function formElement(array $items, $delta, array $element, $langcode, arr ); $element['#weight'] = $delta; + + // If we just loaded from DB we have to translate value to multivalue file + // widgets. + if (!isset($items[$delta]['fids']) && isset($items[$delta]['fid'])) { + $items[$delta]['fids'][0] = $items[$delta]['fid']; + } $element['#default_value'] = !empty($items[$delta]) ? $items[$delta] : $defaults; - if (empty($element['#default_value']['fid'])) { - $element['#description'] = theme('file_upload_help', array('description' => $element['#description'], 'upload_validators' => $element['#upload_validators'])); + $default_fids = $element['#extended'] ? $element['#default_value']['fids'] : $element['#default_value']; + if (empty($default_fids)) { + $cardinality = isset($this->field['cardinality']) ? $this->field['cardinality'] : 1; + $element['#description'] = theme('file_upload_help', array('description' => $element['#description'], 'upload_validators' => $element['#upload_validators'], 'cardinality' => $cardinality)); + $element['#multiple'] = $cardinality != 1 ? TRUE : FALSE; + if ($cardinality != 1 && $cardinality != -1) { + $element['#element_validate'] = array('file_field_widget_multiple_count_validate'); + } } return $element; } + /** + * Implements Drupal\field\Plugin\Type\Widget\WidgetInterface::massageFormValues(). + */ + public function massageFormValues(array $values, array $form, array &$form_state) { + // Since file upload widget now supports uploads of more than one file at + // a time it always returns an array of fids. We have to translate this to + // a single fid, as field expects single value. + $new_values = array(); + foreach ($values as &$value) { + foreach ($value['fids'] as $fid) { + $new_value = $value; + $new_value['fid'] = $fid; + unset($new_value['fids']); + $new_values[] = $new_value; + } + } + + return $new_values; + } + } diff --git a/core/modules/file/lib/Drupal/file/Tests/FileFieldTestBase.php b/core/modules/file/lib/Drupal/file/Tests/FileFieldTestBase.php index c2d922d..65a89a8 100644 --- a/core/modules/file/lib/Drupal/file/Tests/FileFieldTestBase.php +++ b/core/modules/file/lib/Drupal/file/Tests/FileFieldTestBase.php @@ -148,7 +148,7 @@ function uploadNodeFile($file, $field_name, $nid_or_type, $new_revision = TRUE, } // Attach a file to the node. - $edit['files[' . $field_name . '_' . $langcode . '_0]'] = drupal_realpath($file->uri); + $edit['files[' . $field_name . '_' . $langcode . '_0][]'] = drupal_realpath($file->uri); $this->drupalPost("node/$nid/edit", $edit, t('Save and keep published')); return $nid; @@ -173,7 +173,7 @@ function removeNodeFile($nid, $new_revision = TRUE) { */ function replaceNodeFile($file, $field_name, $nid, $new_revision = TRUE) { $edit = array( - 'files[' . $field_name . '_' . LANGUAGE_NOT_SPECIFIED . '_0]' => drupal_realpath($file->uri), + 'files[' . $field_name . '_' . LANGUAGE_NOT_SPECIFIED . '_0][]' => drupal_realpath($file->uri), 'revision' => (string) (int) $new_revision, ); diff --git a/core/modules/file/lib/Drupal/file/Tests/FileFieldWidgetTest.php b/core/modules/file/lib/Drupal/file/Tests/FileFieldWidgetTest.php index 05b0cad..d65c6d1 100644 --- a/core/modules/file/lib/Drupal/file/Tests/FileFieldWidgetTest.php +++ b/core/modules/file/lib/Drupal/file/Tests/FileFieldWidgetTest.php @@ -113,7 +113,7 @@ function testMultiValuedWidget() { $this->drupalGet("node/add/$type_name"); foreach (array($field_name2, $field_name) as $each_field_name) { for ($delta = 0; $delta < 3; $delta++) { - $edit = array('files[' . $each_field_name . '_' . LANGUAGE_NOT_SPECIFIED . '_' . $delta . ']' => drupal_realpath($test_file->uri)); + $edit = array('files[' . $each_field_name . '_' . LANGUAGE_NOT_SPECIFIED . '_' . $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')); @@ -272,7 +272,7 @@ function testPrivateFileComment() { // Add a comment with a file. $text_file = $this->getTestFile('text'); $edit = array( - 'files[field_' . $name . '_' . LANGUAGE_NOT_SPECIFIED . '_' . 0 . ']' => drupal_realpath($text_file->uri), + 'files[field_' . $name . '_' . LANGUAGE_NOT_SPECIFIED . '_' . 0 . '][]' => drupal_realpath($text_file->uri), 'comment_body[' . LANGUAGE_NOT_SPECIFIED . '][0][value]' => $comment_body = $this->randomName(), ); $this->drupalPost(NULL, $edit, t('Save')); diff --git a/core/modules/file/lib/Drupal/file/Tests/FileManagedFileElementTest.php b/core/modules/file/lib/Drupal/file/Tests/FileManagedFileElementTest.php index 9865314..5b4469c 100644 --- a/core/modules/file/lib/Drupal/file/Tests/FileManagedFileElementTest.php +++ b/core/modules/file/lib/Drupal/file/Tests/FileManagedFileElementTest.php @@ -28,7 +28,7 @@ public static function getInfo() { function testManagedFile() { // Check that $element['#size'] is passed to the child upload element. $this->drupalGet('file/test'); - $this->assertFieldByXpath('//input[@name="files[nested_file]" and @size="13"]', NULL, 'The custom #size attribute is passed to the child upload element.'); + $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']. @@ -40,26 +40,26 @@ function testManagedFile() { // 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.')); + $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)); + $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.')); + $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 id is %fid.', array('%fid' => $last_fid)), t('Empty submission did not change an existing file.')); + $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('files[' . $input_base_name . ']' => drupal_realpath($test_file->uri)); + $edit = array('files[' . $input_base_name . '][]' => drupal_realpath($test_file->uri)); if ($ajax) { $this->drupalPostAJAX(NULL, $edit, $input_base_name . '_upload_button'); } @@ -69,7 +69,7 @@ function testManagedFile() { $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.')); + $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); @@ -80,11 +80,11 @@ function testManagedFile() { $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.')); + $this->assertRaw(t('The file ids are %fids.', array('%fids' => implode(',', array()))), 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)); + $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'); @@ -94,7 +94,7 @@ function testManagedFile() { $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.')); + $this->assertRaw(t('The file ids are %fids.', array('%fids' => implode(',', array()))), t('Submission after file upload and removal was successful.')); } } } diff --git a/core/modules/file/lib/Drupal/file/Tests/SaveUploadTest.php b/core/modules/file/lib/Drupal/file/Tests/SaveUploadTest.php index 90fec9d..ca3a613 100644 --- a/core/modules/file/lib/Drupal/file/Tests/SaveUploadTest.php +++ b/core/modules/file/lib/Drupal/file/Tests/SaveUploadTest.php @@ -53,7 +53,7 @@ function setUp() { // Upload with replace to guarantee there's something there. $edit = array( 'file_test_replace' => FILE_EXISTS_REPLACE, - 'files[file_test_upload]' => drupal_realpath($this->image->uri), + 'files[file_test_upload][]' => drupal_realpath($this->image->uri), ); $this->drupalPost('file-test/upload', $edit, t('Submit')); $this->assertResponse(200, t('Received a 200 response for posted test file.')); @@ -82,7 +82,7 @@ function testNormal() { // Upload a second file. $max_fid_before = db_query('SELECT MAX(fid) AS fid FROM {file_managed}')->fetchField(); $image2 = current($this->drupalGetTestFiles('image')); - $edit = array('files[file_test_upload]' => drupal_realpath($image2->uri)); + $edit = array('files[file_test_upload][]' => drupal_realpath($image2->uri)); $this->drupalPost('file-test/upload', $edit, t('Submit')); $this->assertResponse(200, t('Received a 200 response for posted test file.')); $this->assertRaw(t('You WIN!')); @@ -106,7 +106,7 @@ function testNormal() { $image3_realpath = drupal_realpath($image3->uri); $dir = $this->randomName(); $edit = array( - 'files[file_test_upload]' => $image3_realpath, + 'files[file_test_upload][]' => $image3_realpath, 'file_subdir' => $dir, ); $this->drupalPost('file-test/upload', $edit, t('Submit')); @@ -126,7 +126,7 @@ function testHandleExtension() { $extensions = 'foo'; $edit = array( 'file_test_replace' => FILE_EXISTS_REPLACE, - 'files[file_test_upload]' => drupal_realpath($this->image->uri), + 'files[file_test_upload][]' => drupal_realpath($this->image->uri), 'extensions' => $extensions, ); @@ -146,7 +146,7 @@ function testHandleExtension() { // Now tell file_save_upload() to allow the extension of our test image. $edit = array( 'file_test_replace' => FILE_EXISTS_REPLACE, - 'files[file_test_upload]' => drupal_realpath($this->image->uri), + 'files[file_test_upload][]' => drupal_realpath($this->image->uri), 'extensions' => $extensions, ); @@ -164,7 +164,7 @@ function testHandleExtension() { // Now tell file_save_upload() to allow any extension. $edit = array( 'file_test_replace' => FILE_EXISTS_REPLACE, - 'files[file_test_upload]' => drupal_realpath($this->image->uri), + 'files[file_test_upload][]' => drupal_realpath($this->image->uri), 'allow_all_extensions' => TRUE, ); $this->drupalPost('file-test/upload', $edit, t('Submit')); @@ -185,7 +185,7 @@ function testHandleDangerousFile() { // safety. Also check to make sure its MIME type was changed. $edit = array( 'file_test_replace' => FILE_EXISTS_REPLACE, - 'files[file_test_upload]' => drupal_realpath($this->phpfile->uri), + 'files[file_test_upload][]' => drupal_realpath($this->phpfile->uri), 'is_image_file' => FALSE, 'extensions' => 'php', ); @@ -232,7 +232,7 @@ function testHandleFileMunge() { $extensions = $this->image_extension; $edit = array( - 'files[file_test_upload]' => drupal_realpath($this->image->uri), + 'files[file_test_upload][]' => drupal_realpath($this->image->uri), 'extensions' => $extensions, ); @@ -254,7 +254,7 @@ function testHandleFileMunge() { file_test_reset(); $edit = array( - 'files[file_test_upload]' => drupal_realpath($this->image->uri), + 'files[file_test_upload][]' => drupal_realpath($this->image->uri), 'allow_all_extensions' => TRUE, ); @@ -274,7 +274,7 @@ function testHandleFileMunge() { function testExistingRename() { $edit = array( 'file_test_replace' => FILE_EXISTS_RENAME, - 'files[file_test_upload]' => drupal_realpath($this->image->uri) + 'files[file_test_upload][]' => drupal_realpath($this->image->uri) ); $this->drupalPost('file-test/upload', $edit, t('Submit')); $this->assertResponse(200, t('Received a 200 response for posted test file.')); @@ -290,7 +290,7 @@ function testExistingRename() { function testExistingReplace() { $edit = array( 'file_test_replace' => FILE_EXISTS_REPLACE, - 'files[file_test_upload]' => drupal_realpath($this->image->uri) + 'files[file_test_upload][]' => drupal_realpath($this->image->uri) ); $this->drupalPost('file-test/upload', $edit, t('Submit')); $this->assertResponse(200, t('Received a 200 response for posted test file.')); @@ -306,7 +306,7 @@ function testExistingReplace() { function testExistingError() { $edit = array( 'file_test_replace' => FILE_EXISTS_ERROR, - 'files[file_test_upload]' => drupal_realpath($this->image->uri) + 'files[file_test_upload][]' => drupal_realpath($this->image->uri) ); $this->drupalPost('file-test/upload', $edit, t('Submit')); $this->assertResponse(200, t('Received a 200 response for posted test file.')); diff --git a/core/modules/file/tests/file_module_test.module b/core/modules/file/tests/file_module_test.module index b962e2a..bff3041 100644 --- a/core/modules/file/tests/file_module_test.module +++ b/core/modules/file/tests/file_module_test.module @@ -31,7 +31,7 @@ function file_module_test_menu() { * @see file_module_test_form_submit() * @ingroup forms */ -function file_module_test_form($form, &$form_state, $tree = TRUE, $extended = FALSE, $default_fid = NULL) { +function file_module_test_form($form, &$form_state, $tree = TRUE, $extended = TRUE, $default_fids = NULL, $multiple = NULL) { $form['#tree'] = (bool) $tree; $form['nested']['file'] = array( @@ -41,9 +41,11 @@ function file_module_test_form($form, &$form_state, $tree = TRUE, $extended = FA '#progress_message' => t('Please wait...'), '#extended' => (bool) $extended, '#size' => 13, + '#multiple' => (bool) $multiple, ); - if ($default_fid) { - $form['nested']['file']['#default_value'] = $extended ? array('fid' => $default_fid) : $default_fid; + if ($default_fids) { + $default_fids = explode(',', $default_fids); + $form['nested']['file']['#default_value'] = $extended ? array('fids' => $default_fids) : $default_fids; } $form['textfield'] = array( @@ -64,12 +66,22 @@ function file_module_test_form($form, &$form_state, $tree = TRUE, $extended = FA */ 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']; + $uploads = $form_state['values']['nested']['file']; } else { - $fid = $form['nested']['file']['#extended'] ? $form_state['values']['file']['fid'] : $form_state['values']['file']; + $uploads = $form_state['values']['file']; } - drupal_set_message(t('The file id is %fid.', array('%fid' => $fid))); + + if ($form['nested']['file']['#extended']) { + $uploads = $uploads['fids']; + } + + $fids = array(); + foreach ($uploads as $fid) { + $fids[] = $fid; + } + + drupal_set_message(t('The file ids are %fids.', array('%fids' => implode(',', $fids)))); } /** diff --git a/core/modules/file/tests/file_test/file_test.module b/core/modules/file/tests/file_test/file_test.module index 1d924de..8cff297 100644 --- a/core/modules/file/tests/file_test/file_test.module +++ b/core/modules/file/tests/file_test/file_test.module @@ -126,7 +126,7 @@ function _file_test_form_submit(&$form, &$form_state) { $validators['file_validate_extensions'] = array($form_state['values']['extensions']); } - $file = file_save_upload('file_test_upload', $validators, $destination, $form_state['values']['file_test_replace']); + $file = file_save_upload('file_test_upload', $validators, $destination, 0, $form_state['values']['file_test_replace']); if ($file) { $form_state['values']['file_test_upload'] = $file; drupal_set_message(t('File @filepath was uploaded.', array('@filepath' => $file->uri))); diff --git a/core/modules/image/image.field.inc b/core/modules/image/image.field.inc index 11152ae..699e9aa 100644 --- a/core/modules/image/image.field.inc +++ b/core/modules/image/image.field.inc @@ -18,7 +18,7 @@ function image_field_info() { 'description' => t('This field stores the ID of an image file as an integer value.'), 'settings' => array( 'uri_scheme' => file_default_scheme(), - 'default_image' => 0, + 'default_image' => array(), 'column_groups' => array( 'file' => array( 'label' => t('File'), @@ -44,7 +44,7 @@ function image_field_info() { 'title_field_required' => 0, 'max_resolution' => '', 'min_resolution' => '', - 'default_image' => 0, + 'default_image' => array(), ), 'default_widget' => 'image_image', 'default_formatter' => 'image', @@ -230,7 +230,7 @@ function image_field_prepare_view($entity_type, $entities, $field, $instances, $ // If there are no files specified at all, use the default. foreach ($entities as $id => $entity) { if (empty($items[$id])) { - $fid = 0; + $fid = array(); // Use the default for the instance if one is available. if (!empty($instances[$id]['settings']['default_image'])) { $fid = $instances[$id]['settings']['default_image']; @@ -241,7 +241,7 @@ function image_field_prepare_view($entity_type, $entities, $field, $instances, $ } // Add the default image if one is found. - if ($fid && ($file = file_load($fid))) { + if ($fid && ($file = file_load($fid[0]))) { $items[$id][0] = (array) $file + array( 'is_default' => TRUE, 'alt' => '', @@ -312,16 +312,17 @@ function image_field_is_empty($item, $field) { */ function image_field_widget_process($element, &$form_state, $form) { $item = $element['#value']; - $item['fid'] = $element['fid']['#value']; + $item['fids'] = $element['fids']['#value']; $element['#theme'] = 'image_widget'; $element['#attached']['css'][] = drupal_get_path('module', 'image') . '/image.theme.css'; // Add the image preview. - if ($element['#file'] && $element['#preview_image_style']) { + if (!empty($element['#files']) && $element['#preview_image_style']) { + $file = reset($element['#files']); $variables = array( 'style_name' => $element['#preview_image_style'], - 'uri' => $element['#file']->uri, + 'uri' => $file->uri, ); // Determine image dimensions. @@ -330,7 +331,7 @@ function image_field_widget_process($element, &$form_state, $form) { $variables['height'] = $element['#value']['height']; } else { - $info = image_get_info($element['#file']->uri); + $info = image_get_info($file->uri); if (is_array($info)) { $variables['width'] = $info['width']; @@ -371,7 +372,7 @@ function image_field_widget_process($element, &$form_state, $form) { // @see http://www.gawds.org/show.php?contentid=28 '#maxlength' => 512, '#weight' => -2, - '#access' => (bool) $item['fid'] && $element['#alt_field'], + '#access' => (bool) $item['fids'] && $element['#alt_field'], '#element_validate' => $settings['alt_field_required'] == 1 ? array('_image_field_required_fields_validate') : array(), ); $element['title'] = array( @@ -381,7 +382,7 @@ function image_field_widget_process($element, &$form_state, $form) { '#description' => t('The title is used as a tool tip when the user hovers the mouse over the image.'), '#maxlength' => 1024, '#weight' => -1, - '#access' => (bool) $item['fid'] && $element['#title_field'], + '#access' => (bool) $item['fids'] && $element['#title_field'], '#element_validate' => $settings['alt_field_required'] == 1 ? array('_image_field_required_fields_validate') : array(), ); @@ -436,8 +437,9 @@ function theme_image_widget($variables) { } $output .= '
'; - if ($element['fid']['#value'] != 0) { - $element['filename']['#markup'] .= ' (' . format_size($element['#file']->filesize) . ') '; + if (!empty($element['fids']['#value'])) { + $file = reset($element['#files']); + $element['file_' . $file->fid]['filename']['#markup'] .= ' (' . format_size($file->filesize) . ') '; } $output .= drupal_render_children($element); $output .= '
'; diff --git a/core/modules/image/image.module b/core/modules/image/image.module index b2816de..f1922cf 100644 --- a/core/modules/image/image.module +++ b/core/modules/image/image.module @@ -406,8 +406,8 @@ function image_field_delete_field($field) { } // The value of a managed_file element can be an array if #extended == TRUE. - $fid = (is_array($field['settings']['default_image']) ? $field['settings']['default_image']['fid'] : $field['settings']['default_image']); - if ($fid && ($file = file_load($fid))) { + $fid = (isset($field['settings']['default_image']['fids']) ? $field['settings']['default_image']['fids'] : $field['settings']['default_image']); + if ($fid && ($file = file_load($fid[0]))) { file_usage()->delete($file, 'image', 'default_image', $field['id']); } } @@ -421,10 +421,10 @@ function image_field_update_field($field, $prior_field, $has_data) { } // The value of a managed_file element can be an array if #extended == TRUE. - $fid_new = (is_array($field['settings']['default_image']) ? $field['settings']['default_image']['fid'] : $field['settings']['default_image']); - $fid_old = (is_array($prior_field['settings']['default_image']) ? $prior_field['settings']['default_image']['fid'] : $prior_field['settings']['default_image']); + $fid_new = (isset($field['settings']['default_image']['fids']) ? $field['settings']['default_image']['fids'] : $field['settings']['default_image']); + $fid_old = (isset($prior_field['settings']['default_image']['fids']) ? $prior_field['settings']['default_image']['fids'] : $prior_field['settings']['default_image']); - $file_new = $fid_new ? file_load($fid_new) : FALSE; + $file_new = $fid_new ? file_load($fid_new[0]) : FALSE; if ($fid_new != $fid_old) { @@ -436,7 +436,7 @@ function image_field_update_field($field, $prior_field, $has_data) { } // Is there an old file? - if ($fid_old && ($file_old = file_load($fid_old))) { + if ($fid_old && ($file_old = file_load($fid_old[0]))) { file_usage()->delete($file_old, 'image', 'default_image', $field['id']); } } @@ -485,16 +485,16 @@ function image_field_update_instance($instance, $prior_instance) { // The value of a managed_file element can be an array if the #extended // property is set to TRUE. $fid_new = $instance['settings']['default_image']; - if (is_array($fid_new)) { - $fid_new = $fid_new['fid']; + if (isset($fid_new['fids'])) { + $fid_new = $fid_new['fids']; } $fid_old = $prior_instance['settings']['default_image']; - if (is_array($fid_old)) { - $fid_old = $fid_old['fid']; + if (isset($fid_old['fids'])) { + $fid_old = $fid_old['fids']; } // If the old and new files do not match, update the default accordingly. - $file_new = $fid_new ? file_load($fid_new) : FALSE; + $file_new = $fid_new ? file_load($fid_new[0]) : FALSE; if ($fid_new != $fid_old) { // Save the new file, if present. if ($file_new) { @@ -503,7 +503,7 @@ function image_field_update_instance($instance, $prior_instance) { file_usage()->add($file_new, 'image', 'default_image', $instance['id']); } // Delete the old file, if present. - if ($fid_old && ($file_old = file_load($fid_old))) { + if ($fid_old && ($file_old = file_load($fid_old[0]))) { file_usage()->delete($file_old, 'image', 'default_image', $instance['id']); } } @@ -1103,4 +1103,3 @@ function image_filter_keyword($value, $current_pixels, $new_pixels) { function _image_effect_definitions_sort($a, $b) { return strcasecmp($a['name'], $b['name']); } - diff --git a/core/modules/image/lib/Drupal/image/Plugin/field/widget/ImageWidget.php b/core/modules/image/lib/Drupal/image/Plugin/field/widget/ImageWidget.php index 6d2083a..8062ff6 100644 --- a/core/modules/image/lib/Drupal/image/Plugin/field/widget/ImageWidget.php +++ b/core/modules/image/lib/Drupal/image/Plugin/field/widget/ImageWidget.php @@ -61,12 +61,12 @@ protected function formMultipleElements(EntityInterface $entity, array $items, $ if ($this->field['cardinality'] == 1) { // If there's only one field, return it as delta 0. - if (empty($elements[0]['#default_value']['fid'])) { - $elements[0]['#description'] = theme('file_upload_help', array('description' => $this->instance['description'], 'upload_validators' => $elements[0]['#upload_validators'])); + if (empty($elements[0]['#default_value']['fids'])) { + $elements[0]['#description'] = theme('file_upload_help', array('description' => $this->instance['description'], 'upload_validators' => $elements[0]['#upload_validators'], 'cardinality' => $this->field['cardinality'])); } } else { - $elements['#file_upload_description'] = theme('file_upload_help', array('upload_validators' => $elements[0]['#upload_validators'])); + $elements['#file_upload_description'] = theme('file_upload_help', array('upload_validators' => $elements[0]['#upload_validators'], 'cardinality' => $this->field['cardinality'])); } return $elements; diff --git a/core/modules/image/lib/Drupal/image/Tests/ImageFieldDefaultImagesTest.php b/core/modules/image/lib/Drupal/image/Tests/ImageFieldDefaultImagesTest.php index 01fa5f4..a30b7ed 100644 --- a/core/modules/image/lib/Drupal/image/Tests/ImageFieldDefaultImagesTest.php +++ b/core/modules/image/lib/Drupal/image/Tests/ImageFieldDefaultImagesTest.php @@ -43,10 +43,10 @@ function testDefaultImages() { // Create an image field and add an instance to the article content type. $field_name = strtolower($this->randomName()); $field_settings = array( - 'default_image' => $default_images['field']->fid, + 'default_image' => array($default_images['field']->fid), ); $instance_settings = array( - 'default_image' => $default_images['instance']->fid, + 'default_image' => array($default_images['instance']->fid), ); $widget_settings = array( 'preview_image_style' => 'medium', @@ -63,7 +63,7 @@ function testDefaultImages() { 'label' => $instance['label'], 'required' => $instance['required'], 'settings' => array( - 'default_image' => $default_images['instance2']->fid, + 'default_image' => array($default_images['instance2']->fid), ), 'widget' => $instance['widget'], ); @@ -76,7 +76,7 @@ function testDefaultImages() { // Confirm the defaults are present on the article field settings form. $this->drupalGet("admin/structure/types/manage/article/fields/$field_name/field-settings"); $this->assertFieldByXpath( - '//input[@name="field[settings][default_image][fid]"]', + '//input[@name="field[settings][default_image][fids]"]', $default_images['field']->fid, format_string( 'Article image field default equals expected file ID of @fid.', @@ -86,7 +86,7 @@ function testDefaultImages() { // Confirm the defaults are present on the article field edit form. $this->drupalGet("admin/structure/types/manage/article/fields/$field_name"); $this->assertFieldByXpath( - '//input[@name="instance[settings][default_image][fid]"]', + '//input[@name="instance[settings][default_image][fids]"]', $default_images['instance']->fid, format_string( 'Article image field instance default equals expected file ID of @fid.', @@ -97,7 +97,7 @@ function testDefaultImages() { // Confirm the defaults are present on the page field settings form. $this->drupalGet("admin/structure/types/manage/page/fields/$field_name/field-settings"); $this->assertFieldByXpath( - '//input[@name="field[settings][default_image][fid]"]', + '//input[@name="field[settings][default_image][fids]"]', $default_images['field']->fid, format_string( 'Page image field default equals expected file ID of @fid.', @@ -107,7 +107,7 @@ function testDefaultImages() { // Confirm the defaults are present on the page field edit form. $this->drupalGet("admin/structure/types/manage/page/fields/$field_name"); $this->assertFieldByXpath( - '//input[@name="instance[settings][default_image][fid]"]', + '//input[@name="instance[settings][default_image][fids]"]', $default_images['instance2']->fid, format_string( 'Page image field instance default equals expected file ID of @fid.', @@ -140,13 +140,13 @@ function testDefaultImages() { ); // Upload a new default for the field. - $field['settings']['default_image'] = $default_images['field_new']->fid; + $field['settings']['default_image'] = array($default_images['field_new']->fid); field_update_field($field); // Confirm that the new default is used on the article field settings form. $this->drupalGet("admin/structure/types/manage/article/fields/$field_name/field-settings"); $this->assertFieldByXpath( - '//input[@name="field[settings][default_image][fid]"]', + '//input[@name="field[settings][default_image][fids]"]', $default_images['field_new']->fid, format_string( 'Updated image field default equals expected file ID of @fid.', @@ -175,14 +175,14 @@ function testDefaultImages() { ); // Upload a new default for the article's field instance. - $instance['settings']['default_image'] = $default_images['instance_new']->fid; + $instance['settings']['default_image'] = array($default_images['instance_new']->fid); field_update_instance($instance); // Confirm the new field instance default is used on the article field // admin form. $this->drupalGet("admin/structure/types/manage/article/fields/$field_name"); $this->assertFieldByXpath( - '//input[@name="instance[settings][default_image][fid]"]', + '//input[@name="instance[settings][default_image][fids]"]', $default_images['instance_new']->fid, format_string( 'Updated article image field instance default equals expected file ID of @fid.', @@ -220,7 +220,7 @@ function testDefaultImages() { // Confirm the article field instance default has been removed. $this->drupalGet("admin/structure/types/manage/article/fields/$field_name"); $this->assertFieldByXpath( - '//input[@name="instance[settings][default_image][fid]"]', + '//input[@name="instance[settings][default_image][fids]"]', '', 'Updated article image field instance default has been successfully removed.' ); diff --git a/core/modules/image/lib/Drupal/image/Tests/ImageFieldDisplayTest.php b/core/modules/image/lib/Drupal/image/Tests/ImageFieldDisplayTest.php index 7c42a5b..743237a 100644 --- a/core/modules/image/lib/Drupal/image/Tests/ImageFieldDisplayTest.php +++ b/core/modules/image/lib/Drupal/image/Tests/ImageFieldDisplayTest.php @@ -227,13 +227,13 @@ function testImageFieldDefaultImage() { // Add a default image to the public imagefield instance. $images = $this->drupalGetTestFiles('image'); $edit = array( - 'files[field_settings_default_image]' => drupal_realpath($images[0]->uri), + 'files[field_settings_default_image][]' => drupal_realpath($images[0]->uri), ); $this->drupalPost("admin/structure/types/manage/article/fields/$field_name/field-settings", $edit, t('Save field settings')); // Clear field info cache so the new default image is detected. field_info_cache_clear(); $field = field_info_field($field_name); - $image = file_load($field['settings']['default_image']); + $image = file_load($field['settings']['default_image'][0]); $this->assertTrue($image->status == FILE_STATUS_PERMANENT, 'The default image status is permanent.'); $default_output = theme('image', array('uri' => $image->uri)); $this->drupalGet('node/' . $node->nid); @@ -255,7 +255,7 @@ function testImageFieldDefaultImage() { // Remove default image from the field and make sure it is no longer used. $edit = array( - 'field[settings][default_image][fid]' => 0, + 'field[settings][default_image][fids]' => 0, ); $this->drupalPost("admin/structure/types/manage/article/fields/$field_name/field-settings", $edit, t('Save field settings')); // Clear field info cache so the new default image is detected. @@ -268,14 +268,14 @@ function testImageFieldDefaultImage() { $this->createImageField($private_field_name, 'article', array('uri_scheme' => 'private')); // Add a default image to the new field. $edit = array( - 'files[field_settings_default_image]' => drupal_realpath($images[1]->uri), + 'files[field_settings_default_image][]' => drupal_realpath($images[1]->uri), ); $this->drupalPost('admin/structure/types/manage/article/fields/' . $private_field_name . '/field-settings', $edit, t('Save field settings')); // Clear field info cache so the new default image is detected. field_info_cache_clear(); $private_field = field_info_field($private_field_name); - $image = file_load($private_field['settings']['default_image']); + $image = file_load($private_field['settings']['default_image'][0]); $this->assertEqual('private', file_uri_scheme($image->uri), 'Default image uses private:// scheme.'); $this->assertTrue($image->status == FILE_STATUS_PERMANENT, 'The default image status is permanent.'); // Create a new node with no image attached and ensure that default private diff --git a/core/modules/image/lib/Drupal/image/Tests/ImageFieldTestBase.php b/core/modules/image/lib/Drupal/image/Tests/ImageFieldTestBase.php index 5a4e20e..e8518ab 100644 --- a/core/modules/image/lib/Drupal/image/Tests/ImageFieldTestBase.php +++ b/core/modules/image/lib/Drupal/image/Tests/ImageFieldTestBase.php @@ -117,7 +117,7 @@ function uploadNodeImage($image, $field_name, $type) { $edit = array( 'title' => $this->randomName(), ); - $edit['files[' . $field_name . '_' . LANGUAGE_NOT_SPECIFIED . '_0]'] = drupal_realpath($image->uri); + $edit['files[' . $field_name . '_' . LANGUAGE_NOT_SPECIFIED . '_0][]'] = drupal_realpath($image->uri); $this->drupalPost('node/add/' . $type, $edit, t('Save and publish')); // Retrieve ID of the newly created node from the current URL. diff --git a/core/modules/locale/lib/Drupal/locale/Tests/LocaleExportTest.php b/core/modules/locale/lib/Drupal/locale/Tests/LocaleExportTest.php index 7ae1bf7..4f2748b 100644 --- a/core/modules/locale/lib/Drupal/locale/Tests/LocaleExportTest.php +++ b/core/modules/locale/lib/Drupal/locale/Tests/LocaleExportTest.php @@ -55,7 +55,7 @@ function testExportTranslation() { file_put_contents($name, $this->getPoFile()); $this->drupalPost('admin/config/regional/translate/import', array( 'langcode' => 'fr', - 'files[file]' => $name, + 'files[file][]' => $name, ), t('Import')); drupal_unlink($name); @@ -74,7 +74,7 @@ function testExportTranslation() { file_put_contents($name, $this->getCustomPoFile()); $this->drupalPost('admin/config/regional/translate/import', array( 'langcode' => 'fr', - 'files[file]' => $name, + 'files[file][]' => $name, 'customized' => 1, ), t('Import')); drupal_unlink($name); diff --git a/core/modules/locale/lib/Drupal/locale/Tests/LocaleImportFunctionalTest.php b/core/modules/locale/lib/Drupal/locale/Tests/LocaleImportFunctionalTest.php index 06269ab..e06c1d6 100644 --- a/core/modules/locale/lib/Drupal/locale/Tests/LocaleImportFunctionalTest.php +++ b/core/modules/locale/lib/Drupal/locale/Tests/LocaleImportFunctionalTest.php @@ -91,7 +91,7 @@ function testStandalonePoFile() { $name = $this->randomName(16); $this->drupalPost('admin/config/regional/translate/import', array( 'langcode' => 'fr', - 'files[file]' => $name, + 'files[file][]' => $name, ), t('Import')); $this->assertEqual($this->getUrl(), url('admin/config/regional/translate/import', array('absolute' => TRUE)), t('Correct page redirection.')); $this->assertText(t('File to import not found.'), t('File to import not found message.')); @@ -248,7 +248,7 @@ function testEmptyMsgstr() { function importPoFile($contents, array $options = array()) { $name = tempnam('temporary://', "po_") . '.po'; file_put_contents($name, $contents); - $options['files[file]'] = $name; + $options['files[file][]'] = $name; $this->drupalPost('admin/config/regional/translate/import', $options, t('Import')); drupal_unlink($name); } diff --git a/core/modules/locale/lib/Drupal/locale/Tests/LocalePluralFormatTest.php b/core/modules/locale/lib/Drupal/locale/Tests/LocalePluralFormatTest.php index a5ed01b..96a793a 100644 --- a/core/modules/locale/lib/Drupal/locale/Tests/LocalePluralFormatTest.php +++ b/core/modules/locale/lib/Drupal/locale/Tests/LocalePluralFormatTest.php @@ -269,7 +269,7 @@ function testPluralEditExport() { function importPoFile($contents, array $options = array()) { $name = tempnam('temporary://', "po_") . '.po'; file_put_contents($name, $contents); - $options['files[file]'] = $name; + $options['files[file][]'] = $name; $this->drupalPost('admin/config/regional/translate/import', $options, t('Import')); drupal_unlink($name); } diff --git a/core/modules/locale/locale.bulk.inc b/core/modules/locale/locale.bulk.inc index 6a452d8..49273d8 100644 --- a/core/modules/locale/locale.bulk.inc +++ b/core/modules/locale/locale.bulk.inc @@ -108,7 +108,7 @@ function locale_translate_import_form($form, &$form_state) { */ function locale_translate_import_form_submit($form, &$form_state) { // Ensure we have the file uploaded. - if ($file = file_save_upload('file', $form['file']['#upload_validators'], 'translations://')) { + if ($file = file_save_upload('file', $form['file']['#upload_validators'], 'translations://', 0)) { // Add language, if not yet supported. $language = language_load($form_state['values']['langcode']); diff --git a/core/modules/node/node.api.php b/core/modules/node/node.api.php index d5e396c..2862b0e 100644 --- a/core/modules/node/node.api.php +++ b/core/modules/node/node.api.php @@ -1115,7 +1115,7 @@ function hook_delete(\Drupal\Core\Entity\EntityInterface $node) { */ function hook_prepare(\Drupal\Core\Entity\EntityInterface $node) { if ($file = file_check_upload($field_name)) { - $file = file_save_upload($field_name, _image_filename($file->filename, NULL, TRUE)); + $file = file_save_upload($field_name, _image_filename($file->filename, NULL, TRUE), FALSE, 0); if ($file) { if (!image_get_info($file->uri)) { form_set_error($field_name, t('Uploaded file is not a valid image')); diff --git a/core/modules/rdf/lib/Drupal/rdf/Tests/RdfaMarkupTest.php b/core/modules/rdf/lib/Drupal/rdf/Tests/RdfaMarkupTest.php index 6f99dae..589ad4b 100644 --- a/core/modules/rdf/lib/Drupal/rdf/Tests/RdfaMarkupTest.php +++ b/core/modules/rdf/lib/Drupal/rdf/Tests/RdfaMarkupTest.php @@ -139,8 +139,8 @@ function testAttributesInMarkupFile() { // Create an array for drupalPost with the field names as the keys and // the URIs for the test files as the values. - $edit = array("files[" . $field_name . "_" . $langcode . "_0]" => drupal_realpath($file->uri), - "files[" . $image_field . "_" . $langcode . "_0]" => drupal_realpath($image->uri)); + $edit = array("files[" . $field_name . "_" . $langcode . "_0][]" => drupal_realpath($file->uri), + "files[" . $image_field . "_" . $langcode . "_0][]" => drupal_realpath($image->uri)); // Create node and save, then edit node to upload files. $node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1)); diff --git a/core/modules/system/lib/Drupal/system/Tests/Form/StateValuesCleanAdvancedTest.php b/core/modules/system/lib/Drupal/system/Tests/Form/StateValuesCleanAdvancedTest.php index ceae2af..b7c8e22 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Form/StateValuesCleanAdvancedTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Form/StateValuesCleanAdvancedTest.php @@ -47,7 +47,7 @@ function testFormStateValuesCleanAdvanced() { $this->assertTrue(is_file($this->image->uri), "The image file we're going to upload exists."); // "Browse" for the desired file. - $edit = array('files[image]' => drupal_realpath($this->image->uri)); + $edit = array('files[image][]' => drupal_realpath($this->image->uri)); // Post the form. $this->drupalPost('form_test/form-state-values-clean-advanced', $edit, t('Submit')); diff --git a/core/modules/system/lib/Drupal/system/Tests/System/ThemeTest.php b/core/modules/system/lib/Drupal/system/Tests/System/ThemeTest.php index f636849..8e0e498 100644 --- a/core/modules/system/lib/Drupal/system/Tests/System/ThemeTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/System/ThemeTest.php @@ -155,7 +155,7 @@ function testThemeSettings() { $edit = array( 'default_logo' => FALSE, 'logo_path' => '', - 'files[logo_upload]' => drupal_realpath($file->uri), + 'files[logo_upload][]' => drupal_realpath($file->uri), ); $this->drupalPost('admin/appearance/settings', $edit, t('Save configuration')); diff --git a/core/modules/system/lib/Drupal/system/Tests/Upgrade/UserPictureUpgradePathTest.php b/core/modules/system/lib/Drupal/system/Tests/Upgrade/UserPictureUpgradePathTest.php index 04070c9..c26dd86 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Upgrade/UserPictureUpgradePathTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Upgrade/UserPictureUpgradePathTest.php @@ -40,8 +40,8 @@ public function testUserPictureUpgrade() { // Retrieve the field instance and check for migrated settings. $instance = field_info_instance('user', 'user_picture', 'user'); - $file = entity_load('file', $instance['settings']['default_image']); - $this->assertIdentical($instance['settings']['default_image'], $file->id(), 'Default user picture has been migrated.'); + $file = entity_load('file', $instance['settings']['default_image'][0]); + $this->assertIdentical($instance['settings']['default_image'][0], $file->id(), 'Default user picture has been migrated.'); $this->assertEqual($file->uri, 'public://user_pictures_dir/druplicon.png', 'File id matches the uri expected.'); $this->assertEqual($file->filename, 'druplicon.png'); $this->assertEqual($file->langcode, LANGUAGE_NOT_SPECIFIED); diff --git a/core/modules/system/system.admin.inc b/core/modules/system/system.admin.inc index 58e6d58..620095e 100644 --- a/core/modules/system/system.admin.inc +++ b/core/modules/system/system.admin.inc @@ -617,7 +617,7 @@ function system_theme_settings_validate($form, &$form_state) { $validators = array('file_validate_is_image' => array()); // Check for a new uploaded logo. - $file = file_save_upload('logo_upload', $validators); + $file = file_save_upload('logo_upload', $validators, FALSE, 0); if (isset($file)) { // File upload was attempted. if ($file) { @@ -633,7 +633,7 @@ function system_theme_settings_validate($form, &$form_state) { $validators = array('file_validate_extensions' => array('ico png gif jpg jpeg apng svg')); // Check for a new uploaded favicon. - $file = file_save_upload('favicon_upload', $validators); + $file = file_save_upload('favicon_upload', $validators, FALSE, 0); if (isset($file)) { // File upload was attempted. if ($file) { diff --git a/core/modules/system/system.module b/core/modules/system/system.module index 13469b0..7f6c6d5 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -495,6 +495,8 @@ function system_element_info() { ); $types['file'] = array( '#input' => TRUE, + '#multiple' => FALSE, + '#process' => array('form_process_file'), '#size' => 60, '#pre_render' => array('form_pre_render_file'), '#theme' => 'input__file', diff --git a/core/modules/update/lib/Drupal/update/Tests/UpdateUploadTest.php b/core/modules/update/lib/Drupal/update/Tests/UpdateUploadTest.php index d3bcdb8..0a8db21 100644 --- a/core/modules/update/lib/Drupal/update/Tests/UpdateUploadTest.php +++ b/core/modules/update/lib/Drupal/update/Tests/UpdateUploadTest.php @@ -44,7 +44,7 @@ public function testUploadModule() { $imageTestFiles = $this->drupalGetTestFiles('image'); $invalidArchiveFile = reset($imageTestFiles); $edit = array( - 'files[project_upload]' => $invalidArchiveFile->uri, + 'files[project_upload][]' => $invalidArchiveFile->uri, ); // This also checks that the correct archive extensions are allowed. $this->drupalPost('admin/modules/install', $edit, t('Install')); @@ -55,7 +55,7 @@ public function testUploadModule() { // installed until after extraction. $validArchiveFile = drupal_get_path('module', 'update') . '/tests/aaa_update_test.tar.gz'; $edit = array( - 'files[project_upload]' => $validArchiveFile, + 'files[project_upload][]' => $validArchiveFile, ); $this->drupalPost('admin/modules/install', $edit, t('Install')); $this->assertText(t('@module_name is already installed.', array('@module_name' => 'AAA Update test')), 'Existing module was extracted and not reinstalled.'); diff --git a/core/modules/update/update.manager.inc b/core/modules/update/update.manager.inc index 0047a52..bc84058 100644 --- a/core/modules/update/update.manager.inc +++ b/core/modules/update/update.manager.inc @@ -645,7 +645,7 @@ function update_manager_install_form_submit($form, &$form_state) { elseif ($_FILES['files']['name']['project_upload']) { $validators = array('file_validate_extensions' => array(archiver_get_extensions())); $field = 'project_upload'; - if (!($finfo = file_save_upload($field, $validators, NULL, FILE_EXISTS_REPLACE))) { + if (!($finfo = file_save_upload($field, $validators, NULL, 0, FILE_EXISTS_REPLACE))) { // Failed to upload the file. file_save_upload() calls form_set_error() on // failure. return; diff --git a/core/modules/user/lib/Drupal/user/Tests/UserPictureTest.php b/core/modules/user/lib/Drupal/user/Tests/UserPictureTest.php index 7764699..feeb314 100644 --- a/core/modules/user/lib/Drupal/user/Tests/UserPictureTest.php +++ b/core/modules/user/lib/Drupal/user/Tests/UserPictureTest.php @@ -117,7 +117,7 @@ function testPictureOnNodeComment() { * Edits the user picture for the test user. */ function saveUserPicture($image) { - $edit = array('files[user_picture_und_0]' => drupal_realpath($image->uri)); + $edit = array('files[user_picture_und_0][]' => drupal_realpath($image->uri)); $this->drupalPost('user/' . $this->web_user->uid . '/edit', $edit, t('Save')); // Load actual user data from database.