5cbe365d145b6f9acc3ee6f5a763e728da52ec5c
[yaffs-website] / web / core / lib / Drupal / Core / Field / Plugin / Field / FieldType / EntityReferenceItem.php
1 <?php
2
3 namespace Drupal\Core\Field\Plugin\Field\FieldType;
4
5 use Drupal\Component\Utility\Html;
6 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 use Drupal\Core\Field\PreconfiguredFieldUiOptionsInterface;
16 use Drupal\Core\Form\FormStateInterface;
17 use Drupal\Core\Form\OptGroup;
18 use Drupal\Core\Render\Element;
19 use Drupal\Core\Session\AccountInterface;
20 use Drupal\Core\StringTranslation\TranslatableMarkup;
21 use Drupal\Core\TypedData\DataReferenceDefinition;
22 use Drupal\Core\TypedData\DataReferenceTargetDefinition;
23 use Drupal\Core\TypedData\OptionsProviderInterface;
24 use Drupal\Core\Validation\Plugin\Validation\Constraint\AllowedValuesConstraint;
25
26 /**
27  * Defines the 'entity_reference' entity field type.
28  *
29  * Supported settings (below the definition's 'settings' key) are:
30  * - target_type: The entity type to reference. Required.
31  *
32  * @FieldType(
33  *   id = "entity_reference",
34  *   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    * {@inheritdoc}
46    */
47   public static function defaultStorageSettings() {
48     return [
49       'target_type' => \Drupal::moduleHandler()->moduleExists('node') ? 'node' : 'user',
50     ] + parent::defaultStorageSettings();
51   }
52
53   /**
54    * {@inheritdoc}
55    */
56   public static function defaultFieldSettings() {
57     return [
58       'handler' => 'default',
59       'handler_settings' => [],
60     ] + 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::entityManager()->getDefinition($settings['target_type']);
69
70     $target_id_data_type = 'string';
71     if ($target_type_info->entityClassImplements(FieldableEntityInterface::class)) {
72       $id_definition = \Drupal::entityManager()->getBaseFieldDefinitions($settings['target_type'])[$target_type_info->getKey('id')];
73       if ($id_definition->getType() === 'integer') {
74         $target_id_data_type = 'integer';
75       }
76     }
77
78     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       // The entity object is computed out of the entity ID.
94       ->setComputed(TRUE)
95       ->setReadOnly(FALSE)
96       ->setTargetDefinition(EntityDataDefinition::create($settings['target_type']))
97       // We can add a constraint for the target entity type. The list of
98       // referenceable bundles is a field setting, so the corresponding
99       // constraint is added dynamically in ::getConstraints().
100       ->addConstraint('EntityType', $settings['target_type']);
101
102     return $properties;
103   }
104
105   /**
106    * {@inheritdoc}
107    */
108   public static function mainPropertyName() {
109     return 'target_id';
110   }
111
112   /**
113    * {@inheritdoc}
114    */
115   public static function schema(FieldStorageDefinitionInterface $field_definition) {
116     $target_type = $field_definition->getSetting('target_type');
117     $target_type_info = \Drupal::entityManager()->getDefinition($target_type);
118     $properties = static::propertyDefinitions($field_definition)['target_id'];
119     if ($target_type_info->entityClassImplements(FieldableEntityInterface::class) && $properties->getDataType() === 'integer') {
120       $columns = [
121         'target_id' => [
122           'description' => 'The ID of the target entity.',
123           'type' => 'int',
124           'unsigned' => TRUE,
125         ],
126       ];
127     }
128     else {
129       $columns = [
130         'target_id' => [
131           'description' => 'The ID of the target entity.',
132           'type' => 'varchar_ascii',
133           // If the target entities act as bundles for another entity type,
134           // their IDs should not exceed the maximum length for bundles.
135           'length' => $target_type_info->getBundleOf() ? EntityTypeInterface::BUNDLE_MAX_LENGTH : 255,
136         ],
137       ];
138     }
139
140     $schema = [
141       'columns' => $columns,
142       'indexes' => [
143         'target_id' => ['target_id'],
144       ],
145     ];
146
147     return $schema;
148   }
149
150   /**
151    * {@inheritdoc}
152    */
153   public function getConstraints() {
154     $constraints = parent::getConstraints();
155     // Remove the 'AllowedValuesConstraint' validation constraint because entity
156     // reference fields already use the 'ValidReference' constraint.
157     foreach ($constraints as $key => $constraint) {
158       if ($constraint instanceof AllowedValuesConstraint) {
159         unset($constraints[$key]);
160       }
161     }
162     return $constraints;
163   }
164
165   /**
166    * {@inheritdoc}
167    */
168   public function setValue($values, $notify = TRUE) {
169     if (isset($values) && !is_array($values)) {
170       // If either a scalar or an object was passed as the value for the item,
171       // assign it to the 'entity' property since that works for both cases.
172       $this->set('entity', $values, $notify);
173     }
174     else {
175       parent::setValue($values, FALSE);
176       // Support setting the field item with only one property, but make sure
177       // values stay in sync if only property is passed.
178       // NULL is a valid value, so we use array_key_exists().
179       if (is_array($values) && array_key_exists('target_id', $values) && !isset($values['entity'])) {
180         $this->onChange('target_id', FALSE);
181       }
182       elseif (is_array($values) && !array_key_exists('target_id', $values) && isset($values['entity'])) {
183         $this->onChange('entity', FALSE);
184       }
185       elseif (is_array($values) && array_key_exists('target_id', $values) && isset($values['entity'])) {
186         // If both properties are passed, verify the passed values match. The
187         // only exception we allow is when we have a new entity: in this case
188         // its actual id and target_id will be different, due to the new entity
189         // marker.
190         $entity_id = $this->get('entity')->getTargetIdentifier();
191         // If the entity has been saved and we're trying to set both the
192         // target_id and the entity values with a non-null target ID, then the
193         // value for target_id should match the ID of the entity value. The
194         // entity ID as returned by $entity->id() might be a string, but the
195         // provided target_id might be an integer - therefore we have to do a
196         // non-strict comparison.
197         if (!$this->entity->isNew() && $values['target_id'] !== NULL && ($entity_id != $values['target_id'])) {
198           throw new \InvalidArgumentException('The target id and entity passed to the entity reference item do not match.');
199         }
200       }
201       // Notify the parent if necessary.
202       if ($notify && $this->parent) {
203         $this->parent->onChange($this->getName());
204       }
205     }
206
207   }
208
209   /**
210    * {@inheritdoc}
211    */
212   public function getValue() {
213     $values = parent::getValue();
214
215     // If there is an unsaved entity, return it as part of the field item values
216     // to ensure idempotency of getValue() / setValue().
217     if ($this->hasNewEntity()) {
218       $values['entity'] = $this->entity;
219     }
220     return $values;
221   }
222
223   /**
224    * {@inheritdoc}
225    */
226   public function onChange($property_name, $notify = TRUE) {
227     // Make sure that the target ID and the target property stay in sync.
228     if ($property_name == 'entity') {
229       $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    * {@inheritdoc}
241    */
242   public function isEmpty() {
243     // Avoid loading the entity by first checking the 'target_id'.
244     if ($this->target_id !== NULL) {
245       return FALSE;
246     }
247     if ($this->entity && $this->entity instanceof EntityInterface) {
248       return FALSE;
249     }
250     return TRUE;
251   }
252
253   /**
254    * {@inheritdoc}
255    */
256   public function preSave() {
257     if ($this->hasNewEntity()) {
258       // Save the entity if it has not already been saved by some other code.
259       if ($this->entity->isNew()) {
260         $this->entity->save();
261       }
262       // Make sure the parent knows we are updating this property so it can
263       // react properly.
264       $this->target_id = $this->entity->id();
265     }
266     if (!$this->isEmpty() && $this->target_id === NULL) {
267       $this->target_id = $this->entity->id();
268     }
269   }
270
271   /**
272    * {@inheritdoc}
273    */
274   public static function generateSampleValue(FieldDefinitionInterface $field_definition) {
275     // An associative array keyed by the reference type, target type, and
276     // bundle.
277     static $recursion_tracker = [];
278
279     $manager = \Drupal::service('plugin.manager.entity_reference_selection');
280
281     // Instead of calling $manager->getSelectionHandler($field_definition)
282     // replicate the behavior to be able to override the sorting settings.
283     $options = [
284       'target_type' => $field_definition->getFieldStorageDefinition()->getSetting('target_type'),
285       'handler' => $field_definition->getSetting('handler'),
286       'handler_settings' => $field_definition->getSetting('handler_settings') ?: [],
287       'entity' => NULL,
288     ];
289
290     $entity_type = \Drupal::entityManager()->getDefinition($options['target_type']);
291     $options['handler_settings']['sort'] = [
292       'field' => $entity_type->getKey('id'),
293       'direction' => 'DESC',
294     ];
295     $selection_handler = $manager->getInstance($options);
296
297     // Select a random number of references between the last 50 referenceable
298     // entities created.
299     if ($referenceable = $selection_handler->getReferenceableEntities(NULL, 'CONTAINS', 50)) {
300       $group = array_rand($referenceable);
301       $values['target_id'] = array_rand($referenceable[$group]);
302       return $values;
303     }
304
305     // Attempt to create a sample entity, avoiding recursion.
306     $entity_storage = \Drupal::entityTypeManager()->getStorage($options['target_type']);
307     if ($entity_storage instanceof ContentEntityStorageInterface) {
308       $bundle = static::getRandomBundle($entity_type, $options['handler_settings']);
309
310       // Track the generated entity by reference type, target type, and bundle.
311       $key = $field_definition->getTargetEntityTypeId() . ':' . $options['target_type'] . ':' . $bundle;
312
313       // If entity generation was attempted but did not finish, do not continue.
314       if (isset($recursion_tracker[$key])) {
315         return [];
316       }
317
318       // Mark this as an attempt at generation.
319       $recursion_tracker[$key] = TRUE;
320
321       // Mark the sample entity as being a preview.
322       $values['entity'] = $entity_storage->createWithSampleValues($bundle, ['in_preview' => TRUE]);
323
324       // Remove the indicator once the entity is successfully generated.
325       unset($recursion_tracker[$key]);
326       return $values;
327     }
328   }
329
330   /**
331    * Gets a bundle for a given entity type and selection options.
332    *
333    * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
334    *   The entity type.
335    * @param array $selection_settings
336    *   An array of selection settings.
337    *
338    * @return string|null
339    *   Either the bundle string, or NULL if there is no bundle.
340    */
341   protected static function getRandomBundle(EntityTypeInterface $entity_type, array $selection_settings) {
342     if ($bundle_key = $entity_type->getKey('bundle')) {
343       if (!empty($selection_settings['target_bundles'])) {
344         $bundle_ids = $selection_settings['target_bundles'];
345       }
346       else {
347         $bundle_ids = \Drupal::service('entity_type.bundle.info')->getBundleInfo($entity_type->id());
348       }
349       return array_rand($bundle_ids);
350     }
351   }
352
353   /**
354    * {@inheritdoc}
355    */
356   public function storageSettingsForm(array &$form, FormStateInterface $form_state, $has_data) {
357     $element['target_type'] = [
358       '#type' => 'select',
359       '#title' => t('Type of item to reference'),
360       '#options' => \Drupal::entityManager()->getEntityTypeLabels(TRUE),
361       '#default_value' => $this->getSetting('target_type'),
362       '#required' => TRUE,
363       '#disabled' => $has_data,
364       '#size' => 1,
365     ];
366
367     return $element;
368   }
369
370   /**
371    * {@inheritdoc}
372    */
373   public function fieldSettingsForm(array $form, FormStateInterface $form_state) {
374     $field = $form_state->getFormObject()->getEntity();
375
376     // Get all selection plugins for this entity type.
377     $selection_plugins = \Drupal::service('plugin.manager.entity_reference_selection')->getSelectionGroups($this->getSetting('target_type'));
378     $handlers_options = [];
379     foreach (array_keys($selection_plugins) as $selection_group_id) {
380       // We only display base plugins (e.g. 'default', 'views', ...) and not
381       // entity type specific plugins (e.g. 'default:node', 'default:user',
382       // ...).
383       if (array_key_exists($selection_group_id, $selection_plugins[$selection_group_id])) {
384         $handlers_options[$selection_group_id] = Html::escape($selection_plugins[$selection_group_id][$selection_group_id]['label']);
385       }
386       elseif (array_key_exists($selection_group_id . ':' . $this->getSetting('target_type'), $selection_plugins[$selection_group_id])) {
387         $selection_group_plugin = $selection_group_id . ':' . $this->getSetting('target_type');
388         $handlers_options[$selection_group_plugin] = Html::escape($selection_plugins[$selection_group_id][$selection_group_plugin]['base_plugin_label']);
389       }
390     }
391
392     $form = [
393       '#type' => 'container',
394       '#process' => [[get_class($this), 'fieldSettingsAjaxProcess']],
395       '#element_validate' => [[get_class($this), 'fieldSettingsFormValidate']],
396
397     ];
398     $form['handler'] = [
399       '#type' => 'details',
400       '#title' => t('Reference type'),
401       '#open' => TRUE,
402       '#tree' => TRUE,
403       '#process' => [[get_class($this), 'formProcessMergeParent']],
404     ];
405
406     $form['handler']['handler'] = [
407       '#type' => 'select',
408       '#title' => t('Reference method'),
409       '#options' => $handlers_options,
410       '#default_value' => $field->getSetting('handler'),
411       '#required' => TRUE,
412       '#ajax' => TRUE,
413       '#limit_validation_errors' => [],
414     ];
415     $form['handler']['handler_submit'] = [
416       '#type' => 'submit',
417       '#value' => t('Change handler'),
418       '#limit_validation_errors' => [],
419       '#attributes' => [
420         'class' => ['js-hide'],
421       ],
422       '#submit' => [[get_class($this), 'settingsAjaxSubmit']],
423     ];
424
425     $form['handler']['handler_settings'] = [
426       '#type' => 'container',
427       '#attributes' => ['class' => ['entity_reference-settings']],
428     ];
429
430     $handler = \Drupal::service('plugin.manager.entity_reference_selection')->getSelectionHandler($field);
431     $form['handler']['handler_settings'] += $handler->buildConfigurationForm([], $form_state);
432
433     return $form;
434   }
435
436   /**
437    * Form element validation handler; Invokes selection plugin's validation.
438    *
439    * @param array $form
440    *   The form where the settings form is being included in.
441    * @param \Drupal\Core\Form\FormStateInterface $form_state
442    *   The form state of the (entire) configuration form.
443    */
444   public static function fieldSettingsFormValidate(array $form, FormStateInterface $form_state) {
445     $field = $form_state->getFormObject()->getEntity();
446     $handler = \Drupal::service('plugin.manager.entity_reference_selection')->getSelectionHandler($field);
447     $handler->validateConfigurationForm($form, $form_state);
448   }
449
450   /**
451    * Determines whether the item holds an unsaved entity.
452    *
453    * This is notably used for "autocreate" widgets, and more generally to
454    * support referencing freshly created entities (they will get saved
455    * automatically as the hosting entity gets saved).
456    *
457    * @return bool
458    *   TRUE if the item holds an unsaved entity.
459    */
460   public function hasNewEntity() {
461     return !$this->isEmpty() && $this->target_id === NULL && $this->entity->isNew();
462   }
463
464   /**
465    * {@inheritdoc}
466    */
467   public static function calculateDependencies(FieldDefinitionInterface $field_definition) {
468     $dependencies = parent::calculateDependencies($field_definition);
469     $manager = \Drupal::entityManager();
470     $target_entity_type = $manager->getDefinition($field_definition->getFieldStorageDefinition()->getSetting('target_type'));
471
472     // Depend on default values entity types configurations.
473     if ($default_value = $field_definition->getDefaultValueLiteral()) {
474       foreach ($default_value as $value) {
475         if (is_array($value) && isset($value['target_uuid'])) {
476           $entity = \Drupal::entityManager()->loadEntityByUuid($target_entity_type->id(), $value['target_uuid']);
477           // If the entity does not exist do not create the dependency.
478           // @see \Drupal\Core\Field\EntityReferenceFieldItemList::processDefaultValue()
479           if ($entity) {
480             $dependencies[$target_entity_type->getConfigDependencyKey()][] = $entity->getConfigDependencyName();
481           }
482         }
483       }
484     }
485
486     // Depend on target bundle configurations. Dependencies for 'target_bundles'
487     // also covers the 'auto_create_bundle' setting, if any, because its value
488     // is included in the 'target_bundles' list.
489     $handler = $field_definition->getSetting('handler_settings');
490     if (!empty($handler['target_bundles'])) {
491       if ($bundle_entity_type_id = $target_entity_type->getBundleEntityType()) {
492         if ($storage = $manager->getStorage($bundle_entity_type_id)) {
493           foreach ($storage->loadMultiple($handler['target_bundles']) as $bundle) {
494             $dependencies[$bundle->getConfigDependencyKey()][] = $bundle->getConfigDependencyName();
495           }
496         }
497       }
498     }
499
500     return $dependencies;
501   }
502
503   /**
504    * {@inheritdoc}
505    */
506   public static function calculateStorageDependencies(FieldStorageDefinitionInterface $field_definition) {
507     $dependencies = parent::calculateStorageDependencies($field_definition);
508     $target_entity_type = \Drupal::entityManager()->getDefinition($field_definition->getSetting('target_type'));
509     $dependencies['module'][] = $target_entity_type->getProvider();
510     return $dependencies;
511   }
512
513   /**
514    * {@inheritdoc}
515    */
516   public static function onDependencyRemoval(FieldDefinitionInterface $field_definition, array $dependencies) {
517     $changed = parent::onDependencyRemoval($field_definition, $dependencies);
518     $entity_manager = \Drupal::entityManager();
519     $target_entity_type = $entity_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       foreach ($default_value as $key => $value) {
524         if (is_array($value) && isset($value['target_uuid'])) {
525           $entity = $entity_manager->loadEntityByUuid($target_entity_type->id(), $value['target_uuid']);
526           // @see \Drupal\Core\Field\EntityReferenceFieldItemList::processDefaultValue()
527           if ($entity && isset($dependencies[$entity->getConfigDependencyKey()][$entity->getConfigDependencyName()])) {
528             unset($default_value[$key]);
529             $changed = TRUE;
530           }
531         }
532       }
533       if ($changed) {
534         $field_definition->setDefaultValue($default_value);
535       }
536     }
537
538     // Update the 'target_bundles' handler setting if a bundle config dependency
539     // has been removed.
540     $bundles_changed = FALSE;
541     $handler_settings = $field_definition->getSetting('handler_settings');
542     if (!empty($handler_settings['target_bundles'])) {
543       if ($bundle_entity_type_id = $target_entity_type->getBundleEntityType()) {
544         if ($storage = $entity_manager->getStorage($bundle_entity_type_id)) {
545           foreach ($storage->loadMultiple($handler_settings['target_bundles']) as $bundle) {
546             if (isset($dependencies[$bundle->getConfigDependencyKey()][$bundle->getConfigDependencyName()])) {
547               unset($handler_settings['target_bundles'][$bundle->id()]);
548
549               // If this bundle is also used in the 'auto_create_bundle'
550               // setting, disable the auto-creation feature completely.
551               $auto_create_bundle = !empty($handler_settings['auto_create_bundle']) ? $handler_settings['auto_create_bundle'] : FALSE;
552               if ($auto_create_bundle && $auto_create_bundle == $bundle->id()) {
553                 $handler_settings['auto_create'] = NULL;
554                 $handler_settings['auto_create_bundle'] = NULL;
555               }
556
557               $bundles_changed = TRUE;
558             }
559           }
560         }
561       }
562     }
563     if ($bundles_changed) {
564       $field_definition->setSetting('handler_settings', $handler_settings);
565     }
566     $changed |= $bundles_changed;
567
568     return $changed;
569   }
570
571   /**
572    * {@inheritdoc}
573    */
574   public function getPossibleValues(AccountInterface $account = NULL) {
575     return $this->getSettableValues($account);
576   }
577
578   /**
579    * {@inheritdoc}
580    */
581   public function getPossibleOptions(AccountInterface $account = NULL) {
582     return $this->getSettableOptions($account);
583   }
584
585   /**
586    * {@inheritdoc}
587    */
588   public function getSettableValues(AccountInterface $account = NULL) {
589     // Flatten options first, because "settable options" may contain group
590     // arrays.
591     $flatten_options = OptGroup::flattenOptions($this->getSettableOptions($account));
592     return array_keys($flatten_options);
593   }
594
595   /**
596    * {@inheritdoc}
597    */
598   public function getSettableOptions(AccountInterface $account = NULL) {
599     $field_definition = $this->getFieldDefinition();
600     if (!$options = \Drupal::service('plugin.manager.entity_reference_selection')->getSelectionHandler($field_definition, $this->getEntity())->getReferenceableEntities()) {
601       return [];
602     }
603
604     // Rebuild the array by changing the bundle key into the bundle label.
605     $target_type = $field_definition->getSetting('target_type');
606     $bundles = \Drupal::entityManager()->getBundleInfo($target_type);
607
608     $return = [];
609     foreach ($options as $bundle => $entity_ids) {
610       // The label does not need sanitizing since it is used as an optgroup
611       // which is only supported by select elements and auto-escaped.
612       $bundle_label = (string) $bundles[$bundle]['label'];
613       $return[$bundle_label] = $entity_ids;
614     }
615
616     return count($return) == 1 ? reset($return) : $return;
617   }
618
619   /**
620    * Render API callback: Processes the field settings form and allows access to
621    * the form state.
622    *
623    * @see static::fieldSettingsForm()
624    */
625   public static function fieldSettingsAjaxProcess($form, FormStateInterface $form_state) {
626     static::fieldSettingsAjaxProcessElement($form, $form);
627     return $form;
628   }
629
630   /**
631    * Adds entity_reference specific properties to AJAX form elements from the
632    * field settings form.
633    *
634    * @see static::fieldSettingsAjaxProcess()
635    */
636   public static function fieldSettingsAjaxProcessElement(&$element, $main_form) {
637     if (!empty($element['#ajax'])) {
638       $element['#ajax'] = [
639         'callback' => [get_called_class(), 'settingsAjax'],
640         'wrapper' => $main_form['#id'],
641         'element' => $main_form['#array_parents'],
642       ];
643     }
644
645     foreach (Element::children($element) as $key) {
646       static::fieldSettingsAjaxProcessElement($element[$key], $main_form);
647     }
648   }
649
650   /**
651    * Render API callback: Moves entity_reference specific Form API elements
652    * (i.e. 'handler_settings') up a level for easier processing by the
653    * validation and submission handlers.
654    *
655    * @see _entity_reference_field_settings_process()
656    */
657   public static function formProcessMergeParent($element) {
658     $parents = $element['#parents'];
659     array_pop($parents);
660     $element['#parents'] = $parents;
661     return $element;
662   }
663
664   /**
665    * Ajax callback for the handler settings form.
666    *
667    * @see static::fieldSettingsForm()
668    */
669   public static function settingsAjax($form, FormStateInterface $form_state) {
670     return NestedArray::getValue($form, $form_state->getTriggeringElement()['#ajax']['element']);
671   }
672
673   /**
674    * Submit handler for the non-JS case.
675    *
676    * @see static::fieldSettingsForm()
677    */
678   public static function settingsAjaxSubmit($form, FormStateInterface $form_state) {
679     $form_state->setRebuild();
680   }
681
682   /**
683    * {@inheritdoc}
684    */
685   public static function getPreconfiguredOptions() {
686     $options = [];
687
688     // Add all the commonly referenced entity types as distinct pre-configured
689     // options.
690     $entity_types = \Drupal::entityManager()->getDefinitions();
691     $common_references = array_filter($entity_types, function (EntityTypeInterface $entity_type) {
692       return $entity_type->isCommonReferenceTarget();
693     });
694
695     /** @var \Drupal\Core\Entity\EntityTypeInterface $entity_type */
696     foreach ($common_references as $entity_type) {
697       $options[$entity_type->id()] = [
698         'label' => $entity_type->getLabel(),
699         'field_storage_config' => [
700           'settings' => [
701             'target_type' => $entity_type->id(),
702           ],
703         ],
704       ];
705     }
706
707     return $options;
708   }
709
710 }