Issue #3199697: Add JSON:API Translation experimental module
Merge request reports
Activity
added 1 commit
- 2667a83c - Add getRequestAttribute for now so the code wont break.
160 $request->attributes->set(static::ATTR_TRANSLATION_RESOURCE, $translation); 161 162 $response = parent::getIndividual($translation, $request); 163 164 // If a translation resource was accessed via the "Accept-Language" header, 165 // we inform the client about its canonical URL. 166 if (!$resource_language) { 167 $query = $request->query->all(); 168 $query[static::PARAM_LANGCODE] = $translation->language()->getId(); 169 $url = static::getRequestLink($request, $query) 170 ->setAbsolute() 171 ->toString(TRUE); 172 $response->addCacheableDependency($url); 173 $response->headers->set('Content-Location', $url->getGeneratedUrl()); 174 175 // @todo Neither internal nor dynamic page cache support the "Accept-Language" changed this line in version 5 of the diff
33 use Symfony\Component\HttpKernel\Exception\ConflictHttpException; 34 use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; 35 use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException; 36 use Symfony\Component\Serializer\SerializerInterface; 37 38 /** 39 * Process all entity requests taking translatability into account. 40 * 41 * This replaces the parent controller. To provide a stable HTTP API this does 42 * not rely on Drupal's native language negotiation system, which is highly 43 * configurable and does not allow to rely on fixed URL patterns. Instead this 44 * always relies on the following methods to specify a resource's desired 45 * language: 46 * - GET requests may specify the preferred resource language via the 47 * "Accept-Language" header, when accessing entities via the canonical URL. 48 * Alternatively it is possible to use a "lang_code" query string parameter to changed this line in version 5 of the diff
One of the main goals of this issue is actually moving away from the dynamic core language negotiation to avoid the issues mentioned in the issue summary. It is possible to obtain a predictable fallback logic via the
Accept-Language
header. If we want to add some form of fallback support to the query string parameter, we should probably open a separate ticket to discuss it properly :)Edited by Francesco Placella
added 2898 commits
- ad32f100...16feff8e - 2888 earlier commits
- f1bbcd1a - Issue #3383452 by kunal.sachdev, hooroomoo: Replace Claro CSS variables used in Field UI
- 6985e23b - Issue #3303543 by Aditya4478, Gauravvvv: Refactor Claro's card stylesheet
- 59d11a92 - Issue #3332428 by Gauravvvv, lauriii, kostyashupenko, smustgrave, bnjmnm:...
- 71ef375f - Issue #3332428 by Gauravvvv, lauriii, kostyashupenko, smustgrave, bnjmnm,...
- f0ef38ff - Issue #3303548 by Aditya4478, Stanzin, Gauravvvv: Refactor Claro's fieldset stylesheet
- 513a31b9 - Issue #3384071 by Gauravvvv, Harish1688, yash.rode: Claro: Spacing...
- 108d4306 - Issue #3199697: Applied patch from 2794431-126.
- 3f2ec09c - Issue #3199697: Addressed 2794431-116.
- 5f6fec42 - Issue #3199697: Addressed 2794431-124.
- a35978ee - Issue #3199697: Addressed Fabianx's review.
Toggle commit listRebased #126 on top of
11.x
and addressed Gabe's reviews from #116 and #124, together with (parts of) Fabian's review above.Here's a diff with @bbrala's previous code:
diff --git a/core/modules/jsonapi/src/Access/EntityAccessChecker.php b/core/modules/jsonapi/src/Access/EntityAccessChecker.php index a76447f382..4770e2233b 100644 --- a/core/modules/jsonapi/src/Access/EntityAccessChecker.php +++ b/core/modules/jsonapi/src/Access/EntityAccessChecker.php @@ -122,7 +122,7 @@ public function setLatestRevisionCheck(LatestRevisionCheck $latest_revision_chec public function getAccessCheckedResourceObject(EntityInterface $entity, AccountInterface $account = NULL) { $account = $account ?: $this->currentUser; $resource_type = $this->resourceTypeRepository->get($entity->getEntityTypeId(), $entity->bundle()); - $entity = $this->entityRepository->getTranslationFromContext($entity, NULL, ['operation' => 'entity_upcast']); + $entity = $this->getEntityTranslation($entity); $access = $this->checkEntityAccess($entity, 'view', $account); $entity->addCacheableDependency($access); if (!$access->isAllowed()) { @@ -141,6 +141,19 @@ public function getAccessCheckedResourceObject(EntityInterface $entity, AccountI return ResourceObject::createFromEntity($resource_type, $entity); } + /** + * Returns the appropriate translation to operate on. + * + * @param \Drupal\Core\Entity\EntityInterface $entity + * An entity object. + * + * @return \Drupal\Core\Entity\EntityInterface + * An entity translation object. + */ + protected function getEntityTranslation(EntityInterface $entity): EntityInterface { + return $this->entityRepository->getTranslationFromContext($entity, NULL, ['operation' => 'entity_upcast']); + } + /** * Checks access to the given entity. * diff --git a/core/modules/jsonapi/src/Controller/EntityResource.php b/core/modules/jsonapi/src/Controller/EntityResource.php index 20d9c06da6..7244558e55 100644 --- a/core/modules/jsonapi/src/Controller/EntityResource.php +++ b/core/modules/jsonapi/src/Controller/EntityResource.php @@ -232,7 +232,7 @@ public function getIndividual(EntityInterface $entity, Request $request) { * Thrown when the entity does not pass validation. */ public function createIndividual(ResourceType $resource_type, Request $request) { - $parsed_entity = $this->deserialize($resource_type, $request, JsonApiDocumentTopLevel::class); + $parsed_entity = $this->getEntityFromRequest($resource_type, $request); if ($parsed_entity instanceof FieldableEntityInterface) { // Only check 'edit' permissions for fields that were actually submitted @@ -311,9 +311,9 @@ public function patchIndividual(ResourceType $resource_type, EntityInterface $en throw new BadRequestHttpException('Updating a resource object that has a working copy is not yet supported. See https://www.drupal.org/project/drupal/issues/2795279.'); } - $parsed_entity = $this->deserialize($resource_type, $request, JsonApiDocumentTopLevel::class); + $parsed_entity = $this->getEntityFromRequest($resource_type, $request); - $body = Json::decode($request->getContent()); + $body = $this->getRequestBody($request); $data = $body['data']; if ($data['id'] != $entity->uuid()) { throw new BadRequestHttpException(sprintf( @@ -322,8 +322,8 @@ public function patchIndividual(ResourceType $resource_type, EntityInterface $en $data['id'] )); } - $data += ['attributes' => [], 'relationships' => []]; - $field_names = array_map([$resource_type, 'getInternalName'], array_merge(array_keys($data['attributes']), array_keys($data['relationships']))); + + $field_names = $this->getRequestFieldNames($resource_type, $request); // User resource objects contain a read-only attribute that is not a real // field on the user entity type. @@ -804,6 +804,80 @@ public function removeFromRelationshipData(ResourceType $resource_type, EntityIn return $this->getRelationship($resource_type, $entity, $related, $request, 204); } + /** + * Returns the parsed entity. + * + * @param \Drupal\jsonapi\ResourceType\ResourceType $resource_type + * The base JSON:API resource type for the request to be served. + * @param \Symfony\Component\HttpFoundation\Request $request + * The request object. + * + * @return \Drupal\Core\Entity\EntityInterface + * A non-stored entity object. + */ + protected function getEntityFromRequest(ResourceType $resource_type, Request $request): EntityInterface { + $entity = $this->getRequestAttribute($request, 'jsonapi_parsed_entity', function (Request $request) use ($resource_type) { + return $this->deserialize($resource_type, $request, JsonApiDocumentTopLevel::class); + }); + assert($entity instanceof EntityInterface); + return $entity; + } + + /** + * Returns the names of the fields affected by the specified request. + * + * @param \Drupal\jsonapi\ResourceType\ResourceType $resource_type + * The base JSON:API resource type for the request to be served. + * @param \Symfony\Component\HttpFoundation\Request $request + * The request object. + * + * @return string[] + * An array of field names. + */ + protected function getRequestFieldNames(ResourceType $resource_type, Request $request): array { + return $this->getRequestAttribute($request, 'jsonapi_field_names', function (Request $request) use ($resource_type) { + $body = $this->getRequestBody($request); + $data = $body['data']; + $data += ['attributes' => [], 'relationships' => []]; + return array_map([$resource_type, 'getInternalName'], array_merge(array_keys($data['attributes']), array_keys($data['relationships']))); + }); + } + + /** + * Returns the request body. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * The request object. + * + * @return mixed + * The decoded request body. + */ + protected function getRequestBody(Request $request) { + return $this->getRequestAttribute($request, 'jsonapi_body_decoded', function (Request $request) { + return Json::decode($request->getContent()); + }); + } + + /** + * Returns the specified request attribute and populates it if it is missing. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * The request object. + * @param string $key + * The attribute key. + * @param callable $value_callback + * A callback to be used to compute the value, if missing. + * + * @return mixed + * The attribute value. + */ + protected function getRequestAttribute(Request $request, string $key, callable $value_callback) { + if (!$request->attributes->has($key)) { + $request->attributes->set($key, $value_callback($request)); + } + return $request->attributes->get($key); + } + /** * Deserializes a request body, if any. * diff --git a/core/modules/jsonapi_translation/jsonapi_translation.info.yml b/core/modules/jsonapi_translation/jsonapi_translation.info.yml index 5ad992c2ee..24dd4e5704 100644 --- a/core/modules/jsonapi_translation/jsonapi_translation.info.yml +++ b/core/modules/jsonapi_translation/jsonapi_translation.info.yml @@ -1,8 +1,7 @@ name: JSON:API Translation type: module -description: Allows to translate content entities via the web API provided by JSON:API. +description: Allows the translation of content entities via JSON:API endpoints. package: Core (Experimental) dependencies: -# TODO Should we add this? -# - drupal:content_translation + - drupal:content_translation - drupal:jsonapi diff --git a/core/modules/jsonapi_translation/jsonapi_translation.services.yml b/core/modules/jsonapi_translation/jsonapi_translation.services.yml index b356e61f8b..6bb96046da 100644 --- a/core/modules/jsonapi_translation/jsonapi_translation.services.yml +++ b/core/modules/jsonapi_translation/jsonapi_translation.services.yml @@ -12,16 +12,8 @@ services: # Controller. jsonapi_translation.entity_resource: class: Drupal\jsonapi_translation\Controller\EntityResource - arguments: - - '@entity_type.manager' - - '@entity_field.manager' - - '@jsonapi.resource_type.repository' - - '@renderer' - - '@entity.repository' - - '@jsonapi.include_resolver' - - '@jsonapi.entity_access_checker' - - '@jsonapi.field_resolver' - - '@jsonapi.serializer' - - '@datetime.time' - - '@current_user' - - '@language_manager' + parent: jsonapi.entity_resource + calls: + - + method: 'setLanguageManager' + arguments: ['@language_manager'] diff --git a/core/modules/jsonapi_translation/src/Access/EntityAccessChecker.php b/core/modules/jsonapi_translation/src/Access/EntityAccessChecker.php index db4e2e34bf..16a30224b3 100644 --- a/core/modules/jsonapi_translation/src/Access/EntityAccessChecker.php +++ b/core/modules/jsonapi_translation/src/Access/EntityAccessChecker.php @@ -13,7 +13,7 @@ * * @see https://www.drupal.org/project/drupal/issues/3032787 */ -class EntityAccessChecker extends JsonApiEntityAccessChecker { +final class EntityAccessChecker extends JsonApiEntityAccessChecker { /** * {@inheritdoc} diff --git a/core/modules/jsonapi_translation/src/Controller/EntityResource.php b/core/modules/jsonapi_translation/src/Controller/EntityResource.php index c2fdc8a390..f4e5e98d32 100644 --- a/core/modules/jsonapi_translation/src/Controller/EntityResource.php +++ b/core/modules/jsonapi_translation/src/Controller/EntityResource.php @@ -2,22 +2,14 @@ namespace Drupal\jsonapi_translation\Controller; -use Drupal\Component\Datetime\TimeInterface; use Drupal\Core\Cache\CacheableMetadata; use Drupal\Core\Entity\ContentEntityInterface; -use Drupal\Core\Entity\EntityFieldManagerInterface; use Drupal\Core\Entity\EntityInterface; -use Drupal\Core\Entity\EntityRepositoryInterface; use Drupal\Core\Entity\EntityTypeInterface; -use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Language\LanguageInterface; use Drupal\Core\Language\LanguageManagerInterface; -use Drupal\Core\Render\RendererInterface; -use Drupal\Core\Session\AccountInterface; -use Drupal\jsonapi\Access\EntityAccessChecker; -use Drupal\jsonapi\Context\FieldResolver; use Drupal\jsonapi\Controller\EntityResource as JsonApiEntityResource; -use Drupal\jsonapi\IncludeResolver; +use Drupal\jsonapi\JsonApiResource\Data; use Drupal\jsonapi\JsonApiResource\IncludedData; use Drupal\jsonapi\JsonApiResource\Link; use Drupal\jsonapi\JsonApiResource\LinkCollection; @@ -26,14 +18,12 @@ use Drupal\jsonapi\JsonApiResource\TopLevelDataInterface; use Drupal\jsonapi\ResourceResponse; use Drupal\jsonapi\ResourceType\ResourceType; -use Drupal\jsonapi\ResourceType\ResourceTypeRepositoryInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Component\HttpKernel\Exception\ConflictHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException; -use Symfony\Component\Serializer\SerializerInterface; /** * Process all entity requests taking translatability into account. @@ -45,13 +35,13 @@ * language: * - GET requests may specify the preferred resource language via the * "Accept-Language" header, when accessing entities via the canonical URL. - * Alternatively it is possible to use a "lang_code" query string parameter to + * Alternatively it is possible to use a "langCode" query string parameter to * specify the desired resource language. In the former case a missing - * translation will trigger a fallback to the default translation, in the + * translation will trigger a fallback to an existing translation, in the * latter a "404 Not found" will be returned. * - POST, PATCH, DELETE requests may use the two following ways to specify a * resource language: - * - The "lang_code" query string parameter described above. + * - The "langCode" query string parameter described above. * - The "Content-Language" request header. * These are functionally identical and a missing translation will always * result in a "404 Not found" response. @@ -61,11 +51,12 @@ * * @see https://www.drupal.org/project/drupal/issues/3032787 */ -class EntityResource extends JsonApiEntityResource { +final class EntityResource extends JsonApiEntityResource { - const PARAM_LANGCODE = 'lang_code'; + const PARAM_LANGCODE = 'langCode'; const HEADER_CONTENT_LANGUAGE = 'Content-Language'; const ATTR_TRANSLATION_RESOURCE = 'jsonapi_translation_resource'; + const ATTR_TRANSLATION_LANGUAGE = 'jsonapi_translation_language'; /** * The language manager. @@ -75,48 +66,9 @@ class EntityResource extends JsonApiEntityResource { protected $languageManager; /** - * EntityResource constructor. - * - * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager - * The entity type manager. - * @param \Drupal\Core\Entity\EntityFieldManagerInterface $field_manager - * The entity type field manager. - * @param \Drupal\jsonapi\ResourceType\ResourceTypeRepositoryInterface $resource_type_repository - * The JSON:API resource type repository. - * @param \Drupal\Core\Render\RendererInterface $renderer - * The renderer. - * @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository - * The entity repository. - * @param \Drupal\jsonapi\IncludeResolver $include_resolver - * The include resolver. - * @param \Drupal\jsonapi\Access\EntityAccessChecker $entity_access_checker - * The JSON:API entity access checker. - * @param \Drupal\jsonapi\Context\FieldResolver $field_resolver - * The JSON:API field resolver. - * @param \Symfony\Component\Serializer\SerializerInterface|\Symfony\Component\Serializer\Normalizer\DenormalizerInterface $serializer - * The JSON:API serializer. - * @param \Drupal\Component\Datetime\TimeInterface $time - * The time service. - * @param \Drupal\Core\Session\AccountInterface $user - * The current user account. - * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager - * The language manager. + * Injects the language manager. */ - public function __construct( - EntityTypeManagerInterface $entity_type_manager, - EntityFieldManagerInterface $field_manager, - ResourceTypeRepositoryInterface $resource_type_repository, - RendererInterface $renderer, - EntityRepositoryInterface $entity_repository, - IncludeResolver $include_resolver, - EntityAccessChecker $entity_access_checker, - FieldResolver $field_resolver, - SerializerInterface $serializer, - TimeInterface $time, - AccountInterface $user, - LanguageManagerInterface $language_manager - ) { - parent::__construct($entity_type_manager, $field_manager, $resource_type_repository, $renderer, $entity_repository, $include_resolver, $entity_access_checker, $field_resolver, $serializer, $time, $user); + public function setLanguageManager(LanguageManagerInterface $language_manager) { $this->languageManager = $language_manager; } @@ -138,15 +90,15 @@ public function getIndividual(EntityInterface $entity, Request $request) { return parent::getIndividual($entity, $request); } else { - throw new UnprocessableEntityHttpException('The specified resource does not support translation.'); + throw new BadRequestHttpException('The request specified a preferred language, but the requested resource type does not support translations.'); } } $entity = $this->getDefaultTranslation($entity); // If a resource language is explicitly provided, a resource translation was - // specified. Otherwise we rely on the "Accept-Language" header for the - // fallback logic. + // univocally specified. Otherwise we rely on the "Accept-Language" header + // for the fallback logic. if ($resource_language) { $translation = $this->getResourceTranslation($entity, $request); if (!$translation) { @@ -172,8 +124,8 @@ public function getIndividual(EntityInterface $entity, Request $request) { $response->addCacheableDependency($url); $response->headers->set('Content-Location', $url->getGeneratedUrl()); - // @todo Neither internal nor dynamic page cache support the "Accept-Language" - // header currently. Remove this once they do. + // @todo Internal page cache does not support the "Accept-Language" header + // currently. Remove this once it does. See TODO. \Drupal::service('page_cache_kill_switch')->trigger(); } @@ -206,10 +158,10 @@ public function createIndividual(ResourceType $resource_type, Request $request) $langcode_key = $this->entityTypeManager ->getDefinition($resource_type->getEntityTypeId()) ->getKey('langcode'); - $field_name = $resource_type->getPublicName($langcode_key); + if (!isset($body['data']['attributes'][$field_name])) { - $parsed_entity = $this->getParsedEntity($resource_type, $request); + $parsed_entity = $this->getEntityFromRequest($resource_type, $request); assert($parsed_entity instanceof ContentEntityInterface); $parsed_entity->set($langcode_key, $resource_language); } @@ -402,7 +354,7 @@ protected function checkResourceLanguage(ResourceType $resource_type, Request $r throw new UnprocessableEntityHttpException('The specified resource is not language aware.'); } $resource_langcode = $resource_language->getId(); - $parsed_entity = $this->getParsedEntity($resource_type, $request); + $parsed_entity = $this->getEntityFromRequest($resource_type, $request); $parsed_langcode = $parsed_entity->language()->getId(); if ($resource_langcode !== $parsed_langcode) { $message = 'Translation resource language mismatch: "%s" (request metadata) vs "%s" (request payload).'; @@ -470,23 +422,26 @@ protected function checkFieldTranslatability(ResourceType $resource_type, Reques * specified. */ protected function getResourceLanguage(Request $request): ?LanguageInterface { - $language = $this->getRequestAttribute($request, 'jsonapi_translation_language', function (Request $request) { + $language = $this->getRequestAttribute($request, static::ATTR_TRANSLATION_LANGUAGE, function (Request $request) { $param_langcode = $request->query->get(static::PARAM_LANGCODE); - $header_content_language = $request->headers->get(static::HEADER_CONTENT_LANGUAGE); + $content_language_header = $request->headers->get(static::HEADER_CONTENT_LANGUAGE); - if (!$param_langcode && !$header_content_language) { + if (!$param_langcode && !$content_language_header) { return NULL; } if ($request->headers->get('Accept-Language')) { throw new BadRequestHttpException('Specifying both a request language and the "Accept-Language" header is not supported.'); } - if ($param_langcode && $header_content_language && $param_langcode !== $header_content_language) { + if ($content_language_header && $request->isMethodCacheable()) { + throw new BadRequestHttpException('Specifying the "Content-Language" header is not supported in cacheable requests.'); + } + if ($param_langcode && $content_language_header && $param_langcode !== $content_language_header) { $message = 'Translation resource language mismatch: "%s" ("%s" query string parameter) vs "%s" ("%s" header).'; - throw new UnprocessableEntityHttpException(sprintf($message, $param_langcode, static::PARAM_LANGCODE, $header_content_language, static::HEADER_CONTENT_LANGUAGE)); + throw new UnprocessableEntityHttpException(sprintf($message, $param_langcode, static::PARAM_LANGCODE, $content_language_header, static::HEADER_CONTENT_LANGUAGE)); } - $langcode = $param_langcode ?: $header_content_language; + $langcode = $param_langcode ?: $content_language_header; $languages = $this->languageManager->getLanguages(); $language = $languages[$langcode] ?? NULL; @@ -494,7 +449,7 @@ protected function getResourceLanguage(Request $request): ?LanguageInterface { return $language; } - throw new UnprocessableEntityHttpException(sprintf('The specified language ("%s") is invalid or has not been configured', $langcode)); + throw new UnprocessableEntityHttpException(sprintf('The specified language ("%s") is invalid or has not been configured.', $langcode)); }); assert(!isset($language) || $language instanceof LanguageInterface); return $language; @@ -587,7 +542,11 @@ protected function isEntityTypeTranslatable(EntityTypeInterface $entity_type): b /** * {@inheritdoc} */ - protected function buildWrappedResponse(TopLevelDataInterface $primary_data, Request $request, IncludedData $includes, $response_code = 200, array $headers = [], LinkCollection $links = NULL, array $meta = []) { + protected function buildWrappedResponse(TopLevelDataInterface $data, Request $request, IncludedData $includes, $response_code = 200, array $headers = [], LinkCollection $links = NULL, array $meta = []) { + if ($data instanceof Data && $data->getCardinality() !== 1) { + return parent::buildWrappedResponse($data, $request, $includes, $response_code, $headers, $links, $meta); + } + $translation = $request->attributes->get(static::ATTR_TRANSLATION_RESOURCE); if ($translation instanceof ContentEntityInterface) { $translation_langcodes = array_keys($translation->getTranslationLanguages()); @@ -600,51 +559,31 @@ protected function buildWrappedResponse(TopLevelDataInterface $primary_data, Req unset($query[static::PARAM_LANGCODE]); $url = static::getRequestLink($request, $query); - foreach ($primary_data->getData() as $data) { - if ($data instanceof ResourceObject) { - $resource_links = $data->getLinks()->filter(function ($key) { + foreach ($data->getData() as $resource_data) { + if ($resource_data instanceof ResourceObject) { + $resource_links = $resource_data->getLinks()->filter(function ($key) { return $key !== 'self'; }); $self_link = new Link(new CacheableMetadata(), $url, 'self', $attributes); $resource_links = $resource_links->withLink('self', $self_link); $resource_objects[] = new ResourceObject( - $data, - $data->getResourceType(), - $data->getId(), - $data->getVersionIdentifier(), - $data->getFields(), + $resource_data, + $resource_data->getResourceType(), + $resource_data->getId(), + $resource_data->getVersionIdentifier(), + $resource_data->getFields(), $resource_links, - $data->getLanguage() + $resource_data->getLanguage() ); } } if ($resource_objects) { - $primary_data = new ResourceObjectData($resource_objects, count($resource_objects)); + $data = new ResourceObjectData($resource_objects, count($resource_objects)); } } - return parent::buildWrappedResponse($primary_data, $request, $includes, $response_code, $headers, $links, $meta); - } - - /** - * Returns the specified request attribute and populates it if it is missing. - * - * @param \Symfony\Component\HttpFoundation\Request $request - * The request object. - * @param string $key - * The attribute key. - * @param callable $value_callback - * A callback to be used to compute the value, if missing. - * - * @return mixed - * The attribute value. - */ - protected static function getRequestAttribute(Request $request, string $key, callable $value_callback) { - if (!$request->attributes->has($key)) { - $request->attributes->set($key, $value_callback($request)); - } - return $request->attributes->get($key); + return parent::buildWrappedResponse($data, $request, $includes, $response_code, $headers, $links, $meta); } } diff --git a/core/modules/jsonapi_translation/src/EventSubscriber/ResponseSubscriber.php b/core/modules/jsonapi_translation/src/EventSubscriber/ResponseSubscriber.php index ecd5d66245..d80254ac0a 100644 --- a/core/modules/jsonapi_translation/src/EventSubscriber/ResponseSubscriber.php +++ b/core/modules/jsonapi_translation/src/EventSubscriber/ResponseSubscriber.php @@ -18,7 +18,7 @@ * @see https://www.drupal.org/project/drupal/issues/3032787 * @see \Drupal\Core\EventSubscriber\FinishResponseSubscriber */ -class ResponseSubscriber implements EventSubscriberInterface { +final class ResponseSubscriber implements EventSubscriberInterface { /** * Sets the response language. diff --git a/core/modules/jsonapi_translation/src/JsonapiTranslationServiceProvider.php b/core/modules/jsonapi_translation/src/JsonapiTranslationServiceProvider.php index 847e9d447f..b326b088e5 100644 --- a/core/modules/jsonapi_translation/src/JsonapiTranslationServiceProvider.php +++ b/core/modules/jsonapi_translation/src/JsonapiTranslationServiceProvider.php @@ -17,7 +17,7 @@ * * @see https://www.drupal.org/project/drupal/issues/3032787 */ -class JsonapiTranslationServiceProvider extends ServiceProviderBase { +final class JsonapiTranslationServiceProvider extends ServiceProviderBase { /** * {@inheritdoc} @@ -29,7 +29,7 @@ public function alter(ContainerBuilder $container) { $definition->setClass(EntityAccessChecker::class); $definition = $container->getDefinition('jsonapi.entity_resource'); $definition->setClass(EntityResource::class); - $definition->addArgument(new Reference('language_manager')); + $definition->addMethodCall('setLanguageManager', [new Reference('language_manager')]); } } diff --git a/core/modules/jsonapi_translation/src/ParamConverter/EntityUuidConverter.php b/core/modules/jsonapi_translation/src/ParamConverter/EntityUuidConverter.php index aa0078d400..9b390c9d3c 100644 --- a/core/modules/jsonapi_translation/src/ParamConverter/EntityUuidConverter.php +++ b/core/modules/jsonapi_translation/src/ParamConverter/EntityUuidConverter.php @@ -12,7 +12,7 @@ * * @todo Remove when https://www.drupal.org/node/2353611 lands. */ -class EntityUuidConverter extends JsonApiEntityUuidConverter { +final class EntityUuidConverter extends JsonApiEntityUuidConverter { /** * {@inheritdoc} diff --git a/core/modules/jsonapi_translation/src/Routing/RouteSubscriber.php b/core/modules/jsonapi_translation/src/Routing/RouteSubscriber.php index c8f4bc9e0e..cdbf0dabc8 100644 --- a/core/modules/jsonapi_translation/src/Routing/RouteSubscriber.php +++ b/core/modules/jsonapi_translation/src/Routing/RouteSubscriber.php @@ -15,7 +15,7 @@ * * @see https://www.drupal.org/project/drupal/issues/3032787 */ -class RouteSubscriber extends RouteSubscriberBase { +final class RouteSubscriber extends RouteSubscriberBase { /** * The JSON:API resource type repository. diff --git a/core/modules/jsonapi_translation/src/Routing/Routes.php b/core/modules/jsonapi_translation/src/Routing/Routes.php index 27061b79b2..d3cd272560 100644 --- a/core/modules/jsonapi_translation/src/Routing/Routes.php +++ b/core/modules/jsonapi_translation/src/Routing/Routes.php @@ -26,7 +26,7 @@ * @see \Drupal\jsonapi_translation\Controller\EntityResource * @see \Drupal\jsonapi_translation\JsonapiTranslationServiceProvider */ -class Routes extends JsonApiRoutes { +final class Routes extends JsonApiRoutes { /** * {@inheritdoc} @@ -126,7 +126,8 @@ protected function getIndividualTranslationRoutesForResourceType(ResourceType $r $translation_creation_route = new Route($individual_route_path); $translation_creation_route->addDefaults([RouteObjectInterface::CONTROLLER_NAME => static::CONTROLLER_SERVICE_NAME . ':createIndividualTranslation']); $translation_creation_route->setMethods(['POST']); - // @TODO Allow users with translation permissions and no edit permissions to handle translations. + // @todo Allow users with translation permissions and no edit permissions to + // handle translations. See TODO. $translation_creation_route->setRequirement('_entity_access', 'entity.update'); $translation_creation_route->setRequirement('_csrf_request_header_token', 'TRUE'); $routes->add(static::getRouteName($resource_type, 'individual.translation.post'), $translation_creation_route);
I'm planning to look into reverting some of the request attribute logic, as soon as we have test coverage as I don't want to introduce regressions.
Edited by Francesco Placellamentioned in merge request !5086
Susperseded by !5086