Skip to content
Snippets Groups Projects

Issue #3199697: Add JSON:API Translation experimental module

Open Issue #3199697: Add JSON:API Translation experimental module
2 unresolved threads
2 unresolved threads

Merge request reports

Loading
Loading

Activity

Filter activity
  • Approvals
  • Assignees & reviewers
  • Comments (from bots)
  • Comments (from users)
  • Commits & branches
  • Edits
  • Labels
  • Lock status
  • Mentions
  • Merge request status
  • Tracking
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"
  • 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
  • Francesco Placella added 2898 commits

    added 2898 commits

    Compare with previous version

  • Rebased #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 Placella
  • Francesco Placella mentioned in merge request !5086

    mentioned in merge request !5086

  • Please register or sign in to reply
    Loading