function _file_save_upload_single

Same name and namespace in other branches
  1. 8.9.x core/modules/file/file.module \_file_save_upload_single()

Saves a file upload to a new location.

@internal This method should only be called from file_save_upload(). Use that method instead.

Parameters

\SplFileInfo $file_info: The file upload to save.

string $form_field_name: A string that is the associative array key of the upload form element in the form array.

array $validators: (optional) An associative array of callback functions used to validate the file.

bool $destination: (optional) A string containing the URI that the file should be copied to.

int $replace: (optional) The replace behavior when the destination file already exists.

Return value

\Drupal\file\FileInterface|false The created file entity or FALSE if the uploaded file not saved.

Throws

\Drupal\Core\Entity\EntityStorageException

Deprecated

in drupal:9.3.0 and is removed from drupal:10.0.0. Use \Drupal\file\Upload\FileUploadHandler::handleFileUpload() instead.

See also

https://www.drupal.org/node/3239547

file_save_upload()

1 call to _file_save_upload_single()
LegacyFileTest::testFileSaveUploadSingleErrorFormSize in core/modules/file/tests/src/Kernel/LegacyFileTest.php
Tests file size upload errors.

File

core/modules/file/file.module, line 949

Code

function _file_save_upload_single(\SplFileInfo $file_info, $form_field_name, $validators = [], $destination = FALSE, $replace = FileSystemInterface::EXISTS_REPLACE) {
    @trigger_error(__METHOD__ . '() is deprecated in drupal:9.3.0 and is removed from drupal:10.0.0. Use \\Drupal\\file\\Upload\\FileUploadHandler::handleFileUpload() instead. See https://www.drupal.org/node/3239547', E_USER_DEPRECATED);
    $user = \Drupal::currentUser();
    // Remember the original filename so we can print a message if it changes.
    $original_file_name = $file_info->getClientOriginalName();
    // 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' => $original_file_name,
                '%maxsize' => format_size(Environment::getUploadMaxSize()),
            ]));
            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' => $original_file_name,
            ]));
            return FALSE;
        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;
            }
        default:
            // Unknown error
            \Drupal::messenger()->addError(t('The file %file could not be saved. An unknown error has occurred.', [
                '%file' => $original_file_name,
            ]));
            return FALSE;
    }
    // Build a list of allowed extensions.
    $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
        // \Drupal\system\EventSubscriber\SecurityFileUploadEventSubscriber::sanitizeName().
        $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 the destination is not provided, use the temporary directory.
    if (empty($destination)) {
        $destination = 'temporary://';
    }
    
    /** @var \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $stream_wrapper_manager */
    $stream_wrapper_manager = \Drupal::service('stream_wrapper_manager');
    // Assert that the destination contains a valid stream.
    $destination_scheme = $stream_wrapper_manager::getScheme($destination);
    if (!$stream_wrapper_manager->isValidScheme($destination_scheme)) {
        \Drupal::messenger()->addError(t('The file could not be uploaded because the destination %destination is invalid.', [
            '%destination' => $destination,
        ]));
        return FALSE;
    }
    // A file URI may already have a trailing slash or look like "public://".
    if (substr($destination, -1) != '/') {
        $destination .= '/';
    }
    // Call an event to sanitize the filename and to attempt to address security
    // issues caused by common server setups.
    $event = new FileUploadSanitizeNameEvent($original_file_name, $extensions);
    \Drupal::service('event_dispatcher')->dispatch($event);
    // Begin building the file entity.
    $values = [
        'uid' => $user->id(),
        'status' => 0,
        // This will be replaced later with a filename based on the destination.
'filename' => $event->getFilename(),
        'uri' => $file_info->getRealPath(),
        'filesize' => $file_info->getSize(),
    ];
    $file = File::create($values);
    
    /** @var \Drupal\Core\File\FileSystemInterface $file_system */
    $file_system = \Drupal::service('file_system');
    try {
        // Use the result of the sanitization event as the destination name.
        $file->destination = $file_system->getDestinationFilename($destination . $event->getFilename(), $replace);
    } catch (FileException $e) {
        \Drupal::messenger()->addError(t('The file %filename could not be uploaded because the name is invalid.', [
            '%filename' => $file->getFilename(),
        ]));
        return FALSE;
    }
    $guesser = \Drupal::service('file.mime_type.guesser');
    if ($guesser instanceof MimeTypeGuesserInterface) {
        $file->setMimeType($guesser->guessMimeType($values['filename']));
    }
    else {
        $file->setMimeType($guesser->guess($values['filename']));
        @trigger_error('\\Symfony\\Component\\HttpFoundation\\File\\MimeType\\MimeTypeGuesserInterface is deprecated in drupal:9.1.0 and is removed from drupal:10.0.0. Implement \\Symfony\\Component\\Mime\\MimeTypeGuesserInterface instead. See https://www.drupal.org/node/3133341', E_USER_DEPRECATED);
    }
    $file->source = $form_field_name;
    // If the destination is 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;
    }
    // Add in our check of the file name length.
    $validators['file_validate_name_length'] = [];
    // 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;
    }
    $file->setFileUri($file->destination);
    if (!$file_system->moveUploadedFile($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;
    }
    // Update the filename with any changes as a result of the renaming due to an
    // existing file.
    $file->setFilename(\Drupal::service('file_system')->basename($file->destination));
    // If the filename has been modified, let the user know.
    if ($file->getFilename() !== $original_file_name) {
        if ($event->isSecurityRename()) {
            $message = t('For security reasons, your upload has been renamed to %filename.', [
                '%filename' => $file->getFilename(),
            ]);
        }
        else {
            $message = t('Your upload has been renamed to %filename.', [
                '%filename' => $file->getFilename(),
            ]);
        }
        \Drupal::messenger()->addStatus($message);
    }
    // Set the permissions on the new file.
    $file_system->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 == FileSystemInterface::EXISTS_REPLACE) {
        $existing_files = \Drupal::entityTypeManager()->getStorage('file')
            ->loadByProperties([
            'uri' => $file->getFileUri(),
        ]);
        if (count($existing_files)) {
            $existing = reset($existing_files);
            $file->fid = $existing->id();
            $file->setOriginalId($existing->id());
        }
    }
    // Update the filename with any changes as a result of security or renaming
    // due to an existing file.
    $file->setFilename(\Drupal::service('file_system')->basename($file->destination));
    // We can now validate the file object itself before it's saved.
    $violations = $file->validate();
    foreach ($violations as $violation) {
        $errors[] = $violation->getMessage();
    }
    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 made it this far it's safe to record this file in the database.
    $file->save();
    // 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;
}

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.