Skip to content

Commit 2e43003

Browse files
rthideawayklausi
authored andcommitted
fix(data producer): Load translation of the entity before access check. (#923)
1 parent 1b404de commit 2e43003

File tree

6 files changed

+122
-39
lines changed

6 files changed

+122
-39
lines changed

src/Plugin/GraphQL/DataProducer/Entity/EntityLoad.php

+12-11
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,18 @@ public function resolve($type, $id, $language, ?array $bundles, ?bool $access, ?
153153
return NULL;
154154
}
155155

156+
$context->addCacheableDependency($entity);
157+
if (isset($bundles) && !in_array($entity->bundle(), $bundles)) {
158+
// If the entity is not among the allowed bundles, don't return it.
159+
return NULL;
160+
}
161+
162+
// Get the correct translation.
163+
if (isset($language) && $language !== $entity->language()->getId() && $entity instanceof TranslatableInterface) {
164+
$entity = $entity->getTranslation($language);
165+
$entity->addCacheContexts(["static:language:{$language}"]);
166+
}
167+
156168
// Check if the passed user (or current user if none is passed) has access
157169
// to the entity, if not return NULL.
158170
if ($access) {
@@ -164,17 +176,6 @@ public function resolve($type, $id, $language, ?array $bundles, ?bool $access, ?
164176
}
165177
}
166178

167-
if (isset($bundles) && !in_array($entity->bundle(), $bundles)) {
168-
// If the entity is not among the allowed bundles, don't return it.
169-
$context->addCacheableDependency($entity);
170-
return NULL;
171-
}
172-
173-
if (isset($language) && $language !== $entity->language()->getId() && $entity instanceof TranslatableInterface) {
174-
$entity = $entity->getTranslation($language);
175-
$entity->addCacheContexts(["static:language:{$language}"]);
176-
}
177-
178179
return $entity;
179180
});
180181
}

src/Plugin/GraphQL/DataProducer/Entity/EntityLoadByUuid.php

+12-11
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,18 @@ public function resolve($type, $uuid, $language, $bundles, ?bool $access, ?Accou
153153
return NULL;
154154
}
155155

156+
$context->addCacheableDependency($entity);
157+
if (isset($bundles) && !in_array($entity->bundle(), $bundles)) {
158+
// If the entity is not among the allowed bundles, don't return it.
159+
return NULL;
160+
}
161+
162+
// Get the correct translation.
163+
if (isset($language) && $language != $entity->language()->getId() && $entity instanceof TranslatableInterface) {
164+
$entity = $entity->getTranslation($language);
165+
$entity->addCacheContexts(["static:language:{$language}"]);
166+
}
167+
156168
// Check if the passed user (or current user if none is passed) has access
157169
// to the entity, if not return NULL.
158170
if ($access) {
@@ -164,17 +176,6 @@ public function resolve($type, $uuid, $language, $bundles, ?bool $access, ?Accou
164176
}
165177
}
166178

167-
if (isset($bundles) && !in_array($entity->bundle(), $bundles)) {
168-
// If the entity is not among the allowed bundles, don't return it.
169-
$context->addCacheableDependency($entity);
170-
return NULL;
171-
}
172-
173-
if (isset($language) && $language != $entity->language()->getId() && $entity instanceof TranslatableInterface) {
174-
$entity = $entity->getTranslation($language);
175-
$entity->addCacheContexts(["static:language:{$language}"]);
176-
}
177-
178179
return $entity;
179180
});
180181
}

