function FileUploadHandler::handleFileUpload

Same name and namespace in other branches
  1. 9 core/modules/file/src/Upload/FileUploadHandler.php \Drupal\file\Upload\FileUploadHandler::handleFileUpload()
  2. 11.x core/modules/file/src/Upload/FileUploadHandler.php \Drupal\file\Upload\FileUploadHandler::handleFileUpload()

Creates a file from an upload.

Parameters

\Drupal\file\Upload\UploadedFileInterface $uploadedFile: The uploaded file object.

array $validators: The validators to run against the uploaded file.

string $destination: The destination directory.

\Drupal\Core\File\FileExists|int $fileExists: The behavior when the destination file already exists.

bool $throw: (optional) Whether to throw an exception if the file is invalid.

Return value

\Drupal\file\Upload\FileUploadResult The created file entity.

Throws

\Symfony\Component\HttpFoundation\File\Exception\FileException Thrown when a file upload error occurred and $throws is TRUE.

\Drupal\Core\File\Exception\FileWriteException Thrown when there is an error moving the file and $throws is TRUE.

\Drupal\Core\File\Exception\FileException Thrown when a file system error occurs and $throws is TRUE.

\Drupal\file\Upload\FileValidationException Thrown when file validation fails and $throws is TRUE.

\Drupal\Core\Lock\LockAcquiringException Thrown when a lock cannot be acquired.

\ValueError Thrown if $fileExists is a legacy int and not a valid value.

File

core/modules/file/src/Upload/FileUploadHandler.php, line 177

Class

FileUploadHandler
Handles validating and creating file entities from file uploads.

Namespace

Drupal\file\Upload

Code

