EntityReferenceItem.php 25.9 KB
Newer Older
1 2
<?php

3
namespace Drupal\Core\Field\Plugin\Field\FieldType;
4

5 6
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\NestedArray;
7
use Drupal\Core\Entity\ContentEntityStorageInterface;
8
use Drupal\Core\Entity\EntityInterface;
9
use Drupal\Core\Entity\EntityTypeInterface;
10
use Drupal\Core\Entity\FieldableEntityInterface;
11
use Drupal\Core\Entity\TypedData\EntityDataDefinition;
12
use Drupal\Core\Field\FieldDefinitionInterface;
13
use Drupal\Core\Field\FieldItemBase;
14
use Drupal\Core\Field\FieldStorageDefinitionInterface;
15 16 17 18 19
use Drupal\Core\Field\PreconfiguredFieldUiOptionsInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Form\OptGroup;
use Drupal\Core\Render\Element;
use Drupal\Core\Session\AccountInterface;
20
use Drupal\Core\StringTranslation\TranslatableMarkup;
21
use Drupal\Core\TypedData\DataReferenceDefinition;
22
use Drupal\Core\TypedData\DataReferenceTargetDefinition;
23 24
use Drupal\Core\TypedData\OptionsProviderInterface;
use Drupal\Core\Validation\Plugin\Validation\Constraint\AllowedValuesConstraint;
25 26

/**
27
 * Defines the 'entity_reference' entity field type.
28
 *
29 30
 * Supported settings (below the definition's 'settings' key) are:
 * - target_type: The entity type to reference. Required.
31
 *
32 33 34
 * @FieldType(
 *   id = "entity_reference",
 *   label = @Translation("Entity reference"),
35
 *   description = @Translation("An entity field containing an entity reference."),
36
 *   category = @Translation("Reference"),
37
 *   default_widget = "entity_reference_autocomplete",
38
 *   default_formatter = "entity_reference_label",
39
 *   list_class = "\Drupal\Core\Field\EntityReferenceFieldItemList",
40
 * )
41
 */
42
class EntityReferenceItem extends FieldItemBase implements OptionsProviderInterface, PreconfiguredFieldUiOptionsInterface {
43

44 45 46
  /**
   * {@inheritdoc}
   */
47
  public static function defaultStorageSettings() {
48
    return [
49
      'target_type' => \Drupal::moduleHandler()->moduleExists('node') ? 'node' : 'user',
50
    ] + parent::defaultStorageSettings();
51 52 53 54 55
  }

  /**
   * {@inheritdoc}
   */
56
  public static function defaultFieldSettings() {
57
    return [
58
      'handler' => 'default',
59 60
      'handler_settings' => [],
    ] + parent::defaultFieldSettings();
61 62
  }

63
  /**
64
   * {@inheritdoc}
65
   */
66
  public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
67
    $settings = $field_definition->getSettings();
68
    $target_type_info = \Drupal::entityTypeManager()->getDefinition($settings['target_type']);
69

70
    $target_id_data_type = 'string';
71
    if ($target_type_info->entityClassImplements(FieldableEntityInterface::class)) {
72
      $id_definition = \Drupal::service('entity_field.manager')->getBaseFieldDefinitions($settings['target_type'])[$target_type_info->getKey('id')];
73 74 75 76 77 78
      if ($id_definition->getType() === 'integer') {
        $target_id_data_type = 'integer';
      }
    }

