diff --git a/src/Plugin/GraphQL/DataProducer/Entity/EntityLoad.php b/src/Plugin/GraphQL/DataProducer/Entity/EntityLoad.php index 8c52f0814..765632a8c 100644 --- a/src/Plugin/GraphQL/DataProducer/Entity/EntityLoad.php +++ b/src/Plugin/GraphQL/DataProducer/Entity/EntityLoad.php @@ -153,6 +153,18 @@ public function resolve($type, $id, $language, ?array $bundles, ?bool $access, ? return NULL; } + $context->addCacheableDependency($entity); + if (isset($bundles) && !in_array($entity->bundle(), $bundles)) { + // If the entity is not among the allowed bundles, don't return it. + return NULL; + } + + // Get the correct translation. + if (isset($language) && $language !== $entity->language()->getId() && $entity instanceof TranslatableInterface) { + $entity = $entity->getTranslation($language); + $entity->addCacheContexts(["static:language:{$language}"]); + } + // Check if the passed user (or current user if none is passed) has access // to the entity, if not return NULL. if ($access) { @@ -164,17 +176,6 @@ public function resolve($type, $id, $language, ?array $bundles, ?bool $access, ? } } - if (isset($bundles) && !in_array($entity->bundle(), $bundles)) { - // If the entity is not among the allowed bundles, don't return it. - $context->addCacheableDependency($entity); - return NULL; - } - - if (isset($language) && $language !== $entity->language()->getId() && $entity instanceof TranslatableInterface) { - $entity = $entity->getTranslation($language); - $entity->addCacheContexts(["static:language:{$language}"]); - } - return $entity; }); } diff --git a/src/Plugin/GraphQL/DataProducer/Entity/EntityLoadByUuid.php b/src/Plugin/GraphQL/DataProducer/Entity/EntityLoadByUuid.php index a425c0355..6026dc1fc 100644 --- a/src/Plugin/GraphQL/DataProducer/Entity/EntityLoadByUuid.php +++ b/src/Plugin/GraphQL/DataProducer/Entity/EntityLoadByUuid.php @@ -153,6 +153,18 @@ public function resolve($type, $uuid, $language, $bundles, ?bool $access, ?Accou return NULL; } + $context->addCacheableDependency($entity); + if (isset($bundles) && !in_array($entity->bundle(), $bundles)) { + // If the entity is not among the allowed bundles, don't return it. + return NULL; + } + + // Get the correct translation. + if (isset($language) && $language != $entity->language()->getId() && $entity instanceof TranslatableInterface) { + $entity = $entity->getTranslation($language); + $entity->addCacheContexts(["static:language:{$language}"]); + } + // Check if the passed user (or current user if none is passed) has access // to the entity, if not return NULL. if ($access) { @@ -164,17 +176,6 @@ public function resolve($type, $uuid, $language, $bundles, ?bool $access, ?Accou } } - if (isset($bundles) && !in_array($entity->bundle(), $bundles)) { - // If the entity is not among the allowed bundles, don't return it. - $context->addCacheableDependency($entity); - return NULL; - } - - if (isset($language) && $language != $entity->language()->getId() && $entity instanceof TranslatableInterface) { - $entity = $entity->getTranslation($language); - $entity->addCacheContexts(["static:language:{$language}"]); - } - return $entity; }); } diff --git a/src/Plugin/GraphQL/DataProducer/Entity/EntityLoadMultiple.php b/src/Plugin/GraphQL/DataProducer/Entity/EntityLoadMultiple.php index 59f84c6bb..035f07705 100644 --- a/src/Plugin/GraphQL/DataProducer/Entity/EntityLoadMultiple.php +++ b/src/Plugin/GraphQL/DataProducer/Entity/EntityLoadMultiple.php @@ -171,7 +171,7 @@ public function resolve($type, array $ids, $language, ?array $bundles, ?bool $ac if ($access) { /* @var $accessResult \Drupal\Core\Access\AccessResultInterface */ - $accessResult = $entity->access($accessOperation, $accessUser, TRUE); + $accessResult = $entities[$id]->access($accessOperation, $accessUser, TRUE); $context->addCacheableDependency($accessResult); // We need to call isAllowed() because isForbidden() returns FALSE // for neutral access results, which is dangerous. Only an explicitly diff --git a/src/Plugin/GraphQL/DataProducer/Field/EntityReference.php b/src/Plugin/GraphQL/DataProducer/Field/EntityReference.php index 9c611bbc6..ff119e9b2 100644 --- a/src/Plugin/GraphQL/DataProducer/Field/EntityReference.php +++ b/src/Plugin/GraphQL/DataProducer/Field/EntityReference.php @@ -161,6 +161,12 @@ public function resolve(EntityInterface $entity, $field, $language = NULL, ?arra return FALSE; } + // Get the correct translation. + if (isset($language) && $language != $entity->language()->getId() && $entity instanceof TranslatableInterface) { + $entity = $entity->getTranslation($language); + $entity->addCacheContexts(["static:language:{$language}"]); + } + // Check if the passed user (or current user if none is passed) has // access to the entity, if not return NULL. if ($access) { @@ -183,17 +189,6 @@ public function resolve(EntityInterface $entity, $field, $language = NULL, ?arra return NULL; } - if (isset($language)) { - $entities = array_map(function (EntityInterface $entity) use ($language) { - if ($language !== $entity->language()->getId() && $entity instanceof TranslatableInterface && $entity->hasTranslation($language)) { - $entity = $entity->getTranslation($language); - } - - $entity->addCacheContexts(["static:language:{$language}"]); - return $entity; - }, $entities); - } - return $entities; }); } diff --git a/src/Plugin/GraphQL/DataProducer/Routing/RouteEntity.php b/src/Plugin/GraphQL/DataProducer/Routing/RouteEntity.php index 3ffbab834..bda3d1fd4 100644 --- a/src/Plugin/GraphQL/DataProducer/Routing/RouteEntity.php +++ b/src/Plugin/GraphQL/DataProducer/Routing/RouteEntity.php @@ -117,12 +117,15 @@ public function resolve($url, $language = NULL, FieldContext $context) { return NULL; } + // Get the correct translation. + if (isset($language) && $language != $entity->language()->getId() && $entity instanceof TranslatableInterface) { + $entity = $entity->getTranslation($language); + $entity->addCacheContexts(["static:language:{$language}"]); + } + $access = $entity->access('view', NULL, TRUE); $context->addCacheableDependency($access); if ($access->isAllowed()) { - if (isset($language) && $language != $entity->language()->getId() && $entity instanceof TranslatableInterface) { - $entity = $entity->getTranslation($language); - } return $entity; } return NULL; diff --git a/tests/src/Kernel/DataProducer/Routing/RouteEntityTest.php b/tests/src/Kernel/DataProducer/Routing/RouteEntityTest.php index 285c3df80..b0522a842 100644 --- a/tests/src/Kernel/DataProducer/Routing/RouteEntityTest.php +++ b/tests/src/Kernel/DataProducer/Routing/RouteEntityTest.php @@ -29,6 +29,7 @@ public function setUp() { ]); $content_type->save(); + // Published node and published translations. $this->published_node = Node::create([ 'title' => 'Test Event', 'type' => 'event', @@ -42,6 +43,7 @@ public function setUp() { $this->translation_de_published = $this->published_node->addTranslation('de', ['title' => 'Test Event DE']); $this->translation_de_published->save(); + // Unpublished node and unpublished translations. $this->unpublished_node = Node::create([ 'title' => 'Test Unpublished Event', 'type' => 'event', @@ -50,11 +52,45 @@ public function setUp() { $this->unpublished_node->save(); $this->translation_fr_unpublished = $this->unpublished_node->addTranslation('fr', ['title' => 'Test Unpublished Event FR']); + $this->translation_fr_unpublished->status = NodeInterface::NOT_PUBLISHED; $this->translation_fr_unpublished->save(); $this->translation_de_unpublished = $this->unpublished_node->addTranslation('de', ['title' => 'Test Unpublished Event DE']); + $this->translation_de_unpublished->status = NodeInterface::NOT_PUBLISHED; $this->translation_de_unpublished->save(); + // Unpublished node to published translations. + $this->unpublished_to_published_node = Node::create([ + 'title' => 'Test Unpublished to Published Event', + 'type' => 'event', + 'status' => NodeInterface::NOT_PUBLISHED, + ]); + $this->unpublished_to_published_node->save(); + + $this->translation_fr_unpublished_to_published = $this->unpublished_to_published_node->addTranslation('fr', ['title' => 'Test Unpublished to Published Event FR']); + $this->translation_fr_unpublished_to_published->status = NodeInterface::PUBLISHED; + $this->translation_fr_unpublished_to_published->save(); + + $this->translation_de_unpublished_to_published = $this->unpublished_to_published_node->addTranslation('de', ['title' => 'Test Unpublished to Published Event DE']); + $this->translation_de_unpublished_to_published->status = NodeInterface::PUBLISHED; + $this->translation_de_unpublished_to_published->save(); + + // Published node to unpublished translations. + $this->published_to_unpublished_node = Node::create([ + 'title' => 'Test Published to Unpublished Event', + 'type' => 'event', + 'status' => NodeInterface::PUBLISHED, + ]); + $this->published_to_unpublished_node->save(); + + $this->translation_fr_published_to_unpublished = $this->published_to_unpublished_node->addTranslation('fr', ['title' => 'Test Published to Unpublished Event FR']); + $this->translation_fr_published_to_unpublished->status = NodeInterface::NOT_PUBLISHED; + $this->translation_fr_published_to_unpublished->save(); + + $this->translation_de_published_to_unpublished = $this->published_to_unpublished_node->addTranslation('de', ['title' => 'Test Published to Unpublished Event DE']); + $this->translation_de_published_to_unpublished->status = NodeInterface::NOT_PUBLISHED; + $this->translation_de_published_to_unpublished->save(); + \Drupal::service('content_translation.manager')->setEnabled('node', 'event', TRUE); } @@ -62,6 +98,7 @@ public function setUp() { * @covers \Drupal\graphql\Plugin\GraphQL\DataProducer\Routing\RouteEntity::resolve */ public function testRouteEntity() { + // Published node to published translations. $url = Url::fromRoute('entity.node.canonical', ['node' => $this->published_node->id()]); $result = $this->executeDataProducer('route_entity', [ @@ -87,8 +124,8 @@ public function testRouteEntity() { $this->assertEquals($this->translation_de_published->id(), $result->id()); $this->assertEquals($this->translation_de_published->label(), $result->label()); - // Make sure we are not allowed to get the unpublished nodes or - // translations. + // Unpublished node to unpublished translations. Make sure we are not + // allowed to get the unpublished nodes or translations. $url = Url::fromRoute('entity.node.canonical', ['node' => $this->unpublished_node->id()]); foreach ([NULL, 'fr', 'de'] as $lang) { $result = $this->executeDataProducer('route_entity', [ @@ -99,6 +136,52 @@ public function testRouteEntity() { $this->assertNull($result); } + // Unpublished node to published translations. Make sure we are not able to + // get unpublished source, but we are able to get published translations. + $url = Url::fromRoute('entity.node.canonical', ['node' => $this->unpublished_to_published_node->id()]); + + $result = $this->executeDataProducer('route_entity', [ + 'url' => $url, + ]); + + $this->assertNull($result); + + $result = $this->executeDataProducer('route_entity', [ + 'url' => $url, + 'language' => 'fr', + ]); + + $this->assertEquals($this->translation_fr_unpublished_to_published->id(), $result->id()); + $this->assertEquals($this->translation_fr_unpublished_to_published->label(), $result->label()); + + $result = $this->executeDataProducer('route_entity', [ + 'url' => $url, + 'language' => 'de', + ]); + + $this->assertEquals($this->translation_de_unpublished_to_published->id(), $result->id()); + $this->assertEquals($this->translation_de_unpublished_to_published->label(), $result->label()); + + // Published node to unpublished translations. Make sure we are able to get + // published source, but we are not able to get unpublished translations. + $url = Url::fromRoute('entity.node.canonical', ['node' => $this->published_to_unpublished_node->id()]); + + $result = $this->executeDataProducer('route_entity', [ + 'url' => $url, + ]); + + $this->assertEquals($this->published_to_unpublished_node->id(), $result->id()); + $this->assertEquals($this->published_to_unpublished_node->label(), $result->label()); + + foreach (['fr', 'de'] as $lang) { + $result = $this->executeDataProducer('route_entity', [ + 'url' => $url, + 'language' => $lang, + ]); + + $this->assertNull($result); + } + // Test with something which is not a URL. $this->assertNull($this->executeDataProducer('route_entity', [ 'url' => 'not_a_url',