3 namespace Drupal\entity_browser\Plugin\Field\FieldWidget;
5 use Drupal\Core\Entity\EntityInterface;
6 use Drupal\entity_browser\Element\EntityBrowserElement;
7 use Symfony\Component\Validator\ConstraintViolationInterface;
8 use Drupal\Component\Utility\Html;
9 use Drupal\Component\Utility\NestedArray;
10 use Drupal\Core\Entity\ContentEntityInterface;
11 use Drupal\Core\Entity\EntityTypeManagerInterface;
12 use Drupal\Core\Field\FieldDefinitionInterface;
13 use Drupal\Core\Field\FieldItemListInterface;
14 use Drupal\Core\Field\WidgetBase;
15 use Drupal\Core\Form\FormStateInterface;
16 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
18 use Drupal\Core\Validation\Plugin\Validation\Constraint\NotNullConstraint;
19 use Drupal\entity_browser\FieldWidgetDisplayManager;
20 use Symfony\Component\DependencyInjection\ContainerInterface;
21 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
22 use Symfony\Component\Validator\ConstraintViolation;
23 use Symfony\Component\Validator\ConstraintViolationListInterface;
24 use Drupal\Core\Extension\ModuleHandlerInterface;
25 use Drupal\Core\Session\AccountInterface;
28 * Plugin implementation of the 'entity_reference' widget for entity browser.
31 * id = "entity_browser_entity_reference",
32 * label = @Translation("Entity browser"),
33 * description = @Translation("Uses entity browser to select entities."),
34 * multiple_values = TRUE,
40 class EntityReferenceBrowserWidget extends WidgetBase implements ContainerFactoryPluginInterface {
43 * Entity type manager service.
45 * @var \Drupal\Core\Entity\EntityTypeManagerInterface
47 protected $entityTypeManager;
50 * Field widget display plugin manager.
52 * @var \Drupal\entity_browser\FieldWidgetDisplayManager
54 protected $fieldDisplayManager;
57 * The depth of the delete button.
59 * This property exists so it can be changed if subclasses.
63 protected static $deleteDepth = 4;
66 * The module handler interface.
68 * @var \Drupal\Core\Extension\ModuleHandlerInterface
70 protected $moduleHandler;
75 * @var \Drupal\Core\Session\AccountInterface
77 protected $currentUser;
80 * Constructs widget plugin.
82 * @param string $plugin_id
83 * The plugin_id for the plugin instance.
84 * @param mixed $plugin_definition
85 * The plugin implementation definition.
86 * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
87 * The definition of the field to which the widget is associated.
88 * @param array $settings
89 * The widget settings.
90 * @param array $third_party_settings
91 * Any third party settings.
92 * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
93 * Entity type manager service.
94 * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
96 * @param \Drupal\entity_browser\FieldWidgetDisplayManager $field_display_manager
97 * Field widget display plugin manager.
98 * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
99 * The module handler service.
100 * @param \Drupal\Core\Session\AccountInterface $current_user
103 public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, EntityTypeManagerInterface $entity_type_manager, EventDispatcherInterface $event_dispatcher, FieldWidgetDisplayManager $field_display_manager, ModuleHandlerInterface $module_handler, AccountInterface $current_user) {
104 parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings);
105 $this->entityTypeManager = $entity_type_manager;
106 $this->fieldDisplayManager = $field_display_manager;
107 $this->moduleHandler = $module_handler;
108 $this->currentUser = $current_user;
114 public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
118 $configuration['field_definition'],
119 $configuration['settings'],
120 $configuration['third_party_settings'],
121 $container->get('entity_type.manager'),
122 $container->get('event_dispatcher'),
123 $container->get('plugin.manager.entity_browser.field_widget_display'),
124 $container->get('module_handler'),
125 $container->get('current_user')
132 public static function defaultSettings() {
134 'entity_browser' => NULL,
136 'field_widget_display' => 'label',
137 'field_widget_edit' => TRUE,
138 'field_widget_remove' => TRUE,
139 'field_widget_replace' => FALSE,
140 'field_widget_display_settings' => [],
141 'selection_mode' => EntityBrowserElement::SELECTION_MODE_APPEND,
142 ] + parent::defaultSettings();
148 public function settingsForm(array $form, FormStateInterface $form_state) {
149 $element = parent::settingsForm($form, $form_state);
152 /** @var \Drupal\entity_browser\EntityBrowserInterface $browser */
153 foreach ($this->entityTypeManager->getStorage('entity_browser')->loadMultiple() as $browser) {
154 $browsers[$browser->id()] = $browser->label();
157 $element['entity_browser'] = [
158 '#title' => $this->t('Entity browser'),
160 '#default_value' => $this->getSetting('entity_browser'),
161 '#options' => $browsers,
164 $target_type = $this->fieldDefinition->getFieldStorageDefinition()->getSetting('target_type');
165 $entity_type = $this->entityTypeManager->getStorage($target_type)->getEntityType();
168 foreach ($this->fieldDisplayManager->getDefinitions() as $id => $definition) {
169 if ($this->fieldDisplayManager->createInstance($id)->isApplicable($entity_type)) {
170 $displays[$id] = $definition['label'];
174 $id = Html::getUniqueId('field-' . $this->fieldDefinition->getName() . '-display-settings-wrapper');
175 $element['field_widget_display'] = [
176 '#title' => $this->t('Entity display plugin'),
178 '#default_value' => $this->getSetting('field_widget_display'),
179 '#options' => $displays,
181 'callback' => [$this, 'updateSettingsAjax'],
186 $edit_button_access = TRUE;
187 if ($entity_type->id() == 'file') {
188 // For entities of type "file", it only makes sense to have the edit
189 // button if the module "file_entity" is present.
190 $edit_button_access = $this->moduleHandler->moduleExists('file_entity');
192 $element['field_widget_edit'] = [
193 '#title' => $this->t('Display Edit button'),
194 '#type' => 'checkbox',
195 '#default_value' => $this->getSetting('field_widget_edit'),
196 '#access' => $edit_button_access,
199 $element['field_widget_remove'] = [
200 '#title' => $this->t('Display Remove button'),
201 '#type' => 'checkbox',
202 '#default_value' => $this->getSetting('field_widget_remove'),
205 $element['field_widget_replace'] = [
206 '#title' => $this->t('Display Replace button'),
207 '#description' => $this->t('This button will only be displayed if there is a single entity in the current selection.'),
208 '#type' => 'checkbox',
209 '#default_value' => $this->getSetting('field_widget_replace'),
213 '#title' => $this->t('Show widget details as open by default'),
214 '#description' => $this->t('If marked, the fieldset container that wraps the browser on the entity form will be loaded initially expanded.'),
215 '#type' => 'checkbox',
216 '#default_value' => $this->getSetting('open'),
219 $element['selection_mode'] = [
220 '#title' => $this->t('Selection mode'),
221 '#description' => $this->t('Determines how selection in entity browser will be handled. Will selection be appended/prepended or it will be replaced in case of editing.'),
223 '#options' => EntityBrowserElement::getSelectionModeOptions(),
224 '#default_value' => $this->getSetting('selection_mode'),
227 $element['field_widget_display_settings'] = [
228 '#type' => 'fieldset',
229 '#title' => $this->t('Entity display plugin configuration'),
231 '#prefix' => '<div id="' . $id . '">',
232 '#suffix' => '</div>',
235 if ($this->getSetting('field_widget_display')) {
236 $element['field_widget_display_settings'] += $this->fieldDisplayManager
238 $form_state->getValue(
239 ['fields', $this->fieldDefinition->getName(), 'settings_edit_form', 'settings', 'field_widget_display'],
240 $this->getSetting('field_widget_display')
242 $form_state->getValue(
243 ['fields', $this->fieldDefinition->getName(), 'settings_edit_form', 'settings', 'field_widget_display_settings'],
244 $this->getSetting('field_widget_display_settings')
245 ) + ['entity_type' => $this->fieldDefinition->getFieldStorageDefinition()->getSetting('target_type')]
247 ->settingsForm($form, $form_state);
254 * Ajax callback that updates field widget display settings fieldset.
256 public function updateSettingsAjax(array $form, FormStateInterface $form_state) {
257 return $form['fields'][$this->fieldDefinition->getName()]['plugin']['settings_edit_form']['settings']['field_widget_display_settings'];
263 public function settingsSummary() {
264 $summary = $this->summaryBase();
265 $field_widget_display = $this->getSetting('field_widget_display');
267 if (!empty($field_widget_display)) {
268 $plugin = $this->fieldDisplayManager->getDefinition($field_widget_display);
269 $summary[] = $this->t('Entity display: @name', ['@name' => $plugin['label']]);
277 public function flagErrors(FieldItemListInterface $items, ConstraintViolationListInterface $violations, array $form, FormStateInterface $form_state) {
278 if ($violations->count() > 0) {
279 /** @var \Symfony\Component\Validator\ConstraintViolation $violation */
280 foreach ($violations as $offset => $violation) {
281 // The value of the required field is checked through the "not null"
282 // constraint, whose message is not very useful. We override it here for
284 if ($violation->getConstraint() instanceof NotNullConstraint) {
285 $violations->set($offset, new ConstraintViolation(
286 $this->t('@name field is required.', ['@name' => $items->getFieldDefinition()->getLabel()]),
289 $violation->getRoot(),
290 $violation->getPropertyPath(),
291 $violation->getInvalidValue(),
292 $violation->getPlural(),
293 $violation->getCode(),
294 $violation->getConstraint(),
295 $violation->getCause()
301 parent::flagErrors($items, $violations, $form, $form_state);
305 * Returns a key used to store the previously loaded entity.
307 * @param \Drupal\Core\Field\FieldItemListInterface $items
311 * A key for form state storage.
313 protected function getFormStateKey(FieldItemListInterface $items) {
314 return $items->getEntity()->uuid() . ':' . $items->getFieldDefinition()->getName();
320 public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
321 $entity_type = $this->fieldDefinition->getFieldStorageDefinition()->getSetting('target_type');
322 $entities = $this->formElementEntities($items, $element, $form_state);
324 // Get correct ordered list of entity IDs.
326 function (EntityInterface $entity) {
327 return $entity->id();
332 // We store current entity IDs as we might need them in future requests. If
333 // some other part of the form triggers an AJAX request with
334 // #limit_validation_errors we won't have access to the value of the
335 // target_id element and won't be able to build the form as a result of
336 // that. This will cause missing submit (Remove, Edit, ...) elements, which
337 // might result in unpredictable results.
338 $form_state->set(['entity_browser_widget', $this->getFormStateKey($items)], $ids);
340 $hidden_id = Html::getUniqueId('edit-' . $this->fieldDefinition->getName() . '-target-id');
341 $details_id = Html::getUniqueId('edit-' . $this->fieldDefinition->getName());
344 '#id' => $details_id,
345 '#type' => 'details',
346 '#open' => !empty($entities) || $this->getSetting('open'),
347 '#required' => $this->fieldDefinition->isRequired(),
348 // We are not using Entity browser's hidden element since we maintain
349 // selected entities in it during entire process.
353 // We need to repeat ID here as it is otherwise skipped when rendering.
354 '#attributes' => ['id' => $hidden_id],
355 '#default_value' => implode(' ', array_map(
356 function (EntityInterface $item) {
357 return $item->getEntityTypeId() . ':' . $item->id();
361 // #ajax is officially not supported for hidden elements but if we
362 // specify event manually it works.
364 'callback' => [get_class($this), 'updateWidgetCallback'],
365 'wrapper' => $details_id,
366 'event' => 'entity_browser_value_updated',
371 // Get configuration required to check entity browser availability.
372 $cardinality = $this->fieldDefinition->getFieldStorageDefinition()->getCardinality();
373 $selection_mode = $this->getSetting('selection_mode');
375 // Enable entity browser if requirements for that are fulfilled.
376 if (EntityBrowserElement::isEntityBrowserAvailable($selection_mode, $cardinality, count($ids))) {
377 $persistentData = $this->getPersistentData();
379 $element['entity_browser'] = [
380 '#type' => 'entity_browser',
381 '#entity_browser' => $this->getSetting('entity_browser'),
382 '#cardinality' => $cardinality,
383 '#selection_mode' => $selection_mode,
384 '#default_value' => $entities,
385 '#entity_browser_validators' => $persistentData['validators'],
386 '#widget_context' => $persistentData['widget_context'],
387 '#custom_hidden_id' => $hidden_id,
389 ['\Drupal\entity_browser\Element\EntityBrowserElement', 'processEntityBrowser'],
390 [get_called_class(), 'processEntityBrowser'],
395 $element['#attached']['library'][] = 'entity_browser/entity_reference';
397 $field_parents = $element['#field_parents'];
399 $element['current'] = $this->displayCurrentSelection($details_id, $field_parents, $entities);
405 * Render API callback: Processes the entity browser element.
407 public static function processEntityBrowser(&$element, FormStateInterface $form_state, &$complete_form) {
408 $uuid = key($element['#attached']['drupalSettings']['entity_browser']);
409 $element['#attached']['drupalSettings']['entity_browser'][$uuid]['selector'] = '#' . $element['#custom_hidden_id'];
416 public function massageFormValues(array $values, array $form, FormStateInterface $form_state) {
417 $entities = empty($values['target_id']) ? [] : explode(' ', trim($values['target_id']));
419 foreach ($entities as $entity) {
420 $return[]['target_id'] = explode(':', $entity)[1];
427 * AJAX form callback.
429 public static function updateWidgetCallback(array &$form, FormStateInterface $form_state) {
430 $trigger = $form_state->getTriggeringElement();
431 $reopen_browser = FALSE;
432 // AJAX requests can be triggered by hidden "target_id" element when
433 // entities are added or by one of the "Remove" buttons. Depending on that
434 // we need to figure out where root of the widget is in the form structure
435 // and use this information to return correct part of the form.
437 if (!empty($trigger['#ajax']['event']) && $trigger['#ajax']['event'] == 'entity_browser_value_updated') {
438 $parents = array_slice($trigger['#array_parents'], 0, -1);
440 elseif ($trigger['#type'] == 'submit' && strpos($trigger['#name'], '_remove_')) {
441 $parents = array_slice($trigger['#array_parents'], 0, -static::$deleteDepth);
443 elseif ($trigger['#type'] == 'submit' && strpos($trigger['#name'], '_replace_')) {
444 $parents = array_slice($trigger['#array_parents'], 0, -static::$deleteDepth);
445 // We need to re-open the browser. Instead of just passing "TRUE", send
446 // to the JS the unique part of the button's name that needs to be clicked
447 // on to relaunch the browser.
448 $reopen_browser = implode("-", array_slice($trigger['#parents'], 0, -static::$deleteDepth));
451 $parents = NestedArray::getValue($form, $parents);
452 $parents['#attached']['drupalSettings']['entity_browser_reopen_browser'] = $reopen_browser;
459 public function errorElement(array $element, ConstraintViolationInterface $violation, array $form, FormStateInterface $form_state) {
460 if (($trigger = $form_state->getTriggeringElement())) {
461 // Can be triggered by "Remove" button.
462 if (end($trigger['#parents']) === 'remove_button') {
466 return parent::errorElement($element, $violation, $form, $form_state);
470 * Submit callback for remove buttons.
472 public static function removeItemSubmit(&$form, FormStateInterface $form_state) {
473 $triggering_element = $form_state->getTriggeringElement();
474 if (!empty($triggering_element['#attributes']['data-entity-id']) && isset($triggering_element['#attributes']['data-row-id'])) {
475 $id = $triggering_element['#attributes']['data-entity-id'];
476 $row_id = $triggering_element['#attributes']['data-row-id'];
477 $parents = array_slice($triggering_element['#parents'], 0, -static::$deleteDepth);
478 $array_parents = array_slice($triggering_element['#array_parents'], 0, -static::$deleteDepth);
480 // Find and remove correct entity.
481 $values = explode(' ', $form_state->getValue(array_merge($parents, ['target_id'])));
482 foreach ($values as $index => $item) {
483 if ($item == $id && $index == $row_id) {
484 array_splice($values, $index, 1);
489 $target_id_value = implode(' ', $values);
491 // Set new value for this widget.
492 $target_id_element = &NestedArray::getValue($form, array_merge($array_parents, ['target_id']));
493 $form_state->setValueForElement($target_id_element, $target_id_value);
494 NestedArray::setValue($form_state->getUserInput(), $target_id_element['#parents'], $target_id_value);
497 $form_state->setRebuild();
502 * Builds the render array for displaying the current results.
504 * @param string $details_id
505 * The ID for the details element.
506 * @param string[] $field_parents
508 * @param \Drupal\Core\Entity\ContentEntityInterface[] $entities
509 * Array of referenced entities.
512 * The render array for the current selection.
514 protected function displayCurrentSelection($details_id, $field_parents, $entities) {
516 $field_widget_display = $this->fieldDisplayManager->createInstance(
517 $this->getSetting('field_widget_display'),
518 $this->getSetting('field_widget_display_settings') + ['entity_type' => $this->fieldDefinition->getFieldStorageDefinition()->getSetting('target_type')]
521 $classes = ['entities-list'];
522 if ($this->fieldDefinition->getFieldStorageDefinition()->getCardinality() != 1) {
523 $classes[] = 'sortable';
526 // The "Replace" button will only be shown if this setting is enabled in the
527 // widget, and there is only one entity in the current selection.
528 $replace_button_access = $this->getSetting('field_widget_replace') && (count($entities) === 1);
531 '#theme_wrappers' => ['container'],
532 '#attributes' => ['class' => $classes],
533 'items' => array_map(
534 function (ContentEntityInterface $entity, $row_id) use ($field_widget_display, $details_id, $field_parents, $replace_button_access) {
535 $display = $field_widget_display->view($entity);
536 $edit_button_access = $this->getSetting('field_widget_edit') && $entity->access('update', $this->currentUser);
537 if ($entity->getEntityTypeId() == 'file') {
538 // On file entities, the "edit" button shouldn't be visible unless
539 // the module "file_entity" is present, which will allow them to be
540 // edited on their own form.
541 $edit_button_access &= $this->moduleHandler->moduleExists('file_entity');
543 if (is_string($display)) {
544 $display = ['#markup' => $display];
547 '#theme_wrappers' => ['container'],
549 'class' => ['item-container', Html::getClass($field_widget_display->getPluginId())],
550 'data-entity-id' => $entity->getEntityTypeId() . ':' . $entity->id(),
551 'data-row-id' => $row_id,
553 'display' => $display,
556 '#value' => $this->t('Remove'),
558 'callback' => [get_class($this), 'updateWidgetCallback'],
559 'wrapper' => $details_id,
561 '#submit' => [[get_class($this), 'removeItemSubmit']],
562 '#name' => $this->fieldDefinition->getName() . '_remove_' . $entity->id() . '_' . $row_id . '_' . md5(json_encode($field_parents)),
563 '#limit_validation_errors' => [array_merge($field_parents, [$this->fieldDefinition->getName()])],
565 'data-entity-id' => $entity->getEntityTypeId() . ':' . $entity->id(),
566 'data-row-id' => $row_id,
567 'class' => ['remove-button'],
569 '#access' => (bool) $this->getSetting('field_widget_remove'),
571 'replace_button' => [
573 '#value' => $this->t('Replace'),
575 'callback' => [get_class($this), 'updateWidgetCallback'],
576 'wrapper' => $details_id,
578 '#submit' => [[get_class($this), 'removeItemSubmit']],
579 '#name' => $this->fieldDefinition->getName() . '_replace_' . $entity->id() . '_' . $row_id . '_' . md5(json_encode($field_parents)),
580 '#limit_validation_errors' => [array_merge($field_parents, [$this->fieldDefinition->getName()])],
582 'data-entity-id' => $entity->getEntityTypeId() . ':' . $entity->id(),
583 'data-row-id' => $row_id,
584 'class' => ['replace-button'],
586 '#access' => $replace_button_access,
590 '#value' => $this->t('Edit'),
592 'url' => Url::fromRoute(
593 'entity_browser.edit_form', [
594 'entity_type' => $entity->getEntityTypeId(),
595 'entity' => $entity->id(),
600 'details_id' => $details_id,
605 'class' => ['edit-button'],
607 '#access' => $edit_button_access,
612 empty($entities) ? [] : range(0, count($entities) - 1)
618 * Gets data that should persist across Entity Browser renders.
621 * Data that should persist after the Entity Browser is rendered.
623 protected function getPersistentData() {
626 'entity_type' => ['type' => $this->fieldDefinition->getFieldStorageDefinition()->getSetting('target_type')],
628 'widget_context' => [],
633 * Gets options that define where newly added entities are inserted.
636 * Mode labels indexed by key.
638 protected function selectionModeOptions() {
639 return ['append' => $this->t('Append'), 'prepend' => $this->t('Prepend')];
643 * Provides base for settings summary shared by all EB widgets.
646 * A short summary of the widget settings.
648 protected function summaryBase() {
651 $entity_browser_id = $this->getSetting('entity_browser');
652 if (empty($entity_browser_id)) {
653 return [$this->t('No entity browser selected.')];
656 if ($browser = $this->entityTypeManager->getStorage('entity_browser')->load($entity_browser_id)) {
657 $summary[] = $this->t('Entity browser: @browser', ['@browser' => $browser->label()]);
660 drupal_set_message($this->t('Missing entity browser!'), 'error');
661 return [$this->t('Missing entity browser!')];
665 $selection_mode = $this->getSetting('selection_mode');
666 $selection_mode_options = EntityBrowserElement::getSelectionModeOptions();
667 if (isset($selection_mode_options[$selection_mode])) {
668 $summary[] = $this->t('Selection mode: @selection_mode', ['@selection_mode' => $selection_mode_options[$selection_mode]]);
671 $summary[] = $this->t('Undefined selection mode.');
678 * Determines the entities used for the form element.
680 * @param \Drupal\Core\Field\FieldItemListInterface $items
681 * The field item to extract the entities from.
682 * @param array $element
684 * @param \Drupal\Core\Form\FormStateInterface $form_state
687 * @return \Drupal\Core\Entity\EntityInterface[]
688 * The list of entities for the form element.
690 protected function formElementEntities(FieldItemListInterface $items, array $element, FormStateInterface $form_state) {
692 $entity_type = $this->fieldDefinition->getFieldStorageDefinition()->getSetting('target_type');
693 $entity_storage = $this->entityTypeManager->getStorage($entity_type);
695 // Find IDs from target_id element (it stores selected entities in form).
696 // This was added to help solve a really edge casey bug in IEF.
697 if (($target_id_entities = $this->getEntitiesByTargetId($element, $form_state)) !== FALSE) {
698 return $target_id_entities;
701 // Determine if we're submitting and if submit came from this widget.
702 $is_relevant_submit = FALSE;
703 if (($trigger = $form_state->getTriggeringElement())) {
704 // Can be triggered by hidden target_id element or "Remove" button.
705 if (end($trigger['#parents']) === 'target_id' || (end($trigger['#parents']) === 'remove_button')) {
706 $is_relevant_submit = TRUE;
708 // In case there are more instances of this widget on the same page we
709 // need to check if submit came from this instance.
710 $field_name_key = end($trigger['#parents']) === 'target_id' ? 2 : static::$deleteDepth + 1;
711 $field_name_key = count($trigger['#parents']) - $field_name_key;
712 $is_relevant_submit &= ($trigger['#parents'][$field_name_key] === $this->fieldDefinition->getName()) &&
713 (array_slice($trigger['#parents'], 0, count($element['#field_parents'])) == $element['#field_parents']);
717 if ($is_relevant_submit) {
718 // Submit was triggered by hidden "target_id" element when entities were
719 // added via entity browser.
720 if (!empty($trigger['#ajax']['event']) && $trigger['#ajax']['event'] == 'entity_browser_value_updated') {
721 $parents = $trigger['#parents'];
723 // Submit was triggered by one of the "Remove" buttons. We need to walk
724 // few levels up to read value of "target_id" element.
725 elseif ($trigger['#type'] == 'submit' && strpos($trigger['#name'], $this->fieldDefinition->getName() . '_remove_') === 0) {
726 $parents = array_merge(array_slice($trigger['#parents'], 0, -static::$deleteDepth), ['target_id']);
729 if (isset($parents) && $value = $form_state->getValue($parents)) {
730 $entities = EntityBrowserElement::processEntityIds($value);
735 // IDs from a previous request might be saved in the form state.
736 elseif ($form_state->has([
737 'entity_browser_widget',
738 $this->getFormStateKey($items),
741 $stored_ids = $form_state->get([
742 'entity_browser_widget',
743 $this->getFormStateKey($items),
745 $indexed_entities = $entity_storage->loadMultiple($stored_ids);
747 // Selection can contain same entity multiple times. Since loadMultiple()
748 // returns unique list of entities, it's necessary to recreate list of
749 // entities in order to preserve selection of duplicated entities.
750 foreach ($stored_ids as $entity_id) {
751 if (isset($indexed_entities[$entity_id])) {
752 $entities[] = $indexed_entities[$entity_id];
757 // We are loading for for the first time so we need to load any existing
758 // values that might already exist on the entity. Also, remove any leftover
759 // data from removed entity references.
761 foreach ($items as $item) {
762 if (isset($item->target_id)) {
763 $entity = $entity_storage->load($item->target_id);
764 if (!empty($entity)) {
765 $entities[] = $entity;
776 public function calculateDependencies() {
777 $dependencies = parent::calculateDependencies();
779 // If an entity browser is being used in this widget, add it as a config
781 if ($browser_name = $this->getSetting('entity_browser')) {
782 $dependencies['config'][] = 'entity_browser.browser.' . $browser_name;
785 return $dependencies;
789 * Get selected elements from target_id element on form.
791 * @param array $element
793 * @param \Drupal\Core\Form\FormStateInterface $form_state
796 * @return \Drupal\Core\Entity\EntityInterface[]|false
797 * Return list of entities if they are available or false.
799 protected function getEntitiesByTargetId(array $element, FormStateInterface $form_state) {
800 $target_id_element_path = array_merge(
801 $element['#field_parents'],
802 [$this->fieldDefinition->getName(), 'target_id']
805 if (!NestedArray::keyExists($form_state->getUserInput(), $target_id_element_path)) {
809 // TODO Figure out how to avoid using raw user input.
810 $current_user_input = NestedArray::getValue($form_state->getUserInput(), $target_id_element_path);
811 if (!is_array($current_user_input)) {
812 $entities = EntityBrowserElement::processEntityIds($current_user_input);