    if ($target_id_data_type === 'integer') {
79
      $target_id_definition = DataReferenceTargetDefinition::create('integer')
80
        ->setLabel(new TranslatableMarkup('@label ID', ['@label' => $target_type_info->getLabel()]))
81
        ->setSetting('unsigned', TRUE);
82
    }
83
    else {
84
      $target_id_definition = DataReferenceTargetDefinition::create('string')
85
        ->setLabel(new TranslatableMarkup('@label ID', ['@label' => $target_type_info->getLabel()]));
86
    }
87
    $target_id_definition->setRequired(TRUE);
88
    $properties['target_id'] = $target_id_definition;
89

90
    $properties['entity'] = DataReferenceDefinition::create('entity')
91
      ->setLabel($target_type_info->getLabel())
92
      ->setDescription(new TranslatableMarkup('The referenced entity'))
93 94 95
      // The entity object is computed out of the entity ID.
      ->setComputed(TRUE)
      ->setReadOnly(FALSE)
96
      ->setTargetDefinition(EntityDataDefinition::create($settings['target_type']))
97 98 99
      // We can add a constraint for the target entity type. The list of
      // referenceable bundles is a field setting, so the corresponding
      // constraint is added dynamically in ::getConstraints().
100
      ->addConstraint('EntityType', $settings['target_type']);
101 102 103 104 105 106 107 108 109

    return $properties;
  }

  /**
   * {@inheritdoc}
   */
  public static function mainPropertyName() {
    return 'target_id';
110 111
  }

112 113 114
  /**
   * {@inheritdoc}
   */
115
  public static function schema(FieldStorageDefinitionInterface $field_definition) {
116
    $target_type = $field_definition->getSetting('target_type');
117
    $target_type_info = \Drupal::entityTypeManager()->getDefinition($target_type);
118
    $properties = static::propertyDefinitions($field_definition)['target_id'];
119
    if ($target_type_info->entityClassImplements(FieldableEntityInterface::class) && $properties->getDataType() === 'integer') {
120 121
      $columns = [
        'target_id' => [
122 123 124
          'description' => 'The ID of the target entity.',
          'type' => 'int',
          'unsigned' => TRUE,
125 126
        ],
      ];
127 128
    }
    else {
129 130
      $columns = [
        'target_id' => [
131
          'description' => 'The ID of the target entity.',
132
          'type' => 'varchar_ascii',
133 134 135
          // If the target entities act as bundles for another entity type,
          // their IDs should not exceed the maximum length for bundles.
          'length' => $target_type_info->getBundleOf() ? EntityTypeInterface::BUNDLE_MAX_LENGTH : 255,
136 137
        ],
      ];
138 139
    }

140
    $schema = [
141
      'columns' => $columns,
142 143 144 145
      'indexes' => [
        'target_id' => ['target_id'],
      ],
    ];
146 147 148 149

    return $schema;
  }

150 151 152 153 154
  /**
   * {@inheritdoc}
   */
  public function getConstraints() {
    $constraints = parent::getConstraints();
155 156 157 158 159 160 161
    // Remove the 'AllowedValuesConstraint' validation constraint because entity
    // reference fields already use the 'ValidReference' constraint.
    foreach ($constraints as $key => $constraint) {
      if ($constraint instanceof AllowedValuesConstraint) {
        unset($constraints[$key]);
      }
    }
162 163 164
    return $constraints;
  }

165
  /**
166
   * {@inheritdoc}
167 168 169
   */
  public function setValue($values, $notify = TRUE) {
    if (isset($values) && !is_array($values)) {
170 171 172
      // If either a scalar or an object was passed as the value for the item,
      // assign it to the 'entity' property since that works for both cases.
      $this->set('entity', $values, $notify);
173
    }
174
    else {
175 176 177
      parent::setValue($values, FALSE);
      // Support setting the field item with only one property, but make sure
      // values stay in sync if only property is passed.
178 179
      // NULL is a valid value, so we use array_key_exists().
      if (is_array($values) && array_key_exists('target_id', $values) && !isset($values['entity'])) {
180 181
        $this->onChange('target_id', FALSE);
      }
182
      elseif (is_array($values) && !array_key_exists('target_id', $values) && isset($values['entity'])) {
183 184
        $this->onChange('entity', FALSE);
      }
185
      elseif (is_array($values) && array_key_exists('target_id', $values) && isset($values['entity'])) {
186 187 188 189 190
        // If both properties are passed, verify the passed values match. The
        // only exception we allow is when we have a new entity: in this case
        // its actual id and target_id will be different, due to the new entity
        // marker.
        $entity_id = $this->get('entity')->getTargetIdentifier();
191 192
        // If the entity has been saved and we're trying to set both the
        // target_id and the entity values with a non-null target ID, then the
193 194 195 196 197
        // value for target_id should match the ID of the entity value. The
        // entity ID as returned by $entity->id() might be a string, but the
        // provided target_id might be an integer - therefore we have to do a
        // non-strict comparison.
        if (!$this->entity->isNew() && $values['target_id'] !== NULL && ($entity_id != $values['target_id'])) {
198 199 200 201 202 203
          throw new \InvalidArgumentException('The target id and entity passed to the entity reference item do not match.');
        }
      }
      // Notify the parent if necessary.
      if ($notify && $this->parent) {
        $this->parent->onChange($this->getName());
204
      }
205
    }
206

207
  }
