You are here

class FileUpload in Drupal 9

Same name and namespace in other branches
  1. 8 core/modules/jsonapi/src/Controller/FileUpload.php \Drupal\jsonapi\Controller\FileUpload
  2. 10 core/modules/jsonapi/src/Controller/FileUpload.php \Drupal\jsonapi\Controller\FileUpload

Handles file upload requests.

@internal JSON:API maintains no PHP API. The API is the HTTP API. This class may change at any time and could break any dependencies on it.

Hierarchy

Expanded class hierarchy of FileUpload

See also

https://www.drupal.org/project/drupal/issues/3032787

jsonapi.api.php

1 string reference to 'FileUpload'
jsonapi.services.yml in core/modules/jsonapi/jsonapi.services.yml
core/modules/jsonapi/jsonapi.services.yml
1 service uses FileUpload
jsonapi.file_upload in core/modules/jsonapi/jsonapi.services.yml
Drupal\jsonapi\Controller\FileUpload

File

core/modules/jsonapi/src/Controller/FileUpload.php, line 40

Namespace

Drupal\jsonapi\Controller
View source
class FileUpload {
  use EntityValidationTrait;

  /**
   * The current user making the request.
   *
   * @var \Drupal\Core\Session\AccountInterface
   */
  protected $currentUser;

  /**
   * The field manager.
   *
   * @var \Drupal\Core\Entity\EntityFieldManagerInterface
   */
  protected $fieldManager;

  /**
   * The file uploader.
   *
   * @var \Drupal\jsonapi\Controller\TemporaryJsonapiFileFieldUploader
   */
  protected $fileUploader;

  /**
   * An HTTP kernel for making subrequests.
   *
   * @var \Symfony\Component\HttpKernel\HttpKernelInterface
   */
  protected $httpKernel;

  /**
   * Creates a new FileUpload instance.
   *
   * @param \Drupal\Core\Session\AccountInterface $current_user
   *   The current user.
   * @param \Drupal\Core\Entity\EntityFieldManagerInterface $field_manager
   *   The entity field manager.
   * @param \Drupal\jsonapi\Controller\TemporaryJsonapiFileFieldUploader $file_uploader
   *   The file uploader.
   * @param \Symfony\Component\HttpKernel\HttpKernelInterface $http_kernel
   *   An HTTP kernel for making subrequests.
   */
  public function __construct(AccountInterface $current_user, EntityFieldManagerInterface $field_manager, TemporaryJsonapiFileFieldUploader $file_uploader, HttpKernelInterface $http_kernel) {
    $this->currentUser = $current_user;
    $this->fieldManager = $field_manager;
    $this->fileUploader = $file_uploader;
    $this->httpKernel = $http_kernel;
  }

  /**
   * Handles JSON:API file upload requests.
   *
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The HTTP request object.
   * @param \Drupal\jsonapi\ResourceType\ResourceType $resource_type
   *   The JSON:API resource type for the current request.
   * @param string $file_field_name
   *   The file field for which the file is to be uploaded.
   * @param \Drupal\Core\Entity\FieldableEntityInterface $entity
   *   The entity for which the file is to be uploaded.
   *
   * @return \Drupal\jsonapi\ResourceResponse
   *   The response object.
   *
   * @throws \Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException
   *   Thrown when there are validation errors.
   * @throws \Drupal\Core\Entity\EntityStorageException
   *   Thrown if the upload's target resource could not be saved.
   * @throws \Exception
   *   Thrown if an exception occurs during a subrequest to fetch the newly
   *   created file entity.
   */
  public function handleFileUploadForExistingResource(Request $request, ResourceType $resource_type, $file_field_name, FieldableEntityInterface $entity) {
    $file_field_name = $resource_type
      ->getInternalName($file_field_name);
    $field_definition = $this
      ->validateAndLoadFieldDefinition($resource_type
      ->getEntityTypeId(), $resource_type
      ->getBundle(), $file_field_name);
    static::ensureFileUploadAccess($this->currentUser, $field_definition, $entity);
    $filename = $this->fileUploader
      ->validateAndParseContentDispositionHeader($request);
    $file = $this->fileUploader
      ->handleFileUploadForField($field_definition, $filename, $this->currentUser);
    if ($file instanceof EntityConstraintViolationListInterface) {
      $violations = $file;
      $message = "Unprocessable Entity: file validation failed.\n";
      $message .= implode("\n", array_map(function (ConstraintViolationInterface $violation) {
        return PlainTextOutput::renderFromHtml($violation
          ->getMessage());
      }, (array) $violations
        ->getIterator()));
      throw new UnprocessableEntityHttpException($message);
    }
    if ($resource_type
      ->getFieldByInternalName($file_field_name)
      ->hasOne()) {
      $entity->{$file_field_name} = $file;
    }
    else {
      $entity
        ->get($file_field_name)
        ->appendItem($file);
    }
    static::validate($entity, [
      $file_field_name,
    ]);
    $entity
      ->save();
    $route_parameters = [
      'entity' => $entity
        ->uuid(),
    ];
    $route_name = sprintf('jsonapi.%s.%s.related', $resource_type
      ->getTypeName(), $resource_type
      ->getPublicName($file_field_name));
    $related_url = Url::fromRoute($route_name, $route_parameters)
      ->toString(TRUE);
    $request = Request::create($related_url
      ->getGeneratedUrl(), 'GET', [], $request->cookies
      ->all(), [], $request->server
      ->all());
    return $this->httpKernel
      ->handle($request, HttpKernelInterface::SUB_REQUEST);
  }

