1<?php 2 3namespace Drupal\KernelTests\Core\Entity; 4 5use Drupal\Core\Field\BaseFieldDefinition; 6use Drupal\Core\Field\FieldStorageDefinitionInterface; 7use Drupal\entity_test\Entity\EntityTest; 8use Drupal\field\Entity\FieldConfig; 9use Drupal\node\Entity\Node; 10use Drupal\node\NodeInterface; 11use Drupal\Tests\field\Traits\EntityReferenceTestTrait; 12use Drupal\Tests\node\Traits\ContentTypeCreationTrait; 13use Drupal\user\Entity\Role; 14use Drupal\user\Entity\User; 15 16/** 17 * Tests validation constraints for ValidReferenceConstraintValidator. 18 * 19 * @group Validation 20 */ 21class ValidReferenceConstraintValidatorTest extends EntityKernelTestBase { 22 23 use EntityReferenceTestTrait; 24 use ContentTypeCreationTrait; 25 26 /** 27 * The typed data manager to use. 28 * 29 * @var \Drupal\Core\TypedData\TypedDataManager 30 */ 31 protected $typedData; 32 33 /** 34 * {@inheritdoc} 35 */ 36 protected static $modules = ['field', 'node', 'user']; 37 38 /** 39 * {@inheritdoc} 40 */ 41 protected function setUp(): void { 42 parent::setUp(); 43 $this->installSchema('user', ['users_data']); 44 $this->installSchema('node', ['node_access']); 45 $this->installConfig('node'); 46 $this->typedData = $this->container->get('typed_data_manager'); 47 48 $this->createContentType(['type' => 'article', 'name' => 'Article']); 49 $this->createContentType(['type' => 'page', 'name' => 'Basic page']); 50 } 51 52 /** 53 * Tests the ValidReferenceConstraintValidator. 54 */ 55 public function testValidation() { 56 // Create a test entity to be referenced. 57 $entity = $this->createUser(); 58 // By default entity references already have the ValidReference constraint. 59 $definition = BaseFieldDefinition::create('entity_reference') 60 ->setSettings(['target_type' => 'user']); 61 62 $typed_data = $this->typedData->create($definition, ['target_id' => $entity->id()]); 63 $violations = $typed_data->validate(); 64 $this->assertEquals(0, $violations->count(), 'Validation passed for correct value.'); 65 66 // NULL is also considered a valid reference. 67 $typed_data = $this->typedData->create($definition, ['target_id' => NULL]); 68 $violations = $typed_data->validate(); 69 $this->assertEquals(0, $violations->count(), 'Validation passed for correct value.'); 70 71 $typed_data = $this->typedData->create($definition, ['target_id' => $entity->id()]); 72 // Delete the referenced entity. 73 $entity->delete(); 74 $violations = $typed_data->validate(); 75 $this->assertGreaterThan(0, $violations->count(), 'Validation failed for incorrect value.'); 76 77 // Make sure the information provided by a violation is correct. 78 $violation = $violations[0]; 79 $this->assertEquals(t('The referenced entity (%type: %id) does not exist.', ['%type' => 'user', '%id' => $entity->id()]), $violation->getMessage(), 'The message for invalid value is correct.'); 80 $this->assertEquals($typed_data, $violation->getRoot(), 'Violation root is correct.'); 81 } 82 83 /** 84 * Tests the validation of pre-existing items in an entity reference field. 85 */ 86 public function testPreExistingItemsValidation() { 87 // Create two types of users, with and without access to bypass content 88 // access. 89 /** @var \Drupal\user\RoleInterface $role_with_access */ 90 $role_with_access = Role::create(['id' => 'role_with_access']); 91 $role_with_access->grantPermission('access content'); 92 $role_with_access->grantPermission('bypass node access'); 93 $role_with_access->save(); 94 95 /** @var \Drupal\user\RoleInterface $role_without_access */ 96 $role_without_access = Role::create(['id' => 'role_without_access']); 97 $role_without_access->grantPermission('access content'); 98 $role_without_access->save(); 99 100 $user_with_access = User::create(['roles' => ['role_with_access']]); 101 $user_without_access = User::create(['roles' => ['role_without_access']]); 102 103 // Add an entity reference field. 104 $this->createEntityReferenceField( 105 'entity_test', 106 'entity_test', 107 'field_test', 108 'Field test', 109 'node', 110 'default', 111 ['target_bundles' => ['article', 'page']], 112 FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED 113 ); 114 115 // Create four test nodes. 116 $published_node = Node::create([ 117 'title' => 'Test published node', 118 'type' => 'article', 119 'status' => NodeInterface::PUBLISHED, 120 ]); 121 $published_node->save(); 122 123 $unpublished_node = Node::create([ 124 'title' => 'Test unpublished node', 125 'type' => 'article', 126 'status' => NodeInterface::NOT_PUBLISHED, 127 ]); 128 $unpublished_node->save(); 129 130 $different_bundle_node = Node::create([ 131 'title' => 'Test page node', 132 'type' => 'page', 133 'status' => NodeInterface::PUBLISHED, 134 ]); 135 $different_bundle_node->save(); 136 137 $deleted_node = Node::create([ 138 'title' => 'Test deleted node', 139 'type' => 'article', 140 'status' => NodeInterface::PUBLISHED, 141 ]); 142 $deleted_node->save(); 143 144 $referencing_entity = EntityTest::create([ 145 'field_test' => [ 146 ['entity' => $published_node], 147 ['entity' => $unpublished_node], 148 ['entity' => $different_bundle_node], 149 ['entity' => $deleted_node], 150 ], 151 ]); 152 153 // Check that users with access are able pass the validation for fields 154 // without pre-existing content. 155 $this->container->get('account_switcher')->switchTo($user_with_access); 156 157 $violations = $referencing_entity->field_test->validate(); 158 $this->assertCount(0, $violations); 159 160 // Check that users without access are not able pass the validation for 161 // fields without pre-existing content. 162 $this->container->get('account_switcher')->switchTo($user_without_access); 163 164 $violations = $referencing_entity->field_test->validate(); 165 $this->assertCount(1, $violations); 166 $this->assertEquals(t('This entity (%type: %id) cannot be referenced.', [ 167 '%type' => 'node', 168 '%id' => $unpublished_node->id(), 169 ]), $violations[0]->getMessage()); 170 171 // Now save the referencing entity which will create a pre-existing state 172 // for it and repeat the checks. This time, the user without access should 173 // be able to pass the validation as well because it's not changing the 174 // pre-existing state. 175 $referencing_entity->save(); 176 177 $this->container->get('account_switcher')->switchTo($user_with_access); 178 179 $violations = $referencing_entity->field_test->validate(); 180 $this->assertCount(0, $violations); 181 182 // Check that users without access are able pass the validation for fields 183 // with pre-existing content. 184 $this->container->get('account_switcher')->switchTo($user_without_access); 185 186 $violations = $referencing_entity->field_test->validate(); 187 $this->assertCount(0, $violations); 188 189 // Re-save the referencing entity and check that the referenced entity is 190 // not affected. 191 $referencing_entity->name->value = $this->randomString(); 192 $referencing_entity->save(); 193 $this->assertEquals($published_node->id(), $referencing_entity->field_test[0]->target_id); 194 $this->assertEquals($unpublished_node->id(), $referencing_entity->field_test[1]->target_id); 195 $this->assertEquals($different_bundle_node->id(), $referencing_entity->field_test[2]->target_id); 196 $this->assertEquals($deleted_node->id(), $referencing_entity->field_test[3]->target_id); 197 198 $violations = $referencing_entity->field_test->validate(); 199 $this->assertCount(0, $violations); 200 201 // Remove one of the referenceable bundles and check that a pre-existing node 202 // of that bundle can not be referenced anymore. 203 $field = FieldConfig::loadByName('entity_test', 'entity_test', 'field_test'); 204 $field->setSetting('handler_settings', ['target_bundles' => ['article']]); 205 $field->save(); 206 $referencing_entity = $this->reloadEntity($referencing_entity); 207 208 $violations = $referencing_entity->field_test->validate(); 209 $this->assertCount(1, $violations); 210 $this->assertEquals(t('This entity (%type: %id) cannot be referenced.', [ 211 '%type' => 'node', 212 '%id' => $different_bundle_node->id(), 213 ]), $violations[0]->getMessage()); 214 215 // Delete the last node and check that the pre-existing reference is not 216 // valid anymore. 217 $deleted_node->delete(); 218 219 $violations = $referencing_entity->field_test->validate(); 220 $this->assertCount(2, $violations); 221 $this->assertEquals(t('This entity (%type: %id) cannot be referenced.', [ 222 '%type' => 'node', 223 '%id' => $different_bundle_node->id(), 224 ]), $violations[0]->getMessage()); 225 $this->assertEquals(t('The referenced entity (%type: %id) does not exist.', [ 226 '%type' => 'node', 227 '%id' => $deleted_node->id(), 228 ]), $violations[1]->getMessage()); 229 } 230 231} 232