Skip to content
Snippets Groups Projects
Verified Commit 4c8c814c authored by Dave Long's avatar Dave Long
Browse files

Issue #3336994 by mxr576, kksandr, dpi, smustgrave, xjm, alexpott, hchonov,...

Issue #3336994 by mxr576, kksandr, dpi, smustgrave, xjm, alexpott, hchonov, quietone: StringFormatter always displays links to entity even if the user in context does not have access
parent 43e738a6
No related branches found
No related tags found
No related merge requests found
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
namespace Drupal\Core\Field\Plugin\Field\FieldFormatter; namespace Drupal\Core\Field\Plugin\Field\FieldFormatter;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Field\Attribute\FieldFormatter; use Drupal\Core\Field\Attribute\FieldFormatter;
...@@ -122,27 +123,33 @@ public function settingsSummary() { ...@@ -122,27 +123,33 @@ public function settingsSummary() {
*/ */
public function viewElements(FieldItemListInterface $items, $langcode) { public function viewElements(FieldItemListInterface $items, $langcode) {
$elements = []; $elements = [];
$url = NULL;
$entity = $items->getEntity(); $entity = $items->getEntity();
$entity_type = $entity->getEntityType(); $entity_type = $entity->getEntityType();
$render_as_link = FALSE;
if ($this->getSetting('link_to_entity') && !$entity->isNew() && $entity_type->hasLinkTemplate('canonical')) { if ($this->getSetting('link_to_entity') && !$entity->isNew() && $entity_type->hasLinkTemplate('canonical')) {
$url = $this->getEntityUrl($entity); $url = $this->getEntityUrl($entity);
$access = $url->access(return_as_object: TRUE);
(new CacheableMetadata())
->addCacheableDependency($access)
->applyTo($elements);
$render_as_link = $access->isAllowed();
} }
foreach ($items as $delta => $item) { foreach ($items as $delta => $item) {
$view_value = $this->viewValue($item); if ($render_as_link) {
if ($url) { assert(isset($url));
$elements[$delta] = [ $elements[$delta] = [
'#type' => 'link', '#type' => 'link',
'#title' => $view_value, '#title' => $this->viewValue($item),
'#url' => $url, '#url' => $url,
]; ];
} }
else { else {
$elements[$delta] = $view_value; $elements[$delta] = $this->viewValue($item);
} }
} }
return $elements; return $elements;
} }
......
...@@ -181,7 +181,7 @@ public function testListing(): void { ...@@ -181,7 +181,7 @@ public function testListing(): void {
$this->drupalGet('admin/content/block'); $this->drupalGet('admin/content/block');
$this->assertSession()->statusCodeEquals(200); $this->assertSession()->statusCodeEquals(200);
$this->assertSession()->linkNotExists($link_text); $this->assertSession()->linkNotExists($link_text);
$matches = $this->xpath('//td/a'); $matches = $this->xpath('//td[1]');
$actual = $matches[0]->getText(); $actual = $matches[0]->getText();
$this->assertEquals($label, $actual, 'Label found for test block.'); $this->assertEquals($label, $actual, 'Label found for test block.');
$this->assertSession()->linkNotExists('Edit'); $this->assertSession()->linkNotExists('Edit');
......
...@@ -56,7 +56,11 @@ protected function setUp($import_test_views = TRUE): void { ...@@ -56,7 +56,11 @@ protected function setUp($import_test_views = TRUE): void {
$admin_role = Role::create([ $admin_role = Role::create([
'id' => 'admin', 'id' => 'admin',
'permissions' => ['administer comments', 'access user profiles'], 'permissions' => [
'administer comments',
'access user profiles',
'access comments',
],
'label' => 'Admin', 'label' => 'Admin',
]); ]);
$admin_role->save(); $admin_role->save();
...@@ -177,8 +181,11 @@ public function testUsername(): void { ...@@ -177,8 +181,11 @@ public function testUsername(): void {
$this->assertNoLink($this->adminUser->label()); $this->assertNoLink($this->adminUser->label());
// Note: External users aren't pointing to drupal user profiles. // Note: External users aren't pointing to drupal user profiles.
$this->assertLink('barry (not verified)'); $this->assertLink('barry (not verified)');
$this->assertLink('My comment title'); // Anonymous user does not have access to this link but can still see title.
$this->assertLink('Anonymous comment title'); $this->assertText('My comment title');
$this->assertNoLink('My comment title');
$this->assertText('Anonymous comment title');
$this->assertNoLink('Anonymous comment title');
} }
} }
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
use Drupal\field\Entity\FieldConfig; use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig; use Drupal\field\Entity\FieldStorageConfig;
use Drupal\KernelTests\KernelTestBase; use Drupal\KernelTests\KernelTestBase;
use Drupal\Tests\user\Traits\UserCreationTrait;
/** /**
* Tests the creation of text fields. * Tests the creation of text fields.
...@@ -20,6 +21,8 @@ ...@@ -20,6 +21,8 @@
*/ */
class StringFormatterTest extends KernelTestBase { class StringFormatterTest extends KernelTestBase {
use UserCreationTrait;
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
...@@ -68,7 +71,10 @@ protected function setUp(): void { ...@@ -68,7 +71,10 @@ protected function setUp(): void {
// Configure the theme system. // Configure the theme system.
$this->installConfig(['system', 'field']); $this->installConfig(['system', 'field']);
$this->installEntitySchema('entity_test_rev'); $this->installEntitySchema('entity_test_rev');
$this->installEntitySchema('entity_test_label'); $this->setUpCurrentUser(permissions: [
'view test entity',
'administer entity_test content',
]);
$this->entityType = 'entity_test_rev'; $this->entityType = 'entity_test_rev';
$this->bundle = $this->entityType; $this->bundle = $this->entityType;
...@@ -124,7 +130,7 @@ public function testStringFormatter(): void { ...@@ -124,7 +130,7 @@ public function testStringFormatter(): void {
$value .= "\n\n<strong>" . $this->randomString() . '</strong>'; $value .= "\n\n<strong>" . $this->randomString() . '</strong>';
$value .= "\n\n" . $this->randomString(); $value .= "\n\n" . $this->randomString();
$entity = EntityTestRev::create([]); $entity = EntityTestRev::create(['name' => 'view revision']);
$entity->{$this->fieldName}->value = $value; $entity->{$this->fieldName}->value = $value;
// Verify that all HTML is escaped and newlines are retained. // Verify that all HTML is escaped and newlines are retained.
...@@ -194,6 +200,7 @@ public function testStringFormatter(): void { ...@@ -194,6 +200,7 @@ public function testStringFormatter(): void {
*/ */
public function testLinkToContentForEntitiesWithNoCanonicalPath(): void { public function testLinkToContentForEntitiesWithNoCanonicalPath(): void {
$this->enableModules(['entity_test']); $this->enableModules(['entity_test']);
$this->installEntitySchema('entity_test_label');
$field_name = 'test_field_name'; $field_name = 'test_field_name';
$entity_type = $bundle = 'entity_test_label'; $entity_type = $bundle = 'entity_test_label';
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
use Drupal\entity_test\Entity\EntityTest; use Drupal\entity_test\Entity\EntityTest;
use Drupal\KernelTests\KernelTestBase; use Drupal\KernelTests\KernelTestBase;
use Drupal\Tests\user\Traits\UserCreationTrait;
/** /**
* Tests the output of a UUID field. * Tests the output of a UUID field.
...@@ -14,6 +15,7 @@ ...@@ -14,6 +15,7 @@
*/ */
class UuidFormatterTest extends KernelTestBase { class UuidFormatterTest extends KernelTestBase {
use UserCreationTrait;
/** /**
* {@inheritdoc} * {@inheritdoc}
...@@ -28,6 +30,10 @@ protected function setUp(): void { ...@@ -28,6 +30,10 @@ protected function setUp(): void {
$this->installConfig(['system', 'field']); $this->installConfig(['system', 'field']);
$this->installEntitySchema('entity_test'); $this->installEntitySchema('entity_test');
$this->installEntitySchema('user');
$this->setUpCurrentUser(permissions: [
'view test entity',
]);
} }
/** /**
......
<?php
declare(strict_types=1);
namespace Drupal\Tests\field\Unit\Plugin\Field\FieldFormatter;
use Drupal\Core\Access\AccessResultAllowed;
use Drupal\Core\Access\AccessResultForbidden;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\Plugin\Field\FieldFormatter\StringFormatter;
use Drupal\Core\Field\Plugin\Field\FieldType\StringItem;
use Drupal\Core\Url;
use Drupal\Tests\UnitTestCase;
/**
* Tests the string field formatter.
*
* @group field
*
* @coversDefaultClass \Drupal\Core\Field\Plugin\Field\FieldFormatter\StringFormatter
*/
final class StringFormatterTest extends UnitTestCase {
/**
* Checks link visibility depending on link templates and access.
*
* @param bool $hasUrl
* Whether the entity type has a canonical link template.
* @param string|null $accessClass
* The access result for the current user.
* @param bool $expectIsLinkElement
* Whether to expect the text to be wrapped in a link element.
*
* @phpstan-param class-string<\Drupal\Core\Access\AccessResultInterface>|null $accessClass
*
* @dataProvider providerAccessLinkToEntity
*/
public function testLinkToEntity(bool $hasUrl, ?string $accessClass, bool $expectIsLinkElement): void {
$fieldDefinition = $this->prophesize(FieldDefinitionInterface::class);
$entityTypeManager = $this->prophesize(EntityTypeManagerInterface::class);
$fieldFormatter = new StringFormatter('foobar', [], $fieldDefinition->reveal(), [], 'TestLabel', 'default', [], $entityTypeManager->reveal());
$fieldFormatter->setSetting('link_to_entity', TRUE);
$entityType = $this->prophesize(EntityTypeInterface::class);
$entityType->hasLinkTemplate('canonical')->willReturn($hasUrl)->shouldBeCalledTimes(1);
$entityType->hasLinkTemplate('revision')->willReturn(FALSE)->shouldBeCalledTimes($hasUrl ? 1 : 0);
$entity = $this->prophesize(EntityInterface::class);
$entity->isNew()->willReturn(FALSE);
$entity->getEntityType()->willReturn($entityType->reveal());
if ($hasUrl) {
$url = $this->prophesize(Url::class);
$url->access(NULL, TRUE)->willReturn(new $accessClass());
$entity->toUrl('canonical')->willReturn($url);
}
$item = $this->getMockBuilder(StringItem::class)
->disableOriginalConstructor()
->onlyMethods([])
->getMock();
$item->setValue(['value' => 'FooText']);
$items = $this->prophesize(FieldItemListInterface::class);
$items->getEntity()->willReturn($entity->reveal());
$items->valid()->willReturn(TRUE, FALSE);
$items->next();
$items->rewind();
$items->current()->willReturn($item);
$items->key()->willReturn(0);
$elements = $fieldFormatter->viewElements($items->reveal(), 'en');
if ($expectIsLinkElement) {
$this->assertEquals('link', $elements[0]['#type']);
$this->assertEquals('FooText', $elements[0]['#title']['#context']['value']);
}
else {
$this->assertEquals('inline_template', $elements[0]['#type']);
$this->assertEquals('FooText', $elements[0]['#context']['value']);
}
}
/**
* Data provider.
*
* @return \Generator
* Test scenarios.
*/
public static function providerAccessLinkToEntity(): \Generator {
yield 'entity with no URL' => [
FALSE,
NULL,
FALSE,
];
yield 'entity with url, with access' => [
TRUE,
AccessResultAllowed::class,
TRUE,
];
yield 'entity with url, no access' => [
TRUE,
AccessResultForbidden::class,
FALSE,
];
}
}
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
use Drupal\Core\Link; use Drupal\Core\Link;
use Drupal\Core\Render\RenderContext; use Drupal\Core\Render\RenderContext;
use Drupal\Tests\taxonomy\Traits\TaxonomyTestTrait; use Drupal\Tests\taxonomy\Traits\TaxonomyTestTrait;
use Drupal\Tests\user\Traits\UserCreationTrait;
use Drupal\Tests\views\Kernel\ViewsKernelTestBase; use Drupal\Tests\views\Kernel\ViewsKernelTestBase;
use Drupal\views\Tests\ViewTestData; use Drupal\views\Tests\ViewTestData;
use Drupal\views\Views; use Drupal\views\Views;
...@@ -19,6 +20,7 @@ ...@@ -19,6 +20,7 @@
class TaxonomyFieldTidTest extends ViewsKernelTestBase { class TaxonomyFieldTidTest extends ViewsKernelTestBase {
use TaxonomyTestTrait; use TaxonomyTestTrait;
use UserCreationTrait;
/** /**
* {@inheritdoc} * {@inheritdoc}
...@@ -49,7 +51,11 @@ protected function setUp($import_test_views = TRUE): void { ...@@ -49,7 +51,11 @@ protected function setUp($import_test_views = TRUE): void {
parent::setUp($import_test_views); parent::setUp($import_test_views);
$this->installEntitySchema('taxonomy_term'); $this->installEntitySchema('taxonomy_term');
$this->installEntitySchema('user');
$this->installConfig(['filter']); $this->installConfig(['filter']);
$this->setUpCurrentUser(permissions: [
'access content',
]);
/** @var \Drupal\taxonomy\Entity\Vocabulary $vocabulary */ /** @var \Drupal\taxonomy\Entity\Vocabulary $vocabulary */
$vocabulary = $this->createVocabulary(); $vocabulary = $this->createVocabulary();
......
...@@ -333,6 +333,9 @@ public function testFieldAlias(): void { ...@@ -333,6 +333,9 @@ public function testFieldAlias(): void {
* different delta limit / offset. * different delta limit / offset.
*/ */
public function testFieldAliasRender(): void { public function testFieldAliasRender(): void {
$this->setUpCurrentUser(permissions: [
'view test entity',
]);
$executable = Views::getView('test_field_alias_test'); $executable = Views::getView('test_field_alias_test');
$executable->execute(); $executable->execute();
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment