X-Git-Url: http://aleph1.co.uk/gitweb/?a=blobdiff_plain;f=web%2Fcore%2Fmodules%2Ffile%2Ffile.module;h=a6f68094a96025cf423aeb018d577e1223e1ee54;hb=1c1cb0980bfa6caf0c24cce671b6bb541dc87583;hp=6902db6812bb50940a1d445bf45eb8b2eab709a4;hpb=a2bd1bf0c2c1f1a17d188f4dc0726a45494cefae;p=yaffs-website diff --git a/web/core/modules/file/file.module b/web/core/modules/file/file.module index 6902db681..a6f68094a 100644 --- a/web/core/modules/file/file.module +++ b/web/core/modules/file/file.module @@ -8,6 +8,7 @@ use Drupal\Core\Datetime\Entity\DateFormat; use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Messenger\MessengerInterface; use Drupal\Core\Render\BubbleableMetadata; use Drupal\Core\Render\Element; use Drupal\Core\Routing\RouteMatchInterface; @@ -19,6 +20,11 @@ use Drupal\Component\Utility\Unicode; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Template\Attribute; +/** + * The regex pattern used when checking for insecure file types. + */ +define('FILE_INSECURE_EXTENSION_REGEX', '/\.(php|pl|py|cgi|asp|js)(\.|$)/i'); + // Load all Field module hooks for File. require_once __DIR__ . '/file.field.inc'; @@ -48,6 +54,16 @@ function file_help($route_name, RouteMatchInterface $route_match) { } } +/** + * Implements hook_field_widget_info_alter(). + */ +function file_field_widget_info_alter(array &$info) { + // Allows using the 'uri' widget for the 'file_uri' field type, which uses it + // as the default widget. + // @see \Drupal\file\Plugin\Field\FieldType\FileUriItem + $info['uri']['field_types'][] = 'file_uri'; +} + /** * Loads file entities from the database. * @@ -140,13 +156,13 @@ function file_load($fid, $reset = FALSE) { */ function file_copy(FileInterface $source, $destination = NULL, $replace = FILE_EXISTS_RENAME) { if (!file_valid_uri($destination)) { - if (($realpath = drupal_realpath($source->getFileUri())) !== FALSE) { + if (($realpath = \Drupal::service('file_system')->realpath($source->getFileUri())) !== FALSE) { \Drupal::logger('file')->notice('File %file (%realpath) could not be copied because the destination %destination is invalid. This is often caused by improper use of file_copy() or a missing stream wrapper.', ['%file' => $source->getFileUri(), '%realpath' => $realpath, '%destination' => $destination]); } else { \Drupal::logger('file')->notice('File %file could not be copied because the destination %destination is invalid. This is often caused by improper use of file_copy() or a missing stream wrapper.', ['%file' => $source->getFileUri(), '%destination' => $destination]); } - drupal_set_message(t('The specified file %file could not be copied because the destination is invalid. More information is available in the system log.', ['%file' => $source->getFileUri()]), 'error'); + \Drupal::messenger()->addError(t('The specified file %file could not be copied because the destination is invalid. More information is available in the system log.', ['%file' => $source->getFileUri()])); return FALSE; } @@ -215,13 +231,13 @@ function file_copy(FileInterface $source, $destination = NULL, $replace = FILE_E */ function file_move(FileInterface $source, $destination = NULL, $replace = FILE_EXISTS_RENAME) { if (!file_valid_uri($destination)) { - if (($realpath = drupal_realpath($source->getFileUri())) !== FALSE) { + if (($realpath = \Drupal::service('file_system')->realpath($source->getFileUri())) !== FALSE) { \Drupal::logger('file')->notice('File %file (%realpath) could not be moved because the destination %destination is invalid. This may be caused by improper use of file_move() or a missing stream wrapper.', ['%file' => $source->getFileUri(), '%realpath' => $realpath, '%destination' => $destination]); } else { \Drupal::logger('file')->notice('File %file could not be moved because the destination %destination is invalid. This may be caused by improper use of file_move() or a missing stream wrapper.', ['%file' => $source->getFileUri(), '%destination' => $destination]); } - drupal_set_message(t('The specified file %file could not be moved because the destination is invalid. More information is available in the system log.', ['%file' => $source->getFileUri()]), 'error'); + \Drupal::messenger()->addError(t('The specified file %file could not be moved because the destination is invalid. More information is available in the system log.', ['%file' => $source->getFileUri()])); return FALSE; } @@ -396,7 +412,7 @@ function file_validate_is_image(FileInterface $file) { $image = $image_factory->get($file->getFileUri()); if (!$image->isValid()) { $supported_extensions = $image_factory->getSupportedExtensions(); - $errors[] = t('Image type not supported. Allowed types: %types', ['%types' => implode(' ', $supported_extensions)]); + $errors[] = t('The image file is invalid or the image type is not allowed. Allowed types: %types', ['%types' => implode(', ', $supported_extensions)]); } return $errors; @@ -434,24 +450,42 @@ function file_validate_image_resolution(FileInterface $file, $maximum_dimensions // Check first that the file is an image. $image_factory = \Drupal::service('image.factory'); $image = $image_factory->get($file->getFileUri()); + if ($image->isValid()) { + $scaling = FALSE; if ($maximum_dimensions) { // Check that it is smaller than the given dimensions. list($width, $height) = explode('x', $maximum_dimensions); if ($image->getWidth() > $width || $image->getHeight() > $height) { // Try to resize the image to fit the dimensions. if ($image->scale($width, $height)) { + $scaling = TRUE; $image->save(); if (!empty($width) && !empty($height)) { - $message = t('The image was resized to fit within the maximum allowed dimensions of %dimensions pixels.', ['%dimensions' => $maximum_dimensions]); + $message = t('The image was resized to fit within the maximum allowed dimensions of %dimensions pixels. The new dimensions of the resized image are %new_widthx%new_height pixels.', + [ + '%dimensions' => $maximum_dimensions, + '%new_width' => $image->getWidth(), + '%new_height' => $image->getHeight(), + ]); } elseif (empty($width)) { - $message = t('The image was resized to fit within the maximum allowed height of %height pixels.', ['%height' => $height]); + $message = t('The image was resized to fit within the maximum allowed height of %height pixels. The new dimensions of the resized image are %new_widthx%new_height pixels.', + [ + '%height' => $height, + '%new_width' => $image->getWidth(), + '%new_height' => $image->getHeight(), + ]); } elseif (empty($height)) { - $message = t('The image was resized to fit within the maximum allowed width of %width pixels.', ['%width' => $width]); + $message = t('The image was resized to fit within the maximum allowed width of %width pixels. The new dimensions of the resized image are %new_widthx%new_height pixels.', + [ + '%width' => $width, + '%new_width' => $image->getWidth(), + '%new_height' => $image->getHeight(), + ]); } - drupal_set_message($message); + \Drupal::messenger()->addStatus($message); } else { $errors[] = t('The image exceeds the maximum allowed dimensions and an attempt to resize it failed.'); @@ -463,7 +497,22 @@ function file_validate_image_resolution(FileInterface $file, $maximum_dimensions // Check that it is larger than the given dimensions. list($width, $height) = explode('x', $minimum_dimensions); if ($image->getWidth() < $width || $image->getHeight() < $height) { - $errors[] = t('The image is too small; the minimum dimensions are %dimensions pixels.', ['%dimensions' => $minimum_dimensions]); + if ($scaling) { + $errors[] = t('The resized image is too small. The minimum dimensions are %dimensions pixels and after resizing, the image size will be %widthx%height pixels.', + [ + '%dimensions' => $minimum_dimensions, + '%width' => $image->getWidth(), + '%height' => $image->getHeight(), + ]); + } + else { + $errors[] = t('The image is too small. The minimum dimensions are %dimensions pixels and the image size is %widthx%height pixels.', + [ + '%dimensions' => $minimum_dimensions, + '%width' => $image->getWidth(), + '%height' => $image->getHeight(), + ]); + } } } } @@ -504,7 +553,7 @@ function file_save_data($data, $destination = NULL, $replace = FILE_EXISTS_RENAM } if (!file_valid_uri($destination)) { \Drupal::logger('file')->notice('The data could not be saved because the destination %destination is invalid. This may be caused by improper use of file_save_data() or a missing stream wrapper.', ['%destination' => $destination]); - drupal_set_message(t('The data could not be saved because the destination is invalid. More information is available in the system log.'), 'error'); + \Drupal::messenger()->addError(t('The data could not be saved because the destination is invalid. More information is available in the system log.')); return FALSE; } @@ -571,6 +620,12 @@ function file_theme() { 'file_managed_file' => [ 'render element' => 'element', ], + 'file_audio' => [ + 'variables' => ['files' => [], 'attributes' => NULL], + ], + 'file_video' => [ + 'variables' => ['files' => [], 'attributes' => NULL], + ], // From file.field.inc. 'file_widget_multiple' => [ @@ -673,6 +728,90 @@ function file_cron() { } } +/** + * Saves form file uploads. + * + * The files will be added to the {file_managed} table as temporary files. + * Temporary files are periodically cleaned. Use the 'file.usage' service to + * register the usage of the file which will automatically mark it as permanent. + * + * @param array $element + * The FAPI element whose values are being saved. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * @param null|int $delta + * (optional) The delta of the file to return the file entity. + * Defaults to NULL. + * @param int $replace + * (optional) The replace behavior when the destination file already exists. + * Possible values include: + * - FILE_EXISTS_REPLACE: Replace the existing file. + * - FILE_EXISTS_RENAME: (default) Append _{incrementing number} until the + * filename is unique. + * - FILE_EXISTS_ERROR: Do nothing and return FALSE. + * + * @return array|\Drupal\file\FileInterface|null|false + * An array of file entities or a single file entity if $delta != NULL. Each + * array element contains the file entity if the upload succeeded or FALSE if + * there was an error. Function returns NULL if no file was uploaded. + * + * @deprecated in Drupal 8.4.x, will be removed before Drupal 9.0.0. + * For backwards compatibility use core file upload widgets in forms. + * + * @internal + * This function wraps file_save_upload() to allow correct error handling in + * forms. + * + * @todo Revisit after https://www.drupal.org/node/2244513. + */ +function _file_save_upload_from_form(array $element, FormStateInterface $form_state, $delta = NULL, $replace = FILE_EXISTS_RENAME) { + // Get all errors set before calling this method. This will also clear them + // from $_SESSION. + $errors_before = \Drupal::messenger()->deleteByType(MessengerInterface::TYPE_ERROR); + + $upload_location = isset($element['#upload_location']) ? $element['#upload_location'] : FALSE; + $upload_name = implode('_', $element['#parents']); + $upload_validators = isset($element['#upload_validators']) ? $element['#upload_validators'] : []; + + $result = file_save_upload($upload_name, $upload_validators, $upload_location, $delta, $replace); + + // Get new errors that are generated while trying to save the upload. This + // will also clear them from $_SESSION. + $errors_new = \Drupal::messenger()->deleteByType(MessengerInterface::TYPE_ERROR); + if (!empty($errors_new)) { + + if (count($errors_new) > 1) { + // Render multiple errors into a single message. + // This is needed because only one error per element is supported. + $render_array = [ + 'error' => [ + '#markup' => t('One or more files could not be uploaded.'), + ], + 'item_list' => [ + '#theme' => 'item_list', + '#items' => $errors_new, + ], + ]; + $error_message = \Drupal::service('renderer')->renderPlain($render_array); + } + else { + $error_message = reset($errors_new); + } + + $form_state->setError($element, $error_message); + } + + // Ensure that errors set prior to calling this method are still shown to the + // user. + if (!empty($errors_before)) { + foreach ($errors_before as $error) { + \Drupal::messenger()->addError($error); + } + } + + return $result; +} + /** * Saves file uploads to a new location. * @@ -680,6 +819,10 @@ function file_cron() { * Temporary files are periodically cleaned. Use the 'file.usage' service to * register the usage of the file which will automatically mark it as permanent. * + * Note that this function does not support correct form error handling. The + * file upload widgets in core do support this. It is advised to use these in + * any custom form, instead of calling this function. + * * @param string $form_field_name * A string that is the associative array key of the upload form element in * the form array. @@ -710,9 +853,12 @@ function file_cron() { * An array of file entities or a single file entity if $delta != NULL. Each * array element contains the file entity if the upload succeeded or FALSE if * there was an error. Function returns NULL if no file was uploaded. + * + * @see _file_save_upload_from_form() + * + * @todo: move this logic to a service in https://www.drupal.org/node/2244513. */ function file_save_upload($form_field_name, $validators = [], $destination = FALSE, $delta = NULL, $replace = FILE_EXISTS_RENAME) { - $user = \Drupal::currentUser(); static $upload_cache; $all_files = \Drupal::request()->files->get('files', []); @@ -740,184 +886,208 @@ function file_save_upload($form_field_name, $validators = [], $destination = FAL $files = []; foreach ($uploaded_files as $i => $file_info) { - // 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 ($file_info->getError()) { - 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.', ['%file' => $file_info->getFilename(), '%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.', ['%file' => $file_info->getFilename()]), '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($file_info->getRealPath())) { - break; - } + $files[$i] = _file_save_upload_single($file_info, $form_field_name, $validators, $destination, $replace); + } - // Unknown error - default: - drupal_set_message(t('The file %file could not be saved. An unknown error has occurred.', ['%file' => $file_info->getFilename()]), 'error'); - $files[$i] = FALSE; - continue; + // Add files to the cache. + $upload_cache[$form_field_name] = $files; - } - // Begin building file entity. - $values = [ - 'uid' => $user->id(), - 'status' => 0, - 'filename' => $file_info->getClientOriginalName(), - 'uri' => $file_info->getRealPath(), - 'filesize' => $file_info->getSize(), - ]; - $values['filemime'] = \Drupal::service('file.mime_type.guesser')->guess($values['filename']); - $file = File::create($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 { - // 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'] = []; - $validators['file_validate_extensions'][0] = $extensions; - } + return isset($delta) ? $files[$delta] : $files; +} - if (!empty($extensions)) { - // Munge the filename to protect against possible malicious extension - // hiding within an unknown file type (ie: filename.html.foo). - $file->setFilename(file_munge_filename($file->getFilename(), $extensions)); - } +/** + * Saves a file upload to a new location. + * + * @param \SplFileInfo $file_info + * The file upload to save. + * @param string $form_field_name + * A string that is the associative array key of the upload form element in + * the form array. + * @param array $validators + * (optional) An associative array of callback functions used to validate the + * file. + * @param bool $destination + * (optional) A string containing the URI that the file should be copied to. + * @param int $replace + * (optional) The replace behavior when the destination file already exists. + * + * @return \Drupal\file\FileInterface|false + * The created file entity or FALSE if the uploaded file not saved. + * + * @throws \Drupal\Core\Entity\EntityStorageException + * + * @internal + * This method should only be called from file_save_upload(). Use that method + * instead. + * + * @see file_save_upload() + */ +function _file_save_upload_single(\SplFileInfo $file_info, $form_field_name, $validators = [], $destination = FALSE, $replace = FILE_EXISTS_RENAME) { + $user = \Drupal::currentUser(); + // 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 ($file_info->getError()) { + case UPLOAD_ERR_INI_SIZE: + case UPLOAD_ERR_FORM_SIZE: + \Drupal::messenger()->addError(t('The file %file could not be saved because it exceeds %maxsize, the maximum allowed size for uploads.', ['%file' => $file_info->getFilename(), '%maxsize' => format_size(file_upload_max_size())])); + return FALSE; + + case UPLOAD_ERR_PARTIAL: + case UPLOAD_ERR_NO_FILE: + \Drupal::messenger()->addError(t('The file %file could not be saved because the upload did not complete.', ['%file' => $file_info->getFilename()])); + return FALSE; - // 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 (!\Drupal::config('system.file')->get('allow_insecure_uploads') && preg_match('/\.(php|pl|py|cgi|asp|js)(\.|$)/i', $file->getFilename()) && (substr($file->getFilename(), -4) != '.txt')) { - $file->setMimeType('text/plain'); - // The destination filename will also later be used to create the URI. - $file->setFilename($file->getFilename() . '.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.', ['%filename' => $file->getFilename()])); + 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($file_info->getRealPath())) { + break; } - } - // If the destination is not provided, use the temporary directory. - if (empty($destination)) { - $destination = 'temporary://'; - } + default: + // Unknown error + \Drupal::messenger()->addError(t('The file %file could not be saved. An unknown error has occurred.', ['%file' => $file_info->getFilename()])); + 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.', ['%destination' => $destination]), 'error'); - $files[$i] = FALSE; - continue; + } + // Begin building file entity. + $values = [ + 'uid' => $user->id(), + 'status' => 0, + 'filename' => $file_info->getClientOriginalName(), + 'uri' => $file_info->getRealPath(), + 'filesize' => $file_info->getSize(), + ]; + $values['filemime'] = \Drupal::service('file.mime_type.guesser')->guess($values['filename']); + $file = File::create($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]; } - - $file->source = $form_field_name; - // A file URI may already have a trailing slash or look like "public://". - if (substr($destination, -1) != '/') { - $destination .= '/'; + 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']); } - $file->destination = file_destination($destination . $file->getFilename(), $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.', ['%source' => $form_field_name, '%directory' => $destination]), 'error'); - $files[$i] = FALSE; - continue; + } + 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'] = []; + $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->setFilename(file_munge_filename($file->getFilename(), $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 (!\Drupal::config('system.file')->get('allow_insecure_uploads') && preg_match(FILE_INSECURE_EXTENSION_REGEX, $file->getFilename()) && (substr($file->getFilename(), -4) != '.txt')) { + $file->setMimeType('text/plain'); + // The destination filename will also later be used to create the URI. + $file->setFilename($file->getFilename() . '.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::messenger()->addStatus(t('For security reasons, your upload has been renamed to %filename.', ['%filename' => $file->getFilename()])); } + } - // Add in our check of the file name length. - $validators['file_validate_name_length'] = []; + // If the destination is not provided, use the temporary directory. + if (empty($destination)) { + $destination = 'temporary://'; + } - // Call the validation functions specified by this function's caller. - $errors = file_validate($file, $validators); + // Assert that the destination contains a valid stream. + $destination_scheme = file_uri_scheme($destination); + if (!file_stream_wrapper_valid_scheme($destination_scheme)) { + \Drupal::messenger()->addError(t('The file could not be uploaded because the destination %destination is invalid.', ['%destination' => $destination])); + return FALSE; + } - // Check for errors. - if (!empty($errors)) { - $message = [ - 'error' => [ - '#markup' => t('The specified file %name could not be uploaded.', ['%name' => $file->getFilename()]), - ], - 'item_list' => [ - '#theme' => 'item_list', - '#items' => $errors, - ], - ]; - // @todo Add support for render arrays in drupal_set_message()? See - // https://www.drupal.org/node/2505497. - drupal_set_message(\Drupal::service('renderer')->renderPlain($message), 'error'); - $files[$i] = FALSE; - continue; - } + $file->source = $form_field_name; + // A file URI may already have a trailing slash or look like "public://". + if (substr($destination, -1) != '/') { + $destination .= '/'; + } + $file->destination = file_destination($destination . $file->getFilename(), $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::messenger()->addError(t('The file %source could not be uploaded because a file by that name already exists in the destination %directory.', ['%source' => $form_field_name, '%directory' => $destination])); + 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->setFileUri($file->destination); - if (!drupal_move_uploaded_file($file_info->getRealPath(), $file->getFileUri())) { - drupal_set_message(t('File upload error. Could not move uploaded file.'), 'error'); - \Drupal::logger('file')->notice('Upload error. Could not move uploaded file %file to destination %destination.', ['%file' => $file->getFilename(), '%destination' => $file->getFileUri()]); - $files[$i] = FALSE; - continue; - } + // Add in our check of the file name length. + $validators['file_validate_name_length'] = []; - // Set the permissions on the new file. - drupal_chmod($file->getFileUri()); + // Call the validation functions specified by this function's caller. + $errors = file_validate($file, $validators); + + // Check for errors. + if (!empty($errors)) { + $message = [ + 'error' => [ + '#markup' => t('The specified file %name could not be uploaded.', ['%name' => $file->getFilename()]), + ], + 'item_list' => [ + '#theme' => 'item_list', + '#items' => $errors, + ], + ]; + // @todo Add support for render arrays in + // \Drupal\Core\Messenger\MessengerInterface::addMessage()? + // @see https://www.drupal.org/node/2505497. + \Drupal::messenger()->addError(\Drupal::service('renderer')->renderPlain($message)); + return FALSE; + } - // If we are replacing an existing file re-use its database record. - // @todo Do not create a new entity in order to update it. See - // https://www.drupal.org/node/2241865. - if ($replace == FILE_EXISTS_REPLACE) { - $existing_files = entity_load_multiple_by_properties('file', ['uri' => $file->getFileUri()]); - if (count($existing_files)) { - $existing = reset($existing_files); - $file->fid = $existing->id(); - $file->setOriginalId($existing->id()); - } - } + $file->setFileUri($file->destination); + if (!drupal_move_uploaded_file($file_info->getRealPath(), $file->getFileUri())) { + \Drupal::messenger()->addError(t('File upload error. Could not move uploaded file.')); + \Drupal::logger('file')->notice('Upload error. Could not move uploaded file %file to destination %destination.', ['%file' => $file->getFilename(), '%destination' => $file->getFileUri()]); + return FALSE; + } - // If we made it this far it's safe to record this file in the database. - $file->save(); - $files[$i] = $file; - // Allow an anonymous user who creates a non-public file to see it. See - // \Drupal\file\FileAccessControlHandler::checkAccess(). - if ($user->isAnonymous() && $destination_scheme !== 'public') { - $session = \Drupal::request()->getSession(); - $allowed_temp_files = $session->get('anonymous_allowed_file_ids', []); - $allowed_temp_files[$file->id()] = $file->id(); - $session->set('anonymous_allowed_file_ids', $allowed_temp_files); + // Set the permissions on the new file. + drupal_chmod($file->getFileUri()); + + // If we are replacing an existing file re-use its database record. + // @todo Do not create a new entity in order to update it. See + // https://www.drupal.org/node/2241865. + if ($replace == FILE_EXISTS_REPLACE) { + $existing_files = entity_load_multiple_by_properties('file', ['uri' => $file->getFileUri()]); + if (count($existing_files)) { + $existing = reset($existing_files); + $file->fid = $existing->id(); + $file->setOriginalId($existing->id()); } } - // Add files to the cache. - $upload_cache[$form_field_name] = $files; + // If we made it this far it's safe to record this file in the database. + $file->save(); - return isset($delta) ? $files[$delta] : $files; + // Allow an anonymous user who creates a non-public file to see it. See + // \Drupal\file\FileAccessControlHandler::checkAccess(). + if ($user->isAnonymous() && $destination_scheme !== 'public') { + $session = \Drupal::request()->getSession(); + $allowed_temp_files = $session->get('anonymous_allowed_file_ids', []); + $allowed_temp_files[$file->id()] = $file->id(); + $session->set('anonymous_allowed_file_ids', $allowed_temp_files); + } + return $file; } /** @@ -1200,15 +1370,16 @@ function file_managed_file_save_upload($element, FormStateInterface $form_state) $files_uploaded = $element['#multiple'] && count(array_filter($file_upload)) > 0; $files_uploaded |= !$element['#multiple'] && !empty($file_upload); if ($files_uploaded) { - if (!$files = file_save_upload($upload_name, $element['#upload_validators'], $destination)) { + if (!$files = _file_save_upload_from_form($element, $form_state)) { \Drupal::logger('file')->notice('The file upload failed. %upload', ['%upload' => $upload_name]); - $form_state->setError($element, t('Files in the @name field were unable to be uploaded.', ['@name' => $element['#title']])); return []; } // Value callback expects FIDs to be keys. $files = array_filter($files); - $fids = array_map(function($file) { return $file->id(); }, $files); + $fids = array_map(function ($file) { + return $file->id(); + }, $files); return empty($files) ? [] : array_combine($fids, $files); }