Updated all the contrib modules to their latest versions.
[yaffs-website] / web / modules / contrib / entity_browser / src / Plugin / Field / FieldWidget / EntityReferenceBrowserWidget.php
1 <?php
2
3 namespace Drupal\entity_browser\Plugin\Field\FieldWidget;
4
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;
17 use Drupal\Core\Url;
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;
26
27 /**
28  * Plugin implementation of the 'entity_reference' widget for entity browser.
29  *
30  * @FieldWidget(
31  *   id = "entity_browser_entity_reference",
32  *   label = @Translation("Entity browser"),
33  *   description = @Translation("Uses entity browser to select entities."),
34  *   multiple_values = TRUE,
35  *   field_types = {
36  *     "entity_reference"
37  *   }
38  * )
39  */
40 class EntityReferenceBrowserWidget extends WidgetBase implements ContainerFactoryPluginInterface {
41
42   /**
43    * Entity type manager service.
44    *
45    * @var \Drupal\Core\Entity\EntityTypeManagerInterface
46    */
47   protected $entityTypeManager;
48
49   /**
50    * Field widget display plugin manager.
51    *
52    * @var \Drupal\entity_browser\FieldWidgetDisplayManager
53    */
54   protected $fieldDisplayManager;
55
56   /**
57    * The depth of the delete button.
58    *
59    * This property exists so it can be changed if subclasses.
60    *
61    * @var int
62    */
63   protected static $deleteDepth = 4;
64
65   /**
66    * The module handler interface.
67    *
68    * @var \Drupal\Core\Extension\ModuleHandlerInterface
69    */
70   protected $moduleHandler;
71
72   /**
73    * The current user.
74    *
75    * @var \Drupal\Core\Session\AccountInterface
76    */
77   protected $currentUser;
78
79   /**
80    * Constructs widget plugin.
81    *
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
95    *   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
101    *   The current user.
102    */
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;
109   }
110
111   /**
112    * {@inheritdoc}
113    */
114   public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
115     return new static(
116       $plugin_id,
117       $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')
126     );
127   }
128
129   /**
130    * {@inheritdoc}
131    */
132   public static function defaultSettings() {
133     return [
134       'entity_browser' => NULL,
135       'open' => FALSE,
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();
143   }
144
145   /**
146    * {@inheritdoc}
147    */
148   public function settingsForm(array $form, FormStateInterface $form_state) {
149     $element = parent::settingsForm($form, $form_state);
150
151     $browsers = [];
152     /** @var \Drupal\entity_browser\EntityBrowserInterface $browser */
153     foreach ($this->entityTypeManager->getStorage('entity_browser')->loadMultiple() as $browser) {
154       $browsers[$browser->id()] = $browser->label();
155     }
156
157     $element['entity_browser'] = [
158       '#title' => $this->t('Entity browser'),
159       '#type' => 'select',
160       '#default_value' => $this->getSetting('entity_browser'),
161       '#options' => $browsers,
162     ];
163
164     $target_type = $this->fieldDefinition->getFieldStorageDefinition()->getSetting('target_type');
165     $entity_type = $this->entityTypeManager->getStorage($target_type)->getEntityType();
166
167     $displays = [];
168     foreach ($this->fieldDisplayManager->getDefinitions() as $id => $definition) {
169       if ($this->fieldDisplayManager->createInstance($id)->isApplicable($entity_type)) {
170         $displays[$id] = $definition['label'];
171       }
172     }
173
174     $id = Html::getUniqueId('field-' . $this->fieldDefinition->getName() . '-display-settings-wrapper');
175     $element['field_widget_display'] = [
176       '#title' => $this->t('Entity display plugin'),
177       '#type' => 'select',
178       '#default_value' => $this->getSetting('field_widget_display'),
179       '#options' => $displays,
180       '#ajax' => [
181         'callback' => [$this, 'updateSettingsAjax'],
182         'wrapper' => $id,
183       ],
184     ];
185
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');
191     }
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,
197     ];
198
199     $element['field_widget_remove'] = [
200       '#title' => $this->t('Display Remove button'),
201       '#type' => 'checkbox',
202       '#default_value' => $this->getSetting('field_widget_remove'),
203     ];
204
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'),
210     ];
211
212     $element['open'] = [
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'),
217     ];
218
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.'),
222       '#type' => 'select',
223       '#options' => EntityBrowserElement::getSelectionModeOptions(),
224       '#default_value' => $this->getSetting('selection_mode'),
225     ];
226
227     $element['field_widget_display_settings'] = [
228       '#type' => 'fieldset',
229       '#title' => $this->t('Entity display plugin configuration'),
230       '#tree' => TRUE,
231       '#prefix' => '<div id="' . $id . '">',
232       '#suffix' => '</div>',
233     ];
234
235     if ($this->getSetting('field_widget_display')) {
236       $element['field_widget_display_settings'] += $this->fieldDisplayManager
237         ->createInstance(
238           $form_state->getValue(
239             ['fields', $this->fieldDefinition->getName(), 'settings_edit_form', 'settings', 'field_widget_display'],
240             $this->getSetting('field_widget_display')
241           ),
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')]
246         )
247         ->settingsForm($form, $form_state);
248     }
249
250     return $element;
251   }
252
253   /**
254    * Ajax callback that updates field widget display settings fieldset.
255    */
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'];
258   }
259
260   /**
261    * {@inheritdoc}
262    */
263   public function settingsSummary() {
264     $summary = $this->summaryBase();
265     $field_widget_display = $this->getSetting('field_widget_display');
266
267     if (!empty($field_widget_display)) {
268       $plugin = $this->fieldDisplayManager->getDefinition($field_widget_display);
269       $summary[] = $this->t('Entity display: @name', ['@name' => $plugin['label']]);
270     }
271     return $summary;
272   }
273
274   /**
275    * {@inheritdoc}
276    */
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
283         // better UX.
284         if ($violation->getConstraint() instanceof NotNullConstraint) {
285           $violations->set($offset, new ConstraintViolation(
286             $this->t('@name field is required.', ['@name' => $items->getFieldDefinition()->getLabel()]),
287             '',
288             [],
289             $violation->getRoot(),
290             $violation->getPropertyPath(),
291             $violation->getInvalidValue(),
292             $violation->getPlural(),
293             $violation->getCode(),
294             $violation->getConstraint(),
295             $violation->getCause()
296           ));
297         }
298       }
299     }
300
301     parent::flagErrors($items, $violations, $form, $form_state);
302   }
303
304   /**
305    * Returns a key used to store the previously loaded entity.
306    *
307    * @param \Drupal\Core\Field\FieldItemListInterface $items
308    *   The field items.
309    *
310    * @return string
311    *   A key for form state storage.
312    */
313   protected function getFormStateKey(FieldItemListInterface $items) {
314     return $items->getEntity()->uuid() . ':' . $items->getFieldDefinition()->getName();
315   }
316
317   /**
318    * {@inheritdoc}
319    */
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);
323
324     // Get correct ordered list of entity IDs.
325     $ids = array_map(
326       function (EntityInterface $entity) {
327         return $entity->id();
328       },
329       $entities
330     );
331
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);
339
340     $hidden_id = Html::getUniqueId('edit-' . $this->fieldDefinition->getName() . '-target-id');
341     $details_id = Html::getUniqueId('edit-' . $this->fieldDefinition->getName());
342
343     $element += [
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.
350       'target_id' => [
351         '#type' => 'hidden',
352         '#id' => $hidden_id,
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();
358             },
359             $entities
360         )),
361         // #ajax is officially not supported for hidden elements but if we
362         // specify event manually it works.
363         '#ajax' => [
364           'callback' => [get_class($this), 'updateWidgetCallback'],
365           'wrapper' => $details_id,
366           'event' => 'entity_browser_value_updated',
367         ],
368       ],
369     ];
370
371     // Get configuration required to check entity browser availability.
372     $cardinality = $this->fieldDefinition->getFieldStorageDefinition()->getCardinality();
373     $selection_mode = $this->getSetting('selection_mode');
374
375     // Enable entity browser if requirements for that are fulfilled.
376     if (EntityBrowserElement::isEntityBrowserAvailable($selection_mode, $cardinality, count($ids))) {
377       $persistentData = $this->getPersistentData();
378
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,
388         '#process' => [
389           ['\Drupal\entity_browser\Element\EntityBrowserElement', 'processEntityBrowser'],
390           [get_called_class(), 'processEntityBrowser'],
391         ],
392       ];
393     }
394
395     $element['#attached']['library'][] = 'entity_browser/entity_reference';
396
397     $field_parents = $element['#field_parents'];
398
399     $element['current'] = $this->displayCurrentSelection($details_id, $field_parents, $entities);
400
401     return $element;
402   }
403
404   /**
405    * Render API callback: Processes the entity browser element.
406    */
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'];
410     return $element;
411   }
412
413   /**
414    * {@inheritdoc}
415    */
416   public function massageFormValues(array $values, array $form, FormStateInterface $form_state) {
417     $entities = empty($values['target_id']) ? [] : explode(' ', trim($values['target_id']));
418     $return = [];
419     foreach ($entities as $entity) {
420       $return[]['target_id'] = explode(':', $entity)[1];
421     }
422
423     return $return;
424   }
425
426   /**
427    * AJAX form callback.
428    */
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.
436     $parents = [];
437     if (!empty($trigger['#ajax']['event']) && $trigger['#ajax']['event'] == 'entity_browser_value_updated') {
438       $parents = array_slice($trigger['#array_parents'], 0, -1);
439     }
440     elseif ($trigger['#type'] == 'submit' && strpos($trigger['#name'], '_remove_')) {
441       $parents = array_slice($trigger['#array_parents'], 0, -static::$deleteDepth);
442     }
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));
449     }
450
451     $parents = NestedArray::getValue($form, $parents);
452     $parents['#attached']['drupalSettings']['entity_browser_reopen_browser'] = $reopen_browser;
453     return $parents;
454   }
455
456   /**
457    * {@inheritdoc}
458    */
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') {
463         return FALSE;
464       }
465     }
466     return parent::errorElement($element, $violation, $form, $form_state);
467   }
468
469   /**
470    * Submit callback for remove buttons.
471    */
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);
479
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);
485
486           break;
487         }
488       }
489       $target_id_value = implode(' ', $values);
490
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);
495
496       // Rebuild form.
497       $form_state->setRebuild();
498     }
499   }
500
501   /**
502    * Builds the render array for displaying the current results.
503    *
504    * @param string $details_id
505    *   The ID for the details element.
506    * @param string[] $field_parents
507    *   Field parents.
508    * @param \Drupal\Core\Entity\ContentEntityInterface[] $entities
509    *   Array of referenced entities.
510    *
511    * @return array
512    *   The render array for the current selection.
513    */
514   protected function displayCurrentSelection($details_id, $field_parents, $entities) {
515
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')]
519     );
520
521     $classes = ['entities-list'];
522     if ($this->fieldDefinition->getFieldStorageDefinition()->getCardinality() != 1) {
523       $classes[] = 'sortable';
524     }
525
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);
529
530     return [
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');
542           }
543           if (is_string($display)) {
544             $display = ['#markup' => $display];
545           }
546           return [
547             '#theme_wrappers' => ['container'],
548             '#attributes' => [
549               'class' => ['item-container', Html::getClass($field_widget_display->getPluginId())],
550               'data-entity-id' => $entity->getEntityTypeId() . ':' . $entity->id(),
551               'data-row-id' => $row_id,
552             ],
553             'display' => $display,
554             'remove_button' => [
555               '#type' => 'submit',
556               '#value' => $this->t('Remove'),
557               '#ajax' => [
558                 'callback' => [get_class($this), 'updateWidgetCallback'],
559                 'wrapper' => $details_id,
560               ],
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()])],
564               '#attributes' => [
565                 'data-entity-id' => $entity->getEntityTypeId() . ':' . $entity->id(),
566                 'data-row-id' => $row_id,
567                 'class' => ['remove-button'],
568               ],
569               '#access' => (bool) $this->getSetting('field_widget_remove'),
570             ],
571             'replace_button' => [
572               '#type' => 'submit',
573               '#value' => $this->t('Replace'),
574               '#ajax' => [
575                 'callback' => [get_class($this), 'updateWidgetCallback'],
576                 'wrapper' => $details_id,
577               ],
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()])],
581               '#attributes' => [
582                 'data-entity-id' => $entity->getEntityTypeId() . ':' . $entity->id(),
583                 'data-row-id' => $row_id,
584                 'class' => ['replace-button'],
585               ],
586               '#access' => $replace_button_access,
587             ],
588             'edit_button' => [
589               '#type' => 'submit',
590               '#value' => $this->t('Edit'),
591               '#ajax' => [
592                 'url' => Url::fromRoute(
593                   'entity_browser.edit_form', [
594                     'entity_type' => $entity->getEntityTypeId(),
595                     'entity' => $entity->id(),
596                   ]
597                 ),
598                 'options' => [
599                   'query' => [
600                     'details_id' => $details_id,
601                   ],
602                 ],
603               ],
604               '#attributes' => [
605                 'class' => ['edit-button'],
606               ],
607               '#access' => $edit_button_access,
608             ],
609           ];
610         },
611         $entities,
612         empty($entities) ? [] : range(0, count($entities) - 1)
613       ),
614     ];
615   }
616
617   /**
618    * Gets data that should persist across Entity Browser renders.
619    *
620    * @return array
621    *   Data that should persist after the Entity Browser is rendered.
622    */
623   protected function getPersistentData() {
624     return [
625       'validators' => [
626         'entity_type' => ['type' => $this->fieldDefinition->getFieldStorageDefinition()->getSetting('target_type')],
627       ],
628       'widget_context' => [],
629     ];
630   }
631
632   /**
633    * Gets options that define where newly added entities are inserted.
634    *
635    * @return array
636    *   Mode labels indexed by key.
637    */
638   protected function selectionModeOptions() {
639     return ['append' => $this->t('Append'), 'prepend' => $this->t('Prepend')];
640   }
641
642   /**
643    * Provides base for settings summary shared by all EB widgets.
644    *
645    * @return array
646    *   A short summary of the widget settings.
647    */
648   protected function summaryBase() {
649     $summary = [];
650
651     $entity_browser_id = $this->getSetting('entity_browser');
652     if (empty($entity_browser_id)) {
653       return [$this->t('No entity browser selected.')];
654     }
655     else {
656       if ($browser = $this->entityTypeManager->getStorage('entity_browser')->load($entity_browser_id)) {
657         $summary[] = $this->t('Entity browser: @browser', ['@browser' => $browser->label()]);
658       }
659       else {
660         drupal_set_message($this->t('Missing entity browser!'), 'error');
661         return [$this->t('Missing entity browser!')];
662       }
663     }
664
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]]);
669     }
670     else {
671       $summary[] = $this->t('Undefined selection mode.');
672     }
673
674     return $summary;
675   }
676
677   /**
678    * Determines the entities used for the form element.
679    *
680    * @param \Drupal\Core\Field\FieldItemListInterface $items
681    *   The field item to extract the entities from.
682    * @param array $element
683    *   The form element.
684    * @param \Drupal\Core\Form\FormStateInterface $form_state
685    *   The form state.
686    *
687    * @return \Drupal\Core\Entity\EntityInterface[]
688    *   The list of entities for the form element.
689    */
690   protected function formElementEntities(FieldItemListInterface $items, array $element, FormStateInterface $form_state) {
691     $entities = [];
692     $entity_type = $this->fieldDefinition->getFieldStorageDefinition()->getSetting('target_type');
693     $entity_storage = $this->entityTypeManager->getStorage($entity_type);
694
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;
699     }
700
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;
707
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']);
714       }
715     };
716
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'];
722       }
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']);
727       }
728
729       if (isset($parents) && $value = $form_state->getValue($parents)) {
730         $entities = EntityBrowserElement::processEntityIds($value);
731         return $entities;
732       }
733       return $entities;
734     }
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),
739     ])
740     ) {
741       $stored_ids = $form_state->get([
742         'entity_browser_widget',
743         $this->getFormStateKey($items),
744       ]);
745       $indexed_entities = $entity_storage->loadMultiple($stored_ids);
746
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];
753         }
754       }
755       return $entities;
756     }
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.
760     else {
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;
766           }
767         }
768       }
769       return $entities;
770     }
771   }
772
773   /**
774    * {@inheritdoc}
775    */
776   public function calculateDependencies() {
777     $dependencies = parent::calculateDependencies();
778
779     // If an entity browser is being used in this widget, add it as a config
780     // dependency.
781     if ($browser_name = $this->getSetting('entity_browser')) {
782       $dependencies['config'][] = 'entity_browser.browser.' . $browser_name;
783     }
784
785     return $dependencies;
786   }
787
788   /**
789    * Get selected elements from target_id element on form.
790    *
791    * @param array $element
792    *   The form element.
793    * @param \Drupal\Core\Form\FormStateInterface $form_state
794    *   The form state.
795    *
796    * @return \Drupal\Core\Entity\EntityInterface[]|false
797    *   Return list of entities if they are available or false.
798    */
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']
803     );
804
805     if (!NestedArray::keyExists($form_state->getUserInput(), $target_id_element_path)) {
806       return FALSE;
807     }
808
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);
813       return $entities;
814     }
815
816     return FALSE;
817   }
818
819 }