Change record status: 
Project: 
Introduced in branch: 
10.3.x
Introduced in version: 
10.3.0
Description: 

\Drupal\file\Plugin\rest\resource\FileUploadResource no longer duplicates file upload logic, and re-uses the \Drupal\file\Upload\FileUploadHandler.

As such the constructor for FileUploadResource has changed.

Before:

public function __construct(
  array $configuration, 
  $plugin_id, 
  $plugin_definition, 
  $serializer_formats, 
  LoggerInterface $logger, 
  FileSystemInterface $file_system, 
  EntityTypeManagerInterface $entity_type_manager, 
  EntityFieldManagerInterface $entity_field_manager, 
  AccountInterface $current_user, 
  $mime_type_guesser, 
  Token $token, 
  LockBackendInterface $lock, 
  Config $system_file_config, 
  EventDispatcherInterface $event_dispatcher, 
  FileValidatorInterface $file_validator = NULL,
)

After:

public function __construct(
    array $configuration,
    $plugin_id,
    $plugin_definition,
    $serializer_formats,
    LoggerInterface $logger,
    protected FileSystemInterface $fileSystem,
    protected EntityTypeManagerInterface $entityTypeManager,
    protected EntityFieldManagerInterface $entityFieldManager,
    protected FileValidatorInterface $fileValidator,
    protected InputStreamFileWriterInterface $inputStreamFileWriter,
    protected FileUploadHandler $fileUploadHandler,
)

Comments

didebru’s picture

I miss an example on how to handle file uploads now.

Example of old working solution.

$scheme = $element['#uri_scheme'] ?? 'private';
    $destination = $scheme . '://webform/' . $webform->id() . '/_sid_';

    $filename = ContentDispositionFilenameParser::parseFilename($request);

    // Check the destination file path is writable.
    if (!$this->fileSystem->prepareDirectory($destination, FileSystemInterface::CREATE_DIRECTORY)) {
      return new ModifiedResourceResponse([
        'error' => 'Destination file path is not writable.',
      ]);
    }

    $hash = bin2hex(random_bytes(18));

    // Create the file.
    $file_uri = "{$destination}/{$hash}-{$filename}";

    $temp_file_path = $this->streamUploadData();

    $file_uri = $this->fileSystem->getDestinationFilename($file_uri, FileExists::Rename);

    // Lock based on the prepared file URI.
    $lock_id = $this->generateLockIdFromFileUri($file_uri);

    if (!$this->lock->acquire($lock_id)) {
      return new ModifiedResourceResponse([
        'error' => 'Failed to save file.',
      ]);
    }

    // Begin building file entity.
    $file = File::create([]);
    $file->setOwnerId($this->currentUser->id());
    $file->setFilename($filename);
    if ($this->mimeTypeGuesser instanceof MimeTypeGuesserInterface) {
      $file->setMimeType($this->mimeTypeGuesser->guessMimeType($filename));
    }
    else {
      $file->setMimeType($this->mimeTypeGuesser->guess($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);
    }

    $validators = [];
    // Validate allowed extensions.
    if (!empty($element['#file_extensions'])) {
      $allowed_extensions = $element['#file_extensions'];
    }
    else {
      $file_type = str_replace('webform_', '', $element['#type']);
      // @phpstan-ignore-next-line
      $allowed_extensions = \Drupal::config('webform.settings')->get("file.default_{$file_type}_extensions");
    }
    $validators['file_validate_extensions'] = [$allowed_extensions];

    $file->setFileUri($temp_file_path);
    // Set the size. This is done in File::preSave() but we validate the file
    // before it is saved.
    $file->setSize(@filesize($temp_file_path));

    // Cap the upload size according to the PHP limit.
    $max_filesize = Bytes::toNumber(Environment::getUploadMaxSize());
    if (!empty($element['#max_filesize'])) {
      $max_filesize = min($max_filesize, Bytes::toNumber($element['#max_filesize']) * 1024 * 1024);
    }
    $validators['file_validate_size'] = [$max_filesize];

    // Validate the file against field-level validators first while the file is
    // still a temporary file. Validation is split up in 2 steps to be the same
    // as in _file_save_upload_single().
    // For backwards compatibility this part is copied from ::validate() to
    // leave that method behavior unchanged.
    // @todo Improve this with a file uploader service in
    //   https://www.drupal.org/project/drupal/issues/2940383
    $validators['file_validate_name_length'] = [];

    $errors = $this->fileValidator->validate($file, $validators);

    if (!empty($errors)) {
      $message = "Unprocessable Entity: file validation failed.\n";
      $message .= implode("\n", array_map([
        PlainTextOutput::class,
        'renderFromHtml',
      ], $errors));
      return new ModifiedResourceResponse([
        'error' => $message,
      ]);
    }

    $file->setFileUri($file_uri);
    // Move the file to the correct location after validation. Use
    // FileSystemInterface::EXISTS_ERROR as the file location has already been
    // determined above in FileSystem::getDestinationFilename().
    try {
      $this->fileSystem->move($temp_file_path, $file_uri, FileSystemInterface::EXISTS_ERROR);
    }
    catch (FileException $e) {
      return new ModifiedResourceResponse([
        'error' => 'Temporary file could not be moved to file location',
      ]);
    }

    // Second step of the validation on the file object itself now.
    $this->resourceValidate($file);

    $file->save();

    $this->lock->release($lock_id);

How can this be done with the new logic?