diff --git a/core/config/schema/core.data_types.schema.yml b/core/config/schema/core.data_types.schema.yml index 73db361..5db50dd 100644 --- a/core/config/schema/core.data_types.schema.yml +++ b/core/config/schema/core.data_types.schema.yml @@ -798,6 +798,9 @@ entity_reference_selection: 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' entity_reference_selection.*: type: entity_reference_selection diff --git a/core/lib/Drupal/Core/Entity/EntityAutocompleteMatcher.php b/core/lib/Drupal/Core/Entity/EntityAutocompleteMatcher.php index 01a29b2..e0b108c 100644 --- a/core/lib/Drupal/Core/Entity/EntityAutocompleteMatcher.php +++ b/core/lib/Drupal/Core/Entity/EntityAutocompleteMatcher.php @@ -39,6 +39,8 @@ public function __construct(SelectionPluginManagerInterface $selection_manager) * 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 @@ -49,13 +51,14 @@ public function __construct(SelectionPluginManagerInterface $selection_manager) * * @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 = array(); $options = array( 'target_type' => $target_type, 'handler' => $selection_handler, 'handler_settings' => $selection_settings, + 'entity' => $host_entity, ); $handler = $this->selectionManager->getInstance($options); diff --git a/core/lib/Drupal/Core/Entity/Plugin/EntityReferenceSelection/DefaultSelection.php b/core/lib/Drupal/Core/Entity/Plugin/EntityReferenceSelection/DefaultSelection.php index 9565e77..0ae9df0 100644 --- a/core/lib/Drupal/Core/Entity/Plugin/EntityReferenceSelection/DefaultSelection.php +++ b/core/lib/Drupal/Core/Entity/Plugin/EntityReferenceSelection/DefaultSelection.php @@ -118,6 +118,7 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta ), 'auto_create' => FALSE, 'auto_create_bundle' => NULL, + 'allow_self_reference' => TRUE, ); if ($entity_type->hasKey('bundle')) { @@ -240,6 +241,12 @@ public function buildConfigurationForm(array $form, FormStateInterface $form_sta ]; } + $form['allow_self_reference'] = array( + '#type' => 'checkbox', + '#title' => $this->t('Allow an entity to reference itself'), + '#default_value' => $selection_handler_settings['allow_self_reference'], + ); + return $form; } @@ -397,6 +404,11 @@ protected function buildEntityQuery($match = NULL, $match_operator = 'CONTAINS') $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(), '<>'); + } + // Add entity-access tag. $query->addTag($target_type . '_access'); diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/EntityReferenceAutocompleteWidget.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/EntityReferenceAutocompleteWidget.php index 3824810..a7eb20f 100644 --- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/EntityReferenceAutocompleteWidget.php +++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldWidget/EntityReferenceAutocompleteWidget.php @@ -88,7 +88,10 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen $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 += array( '#type' => 'entity_autocomplete', diff --git a/core/modules/system/src/Controller/EntityAutocompleteController.php b/core/modules/system/src/Controller/EntityAutocompleteController.php index 53ec818..9913b93 100644 --- a/core/modules/system/src/Controller/EntityAutocompleteController.php +++ b/core/modules/system/src/Controller/EntityAutocompleteController.php @@ -78,6 +78,7 @@ public static function create(ContainerInterface $container) { */ public function handleAutocomplete(Request $request, $target_type, $selection_handler, $selection_settings_key) { $matches = array(); + // Get the typed string from the URL, if it exists. if ($input = $request->query->get('q')) { $typed_string = Tags::explode($input); @@ -100,7 +101,12 @@ public function handleAutocomplete(Request $request, $target_type, $selection_ha 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); diff --git a/core/modules/system/system.routing.yml b/core/modules/system/system.routing.yml index ae89fb0..6f447a2 100644 --- a/core/modules/system/system.routing.yml +++ b/core/modules/system/system.routing.yml @@ -495,9 +495,10 @@ system.admin_content: _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 --git a/core/modules/system/tests/src/Kernel/Entity/EntityReferenceSelectionReferenceableTest.php b/core/modules/system/tests/src/Kernel/Entity/EntityReferenceSelectionReferenceableTest.php index f7fa5d8..fc0b5f6 100644 --- a/core/modules/system/tests/src/Kernel/Entity/EntityReferenceSelectionReferenceableTest.php +++ b/core/modules/system/tests/src/Kernel/Entity/EntityReferenceSelectionReferenceableTest.php @@ -4,6 +4,7 @@ use Drupal\Component\Utility\Html; use Drupal\Component\Utility\Unicode; +use Drupal\entity_test\Entity\EntityTest; use Drupal\field\Tests\EntityReference\EntityReferenceTestTrait; use Drupal\field\Entity\FieldConfig; use Drupal\node\Entity\NodeType; @@ -50,6 +51,7 @@ class EntityReferenceSelectionReferenceableTest extends KernelTestBase { protected function setUp() { parent::setUp(); + $this->installEntitySchema('entity_test'); $this->installEntitySchema('entity_test_no_label'); /** @var \Drupal\Core\Entity\EntityStorageInterface $storage */ @@ -83,6 +85,32 @@ protected function setUp() { } /** + * Tests the 'allow_self_reference' selection handler setting. + */ + public function testAllowSelfReference() { + $field_name = 'field_test'; + $this->createEntityReferenceField('entity_test', 'entity_test', $field_name, 'Test entity reference', 'entity_test'); + + // Create a test entity and check that it cannot reference itself. + $host_entity = EntityTest::create(['name' => $this->randomMachineName()]); + $host_entity->save(); + + $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()])); + + $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()])); + } + + /** * Tests values returned by SelectionInterface::getReferenceableEntities() * when the target entity type has no 'label' key. *