src/Plugin/GraphQL/DataProducer/Entity/EntityLoadMultiple.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ public function resolve($type, array $ids, $language, ?array $bundles, ?bool $ac
171171

172172
if ($access) {
173173
/* @var $accessResult \Drupal\Core\Access\AccessResultInterface */
174-
$accessResult = $entity->access($accessOperation, $accessUser, TRUE);
174+
$accessResult = $entities[$id]->access($accessOperation, $accessUser, TRUE);
175175
$context->addCacheableDependency($accessResult);
176176
// We need to call isAllowed() because isForbidden() returns FALSE
177177
// for neutral access results, which is dangerous. Only an explicitly

src/Plugin/GraphQL/DataProducer/Field/EntityReference.php

+6-11
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,12 @@ public function resolve(EntityInterface $entity, $field, $language = NULL, ?arra
161161
return FALSE;
162162
}
163163

164+
// Get the correct translation.
165+
if (isset($language) && $language != $entity->language()->getId() && $entity instanceof TranslatableInterface) {
166+
$entity = $entity->getTranslation($language);
167+
$entity->addCacheContexts(["static:language:{$language}"]);
168+
}
169+
164170
// Check if the passed user (or current user if none is passed) has
165171
// access to the entity, if not return NULL.
166172
if ($access) {
@@ -183,17 +189,6 @@ public function resolve(EntityInterface $entity, $field, $language = NULL, ?arra
183189
return NULL;
184190
}
185191

186-
if (isset($language)) {
187-
$entities = array_map(function (EntityInterface $entity) use ($language) {
188-
if ($language !== $entity->language()->getId() && $entity instanceof TranslatableInterface && $entity->hasTranslation($language)) {
189-
$entity = $entity->getTranslation($language);
190-
}
191-
192-
$entity->addCacheContexts(["static:language:{$language}"]);
193-
return $entity;
194-
}, $entities);
195-
}
196-
197192
return $entities;
198193
});
199194
}

src/Plugin/GraphQL/DataProducer/Routing/RouteEntity.php

+6-3
Original file line numberDiff line numberDiff line change
@@ -117,12 +117,15 @@ public function resolve($url, $language = NULL, FieldContext $context) {
117117
return NULL;
118118
}
119119

120+
// Get the correct translation.
121+
if (isset($language) && $language != $entity->language()->getId() && $entity instanceof TranslatableInterface) {
122+
$entity = $entity->getTranslation($language);
123+
$entity->addCacheContexts(["static:language:{$language}"]);
124+
}
125+
120126
$access = $entity->access('view', NULL, TRUE);
121127
$context->addCacheableDependency($access);
122128
if ($access->isAllowed()) {
123-
if (isset($language) && $language != $entity->language()->getId() && $entity instanceof TranslatableInterface) {
124-
$entity = $entity->getTranslation($language);
125-
}
126129
return $entity;
127130
}
128131
return NULL;

tests/src/Kernel/DataProducer/Routing/RouteEntityTest.php

+85-2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public function setUp() {
2929
]);
3030
$content_type->save();
3131