208

209 210 211
  /**
   * {@inheritdoc}
   */
212 213
  public function getValue() {
    $values = parent::getValue();
214 215 216

    // If there is an unsaved entity, return it as part of the field item values
    // to ensure idempotency of getValue() / setValue().
217
    if ($this->hasNewEntity()) {
218 219 220 221 222
      $values['entity'] = $this->entity;
    }
    return $values;
  }

223 224 225
  /**
   * {@inheritdoc}
   */
226
  public function onChange($property_name, $notify = TRUE) {
227
    // Make sure that the target ID and the target property stay in sync.
228 229
    if ($property_name == 'entity') {
      $property = $this->get('entity');
230
      $target_id = $property->isTargetNew() ? NULL : $property->getTargetIdentifier();
231
      $this->writePropertyValue('target_id', $target_id);
232
    }
233
    elseif ($property_name == 'target_id') {
234
      $this->writePropertyValue('entity', $this->target_id);
235
    }
236
    parent::onChange($property_name, $notify);
237
  }
238

239 240 241 242 243
  /**
   * {@inheritdoc}
   */
  public function isEmpty() {
    // Avoid loading the entity by first checking the 'target_id'.
244
    if ($this->target_id !== NULL) {
245 246
      return FALSE;
    }
247
    if ($this->entity && $this->entity instanceof EntityInterface) {
248 249 250 251 252 253 254 255 256
      return FALSE;
    }
    return TRUE;
  }

  /**
   * {@inheritdoc}
   */
  public function preSave() {
257
    if ($this->hasNewEntity()) {
258 259 260 261 262 263
      // Save the entity if it has not already been saved by some other code.
      if ($this->entity->isNew()) {
        $this->entity->save();
      }
      // Make sure the parent knows we are updating this property so it can
      // react properly.
264 265
      $this->target_id = $this->entity->id();
    }
266 267 268
    if (!$this->isEmpty() && $this->target_id === NULL) {
      $this->target_id = $this->entity->id();
    }
269 270
  }

