diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/StringFormatter.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/StringFormatter.php index 32f5c20d6ec17125d17db54253319092142644e2..9ccfc05ce9f70d822bb47c423c867c1071f0499c 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/StringFormatter.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldFormatter/StringFormatter.php @@ -2,6 +2,7 @@ namespace Drupal\Core\Field\Plugin\Field\FieldFormatter; +use Drupal\Core\Cache\CacheableMetadata; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Field\Attribute\FieldFormatter; @@ -122,27 +123,33 @@ public function settingsSummary() { */ public function viewElements(FieldItemListInterface $items, $langcode) { $elements = []; - $url = NULL; $entity = $items->getEntity(); $entity_type = $entity->getEntityType(); + $render_as_link = FALSE; if ($this->getSetting('link_to_entity') && !$entity->isNew() && $entity_type->hasLinkTemplate('canonical')) { $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) { - $view_value = $this->viewValue($item); - if ($url) { + if ($render_as_link) { + assert(isset($url)); $elements[$delta] = [ '#type' => 'link', - '#title' => $view_value, + '#title' => $this->viewValue($item), '#url' => $url, ]; } else { - $elements[$delta] = $view_value; + $elements[$delta] = $this->viewValue($item); } } + return $elements; } diff --git a/core/modules/block_content/tests/src/Functional/BlockContentListViewsTest.php b/core/modules/block_content/tests/src/Functional/BlockContentListViewsTest.php index 9c00870e9d3f2ad04db4b5387fa6ea20a448c1e3..0d624cceb69038eb97a2794fe8a9f3ce4eb575c0 100644 --- a/core/modules/block_content/tests/src/Functional/BlockContentListViewsTest.php +++ b/core/modules/block_content/tests/src/Functional/BlockContentListViewsTest.php @@ -181,7 +181,7 @@ public function testListing(): void { $this->drupalGet('admin/content/block'); $this->assertSession()->statusCodeEquals(200); $this->assertSession()->linkNotExists($link_text); - $matches = $this->xpath('//td/a'); + $matches = $this->xpath('//td[1]'); $actual = $matches[0]->getText(); $this->assertEquals($label, $actual, 'Label found for test block.'); $this->assertSession()->linkNotExists('Edit'); diff --git a/core/modules/comment/tests/src/Kernel/Views/CommentUserNameTest.php b/core/modules/comment/tests/src/Kernel/Views/CommentUserNameTest.php index 02b2eb0b23c76a1ddd8d18e00e8caf737f4d63e3..e0b88a22e3f092563070c003de58b8c20410475d 100644 --- a/core/modules/comment/tests/src/Kernel/Views/CommentUserNameTest.php +++ b/core/modules/comment/tests/src/Kernel/Views/CommentUserNameTest.php @@ -56,7 +56,11 @@ protected function setUp($import_test_views = TRUE): void { $admin_role = Role::create([ 'id' => 'admin', - 'permissions' => ['administer comments', 'access user profiles'], + 'permissions' => [ + 'administer comments', + 'access user profiles', + 'access comments', + ], 'label' => 'Admin', ]); $admin_role->save(); @@ -177,8 +181,11 @@ public function testUsername(): void { $this->assertNoLink($this->adminUser->label()); // Note: External users aren't pointing to drupal user profiles. $this->assertLink('barry (not verified)'); - $this->assertLink('My comment title'); - $this->assertLink('Anonymous comment title'); + // Anonymous user does not have access to this link but can still see title. + $this->assertText('My comment title'); + $this->assertNoLink('My comment title'); + $this->assertText('Anonymous comment title'); + $this->assertNoLink('Anonymous comment title'); } } diff --git a/core/modules/field/tests/src/Kernel/KernelString/StringFormatterTest.php b/core/modules/field/tests/src/Kernel/KernelString/StringFormatterTest.php index 4c8b4027a510d3d11b3660848e1d3d86bbbb4d36..a591e3cccbad400331dcbaa87bf15f2bfa16677b 100644 --- a/core/modules/field/tests/src/Kernel/KernelString/StringFormatterTest.php +++ b/core/modules/field/tests/src/Kernel/KernelString/StringFormatterTest.php @@ -12,6 +12,7 @@ use Drupal\field\Entity\FieldConfig; use Drupal\field\Entity\FieldStorageConfig; use Drupal\KernelTests\KernelTestBase; +use Drupal\Tests\user\Traits\UserCreationTrait; /** * Tests the creation of text fields. @@ -20,6 +21,8 @@ */ class StringFormatterTest extends KernelTestBase { + use UserCreationTrait; + /** * {@inheritdoc} */ @@ -68,7 +71,10 @@ protected function setUp(): void { // Configure the theme system. $this->installConfig(['system', 'field']); $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->bundle = $this->entityType; @@ -124,7 +130,7 @@ public function testStringFormatter(): void { $value .= "\n\n<strong>" . $this->randomString() . '</strong>'; $value .= "\n\n" . $this->randomString(); - $entity = EntityTestRev::create([]); + $entity = EntityTestRev::create(['name' => 'view revision']); $entity->{$this->fieldName}->value = $value; // Verify that all HTML is escaped and newlines are retained. @@ -194,6 +200,7 @@ public function testStringFormatter(): void { */ public function testLinkToContentForEntitiesWithNoCanonicalPath(): void { $this->enableModules(['entity_test']); + $this->installEntitySchema('entity_test_label'); $field_name = 'test_field_name'; $entity_type = $bundle = 'entity_test_label'; diff --git a/core/modules/field/tests/src/Kernel/KernelString/UuidFormatterTest.php b/core/modules/field/tests/src/Kernel/KernelString/UuidFormatterTest.php index 736d646267219e3b6684b064dda6837e02f585f7..1defb4452ab3d7adb5efea581913378a861a0967 100644 --- a/core/modules/field/tests/src/Kernel/KernelString/UuidFormatterTest.php +++ b/core/modules/field/tests/src/Kernel/KernelString/UuidFormatterTest.php @@ -6,6 +6,7 @@ use Drupal\entity_test\Entity\EntityTest; use Drupal\KernelTests\KernelTestBase; +use Drupal\Tests\user\Traits\UserCreationTrait; /** * Tests the output of a UUID field. @@ -14,6 +15,7 @@ */ class UuidFormatterTest extends KernelTestBase { + use UserCreationTrait; /** * {@inheritdoc} @@ -28,6 +30,10 @@ protected function setUp(): void { $this->installConfig(['system', 'field']); $this->installEntitySchema('entity_test'); + $this->installEntitySchema('user'); + $this->setUpCurrentUser(permissions: [ + 'view test entity', + ]); } /** diff --git a/core/modules/field/tests/src/Unit/Plugin/Field/FieldFormatter/StringFormatterTest.php b/core/modules/field/tests/src/Unit/Plugin/Field/FieldFormatter/StringFormatterTest.php new file mode 100644 index 0000000000000000000000000000000000000000..c7a9f21826fff6c1c9f39263b6ddbc084a8421a7 --- /dev/null +++ b/core/modules/field/tests/src/Unit/Plugin/Field/FieldFormatter/StringFormatterTest.php @@ -0,0 +1,110 @@ +<?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, + ]; + } + +} diff --git a/core/modules/taxonomy/tests/src/Kernel/Views/TaxonomyFieldTidTest.php b/core/modules/taxonomy/tests/src/Kernel/Views/TaxonomyFieldTidTest.php index fd3037bd3abc21655cb039212d2eb4b6c3ff0a87..d66a211b5728f5e74871ba7a1d732091e8a10685 100644 --- a/core/modules/taxonomy/tests/src/Kernel/Views/TaxonomyFieldTidTest.php +++ b/core/modules/taxonomy/tests/src/Kernel/Views/TaxonomyFieldTidTest.php @@ -7,6 +7,7 @@ use Drupal\Core\Link; use Drupal\Core\Render\RenderContext; use Drupal\Tests\taxonomy\Traits\TaxonomyTestTrait; +use Drupal\Tests\user\Traits\UserCreationTrait; use Drupal\Tests\views\Kernel\ViewsKernelTestBase; use Drupal\views\Tests\ViewTestData; use Drupal\views\Views; @@ -19,6 +20,7 @@ class TaxonomyFieldTidTest extends ViewsKernelTestBase { use TaxonomyTestTrait; + use UserCreationTrait; /** * {@inheritdoc} @@ -49,7 +51,11 @@ protected function setUp($import_test_views = TRUE): void { parent::setUp($import_test_views); $this->installEntitySchema('taxonomy_term'); + $this->installEntitySchema('user'); $this->installConfig(['filter']); + $this->setUpCurrentUser(permissions: [ + 'access content', + ]); /** @var \Drupal\taxonomy\Entity\Vocabulary $vocabulary */ $vocabulary = $this->createVocabulary(); diff --git a/core/modules/views/tests/src/Kernel/Handler/FieldFieldTest.php b/core/modules/views/tests/src/Kernel/Handler/FieldFieldTest.php index e57953228cdc6a0b94818822c5780d15eb19168c..bb07cf3453cc00f4bdbdbb2b053fd5d3bce6f336 100644 --- a/core/modules/views/tests/src/Kernel/Handler/FieldFieldTest.php +++ b/core/modules/views/tests/src/Kernel/Handler/FieldFieldTest.php @@ -333,6 +333,9 @@ public function testFieldAlias(): void { * different delta limit / offset. */ public function testFieldAliasRender(): void { + $this->setUpCurrentUser(permissions: [ + 'view test entity', + ]); $executable = Views::getView('test_field_alias_test'); $executable->execute();