Mercurial > hg > cmmr2012-drupal-site
comparison core/modules/jsonapi/src/Controller/FileUpload.php @ 5:12f9dff5fda9 tip
Update to Drupal core 8.7.1
author | Chris Cannam |
---|---|
date | Thu, 09 May 2019 15:34:47 +0100 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
4:a9cd425dd02b | 5:12f9dff5fda9 |
---|---|
1 <?php | |
2 | |
3 namespace Drupal\jsonapi\Controller; | |
4 | |
5 use Drupal\Component\Render\PlainTextOutput; | |
6 use Drupal\Core\Access\AccessResultReasonInterface; | |
7 use Drupal\Core\Cache\CacheableMetadata; | |
8 use Drupal\Core\Entity\EntityConstraintViolationListInterface; | |
9 use Drupal\Core\Entity\EntityFieldManagerInterface; | |
10 use Drupal\Core\Entity\FieldableEntityInterface; | |
11 use Drupal\Core\Field\FieldDefinitionInterface; | |
12 use Drupal\Core\Session\AccountInterface; | |
13 use Drupal\Core\Url; | |
14 use Drupal\jsonapi\Entity\EntityValidationTrait; | |
15 use Drupal\jsonapi\JsonApiResource\JsonApiDocumentTopLevel; | |
16 use Drupal\jsonapi\JsonApiResource\Link; | |
17 use Drupal\jsonapi\JsonApiResource\LinkCollection; | |
18 use Drupal\jsonapi\JsonApiResource\NullIncludedData; | |
19 use Drupal\jsonapi\JsonApiResource\ResourceObject; | |
20 use Drupal\jsonapi\JsonApiResource\ResourceObjectData; | |
21 use Drupal\jsonapi\ResourceResponse; | |
22 use Drupal\jsonapi\ResourceType\ResourceType; | |
23 use Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException; | |
24 use Symfony\Component\HttpFoundation\Request; | |
25 use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; | |
26 use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; | |
27 use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException; | |
28 use Symfony\Component\HttpKernel\HttpKernelInterface; | |
29 use Symfony\Component\Validator\ConstraintViolationInterface; | |
30 | |
31 /** | |
32 * Handles file upload requests. | |
33 * | |
34 * @internal JSON:API maintains no PHP API. The API is the HTTP API. This class | |
35 * may change at any time and could break any dependencies on it. | |
36 * | |
37 * @see https://www.drupal.org/project/jsonapi/issues/3032787 | |
38 * @see jsonapi.api.php | |
39 */ | |
40 class FileUpload { | |
41 | |
42 use EntityValidationTrait; | |
43 | |
44 /** | |
45 * The current user making the request. | |
46 * | |
47 * @var \Drupal\Core\Session\AccountInterface | |
48 */ | |
49 protected $currentUser; | |
50 | |
51 /** | |
52 * The field manager. | |
53 * | |
54 * @var \Drupal\Core\Entity\EntityFieldManagerInterface | |
55 */ | |
56 protected $fieldManager; | |
57 | |
58 /** | |
59 * The file uploader. | |
60 * | |
61 * @var \Drupal\jsonapi\Controller\TemporaryJsonapiFileFieldUploader | |
62 */ | |
63 protected $fileUploader; | |
64 | |
65 /** | |
66 * An HTTP kernel for making subrequests. | |
67 * | |
68 * @var \Symfony\Component\HttpKernel\HttpKernelInterface | |
69 */ | |
70 protected $httpKernel; | |
71 | |
72 /** | |
73 * Creates a new FileUpload instance. | |
74 * | |
75 * @param \Drupal\Core\Session\AccountInterface $current_user | |
76 * The current user. | |
77 * @param \Drupal\Core\Entity\EntityFieldManagerInterface $field_manager | |
78 * The entity field manager. | |
79 * @param \Drupal\jsonapi\Controller\TemporaryJsonapiFileFieldUploader $file_uploader | |
80 * The file uploader. | |
81 * @param \Symfony\Component\HttpKernel\HttpKernelInterface $http_kernel | |
82 * An HTTP kernel for making subrequests. | |
83 */ | |
84 public function __construct(AccountInterface $current_user, EntityFieldManagerInterface $field_manager, TemporaryJsonapiFileFieldUploader $file_uploader, HttpKernelInterface $http_kernel) { | |
85 $this->currentUser = $current_user; | |
86 $this->fieldManager = $field_manager; | |
87 $this->fileUploader = $file_uploader; | |
88 $this->httpKernel = $http_kernel; | |
89 } | |
90 | |
91 /** | |
92 * Handles JSON:API file upload requests. | |
93 * | |
94 * @param \Symfony\Component\HttpFoundation\Request $request | |
95 * The HTTP request object. | |
96 * @param \Drupal\jsonapi\ResourceType\ResourceType $resource_type | |
97 * The JSON:API resource type for the current request. | |
98 * @param string $file_field_name | |
99 * The file field for which the file is to be uploaded. | |
100 * @param \Drupal\Core\Entity\FieldableEntityInterface $entity | |
101 * The entity for which the file is to be uploaded. | |
102 * | |
103 * @return \Drupal\jsonapi\ResourceResponse | |
104 * The response object. | |
105 * | |
106 * @throws \Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException | |
107 * Thrown when there are validation errors. | |
108 * @throws \Drupal\Core\Entity\EntityStorageException | |
109 * Thrown if the upload's target resource could not be saved. | |
110 * @throws \Exception | |
111 * Thrown if an exception occurs during a subrequest to fetch the newly | |
112 * created file entity. | |
113 */ | |
114 public function handleFileUploadForExistingResource(Request $request, ResourceType $resource_type, $file_field_name, FieldableEntityInterface $entity) { | |
115 $field_definition = $this->validateAndLoadFieldDefinition($resource_type->getEntityTypeId(), $resource_type->getBundle(), $file_field_name); | |
116 | |
117 static::ensureFileUploadAccess($this->currentUser, $field_definition, $entity); | |
118 | |
119 $filename = $this->fileUploader->validateAndParseContentDispositionHeader($request); | |
120 $file = $this->fileUploader->handleFileUploadForField($field_definition, $filename, $this->currentUser); | |
121 | |
122 if ($file instanceof EntityConstraintViolationListInterface) { | |
123 $violations = $file; | |
124 $message = "Unprocessable Entity: file validation failed.\n"; | |
125 $message .= implode("\n", array_map(function (ConstraintViolationInterface $violation) { | |
126 return PlainTextOutput::renderFromHtml($violation->getMessage()); | |
127 }, (array) $violations->getIterator())); | |
128 throw new UnprocessableEntityHttpException($message); | |
129 } | |
130 | |
131 if ($field_definition->getFieldStorageDefinition()->getCardinality() === 1) { | |
132 $entity->{$file_field_name} = $file; | |
133 } | |
134 else { | |
135 $entity->get($file_field_name)->appendItem($file); | |
136 } | |
137 static::validate($entity, [$file_field_name]); | |
138 $entity->save(); | |
139 | |
140 $route_parameters = ['entity' => $entity->uuid()]; | |
141 $route_name = sprintf('jsonapi.%s.%s.related', $resource_type->getTypeName(), $file_field_name); | |
142 $related_url = Url::fromRoute($route_name, $route_parameters)->toString(TRUE); | |
143 $request = Request::create($related_url->getGeneratedUrl(), 'GET', [], $request->cookies->all(), [], $request->server->all()); | |
144 return $this->httpKernel->handle($request, HttpKernelInterface::SUB_REQUEST); | |
145 } | |
146 | |
147 /** | |
148 * Handles JSON:API file upload requests. | |
149 * | |
150 * @param \Symfony\Component\HttpFoundation\Request $request | |
151 * The HTTP request object. | |
152 * @param \Drupal\jsonapi\ResourceType\ResourceType $resource_type | |
153 * The JSON:API resource type for the current request. | |
154 * @param string $file_field_name | |
155 * The file field for which the file is to be uploaded. | |
156 * | |
157 * @return \Drupal\jsonapi\ResourceResponse | |
158 * The response object. | |
159 * | |
160 * @throws \Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException | |
161 * Thrown when there are validation errors. | |
162 */ | |
163 public function handleFileUploadForNewResource(Request $request, ResourceType $resource_type, $file_field_name) { | |
164 $field_definition = $this->validateAndLoadFieldDefinition($resource_type->getEntityTypeId(), $resource_type->getBundle(), $file_field_name); | |
165 | |
166 static::ensureFileUploadAccess($this->currentUser, $field_definition); | |
167 | |
168 $filename = $this->fileUploader->validateAndParseContentDispositionHeader($request); | |
169 $file = $this->fileUploader->handleFileUploadForField($field_definition, $filename, $this->currentUser); | |
170 | |
171 if ($file instanceof EntityConstraintViolationListInterface) { | |
172 $violations = $file; | |
173 $message = "Unprocessable Entity: file validation failed.\n"; | |
174 $message .= implode("\n", array_map(function (ConstraintViolationInterface $violation) { | |
175 return PlainTextOutput::renderFromHtml($violation->getMessage()); | |
176 }, iterator_to_array($violations))); | |
177 throw new UnprocessableEntityHttpException($message); | |
178 } | |
179 | |
180 // @todo Remove line below in favor of commented line in https://www.drupal.org/project/jsonapi/issues/2878463. | |
181 $self_link = new Link(new CacheableMetadata(), Url::fromRoute('jsonapi.file--file.individual', ['entity' => $file->uuid()]), ['self']); | |
182 /* $self_link = new Link(new CacheableMetadata(), $this->entity->toUrl('jsonapi'), ['self']); */ | |
183 $links = new LinkCollection(['self' => $self_link]); | |
184 | |
185 $relatable_resource_types = $resource_type->getRelatableResourceTypesByField($file_field_name); | |
186 $file_resource_type = reset($relatable_resource_types); | |
187 $resource_object = ResourceObject::createFromEntity($file_resource_type, $file); | |
188 return new ResourceResponse(new JsonApiDocumentTopLevel(new ResourceObjectData([$resource_object], 1), new NullIncludedData(), $links), 201, []); | |
189 } | |
190 | |
191 /** | |
192 * Ensures that the given account is allowed to upload a file. | |
193 * | |
194 * @param \Drupal\Core\Session\AccountInterface $account | |
195 * The account for which access should be checked. | |
196 * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition | |
197 * The field for which the file is to be uploaded. | |
198 * @param \Drupal\Core\Entity\FieldableEntityInterface|null $entity | |
199 * The entity, if one exists, for which the file is to be uploaded. | |
200 */ | |
201 protected static function ensureFileUploadAccess(AccountInterface $account, FieldDefinitionInterface $field_definition, FieldableEntityInterface $entity = NULL) { | |
202 $access_result = $entity | |
203 ? TemporaryJsonapiFileFieldUploader::checkFileUploadAccess($account, $field_definition, $entity) | |
204 : TemporaryJsonapiFileFieldUploader::checkFileUploadAccess($account, $field_definition); | |
205 if (!$access_result->isAllowed()) { | |
206 $reason = 'The current user is not permitted to upload a file for this field.'; | |
207 if ($access_result instanceof AccessResultReasonInterface) { | |
208 $reason .= ' ' . $access_result->getReason(); | |
209 } | |
210 throw new AccessDeniedHttpException($reason); | |
211 } | |
212 } | |
213 | |
214 /** | |
215 * Validates and loads a field definition instance. | |
216 * | |
217 * @param string $entity_type_id | |
218 * The entity type ID the field is attached to. | |
219 * @param string $bundle | |
220 * The bundle the field is attached to. | |
221 * @param string $field_name | |
222 * The field name. | |
223 * | |
224 * @return \Drupal\Core\Field\FieldDefinitionInterface | |
225 * The field definition. | |
226 * | |
227 * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException | |
228 * Thrown when the field does not exist. | |
229 * @throws \Symfony\Component\HttpFoundation\File\Exception\AccessDeniedException | |
230 * Thrown when the target type of the field is not a file, or the current | |
231 * user does not have 'edit' access for the field. | |
232 */ | |
233 protected function validateAndLoadFieldDefinition($entity_type_id, $bundle, $field_name) { | |
234 $field_definitions = $this->fieldManager->getFieldDefinitions($entity_type_id, $bundle); | |
235 if (!isset($field_definitions[$field_name])) { | |
236 throw new NotFoundHttpException(sprintf('Field "%s" does not exist.', $field_name)); | |
237 } | |
238 | |
239 /** @var \Drupal\Core\Field\FieldDefinitionInterface $field_definition */ | |
240 $field_definition = $field_definitions[$field_name]; | |
241 if ($field_definition->getSetting('target_type') !== 'file') { | |
242 throw new AccessDeniedException(sprintf('"%s" is not a file field', $field_name)); | |
243 } | |
244 | |
245 return $field_definition; | |
246 } | |
247 | |
248 } |