32+
// Published node and published translations.
3233
$this->published_node = Node::create([
3334
'title' => 'Test Event',
3435
'type' => 'event',
@@ -42,6 +43,7 @@ public function setUp() {
4243
$this->translation_de_published = $this->published_node->addTranslation('de', ['title' => 'Test Event DE']);
4344
$this->translation_de_published->save();
4445

46+
// Unpublished node and unpublished translations.
4547
$this->unpublished_node = Node::create([
4648
'title' => 'Test Unpublished Event',
4749
'type' => 'event',
@@ -50,18 +52,53 @@ public function setUp() {
5052
$this->unpublished_node->save();
5153

5254
$this->translation_fr_unpublished = $this->unpublished_node->addTranslation('fr', ['title' => 'Test Unpublished Event FR']);
55+
$this->translation_fr_unpublished->status = NodeInterface::NOT_PUBLISHED;
5356
$this->translation_fr_unpublished->save();
5457

5558
$this->translation_de_unpublished = $this->unpublished_node->addTranslation('de', ['title' => 'Test Unpublished Event DE']);
59+
$this->translation_de_unpublished->status = NodeInterface::NOT_PUBLISHED;
5660
$this->translation_de_unpublished->save();
5761

62+
// Unpublished node to published translations.
63+
$this->unpublished_to_published_node = Node::create([
64+
'title' => 'Test Unpublished to Published Event',
65+
'type' => 'event',
66+
'status' => NodeInterface::NOT_PUBLISHED,
67+
]);
68+
$this->unpublished_to_published_node->save();
69+
70+
$this->translation_fr_unpublished_to_published = $this->unpublished_to_published_node->addTranslation('fr', ['title' => 'Test Unpublished to Published Event FR']);
71+
$this->translation_fr_unpublished_to_published->status = NodeInterface::PUBLISHED;
72+
$this->translation_fr_unpublished_to_published->save();
73+
74+
$this->translation_de_unpublished_to_published = $this->unpublished_to_published_node->addTranslation('de', ['title' => 'Test Unpublished to Published Event DE']);
75+
$this->translation_de_unpublished_to_published->status = NodeInterface::PUBLISHED;
76+
$this->translation_de_unpublished_to_published->save();
77+
78+
// Published node to unpublished translations.
79+
$this->published_to_unpublished_node = Node::create([
80+
'title' => 'Test Published to Unpublished Event',
81+
'type' => 'event',
82+
'status' => NodeInterface::PUBLISHED,
83+
]);
84+
$this->published_to_unpublished_node->save();
85+
86+
$this->translation_fr_published_to_unpublished = $this->published_to_unpublished_node->addTranslation('fr', ['title' => 'Test Published to Unpublished Event FR']);
87+
$this->translation_fr_published_to_unpublished->status = NodeInterface::NOT_PUBLISHED;
88+
$this->translation_fr_published_to_unpublished->save();
89+
90+
$this->translation_de_published_to_unpublished = $this->published_to_unpublished_node->addTranslation('de', ['title' => 'Test Published to Unpublished Event DE']);
91+
$this->translation_de_published_to_unpublished->status = NodeInterface::NOT_PUBLISHED;
92+
$this->translation_de_published_to_unpublished->save();
93+
5894
\Drupal::service('content_translation.manager')->setEnabled('node', 'event', TRUE);
5995
}
6096

6197
/**
6298
* @covers \Drupal\graphql\Plugin\GraphQL\DataProducer\Routing\RouteEntity::resolve
6399
*/
64100
public function testRouteEntity() {
101+
// Published node to published translations.
65102
$url = Url::fromRoute('entity.node.canonical', ['node' => $this->published_node->id()]);
66103

67104
$result = $this->executeDataProducer('route_entity', [
@@ -87,8 +124,8 @@ public function testRouteEntity() {
87124
$this->assertEquals($this->translation_de_published->id(), $result->id());
88125
$this->assertEquals($this->translation_de_published->label(), $result->label());
89126

90-
// Make sure we are not allowed to get the unpublished nodes or
91-
// translations.
127+
// Unpublished node to unpublished translations. Make sure we are not
128+
// allowed to get the unpublished nodes or translations.
92129
$url = Url::fromRoute('entity.node.canonical', ['node' => $this->unpublished_node->id()]);
93130
foreach ([NULL, 'fr', 'de'] as $lang) {
94131
$result = $this->executeDataProducer('route_entity', [
@@ -99,6 +136,52 @@ public function testRouteEntity() {
99136
$this->assertNull($result);
100137
}
101138

139+
// Unpublished node to published translations. Make sure we are not able to
140+
// get unpublished source, but we are able to get published translations.
141+
$url = Url::fromRoute('entity.node.canonical', ['node' => $this->unpublished_to_published_node->id()]);
142+
143+
$result = $this->executeDataProducer('route_entity', [
144+
'url' => $url,
145+
]);
146+
147+
$this->assertNull($result);
148+
149+
$result = $this->executeDataProducer('route_entity', [
150+
'url' => $url,
151+
'language' => 'fr',
152+
]);
153+
154+
$this->assertEquals($this->translation_fr_unpublished_to_published->id(), $result->id());
155+
$this->assertEquals($this->translation_fr_unpublished_to_published->label(), $result->label());
156+
157+
$result = $this->executeDataProducer('route_entity', [
158+
'url' => $url,
159+
'language' => 'de',
160+
]);
161+
162+
$this->assertEquals($this->translation_de_unpublished_to_published->id(), $result->id());
163+
$this->assertEquals($this->translation_de_unpublished_to_published->label(), $result->label());
164+
165+
// Published node to unpublished translations. Make sure we are able to get
166+
// published source, but we are not able to get unpublished translations.
167+
$url = Url::fromRoute('entity.node.canonical', ['node' => $this->published_to_unpublished_node->id()]);
168+
169+
$result = $this->executeDataProducer('route_entity', [
170+
'url' => $url,
171+
]);
172+
173+
$this->assertEquals($this->published_to_unpublished_node->id(), $result->id());
174+
$this->assertEquals($this->published_to_unpublished_node->label(), $result->label());
175+
176+
foreach (['fr', 'de'] as $lang) {
177+
$result = $this->executeDataProducer('route_entity', [
178+
'url' => $url,
179+
'language' => $lang,
180+
]);
181+
182+
$this->assertNull($result);
183+
}
184+
102185
// Test with something which is not a URL.
103186
$this->assertNull($this->executeDataProducer('route_entity', [
104187
'url' => 'not_a_url',

0 commit comments

Comments
 (0)