271 272 273 274
  /**
   * {@inheritdoc}
   */
  public static function generateSampleValue(FieldDefinitionInterface $field_definition) {
275 276 277 278
    // An associative array keyed by the reference type, target type, and
    // bundle.
    static $recursion_tracker = [];

279
    $manager = \Drupal::service('plugin.manager.entity_reference_selection');
280 281 282

    // Instead of calling $manager->getSelectionHandler($field_definition)
    // replicate the behavior to be able to override the sorting settings.
283
    $options = [
284 285 286
      'target_type' => $field_definition->getFieldStorageDefinition()->getSetting('target_type'),
      'handler' => $field_definition->getSetting('handler'),
      'entity' => NULL,
287
    ] + $field_definition->getSetting('handler_settings') ?: [];
288

289
    $entity_type = \Drupal::entityTypeManager()->getDefinition($options['target_type']);
290
    $options['sort'] = [
291 292 293 294 295 296 297 298
      'field' => $entity_type->getKey('id'),
      'direction' => 'DESC',
    ];
    $selection_handler = $manager->getInstance($options);

    // Select a random number of references between the last 50 referenceable
    // entities created.
    if ($referenceable = $selection_handler->getReferenceableEntities(NULL, 'CONTAINS', 50)) {
299 300 301 302
      $group = array_rand($referenceable);
      $values['target_id'] = array_rand($referenceable[$group]);
      return $values;
    }
303 304 305

    // Attempt to create a sample entity, avoiding recursion.
    $entity_storage = \Drupal::entityTypeManager()->getStorage($options['target_type']);
306
    if ($entity_storage instanceof ContentEntityStorageInterface) {
307
      $bundle = static::getRandomBundle($entity_type, $options);
308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324

      // Track the generated entity by reference type, target type, and bundle.
      $key = $field_definition->getTargetEntityTypeId() . ':' . $options['target_type'] . ':' . $bundle;

      // If entity generation was attempted but did not finish, do not continue.
      if (isset($recursion_tracker[$key])) {
        return [];
      }

      // Mark this as an attempt at generation.
      $recursion_tracker[$key] = TRUE;

      // Mark the sample entity as being a preview.
      $values['entity'] = $entity_storage->createWithSampleValues($bundle, ['in_preview' => TRUE]);

      // Remove the indicator once the entity is successfully generated.
      unset($recursion_tracker[$key]);
325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349
      return $values;
    }
  }

  /**
   * Gets a bundle for a given entity type and selection options.
   *
   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
   *   The entity type.
   * @param array $selection_settings
   *   An array of selection settings.
   *
   * @return string|null
   *   Either the bundle string, or NULL if there is no bundle.
   */
  protected static function getRandomBundle(EntityTypeInterface $entity_type, array $selection_settings) {
    if ($bundle_key = $entity_type->getKey('bundle')) {
      if (!empty($selection_settings['target_bundles'])) {
        $bundle_ids = $selection_settings['target_bundles'];
      }
      else {
        $bundle_ids = \Drupal::service('entity_type.bundle.info')->getBundleInfo($entity_type->id());
      }
      return array_rand($bundle_ids);
    }
350 351
  }

352 353 354 355
  /**
   * {@inheritdoc}
   */
  public function storageSettingsForm(array &$form, FormStateInterface $form_state, $has_data) {
356
    $element['target_type'] = [
357 358
      '#type' => 'select',
      '#title' => t('Type of item to reference'),
359
      '#options' => \Drupal::service('entity_type.repository')->getEntityTypeLabels(TRUE),
360 361 362 363
      '#default_value' => $this->getSetting('target_type'),
      '#required' => TRUE,
      '#disabled' => $has_data,
      '#size' => 1,
364
    ];
365 366 367 368 369 370 371 372 373 374 375 376

    return $element;
  }

  /**
   * {@inheritdoc}
   */
  public function fieldSettingsForm(array $form, FormStateInterface $form_state) {
    $field = $form_state->getFormObject()->getEntity();

    // Get all selection plugins for this entity type.
    $selection_plugins = \Drupal::service('plugin.manager.entity_reference_selection')->getSelectionGroups($this->getSetting('target_type'));
377
    $handlers_options = [];
378 379 380 381 382 383 384 385 386 387 388 389 390
    foreach (array_keys($selection_plugins) as $selection_group_id) {
      // We only display base plugins (e.g. 'default', 'views', ...) and not
      // entity type specific plugins (e.g. 'default:node', 'default:user',
      // ...).
      if (array_key_exists($selection_group_id, $selection_plugins[$selection_group_id])) {
        $handlers_options[$selection_group_id] = Html::escape($selection_plugins[$selection_group_id][$selection_group_id]['label']);
      }
      elseif (array_key_exists($selection_group_id . ':' . $this->getSetting('target_type'), $selection_plugins[$selection_group_id])) {
        $selection_group_plugin = $selection_group_id . ':' . $this->getSetting('target_type');
        $handlers_options[$selection_group_plugin] = Html::escape($selection_plugins[$selection_group_id][$selection_group_plugin]['base_plugin_label']);
      }
    }

391
    $form = [
392
      '#type' => 'container',
393 394
      '#process' => [[get_class($this), 'fieldSettingsAjaxProcess']],
      '#element_validate' => [[get_class($this), 'fieldSettingsFormValidate']],
395

396 397
    ];
    $form['handler'] = [
398 399 400 401
      '#type' => 'details',
      '#title' => t('Reference type'),
      '#open' => TRUE,
      '#tree' => TRUE,
402 403
      '#process' => [[get_class($this), 'formProcessMergeParent']],
    ];
404

405
    $form['handler']['handler'] = [
406 407 408 409 410 411
      '#type' => 'select',
      '#title' => t('Reference method'),
      '#options' => $handlers_options,
      '#default_value' => $field->getSetting('handler'),
      '#required' => TRUE,
      '#ajax' => TRUE,
412 413 414
      '#limit_validation_errors' => [],
    ];
    $form['handler']['handler_submit'] = [
415 416
      '#type' => 'submit',
      '#value' => t('Change handler'),
417 418 419 420 421 422 423 424
      '#limit_validation_errors' => [],
      '#attributes' => [
        'class' => ['js-hide'],
      ],
      '#submit' => [[get_class($this), 'settingsAjaxSubmit']],
    ];

    $form['handler']['handler_settings'] = [
425
      '#type' => 'container',
426 427
      '#attributes' => ['class' => ['entity_reference-settings']],
    ];
428 429

    $handler = \Drupal::service('plugin.manager.entity_reference_selection')->getSelectionHandler($field);
430
    $form['handler']['handler_settings'] += $handler->buildConfigurationForm([], $form_state);
431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448

    return $form;
  }

  /**
   * Form element validation handler; Invokes selection plugin's validation.
   *
   * @param array $form
   *   The form where the settings form is being included in.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state of the (entire) configuration form.
   */
  public static function fieldSettingsFormValidate(array $form, FormStateInterface $form_state) {
    $field = $form_state->getFormObject()->getEntity();
    $handler = \Drupal::service('plugin.manager.entity_reference_selection')->getSelectionHandler($field);
    $handler->validateConfigurationForm($form, $form_state);
  }

