access('view')) { throw new AccessDeniedHttpException(); } foreach ($entity as $field_name => $field) { if (!$field->access('view')) { unset($entity->{$field_name}); } } $response = new ResourceResponse($entity, 200); // Make the response use the entity's cacheability metadata. // @todo include access cacheability metadata, for the access checks above. $response->addCacheableDependency($entity); return $response; } /** * Responds to entity POST requests and saves the new entity. * * @param \Drupal\Core\Entity\EntityInterface $entity * The entity. * * @return \Drupal\rest\ResourceResponse * The HTTP response object. * * @throws \Symfony\Component\HttpKernel\Exception\HttpException */ public function post(EntityInterface $entity = NULL) { if ($entity == NULL) { throw new BadRequestHttpException('No entity content received.'); } if (!$entity->access('create')) { throw new AccessDeniedHttpException(); } $definition = $this->getPluginDefinition(); // Verify that the deserialized entity is of the type that we expect to // prevent security issues. if ($entity->getEntityTypeId() != $definition['entity_type']) { throw new BadRequestHttpException('Invalid entity type'); } // POSTed entities must not have an ID set, because we always want to create // new entities here. if (!$entity->isNew()) { throw new BadRequestHttpException('Only new entities can be created'); } // Only check 'edit' permissions for fields that were actually // submitted by the user. Field access makes no difference between 'create' // and 'update', so the 'edit' operation is used here. foreach ($entity->_restSubmittedFields as $key => $field_name) { if (!$entity->get($field_name)->access('edit')) { throw new AccessDeniedHttpException(SafeMarkup::format('Access denied on creating field @field', array('@field' => $field_name))); } } // Validate the received data before saving. $this->validate($entity); try { $entity->save(); $this->logger->notice('Created entity %type with ID %id.', array('%type' => $entity->getEntityTypeId(), '%id' => $entity->id())); // 201 Created responses have an empty body. return new ResourceResponse(NULL, 201, array('Location' => $entity->url('canonical', ['absolute' => TRUE]))); } catch (EntityStorageException $e) { throw new HttpException(500, 'Internal Server Error', $e); } } /** * Responds to entity PATCH requests. * * @param \Drupal\Core\Entity\EntityInterface $original_entity * The original entity object. * @param \Drupal\Core\Entity\EntityInterface $entity * The entity. * * @return \Drupal\rest\ResourceResponse * The HTTP response object. * * @throws \Symfony\Component\HttpKernel\Exception\HttpException */ public function patch(EntityInterface $original_entity, EntityInterface $entity = NULL) { if ($entity == NULL) { throw new BadRequestHttpException('No entity content received.'); } $definition = $this->getPluginDefinition(); if ($entity->getEntityTypeId() != $definition['entity_type']) { throw new BadRequestHttpException('Invalid entity type'); } if (!$original_entity->access('update')) { throw new AccessDeniedHttpException(); } // Overwrite the received properties. $langcode_key = $entity->getEntityType()->getKey('langcode'); foreach ($entity->_restSubmittedFields as $field_name) { $field = $entity->get($field_name); // It is not possible to set the language to NULL as it is automatically // re-initialized. As it must not be empty, skip it if it is. if ($field_name == $langcode_key && $field->isEmpty()) { continue; } if (!$original_entity->get($field_name)->access('edit')) { throw new AccessDeniedHttpException(SafeMarkup::format('Access denied on updating field @field.', array('@field' => $field_name))); } $original_entity->set($field_name, $field->getValue()); } // Validate the received data before saving. $this->validate($original_entity); try { $original_entity->save(); $this->logger->notice('Updated entity %type with ID %id.', array('%type' => $entity->getEntityTypeId(), '%id' => $entity->id())); // Update responses have an empty body. return new ResourceResponse(NULL, 204); } catch (EntityStorageException $e) { throw new HttpException(500, 'Internal Server Error', $e); } } /** * Responds to entity DELETE requests. * * @param \Drupal\Core\Entity\EntityInterface $entity * The entity object. * * @return \Drupal\rest\ResourceResponse * The HTTP response object. * * @throws \Symfony\Component\HttpKernel\Exception\HttpException */ public function delete(EntityInterface $entity) { if (!$entity->access('delete')) { throw new AccessDeniedHttpException(); } try { $entity->delete(); $this->logger->notice('Deleted entity %type with ID %id.', array('%type' => $entity->getEntityTypeId(), '%id' => $entity->id())); // Delete responses have an empty body. return new ResourceResponse(NULL, 204); } catch (EntityStorageException $e) { throw new HttpException(500, 'Internal Server Error', $e); } } /** * Verifies that the whole entity does not violate any validation constraints. * * @param \Drupal\Core\Entity\EntityInterface $entity * The entity object. * * @throws \Symfony\Component\HttpKernel\Exception\HttpException * If validation errors are found. */ protected function validate(EntityInterface $entity) { $violations = $entity->validate(); // Remove violations of inaccessible fields as they cannot stem from our // changes. $violations->filterByFieldAccess(); if (count($violations) > 0) { $message = "Unprocessable Entity: validation failed.\n"; foreach ($violations as $violation) { $message .= $violation->getPropertyPath() . ': ' . $violation->getMessage() . "\n"; } // Instead of returning a generic 400 response we use the more specific // 422 Unprocessable Entity code from RFC 4918. That way clients can // distinguish between general syntax errors in bad serializations (code // 400) and semantic errors in well-formed requests (code 422). throw new HttpException(422, $message); } } /** * {@inheritdoc} */ protected function getBaseRoute($canonical_path, $method) { $route = parent::getBaseRoute($canonical_path, $method); $definition = $this->getPluginDefinition(); $parameters = $route->getOption('parameters') ?: array(); $parameters[$definition['entity_type']]['type'] = 'entity:' . $definition['entity_type']; $route->setOption('parameters', $parameters); return $route; } }