diff -u b/core/config/schema/core.data_types.schema.yml b/core/config/schema/core.data_types.schema.yml --- b/core/config/schema/core.data_types.schema.yml +++ b/core/config/schema/core.data_types.schema.yml @@ -808,6 +808,9 @@ auto_create_bundle: type: string label: 'Bundle assigned to the auto-created entities.' + allow_self_reference: + type: boolean + label: 'Allow an entity to reference itself' # Schema for all entity reference 'default:*' selection handlers that are not # providing a specific schema. @@ -822,9 +825,6 @@ auto_create_bundle: type: string label: 'Bundle assigned to the auto-created entities.' - allow_self_reference: - type: boolean - label: 'Allow an entity to reference itself' # Schema for all entity reference 'default:*' selection handlers that are not # providing a specific schema. reverted: --- b/core/lib/Drupal/Core/Entity/EntityAutocompleteMatcher.php +++ a/core/lib/Drupal/Core/Entity/EntityAutocompleteMatcher.php @@ -39,8 +39,6 @@ * An array of settings that will be passed to the selection handler. * @param string $string * (optional) The label of the entity to query by. - * @param \Drupal\Core\Entity\EntityInterface $host_entity - * (optional) The referencing entity, if any. Defaults to NULL. * * @return array * An array of matched entity labels, in the format required by the AJAX @@ -51,13 +49,12 @@ * * @see \Drupal\system\Controller\EntityAutocompleteController */ + public function getMatches($target_type, $selection_handler, $selection_settings, $string = '') { - public function getMatches($target_type, $selection_handler, $selection_settings, $string = '', EntityInterface $host_entity = NULL) { $matches = []; $options = $selection_settings + [ 'target_type' => $target_type, 'handler' => $selection_handler, - 'entity' => $host_entity, ]; $handler = $this->selectionManager->getInstance($options); diff -u b/core/lib/Drupal/Core/Entity/Plugin/EntityReferenceSelection/DefaultSelection.php b/core/lib/Drupal/Core/Entity/Plugin/EntityReferenceSelection/DefaultSelection.php --- b/core/lib/Drupal/Core/Entity/Plugin/EntityReferenceSelection/DefaultSelection.php +++ b/core/lib/Drupal/Core/Entity/Plugin/EntityReferenceSelection/DefaultSelection.php @@ -4,6 +4,7 @@ use Drupal\Component\Utility\Html; use Drupal\Core\Database\Query\AlterableInterface; +use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\Entity\EntityReferenceSelection\SelectionPluginBase; use Drupal\Core\Entity\EntityReferenceSelection\SelectionTrait; @@ -201,7 +202,7 @@ $form['allow_self_reference'] = [ '#type' => 'checkbox', '#title' => $this->t('Allow an entity to reference itself'), - '#default_value' => $selection_handler_settings['allow_self_reference'], + '#default_value' => $configuration['allow_self_reference'], ]; return $form; @@ -359,9 +360,18 @@ $query->condition($label_key, $match, $match_operator); } - // Disallow self-references if possible. - if (isset($this->configuration['entity']) && $this->configuration['entity']->id() && isset($handler_settings['allow_self_reference']) && !$handler_settings['allow_self_reference']) { - $query->condition($entity_type->getKey('id'), $this->configuration['entity']->id(), '<>'); + // Disallow references to the referencing entity. + $entity = $this->configuration['entity'] ? $this->configuration['entity'] : NULL; + if ($entity instanceof EntityInterface) { + // To maintain backwards compatibility: if the setting is not set, then + // allow referencing self. + $allow_self_reference = isset($configuration['handler_settings']['allow_self_reference']) ? !empty($configuration['handler_settings']['allow_self_reference']) : TRUE; + if (!$allow_self_reference) { + // Referencing entity must be the same entity type as the query. + if ($entity->getEntityTypeId() == $entity_type->id()) { + $query->condition($entity_type->getKey('id'), $entity->id(), '<>'); + } + } } // Add entity-access tag. reverted: --- b/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/EntityReferenceAutocompleteWidget.php +++ a/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/EntityReferenceAutocompleteWidget.php @@ -88,10 +88,7 @@ $referenced_entities = $items->referencedEntities(); // Append the match operation to the selection settings. + $selection_settings = $this->getFieldSetting('handler_settings') + ['match_operator' => $this->getSetting('match_operator')]; - $selection_settings = $this->getFieldSetting('handler_settings') + [ - 'match_operator' => $this->getSetting('match_operator'), - 'entity' => $entity, - ]; $element += [ '#type' => 'entity_autocomplete', reverted: --- b/core/modules/system/src/Controller/EntityAutocompleteController.php +++ a/core/modules/system/src/Controller/EntityAutocompleteController.php @@ -78,7 +78,6 @@ */ public function handleAutocomplete(Request $request, $target_type, $selection_handler, $selection_settings_key) { $matches = []; - // Get the typed string from the URL, if it exists. if ($input = $request->query->get('q')) { $typed_string = Tags::explode($input); @@ -101,12 +100,7 @@ throw new AccessDeniedHttpException(); } + $matches = $this->matcher->getMatches($target_type, $selection_handler, $selection_settings, $typed_string); - // The host entity was not passed in the selection settings. Set to NULL. - if (empty($selection_settings['entity'])) { - $selection_settings['entity'] = NULL; - } - - $matches = $this->matcher->getMatches($target_type, $selection_handler, $selection_settings, $typed_string, $selection_settings['entity']); } return new JsonResponse($matches); reverted: --- b/core/modules/system/system.routing.yml +++ a/core/modules/system/system.routing.yml @@ -501,10 +501,9 @@ _permission: 'access administration pages' system.entity_autocomplete: + path: '/entity_reference_autocomplete/{target_type}/{selection_handler}/{selection_settings_key}' - path: '/entity_reference_autocomplete/{target_type}/{selection_handler}/{selection_settings_key}/{host_entity_id}' defaults: _controller: '\Drupal\system\Controller\EntityAutocompleteController::handleAutocomplete' - host_entity_id: '' requirements: _access: 'TRUE' diff -u b/core/modules/system/tests/src/Kernel/Entity/EntityReferenceSelectionReferenceableTest.php b/core/modules/system/tests/src/Kernel/Entity/EntityReferenceSelectionReferenceableTest.php --- b/core/modules/system/tests/src/Kernel/Entity/EntityReferenceSelectionReferenceableTest.php +++ b/core/modules/system/tests/src/Kernel/Entity/EntityReferenceSelectionReferenceableTest.php @@ -5,6 +5,7 @@ use Drupal\Component\Utility\Html; use Drupal\Component\Utility\Unicode; use Drupal\entity_test\Entity\EntityTest; +use Drupal\entity_test\Entity\EntityTestNoLabel; use Drupal\field\Tests\EntityReference\EntityReferenceTestTrait; use Drupal\field\Entity\FieldConfig; use Drupal\node\Entity\NodeType; @@ -54,10 +55,6 @@ $this->installEntitySchema('entity_test'); $this->installEntitySchema('entity_test_no_label'); - /** @var \Drupal\Core\Entity\EntityStorageInterface $storage */ - $storage = $this->container->get('entity.manager') - ->getStorage('entity_test_no_label'); - // Create a new node-type. NodeType::create([ 'type' => $node_type = Unicode::strtolower($this->randomMachineName()), @@ -73,41 +70,101 @@ // Generate a bundle name to be used with 'entity_test_no_label'. $this->bundle = Unicode::strtolower($this->randomMachineName()); - - // Create 6 entities to be referenced by the field. - foreach (static::$labels as $name) { - $storage->create([ - 'id' => Unicode::strtolower($this->randomMachineName()), - 'name' => $name, - 'type' => $this->bundle, - ])->save(); - } } /** * Tests the 'allow_self_reference' selection handler setting. + * + * @param $allow_self_reference + * Value for 'allow_self_reference' selection handler setting. + * @param $entity_is_referencable + * Assert whether the referencing entity is referencable. + * + * @dataProvider providerAllowSelfReference */ - public function testAllowSelfReference() { + public function testAllowSelfReference($allow_self_reference, $entity_is_referencable) { $field_name = 'field_test'; $this->createEntityReferenceField('entity_test', 'entity_test', $field_name, 'Test entity reference', 'entity_test'); + $field_config = FieldConfig::loadByName('entity_test', 'entity_test', $field_name); + $handler_settings = $field_config->getSetting('handler_settings'); + $handler_settings['allow_self_reference'] = $allow_self_reference; + $field_config + ->setSetting('handler_settings', $handler_settings) + ->save(); + + $entity = EntityTestNoLabel::create(['name' => $this->randomMachineName()]); + $entity->save(); + $other = EntityTest::create(['name' => $this->randomMachineName()]); + $other->save(); + + $handler = $this->container + ->get('plugin.manager.entity_reference_selection') + ->getSelectionHandler($field_config, $entity); + + $referenceable = $handler->getReferenceableEntities()['entity_test']; + if ($entity_is_referencable) { + $this->assertArrayHasKey($entity->id(), $referenceable, 'Can reference entity.'); + } + else { + $this->assertArrayNotHasKey($entity->id(), $referenceable, 'Can not reference entity.'); + } + // Other entity is always referencable. + $this->assertArrayHasKey($other->id(), $referenceable, 'Can reference other entity.'); + } - // Create a test entity and check that it cannot reference itself. - $host_entity = EntityTest::create(['name' => $this->randomMachineName()]); - $host_entity->save(); + /** + * Data provider for testAllowSelfReference(). + */ + public function providerAllowSelfReference() { + return [ + 'when setting is omitted, referencing entity is referencable' => [ + NULL, + TRUE, + ], + 'when setting is true, referencing entity is referencable' => [ + TRUE, + TRUE, + ], + 'when setting is false, referencing entity is not referencable' => [ + FALSE, + FALSE, + ], + ]; + } + /** + * Tests entities can be referenced despite having the same ID as referencer. + * + * Ensures that the 'allow_self_reference' selection handler setting does not + * attempt to exclude entities that are different entity types. + */ + public function testPreventSelfReferenceDifferentEntityType() { + $field_name = 'field_test'; + $this->createEntityReferenceField('entity_test', 'entity_test', $field_name, 'Test entity reference', 'entity_test'); $field_config = FieldConfig::loadByName('entity_test', 'entity_test', $field_name); - $this->selectionHandler = $this->container->get('plugin.manager.entity_reference_selection')->getSelectionHandler($field_config, $host_entity); - - $referenceables = $this->selectionHandler->getReferenceableEntities(); - $this->assertTrue(isset($referenceables['entity_test'][$host_entity->id()])); + $handler_settings = $field_config->getSetting('handler_settings'); + $handler_settings['allow_self_reference'] = FALSE; + $field_config + ->setSetting('handler_settings', $handler_settings) + ->save(); + + $entity = EntityTestNoLabel::create(['name' => $this->randomMachineName()]); + $entity->save(); + $other = EntityTest::create(['name' => $this->randomMachineName()]); + $other->save(); + + // The entity type doesn't matter, so long as it is a different type to the + // other entity. + $this->assertNotEquals($entity->getEntityTypeId(), $other->getEntityTypeId()); + // This test requires ID's to be the same. + $this->assertEquals($entity->id(), $other->id()); + + $handler = $this->container + ->get('plugin.manager.entity_reference_selection') + ->getSelectionHandler($field_config, $entity); - $selection_setttings = $field_config->getSetting('handler_settings'); - $selection_setttings['allow_self_reference'] = FALSE; - $field_config->setSetting('handler_settings', $selection_setttings)->save(); - - $this->selectionHandler = $this->container->get('plugin.manager.entity_reference_selection')->getSelectionHandler($field_config, $host_entity); - $referenceables = $this->selectionHandler->getReferenceableEntities(); - $this->assertTrue(!isset($referenceables['entity_test'][$host_entity->id()])); + $referenceable = $handler->getReferenceableEntities()['entity_test']; + $this->assertArrayHasKey($entity->id(), $referenceable, 'Can reference entity even though the IDs are the same.'); } /** @@ -130,6 +187,19 @@ * @dataProvider providerTestCases */ public function testReferenceablesWithNoLabelKey($match, $match_operator, $limit, $count_limited, array $items, $count_all) { + /** @var \Drupal\Core\Entity\EntityStorageInterface $storage */ + $storage = $this->container->get('entity.manager') + ->getStorage('entity_test_no_label'); + + // Create 6 entities to be referenced by the field. + foreach (static::$labels as $name) { + $storage->create([ + 'id' => Unicode::strtolower($this->randomMachineName()), + 'name' => $name, + 'type' => $this->bundle, + ])->save(); + } + // Test ::getReferenceableEntities(). $referenceables = $this->selectionHandler->getReferenceableEntities($match, $match_operator, $limit);