449 450 451 452 453 454 455 456 457 458
  /**
   * Determines whether the item holds an unsaved entity.
   *
   * This is notably used for "autocreate" widgets, and more generally to
   * support referencing freshly created entities (they will get saved
   * automatically as the hosting entity gets saved).
   *
   * @return bool
   *   TRUE if the item holds an unsaved entity.
   */
459
  public function hasNewEntity() {
460
    return !$this->isEmpty() && $this->target_id === NULL && $this->entity->isNew();
461
  }
462

463 464 465 466
  /**
   * {@inheritdoc}
   */
  public static function calculateDependencies(FieldDefinitionInterface $field_definition) {
467
    $dependencies = parent::calculateDependencies($field_definition);
468 469
    $entity_type_manager = \Drupal::entityTypeManager();
    $target_entity_type = $entity_type_manager->getDefinition($field_definition->getFieldStorageDefinition()->getSetting('target_type'));
470 471

    // Depend on default values entity types configurations.
472
    if ($default_value = $field_definition->getDefaultValueLiteral()) {
473
      $entity_repository = \Drupal::service('entity.repository');
474 475
      foreach ($default_value as $value) {
        if (is_array($value) && isset($value['target_uuid'])) {
476
          $entity = $entity_repository->loadEntityByUuid($target_entity_type->id(), $value['target_uuid']);
477 478 479
          // If the entity does not exist do not create the dependency.
          // @see \Drupal\Core\Field\EntityReferenceFieldItemList::processDefaultValue()
          if ($entity) {
480
            $dependencies[$target_entity_type->getConfigDependencyKey()][] = $entity->getConfigDependencyName();
481 482 483 484
          }
        }
      }
    }
485

486 487 488
    // Depend on target bundle configurations. Dependencies for 'target_bundles'
    // also covers the 'auto_create_bundle' setting, if any, because its value
    // is included in the 'target_bundles' list.
489 490 491
    $handler = $field_definition->getSetting('handler_settings');
    if (!empty($handler['target_bundles'])) {
      if ($bundle_entity_type_id = $target_entity_type->getBundleEntityType()) {
492
        if ($storage = $entity_type_manager->getStorage($bundle_entity_type_id)) {
493 494 495 496 497 498 499
          foreach ($storage->loadMultiple($handler['target_bundles']) as $bundle) {
            $dependencies[$bundle->getConfigDependencyKey()][] = $bundle->getConfigDependencyName();
          }
        }
      }
    }

500 501 502
    return $dependencies;
  }

