class FileUpload

Same name in other branches
  1. 9 core/modules/jsonapi/src/Controller/FileUpload.php \Drupal\jsonapi\Controller\FileUpload
  2. 8.9.x core/modules/jsonapi/src/Controller/FileUpload.php \Drupal\jsonapi\Controller\FileUpload
  3. 11.x 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 file declares its use of FileUpload
FileUploadTest.php in core/modules/jsonapi/tests/src/Kernel/Controller/FileUploadTest.php
2 string references to 'FileUpload'
jsonapi.services.yml in core/modules/jsonapi/jsonapi.services.yml
core/modules/jsonapi/jsonapi.services.yml
ManagedFile::processManagedFile in core/modules/file/src/Element/ManagedFile.php
Render API callback: Expands the managed_file element type.
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 58

Namespace

Drupal\jsonapi\Controller
View source
class FileUpload {
    use DeprecatedServicePropertyTrait;
    use EntityValidationTrait;
    use FileUploadLocationTrait;
    use FileValidatorSettingsTrait;
    
    /**
     * {@inheritdoc}
     */
    protected array $deprecatedProperties = [
        'fileUploader' => 'jsonapi.file.uploader.field',
    ];
    
    /**
     * Constructs a new FileUpload object.
     *
     * @phpstan-ignore-next-line
     */
    public function __construct(AccountInterface $currentUser, EntityFieldManagerInterface $fieldManager, FileUploadHandler|TemporaryJsonapiFileFieldUploader $fileUploadHandler, HttpKernelInterface $httpKernel, ?InputStreamFileWriterInterface $inputStreamFileWriter = NULL, ?FileSystemInterface $fileSystem = NULL) {
        if (!$this->fileUploadHandler instanceof FileUploadHandler) {
            @trigger_error('Calling ' . __METHOD__ . '() without the $fileUploadHandler argument being an instance of ' . FileUploadHandler::class . ' is deprecated in drupal:10.3.0 and it will be required in drupal:11.0.0. See https://www.drupal.org/node/3445266', E_USER_DEPRECATED);
            $this->fileUploadHandler = \Drupal::service('file.upload.handler');
        }
        if (!$this->inputStreamFileWriter) {
            @trigger_error('Calling ' . __METHOD__ . '() without the $inputStreamFileWriter argument is deprecated in drupal:10.3.0 and it will be required in drupal:11.0.0. See https://www.drupal.org/node/3445266', E_USER_DEPRECATED);
            $this->inputStreamFileWriter = \Drupal::service('file.input_stream_file_writer');
        }
        if (!$this->fileSystem) {
            @trigger_error('Calling ' . __METHOD__ . '() without the $fileSystem argument is deprecated in drupal:10.3.0 and it will be required in drupal:11.0.0. See https://www.drupal.org/node/3445266', E_USER_DEPRECATED);
            $this->fileSystem = \Drupal::service('file_system');
        }
    }
    
