Chris@18: currentUser = $current_user; Chris@18: $this->fieldManager = $field_manager; Chris@18: $this->fileUploader = $file_uploader; Chris@18: $this->httpKernel = $http_kernel; Chris@18: } Chris@18: Chris@18: /** Chris@18: * Handles JSON:API file upload requests. Chris@18: * Chris@18: * @param \Symfony\Component\HttpFoundation\Request $request Chris@18: * The HTTP request object. Chris@18: * @param \Drupal\jsonapi\ResourceType\ResourceType $resource_type Chris@18: * The JSON:API resource type for the current request. Chris@18: * @param string $file_field_name Chris@18: * The file field for which the file is to be uploaded. Chris@18: * @param \Drupal\Core\Entity\FieldableEntityInterface $entity Chris@18: * The entity for which the file is to be uploaded. Chris@18: * Chris@18: * @return \Drupal\jsonapi\ResourceResponse Chris@18: * The response object. Chris@18: * Chris@18: * @throws \Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException Chris@18: * Thrown when there are validation errors. Chris@18: * @throws \Drupal\Core\Entity\EntityStorageException Chris@18: * Thrown if the upload's target resource could not be saved. Chris@18: * @throws \Exception Chris@18: * Thrown if an exception occurs during a subrequest to fetch the newly Chris@18: * created file entity. Chris@18: */ Chris@18: public function handleFileUploadForExistingResource(Request $request, ResourceType $resource_type, $file_field_name, FieldableEntityInterface $entity) { Chris@18: $field_definition = $this->validateAndLoadFieldDefinition($resource_type->getEntityTypeId(), $resource_type->getBundle(), $file_field_name); Chris@18: Chris@18: static::ensureFileUploadAccess($this->currentUser, $field_definition, $entity); Chris@18: Chris@18: $filename = $this->fileUploader->validateAndParseContentDispositionHeader($request); Chris@18: $file = $this->fileUploader->handleFileUploadForField($field_definition, $filename, $this->currentUser); Chris@18: Chris@18: if ($file instanceof EntityConstraintViolationListInterface) { Chris@18: $violations = $file; Chris@18: $message = "Unprocessable Entity: file validation failed.\n"; Chris@18: $message .= implode("\n", array_map(function (ConstraintViolationInterface $violation) { Chris@18: return PlainTextOutput::renderFromHtml($violation->getMessage()); Chris@18: }, (array) $violations->getIterator())); Chris@18: throw new UnprocessableEntityHttpException($message); Chris@18: } Chris@18: Chris@18: if ($field_definition->getFieldStorageDefinition()->getCardinality() === 1) { Chris@18: $entity->{$file_field_name} = $file; Chris@18: } Chris@18: else { Chris@18: $entity->get($file_field_name)->appendItem($file); Chris@18: } Chris@18: static::validate($entity, [$file_field_name]); Chris@18: $entity->save(); Chris@18: Chris@18: $route_parameters = ['entity' => $entity->uuid()]; Chris@18: $route_name = sprintf('jsonapi.%s.%s.related', $resource_type->getTypeName(), $file_field_name); Chris@18: $related_url = Url::fromRoute($route_name, $route_parameters)->toString(TRUE); Chris@18: $request = Request::create($related_url->getGeneratedUrl(), 'GET', [], $request->cookies->all(), [], $request->server->all()); Chris@18: return $this->httpKernel->handle($request, HttpKernelInterface::SUB_REQUEST); Chris@18: } Chris@18: Chris@18: /** Chris@18: * Handles JSON:API file upload requests. Chris@18: * Chris@18: * @param \Symfony\Component\HttpFoundation\Request $request Chris@18: * The HTTP request object. Chris@18: * @param \Drupal\jsonapi\ResourceType\ResourceType $resource_type Chris@18: * The JSON:API resource type for the current request. Chris@18: * @param string $file_field_name Chris@18: * The file field for which the file is to be uploaded. Chris@18: * Chris@18: * @return \Drupal\jsonapi\ResourceResponse Chris@18: * The response object. Chris@18: * Chris@18: * @throws \Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException Chris@18: * Thrown when there are validation errors. Chris@18: */ Chris@18: public function handleFileUploadForNewResource(Request $request, ResourceType $resource_type, $file_field_name) { Chris@18: $field_definition = $this->validateAndLoadFieldDefinition($resource_type->getEntityTypeId(), $resource_type->getBundle(), $file_field_name); Chris@18: Chris@18: static::ensureFileUploadAccess($this->currentUser, $field_definition); Chris@18: Chris@18: $filename = $this->fileUploader->validateAndParseContentDispositionHeader($request); Chris@18: $file = $this->fileUploader->handleFileUploadForField($field_definition, $filename, $this->currentUser); Chris@18: Chris@18: if ($file instanceof EntityConstraintViolationListInterface) { Chris@18: $violations = $file; Chris@18: $message = "Unprocessable Entity: file validation failed.\n"; Chris@18: $message .= implode("\n", array_map(function (ConstraintViolationInterface $violation) { Chris@18: return PlainTextOutput::renderFromHtml($violation->getMessage()); Chris@18: }, iterator_to_array($violations))); Chris@18: throw new UnprocessableEntityHttpException($message); Chris@18: } Chris@18: Chris@18: // @todo Remove line below in favor of commented line in https://www.drupal.org/project/jsonapi/issues/2878463. Chris@18: $self_link = new Link(new CacheableMetadata(), Url::fromRoute('jsonapi.file--file.individual', ['entity' => $file->uuid()]), ['self']); Chris@18: /* $self_link = new Link(new CacheableMetadata(), $this->entity->toUrl('jsonapi'), ['self']); */ Chris@18: $links = new LinkCollection(['self' => $self_link]); Chris@18: Chris@18: $relatable_resource_types = $resource_type->getRelatableResourceTypesByField($file_field_name); Chris@18: $file_resource_type = reset($relatable_resource_types); Chris@18: $resource_object = ResourceObject::createFromEntity($file_resource_type, $file); Chris@18: return new ResourceResponse(new JsonApiDocumentTopLevel(new ResourceObjectData([$resource_object], 1), new NullIncludedData(), $links), 201, []); Chris@18: } Chris@18: Chris@18: /** Chris@18: * Ensures that the given account is allowed to upload a file. Chris@18: * Chris@18: * @param \Drupal\Core\Session\AccountInterface $account Chris@18: * The account for which access should be checked. Chris@18: * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition Chris@18: * The field for which the file is to be uploaded. Chris@18: * @param \Drupal\Core\Entity\FieldableEntityInterface|null $entity Chris@18: * The entity, if one exists, for which the file is to be uploaded. Chris@18: */ Chris@18: protected static function ensureFileUploadAccess(AccountInterface $account, FieldDefinitionInterface $field_definition, FieldableEntityInterface $entity = NULL) { Chris@18: $access_result = $entity Chris@18: ? TemporaryJsonapiFileFieldUploader::checkFileUploadAccess($account, $field_definition, $entity) Chris@18: : TemporaryJsonapiFileFieldUploader::checkFileUploadAccess($account, $field_definition); Chris@18: if (!$access_result->isAllowed()) { Chris@18: $reason = 'The current user is not permitted to upload a file for this field.'; Chris@18: if ($access_result instanceof AccessResultReasonInterface) { Chris@18: $reason .= ' ' . $access_result->getReason(); Chris@18: } Chris@18: throw new AccessDeniedHttpException($reason); Chris@18: } Chris@18: } Chris@18: Chris@18: /** Chris@18: * Validates and loads a field definition instance. Chris@18: * Chris@18: * @param string $entity_type_id Chris@18: * The entity type ID the field is attached to. Chris@18: * @param string $bundle Chris@18: * The bundle the field is attached to. Chris@18: * @param string $field_name Chris@18: * The field name. Chris@18: * Chris@18: * @return \Drupal\Core\Field\FieldDefinitionInterface Chris@18: * The field definition. Chris@18: * Chris@18: * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException Chris@18: * Thrown when the field does not exist. Chris@18: * @throws \Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException Chris@18: * Thrown when the target type of the field is not a file, or the current Chris@18: * user does not have 'edit' access for the field. Chris@18: */ Chris@18: protected function validateAndLoadFieldDefinition($entity_type_id, $bundle, $field_name) { Chris@18: $field_definitions = $this->fieldManager->getFieldDefinitions($entity_type_id, $bundle); Chris@18: if (!isset($field_definitions[$field_name])) { Chris@18: throw new NotFoundHttpException(sprintf('Field "%s" does not exist.', $field_name)); Chris@18: } Chris@18: Chris@18: /** @var \Drupal\Core\Field\FieldDefinitionInterface $field_definition */ Chris@18: $field_definition = $field_definitions[$field_name]; Chris@18: if ($field_definition->getSetting('target_type') !== 'file') { Chris@18: throw new AccessDeniedException(sprintf('"%s" is not a file field', $field_name)); Chris@18: } Chris@18: Chris@18: return $field_definition; Chris@18: } Chris@18: Chris@18: }