503 504 505 506 507
  /**
   * {@inheritdoc}
   */
  public static function calculateStorageDependencies(FieldStorageDefinitionInterface $field_definition) {
    $dependencies = parent::calculateStorageDependencies($field_definition);
508
    $target_entity_type = \Drupal::entityTypeManager()->getDefinition($field_definition->getSetting('target_type'));
509 510 511 512
    $dependencies['module'][] = $target_entity_type->getProvider();
    return $dependencies;
  }

513 514 515 516
  /**
   * {@inheritdoc}
   */
  public static function onDependencyRemoval(FieldDefinitionInterface $field_definition, array $dependencies) {
517
    $changed = parent::onDependencyRemoval($field_definition, $dependencies);
518 519
    $entity_type_manager = \Drupal::entityTypeManager();
    $target_entity_type = $entity_type_manager->getDefinition($field_definition->getFieldStorageDefinition()->getSetting('target_type'));
520 521

    // Try to update the default value config dependency, if possible.
522
    if ($default_value = $field_definition->getDefaultValueLiteral()) {
523
      $entity_repository = \Drupal::service('entity.repository');
524 525
      foreach ($default_value as $key => $value) {
        if (is_array($value) && isset($value['target_uuid'])) {
526
          $entity = $entity_repository->loadEntityByUuid($target_entity_type->id(), $value['target_uuid']);
527 528
          // @see \Drupal\Core\Field\EntityReferenceFieldItemList::processDefaultValue()
          if ($entity && isset($dependencies[$entity->getConfigDependencyKey()][$entity->getConfigDependencyName()])) {
529
            unset($default_value[$key]);
530 531 532 533
            $changed = TRUE;
          }
        }
      }
534 535 536
      if ($changed) {
        $field_definition->setDefaultValue($default_value);
      }
537
    }
538 539 540 541 542 543 544

    // Update the 'target_bundles' handler setting if a bundle config dependency
    // has been removed.
    $bundles_changed = FALSE;
    $handler_settings = $field_definition->getSetting('handler_settings');
    if (!empty($handler_settings['target_bundles'])) {
      if ($bundle_entity_type_id = $target_entity_type->getBundleEntityType()) {
545
        if ($storage = $entity_type_manager->getStorage($bundle_entity_type_id)) {
546 547 548
          foreach ($storage->loadMultiple($handler_settings['target_bundles']) as $bundle) {
            if (isset($dependencies[$bundle->getConfigDependencyKey()][$bundle->getConfigDependencyName()])) {
              unset($handler_settings['target_bundles'][$bundle->id()]);
549 550 551 552 553

              // If this bundle is also used in the 'auto_create_bundle'
              // setting, disable the auto-creation feature completely.
              $auto_create_bundle = !empty($handler_settings['auto_create_bundle']) ? $handler_settings['auto_create_bundle'] : FALSE;
              if ($auto_create_bundle && $auto_create_bundle == $bundle->id()) {
554
                $handler_settings['auto_create'] = FALSE;
555 556 557
                $handler_settings['auto_create_bundle'] = NULL;
              }

558 559 560 561 562 563 564 565 566 567 568
              $bundles_changed = TRUE;
            }
          }
        }
      }
    }
    if ($bundles_changed) {
      $field_definition->setSetting('handler_settings', $handler_settings);
    }
    $changed |= $bundles_changed;

569 570 571
    return $changed;
  }