    /**
     * 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, string $file_field_name, FieldableEntityInterface $entity) : Response {
        $result = $this->handleFileUploadForResource($request, $resource_type, $file_field_name, $entity);
        $file = $result->getFile();
        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, string $file_field_name) : ResourceResponse {
        $result = $this->handleFileUploadForResource($request, $resource_type, $file_field_name);
        $file = $result->getFile();
        // @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, []);
    }
    
    /**
     * 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|null $entity
     *   (optional) The entity for which the file is to be uploaded.
     *
     * @return \Drupal\file\Upload\FileUploadResult
     *   The file upload result.
     */
    protected function handleFileUploadForResource(Request $request, ResourceType $resource_type, string $file_field_name, ?FieldableEntityInterface $entity = NULL) : FileUploadResult {
        $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 = ContentDispositionFilenameParser::parseFilename($request);
        $tempPath = $this->inputStreamFileWriter
            ->writeStreamToFile();
        $uploadedFile = new InputStreamUploadedFile($filename, $filename, $tempPath, @filesize($tempPath));
        $settings = $field_definition->getSettings();
        $validators = $this->getFileUploadValidators($settings);
        if (!array_key_exists('FileExtension', $validators) && $settings['file_extensions'] === '') {
            // An empty string means 'all file extensions' but the FileUploadHandler
            // needs the FileExtension entry to be present and empty in order for this
            // to be respected. An empty array means 'all file extensions'.
            // @see \Drupal\file\Upload\FileUploadHandler::handleExtensionValidation
            $validators['FileExtension'] = [];
        }
        $destination = $this->getUploadLocation($field_definition);
        // Check the destination file path is writable.
        if (!$this->fileSystem
            ->prepareDirectory($destination, FileSystemInterface::CREATE_DIRECTORY)) {
            throw new HttpException(500, 'Destination file path is not writable');
        }
        try {
            $result = $this->fileUploadHandler
                ->handleFileUpload($uploadedFile, $validators, $destination, FileExists::Rename, FALSE);
        } catch (LockAcquiringException $e) {
            throw new HttpException(503, $e->getMessage(), NULL, [
                'Retry-After' => 1,
            ]);
        } catch (UploadException $e) {
            throw new HttpException(500, 'Input file data could not be read', $e);
        } catch (CannotWriteFileException $e) {
            throw new HttpException(500, 'Temporary file data could not be written', $e);
        } catch (NoFileException $e) {
            throw new HttpException(500, 'Temporary file could not be opened', $e);
        } catch (FileExistsException $e) {
            throw new HttpException(500, $e->getMessage(), $e);
        } catch (FileException $e) {
            throw new HttpException(500, 'Temporary file could not be moved to file location');
        }
        if ($result->hasViolations()) {
            $message = "Unprocessable Entity: file validation failed.\n";
            $message .= implode("\n", array_map(function (ConstraintViolationInterface $violation) {
                return PlainTextOutput::renderFromHtml($violation->getMessage());
            }, (array) $result->getViolations()
                ->getIterator()));
            throw new UnprocessableEntityHttpException($message);
        }
        return $result;
    }
    
    /**
     * Checks if the current user has access to upload the file.
     *
     * @param \Drupal\Core\Session\AccountInterface $account
     *   The account for which file upload access should be checked.
     * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
     *   The field definition for which to get validators.
     * @param \Drupal\Core\Entity\EntityInterface $entity
     *   (optional) The entity to which the file is to be uploaded, if it exists.
     *   If the entity does not exist and it is not given, create access to the
     *   entity the file is attached to will be checked.
     *
     * @return \Drupal\Core\Access\AccessResultInterface
     *   The file upload access result.
     */
    public static function checkFileUploadAccess(AccountInterface $account, FieldDefinitionInterface $field_definition, ?EntityInterface $entity = NULL) {
        assert(is_null($entity) || $field_definition->getTargetEntityTypeId() === $entity->getEntityTypeId() && (is_null($field_definition->getTargetBundle()) || $field_definition->getTargetBundle() === $entity->bundle()));
        $entity_type_manager = \Drupal::entityTypeManager();
        $entity_access_control_handler = $entity_type_manager->getAccessControlHandler($field_definition->getTargetEntityTypeId());
        $bundle = $entity_type_manager->getDefinition($field_definition->getTargetEntityTypeId())
            ->hasKey('bundle') ? $field_definition->getTargetBundle() : NULL;
        $entity_access_result = $entity ? $entity_access_control_handler->access($entity, 'update', $account, TRUE) : $entity_access_control_handler->createAccess($bundle, $account, [], TRUE);
        $field_access_result = $entity_access_control_handler->fieldAccess('edit', $field_definition, NULL, NULL, TRUE);
        return $entity_access_result->andIf($field_access_result);
    }
    
    /**
     * 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 ? static::checkFileUploadAccess($account, $field_definition, $entity) : static::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

Title Sort descending Modifiers Object type Summary
DeprecatedServicePropertyTrait::__get public function Allows to access deprecated/removed properties.
EntityValidationTrait::validate protected static function Verifies that an entity does not violate any validation constraints.
FileUpload::$deprecatedProperties protected property
FileUpload::checkFileUploadAccess public static function Checks if the current user has access to upload the file.
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::handleFileUploadForResource protected function Handles JSON:API file upload requests.
FileUpload::validateAndLoadFieldDefinition protected function Validates and loads a field definition instance.
FileUpload::__construct public function Constructs a new FileUpload object.
FileUploadLocationTrait::getUploadLocation public function Resolves the file upload location from a file field definition.
FileValidatorSettingsTrait::getFileUploadValidators public function Gets the upload validators for the specified settings.

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