  /**
   * Handles JSON:API file upload requests.
   *
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   The HTTP request object.
   * @param \Drupal\jsonapi\ResourceType\ResourceType $resource_type
   *   The JSON:API resource type for the current request.
   * @param string $file_field_name
   *   The file field for which the file is to be uploaded.
   *
   * @return \Drupal\jsonapi\ResourceResponse
   *   The response object.
   *
   * @throws \Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException
   *   Thrown when there are validation errors.
   */
  public function handleFileUploadForNewResource(Request $request, ResourceType $resource_type, $file_field_name) {
    $file_field_name = $resource_type
      ->getInternalName($file_field_name);
    $field_definition = $this
      ->validateAndLoadFieldDefinition($resource_type
      ->getEntityTypeId(), $resource_type
      ->getBundle(), $file_field_name);
    static::ensureFileUploadAccess($this->currentUser, $field_definition);
    $filename = $this->fileUploader
      ->validateAndParseContentDispositionHeader($request);
    $file = $this->fileUploader
      ->handleFileUploadForField($field_definition, $filename, $this->currentUser);
    if ($file instanceof EntityConstraintViolationListInterface) {
      $violations = $file;
      $message = "Unprocessable Entity: file validation failed.\n";
      $message .= implode("\n", array_map(function (ConstraintViolationInterface $violation) {
        return PlainTextOutput::renderFromHtml($violation
          ->getMessage());
      }, iterator_to_array($violations)));
      throw new UnprocessableEntityHttpException($message);
    }

    // @todo Remove line below in favor of commented line in https://www.drupal.org/project/drupal/issues/2878463.
    $self_link = new Link(new CacheableMetadata(), Url::fromRoute('jsonapi.file--file.individual', [
      'entity' => $file
        ->uuid(),
    ]), 'self');

    /* $self_link = new Link(new CacheableMetadata(), $this->entity->toUrl('jsonapi'), ['self']); */
    $links = new LinkCollection([
      'self' => $self_link,
    ]);
    $relatable_resource_types = $resource_type
      ->getRelatableResourceTypesByField($resource_type
      ->getPublicName($file_field_name));
    $file_resource_type = reset($relatable_resource_types);
    $resource_object = ResourceObject::createFromEntity($file_resource_type, $file);
    return new ResourceResponse(new JsonApiDocumentTopLevel(new ResourceObjectData([
      $resource_object,
    ], 1), new NullIncludedData(), $links), 201, []);
  }

  /**
   * Ensures that the given account is allowed to upload a file.
   *
   * @param \Drupal\Core\Session\AccountInterface $account
   *   The account for which access should be checked.
   * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
   *   The field for which the file is to be uploaded.
   * @param \Drupal\Core\Entity\FieldableEntityInterface|null $entity
   *   The entity, if one exists, for which the file is to be uploaded.
   */
  protected static function ensureFileUploadAccess(AccountInterface $account, FieldDefinitionInterface $field_definition, FieldableEntityInterface $entity = NULL) {
    $access_result = $entity ? TemporaryJsonapiFileFieldUploader::checkFileUploadAccess($account, $field_definition, $entity) : TemporaryJsonapiFileFieldUploader::checkFileUploadAccess($account, $field_definition);
    if (!$access_result
      ->isAllowed()) {
      $reason = 'The current user is not permitted to upload a file for this field.';
      if ($access_result instanceof AccessResultReasonInterface) {
        $reason .= ' ' . $access_result
          ->getReason();
      }
      throw new AccessDeniedHttpException($reason);
    }
  }

  /**
   * Validates and loads a field definition instance.
   *
   * @param string $entity_type_id
   *   The entity type ID the field is attached to.
   * @param string $bundle
   *   The bundle the field is attached to.
   * @param string $field_name
   *   The field name.
   *
   * @return \Drupal\Core\Field\FieldDefinitionInterface
   *   The field definition.
   *
   * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
   *   Thrown when the field does not exist.
   * @throws \Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException
   *   Thrown when the target type of the field is not a file, or the current
   *   user does not have 'edit' access for the field.
   */
  protected function validateAndLoadFieldDefinition($entity_type_id, $bundle, $field_name) {
    $field_definitions = $this->fieldManager
      ->getFieldDefinitions($entity_type_id, $bundle);
    if (!isset($field_definitions[$field_name])) {
      throw new NotFoundHttpException(sprintf('Field "%s" does not exist.', $field_name));
    }

    /** @var \Drupal\Core\Field\FieldDefinitionInterface $field_definition */
    $field_definition = $field_definitions[$field_name];
    if ($field_definition
      ->getSetting('target_type') !== 'file') {
      throw new AccessDeniedException(sprintf('"%s" is not a file field', $field_name));
    }
    return $field_definition;
  }

}

Members

Namesort descending Modifiers Type Description Overrides
EntityValidationTrait::validate protected static function Verifies that an entity does not violate any validation constraints.
FileUpload::$currentUser protected property The current user making the request.
FileUpload::$fieldManager protected property The field manager.
FileUpload::$fileUploader protected property The file uploader.
FileUpload::$httpKernel protected property An HTTP kernel for making subrequests.
FileUpload::ensureFileUploadAccess protected static function Ensures that the given account is allowed to upload a file.
FileUpload::handleFileUploadForExistingResource public function Handles JSON:API file upload requests.
FileUpload::handleFileUploadForNewResource public function Handles JSON:API file upload requests.
FileUpload::validateAndLoadFieldDefinition protected function Validates and loads a field definition instance.
FileUpload::__construct public function Creates a new FileUpload instance.