572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601
  /**
   * {@inheritdoc}
   */
  public function getPossibleValues(AccountInterface $account = NULL) {
    return $this->getSettableValues($account);
  }

  /**
   * {@inheritdoc}
   */
  public function getPossibleOptions(AccountInterface $account = NULL) {
    return $this->getSettableOptions($account);
  }

  /**
   * {@inheritdoc}
   */
  public function getSettableValues(AccountInterface $account = NULL) {
    // Flatten options first, because "settable options" may contain group
    // arrays.
    $flatten_options = OptGroup::flattenOptions($this->getSettableOptions($account));
    return array_keys($flatten_options);
  }

  /**
   * {@inheritdoc}
   */
  public function getSettableOptions(AccountInterface $account = NULL) {
    $field_definition = $this->getFieldDefinition();
    if (!$options = \Drupal::service('plugin.manager.entity_reference_selection')->getSelectionHandler($field_definition, $this->getEntity())->getReferenceableEntities()) {
602
      return [];
603 604 605 606
    }

    // Rebuild the array by changing the bundle key into the bundle label.
    $target_type = $field_definition->getSetting('target_type');
607
    $bundles = \Drupal::service('entity_type.bundle.info')->getBundleInfo($target_type);
608

609
    $return = [];
610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638
    foreach ($options as $bundle => $entity_ids) {
      // The label does not need sanitizing since it is used as an optgroup
      // which is only supported by select elements and auto-escaped.
      $bundle_label = (string) $bundles[$bundle]['label'];
      $return[$bundle_label] = $entity_ids;
    }

    return count($return) == 1 ? reset($return) : $return;
  }

  /**
   * Render API callback: Processes the field settings form and allows access to
   * the form state.
   *
   * @see static::fieldSettingsForm()
   */
  public static function fieldSettingsAjaxProcess($form, FormStateInterface $form_state) {
    static::fieldSettingsAjaxProcessElement($form, $form);
    return $form;
  }

  /**
   * Adds entity_reference specific properties to AJAX form elements from the
   * field settings form.
   *
   * @see static::fieldSettingsAjaxProcess()
   */
  public static function fieldSettingsAjaxProcessElement(&$element, $main_form) {
    if (!empty($element['#ajax'])) {
639 640
      $element['#ajax'] = [
        'callback' => [get_called_class(), 'settingsAjax'],
641 642
        'wrapper' => $main_form['#id'],
        'element' => $main_form['#array_parents'],
643
      ];
644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682
    }

    foreach (Element::children($element) as $key) {
      static::fieldSettingsAjaxProcessElement($element[$key], $main_form);
    }
  }

  /**
   * Render API callback: Moves entity_reference specific Form API elements
   * (i.e. 'handler_settings') up a level for easier processing by the
   * validation and submission handlers.
   *
   * @see _entity_reference_field_settings_process()
   */
  public static function formProcessMergeParent($element) {
    $parents = $element['#parents'];
    array_pop($parents);
    $element['#parents'] = $parents;
    return $element;
  }

  /**
   * Ajax callback for the handler settings form.
   *
   * @see static::fieldSettingsForm()
   */
  public static function settingsAjax($form, FormStateInterface $form_state) {
    return NestedArray::getValue($form, $form_state->getTriggeringElement()['#ajax']['element']);
  }

  /**
   * Submit handler for the non-JS case.
   *
   * @see static::fieldSettingsForm()
   */
  public static function settingsAjaxSubmit($form, FormStateInterface $form_state) {
    $form_state->setRebuild();
  }

683
  /**
684 685 686
   * {@inheritdoc}
   */
  public static function getPreconfiguredOptions() {
687
    $options = [];
688 689 690

    // Add all the commonly referenced entity types as distinct pre-configured
    // options.
691
    $entity_types = \Drupal::entityTypeManager()->getDefinitions();
692 693 694 695 696 697 698 699 700 701 702
    $common_references = array_filter($entity_types, function (EntityTypeInterface $entity_type) {
      return $entity_type->isCommonReferenceTarget();
    });

    /** @var \Drupal\Core\Entity\EntityTypeInterface $entity_type */
    foreach ($common_references as $entity_type) {
      $options[$entity_type->id()] = [
        'label' => $entity_type->getLabel(),
        'field_storage_config' => [
          'settings' => [
            'target_type' => $entity_type->id(),
703 704
          ],
        ],
705 706 707 708 709 710
      ];
    }

    return $options;
  }

711
}