public function handleFileUpload(UploadedFileInterface $uploadedFile, array $validators = [], string $destination = 'temporary://', $fileExists = FileExists::Replace, bool $throw = TRUE) : FileUploadResult {
  if (!$fileExists instanceof FileExists) {
    // @phpstan-ignore-next-line
    $fileExists = FileExists::fromLegacyInt($fileExists, __METHOD__);
  }
  $result = new FileUploadResult();
  if ($throw) {
    @trigger_error('Calling ' . __METHOD__ . '() with the $throw argument as TRUE is deprecated in drupal:10.3.0 and will be removed in drupal:11.0.0. Use \\Drupal\\file\\Upload\\FileUploadResult::getViolations() instead. See https://www.drupal.org/node/3375456', E_USER_DEPRECATED);
    // @phpstan-ignore-next-line
    if (!$uploadedFile->isValid()) {
      // @phpstan-ignore-next-line
      switch ($uploadedFile->getError()) {
        case \UPLOAD_ERR_INI_SIZE:
          // @phpstan-ignore-next-line
          throw new IniSizeFileException($uploadedFile->getErrorMessage());
        case \UPLOAD_ERR_FORM_SIZE:
          // @phpstan-ignore-next-line
          throw new FormSizeFileException($uploadedFile->getErrorMessage());
        case \UPLOAD_ERR_PARTIAL:
          // @phpstan-ignore-next-line
          throw new PartialFileException($uploadedFile->getErrorMessage());
        case \UPLOAD_ERR_NO_FILE:
          // @phpstan-ignore-next-line
          throw new NoFileException($uploadedFile->getErrorMessage());
        case \UPLOAD_ERR_CANT_WRITE:
          // @phpstan-ignore-next-line
          throw new CannotWriteFileException($uploadedFile->getErrorMessage());
        case \UPLOAD_ERR_NO_TMP_DIR:
          // @phpstan-ignore-next-line
          throw new NoTmpDirFileException($uploadedFile->getErrorMessage());
        case \UPLOAD_ERR_EXTENSION:
          // @phpstan-ignore-next-line
          throw new ExtensionFileException($uploadedFile->getErrorMessage());
      }
      // @phpstan-ignore-next-line
      throw new FileException($uploadedFile->getErrorMessage());
    }
  }
  else {
    $violations = $uploadedFile->validate($this->validatorFactory
      ->createValidator());
    if (count($violations) > 0) {
      $result->addViolations($violations);
      return $result;
    }
  }
  $originalName = $uploadedFile->getClientOriginalName();
  $extensions = $this->handleExtensionValidation($validators);
  // Assert that the destination contains a valid stream.
  $destinationScheme = $this->streamWrapperManager::getScheme($destination);
  if (!$this->streamWrapperManager
    ->isValidScheme($destinationScheme)) {
    throw new InvalidStreamWrapperException(sprintf('The file could not be uploaded because the destination "%s" is invalid.', $destination));
  }
  // A file URI may already have a trailing slash or look like "public://".
  if (!str_ends_with($destination, '/')) {
    $destination .= '/';
  }
  // Call an event to sanitize the filename and to attempt to address security
  // issues caused by common server setups.
  $event = new FileUploadSanitizeNameEvent($originalName, $extensions);
  $this->eventDispatcher
    ->dispatch($event);
  $filename = $event->getFilename();
  $mimeType = $this->mimeTypeGuesser
    ->guessMimeType($filename);
  $destinationFilename = $this->fileSystem
    ->getDestinationFilename($destination . $filename, $fileExists);
  if ($destinationFilename === FALSE) {
    throw new FileExistsException(sprintf('Destination file "%s" exists', $destinationFilename));
  }
  // Lock based on the prepared file URI.
  $lock_id = $this->generateLockId($destinationFilename);
  try {
    if (!$this->lock
      ->acquire($lock_id)) {
      throw new LockAcquiringException(sprintf('File "%s" is already locked for writing.', $destinationFilename));
    }
    $file = File::create([
      'uid' => $this->currentUser
        ->id(),
      'status' => 0,
      'uri' => $uploadedFile->getRealPath(),
    ]);
    // This will be replaced later with a filename based on the destination.
    $file->setFilename($filename);
    $file->setMimeType($mimeType);
    $file->setSize($uploadedFile->getSize());
    // Add in our check of the file name length.
    $validators['FileNameLength'] = [];
    // Call the validation functions specified by this function's caller.
    $violations = $this->fileValidator
      ->validate($file, $validators);
    if (count($violations) > 0) {
      $result->addViolations($violations);
      return $result;
    }
    if ($throw) {
      $errors = [];
      foreach ($violations as $violation) {
        $errors[] = $violation->getMessage();
      }
      if (!empty($errors)) {
        throw new FileValidationException('File validation failed', $filename, $errors);
      }
    }
    $file->setFileUri($destinationFilename);
    if (!$this->moveUploadedFile($uploadedFile, $file->getFileUri())) {
      throw new FileWriteException('File upload error. Could not move uploaded file.');
    }
    // Update the filename with any changes as a result of security or
    // renaming due to an existing file.
    $file->setFilename($this->fileSystem
      ->basename($file->getFileUri()));
    if ($fileExists === FileExists::Replace) {
      $existingFile = $this->fileRepository
        ->loadByUri($file->getFileUri());
      if ($existingFile) {
        $file->fid = $existingFile->id();
        $file->setOriginalId($existingFile->id());
      }
    }
    $result->setOriginalFilename($originalName)
      ->setSanitizedFilename($filename)
      ->setFile($file);
    // If the filename has been modified, let the user know.
    if ($event->isSecurityRename()) {
      $result->setSecurityRename();
    }
    // Set the permissions on the new file.
    $this->fileSystem
      ->chmod($file->getFileUri());
    // We can now validate the file object itself before it's saved.
    $violations = $file->validate();
    if ($throw) {
      foreach ($violations as $violation) {
        $errors[] = $violation->getMessage();
      }
      if (!empty($errors)) {
        throw new FileValidationException('File validation failed', $filename, $errors);
      }
    }
    if (count($violations) > 0) {
      $result->addViolations($violations);
      return $result;
    }
    // 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 ($this->currentUser
      ->isAnonymous() && $destinationScheme !== 'public') {
      $session = $this->requestStack
        ->getCurrentRequest()
        ->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);
    }
  } finally {
    $this->lock
      ->release($lock_id);
  }
  return $result;
}

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