From 3eebb1b1fb88b8d20eae5263ec8bec767b4acf9c Mon Sep 17 00:00:00 2001
From: Lee Rowlands <lee.rowlands@previousnext.com.au>
Date: Tue, 11 May 2021 09:05:35 +1000
Subject: [PATCH] Issue #2608750 by phenaproxima, shriaas2898, KapilV,
 mohit_aghera, RenatoG, akhoury, guilhermevp, praveenmoses61, sulfikar_s,
 Abhijith S, larowlan, pameeela, Sid_omp: Exception when creating an entity
 reference field targeting an entity type without an ID

---
 .../Field/FieldType/EntityReferenceItem.php   | 24 +++++++--
 .../EntityReferenceFieldCreationTest.php      | 51 +++++++++++++++++++
 2 files changed, 72 insertions(+), 3 deletions(-)
 create mode 100644 core/modules/system/tests/src/Functional/Entity/EntityReferenceFieldCreationTest.php

diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php
index 01761e14c5b3..dbe2762d940c 100644
--- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php
@@ -10,6 +10,7 @@
 use Drupal\Core\Entity\FieldableEntityInterface;
 use Drupal\Core\Entity\TypedData\EntityDataDefinition;
 use Drupal\Core\Field\FieldDefinitionInterface;
+use Drupal\Core\Field\FieldException;
 use Drupal\Core\Field\FieldItemBase;
 use Drupal\Core\Field\FieldStorageDefinitionInterface;
 use Drupal\Core\Field\PreconfiguredFieldUiOptionsInterface;
@@ -67,6 +68,12 @@ public static function propertyDefinitions(FieldStorageDefinitionInterface $fiel
     $settings = $field_definition->getSettings();
     $target_type_info = \Drupal::entityTypeManager()->getDefinition($settings['target_type']);
 
+    // If the target entity type doesn't have an ID key, we cannot determine
+    // the target_id data type.
+    if (!$target_type_info->hasKey('id')) {
+      throw new FieldException('Entity type "' . $target_type_info->id() . '" has no ID key and cannot be targeted by entity reference field "' . $field_definition->getName() . '"');
+    }
+
     $target_id_data_type = 'string';
     if ($target_type_info->entityClassImplements(FieldableEntityInterface::class)) {
       $id_definition = \Drupal::service('entity_field.manager')->getBaseFieldDefinitions($settings['target_type'])[$target_type_info->getKey('id')];
@@ -356,13 +363,23 @@ public function storageSettingsForm(array &$form, FormStateInterface $form_state
     $element['target_type'] = [
       '#type' => 'select',
       '#title' => t('Type of item to reference'),
-      '#options' => \Drupal::service('entity_type.repository')->getEntityTypeLabels(TRUE),
       '#default_value' => $this->getSetting('target_type'),
       '#required' => TRUE,
       '#disabled' => $has_data,
       '#size' => 1,
     ];
 
+    // Only allow the field to target entity types that have an ID key. This
+    // is enforced in ::propertyDefinitions().
+    $entity_type_manager = \Drupal::entityTypeManager();
+    $filter = function (string $entity_type_id) use ($entity_type_manager): bool {
+      return $entity_type_manager->getDefinition($entity_type_id)
+        ->hasKey('id');
+    };
+    $options = \Drupal::service('entity_type.repository')->getEntityTypeLabels(TRUE);
+    foreach ($options as $group_name => $group) {
+      $element['target_type']['#options'][$group_name] = array_filter($group, $filter, ARRAY_FILTER_USE_KEY);
+    }
     return $element;
   }
 
@@ -618,8 +635,9 @@ public function getSettableOptions(AccountInterface $account = NULL) {
   }
 
   /**
-   * Render API callback: Processes the field settings form and allows access to
-   * the form state.
+   * Render API callback: Processes the field settings form.
+   *
+   * Allows access to the form state.
    *
    * @see static::fieldSettingsForm()
    */
diff --git a/core/modules/system/tests/src/Functional/Entity/EntityReferenceFieldCreationTest.php b/core/modules/system/tests/src/Functional/Entity/EntityReferenceFieldCreationTest.php
new file mode 100644
index 000000000000..9062c5cf81e8
--- /dev/null
+++ b/core/modules/system/tests/src/Functional/Entity/EntityReferenceFieldCreationTest.php
@@ -0,0 +1,51 @@
+<?php
+
+namespace Drupal\Tests\system\Functional\Entity;
+
+use Drupal\Tests\BrowserTestBase;
+use Drupal\Tests\field\Traits\EntityReferenceTestTrait;
+
+/**
+ * Tests creating entity reference fields in the UI.
+ *
+ * @group entity
+ */
+class EntityReferenceFieldCreationTest extends BrowserTestBase {
+
+  use EntityReferenceTestTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = ['entity_test', 'node', 'field_ui'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $defaultTheme = 'stark';
+
+  /**
+   * Tests that entity reference fields cannot target entity types without IDs.
+   */
+  public function testAddReferenceFieldTargetingEntityTypeWithoutId() {
+    $this->drupalLogin($this->rootUser);
+    $node_type = $this->drupalCreateContentType()->id();
+
+    // Entity types without an ID key should not be presented as options when
+    // creating an entity reference field in the UI.
+    $this->drupalGet("/admin/structure/types/manage/$node_type/fields/add-field");
+    $edit = [
+      'new_storage_type' => 'entity_reference',
+      'label' => 'Test Field',
+      'field_name' => 'test_reference_field',
+    ];
+    $this->submitForm($edit, 'Save and continue');
+    $this->assertSession()->optionNotExists('settings[target_type]', 'entity_test_no_id');
+
+    // Trying to do it programmatically should raise an exception.
+    $this->expectException('\Drupal\Core\Field\FieldException');
+    $this->expectExceptionMessage('Entity type "entity_test_no_id" has no ID key and cannot be targeted by entity reference field "test_reference_field"');
+    $this->createEntityReferenceField('node', $node_type, 'test_reference_field', 'Test Field', 'entity_test_no_id');
+  }
+
+}
-- 
GitLab