Completed
Push — 8.x-1.x ( f23998...729d7d )
by Janez
02:46
created

EntityReferenceBrowserWidget::errorElement()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 5
c 1
b 0
f 0
nc 3
nop 4
dl 0
loc 9
rs 9.6666
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\FieldStorageDefinitionInterface;
15
use Drupal\Core\Field\WidgetBase;
16
use Drupal\Core\Form\FormStateInterface;
17
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
18
use Drupal\Core\Url;
19
use Drupal\Core\Validation\Plugin\Validation\Constraint\NotNullConstraint;
20
use Drupal\entity_browser\FieldWidgetDisplayManager;
21
use Symfony\Component\DependencyInjection\ContainerInterface;
22
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
23
use Symfony\Component\Validator\ConstraintViolation;
24
use Symfony\Component\Validator\ConstraintViolationListInterface;
25
26
/**
27
 * Plugin implementation of the 'entity_reference' widget for entity browser.
28
 *
29
 * @FieldWidget(
30
 *   id = "entity_browser_entity_reference",
31
 *   label = @Translation("Entity browser"),
32
 *   description = @Translation("Uses entity browser to select entities."),
33
 *   multiple_values = TRUE,
34
 *   field_types = {
35
 *     "entity_reference"
36
 *   }
37
 * )
38
 */
39
class EntityReferenceBrowserWidget extends WidgetBase implements ContainerFactoryPluginInterface {
40
41
  /**
42
   * Entity type manager service.
43
   *
44
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
45
   */
46
  protected $entityTypeManager;
47
48
  /**
49
   * Field widget display plugin manager.
50
   *
51
   * @var \Drupal\entity_browser\FieldWidgetDisplayManager
52
   */
53
  protected $fieldDisplayManager;
54
55
  /**
56
   * The depth of the delete button.
57
   *
58
   * This property exists so it can be changed if subclasses.
59
   *
60
   * @var int
61
   */
62
  protected static $deleteDepth = 4;
63
64
  /**
65
   * Constructs widget plugin.
66
   *
67
   * @param string $plugin_id
68
   *   The plugin_id for the plugin instance.
69
   * @param mixed $plugin_definition
70
   *   The plugin implementation definition.
71
   * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
72
   *   The definition of the field to which the widget is associated.
73
   * @param array $settings
74
   *   The widget settings.
75
   * @param array $third_party_settings
76
   *   Any third party settings.
77
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
78
   *   Entity type manager service.
79
   * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
80
   *   Event dispatcher.
81
   * @param \Drupal\entity_browser\FieldWidgetDisplayManager $field_display_manager
82
   *   Field widget display plugin manager.
83
   */
84
  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) {
85
    parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings);
86
    $this->entityTypeManager = $entity_type_manager;
87
    $this->fieldDisplayManager = $field_display_manager;
88
  }
89
90
  /**
91
   * {@inheritdoc}
92
   */
93
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
94
    return new static(
95
      $plugin_id,
96
      $plugin_definition,
97
      $configuration['field_definition'],
98
      $configuration['settings'],
99
      $configuration['third_party_settings'],
100
      $container->get('entity_type.manager'),
101
      $container->get('event_dispatcher'),
102
      $container->get('plugin.manager.entity_browser.field_widget_display')
103
    );
104
  }
105
106
  /**
107
   * {@inheritdoc}
108
   */
109
  public static function defaultSettings() {
110
    return array(
111
      'entity_browser' => NULL,
112
      'open' => FALSE,
113
      'field_widget_display' => NULL,
114
      'field_widget_edit' => TRUE,
115
      'field_widget_remove' => TRUE,
116
      'field_widget_display_settings' => [],
117
    ) + parent::defaultSettings();
118
  }
119
120
  /**
121
   * {@inheritdoc}
122
   */
123
  public function settingsForm(array $form, FormStateInterface $form_state) {
124
    $element = parent::settingsForm($form, $form_state);
125
126
    $browsers = [];
127
    /** @var \Drupal\entity_browser\EntityBrowserInterface $browser */
128
    foreach ($this->entityTypeManager->getStorage('entity_browser')->loadMultiple() as $browser) {
129
      $browsers[$browser->id()] = $browser->label();
130
    }
131
132
    $element['entity_browser'] = [
133
      '#title' => t('Entity browser'),
134
      '#type' => 'select',
135
      '#default_value' => $this->getSetting('entity_browser'),
136
      '#options' => $browsers,
137
    ];
138
139
    $target_type = $this->fieldDefinition->getFieldStorageDefinition()->getSetting('target_type');
140
    $entity_type = $this->entityTypeManager->getStorage($target_type)->getEntityType();
141
142
    $displays = [];
143
    foreach ($this->fieldDisplayManager->getDefinitions() as $id => $definition) {
144
      if ($this->fieldDisplayManager->createInstance($id)->isApplicable($entity_type)) {
145
        $displays[$id] = $definition['label'];
146
      }
147
    }
148
149
    $id = Html::getUniqueId('field-' . $this->fieldDefinition->getName() . '-display-settings-wrapper');
150
    $element['field_widget_display'] = [
151
      '#title' => t('Entity display plugin'),
152
      '#type' => 'select',
153
      '#default_value' => $this->getSetting('field_widget_display'),
154
      '#options' => $displays,
155
      '#ajax' => [
156
        'callback' => array($this, 'updateSettingsAjax'),
157
        'wrapper' => $id,
158
      ],
159
    ];
160
161
    $element['field_widget_edit'] = [
162
      '#title' => t('Display Edit button'),
163
      '#type' => 'checkbox',
164
      '#default_value' => $this->getSetting('field_widget_edit'),
165
    ];
166
167
    $element['field_widget_remove'] = [
168
      '#title' => t('Display Remove button'),
169
      '#type' => 'checkbox',
170
      '#default_value' => $this->getSetting('field_widget_remove'),
171
    ];
172
173
    $element['open'] = [
174
      '#title' => t('Show widget details as open by default'),
175
      '#type' => 'checkbox',
176
      '#default_value' => $this->getSetting('open'),
177
    ];
178
179
    $element['field_widget_display_settings'] = [
180
      '#type' => 'fieldset',
181
      '#title' => t('Entity display plugin configuration'),
182
      '#tree' => TRUE,
183
      '#prefix' => '<div id="' . $id . '">',
184
      '#suffix' => '</div>',
185
    ];
186
187
    if ($this->getSetting('field_widget_display')) {
188
      $element['field_widget_display_settings'] += $this->fieldDisplayManager
189
        ->createInstance(
190
          $form_state->getValue(
191
            ['fields', $this->fieldDefinition->getName(), 'settings_edit_form', 'settings', 'field_widget_display'],
192
            $this->getSetting('field_widget_display')
193
          ),
194
          $form_state->getValue(
195
            ['fields', $this->fieldDefinition->getName(), 'settings_edit_form', 'settings', 'field_widget_display_settings'],
196
            $this->getSetting('field_widget_display_settings')
197
          ) + ['entity_type' => $this->fieldDefinition->getFieldStorageDefinition()->getSetting('target_type')]
198
        )
199
        ->settingsForm($form, $form_state);
200
    }
201
202
    return $element;
203
  }
204
205
  /**
206
   * Ajax callback that updates field widget display settings fieldset.
207
   */
208
  public function updateSettingsAjax(array $form, FormStateInterface $form_state) {
209
    return $form['fields'][$this->fieldDefinition->getName()]['plugin']['settings_edit_form']['settings']['field_widget_display_settings'];
210
  }
211
212
  /**
213
   * {@inheritdoc}
214
   */
215
  public function settingsSummary() {
216
    $summary = [];
217
    $entity_browser_id = $this->getSetting('entity_browser');
218
    $field_widget_display = $this->getSetting('field_widget_display');
219
220
    if (empty($entity_browser_id)) {
221
      return [t('No entity browser selected.')];
222
    }
223
    else {
224
      if ($browser = $this->entityTypeManager->getStorage('entity_browser')->load($entity_browser_id)) {
225
        $summary[] = t('Entity browser: @browser', ['@browser' => $browser->label()]);
226
      }
227
      else {
228
        drupal_set_message(t('Missing entity browser!'), 'error');
229
        return [t('Missing entity browser!')];
230
      }
231
    }
232
233
    if (!empty($field_widget_display)) {
234
      $plugin = $this->fieldDisplayManager->getDefinition($field_widget_display);
235
      $summary[] = t('Entity display: @name', ['@name' => $plugin['label']]);
236
    }
237
238
    return $summary;
239
  }
240
241
  /**
242
   * {@inheritdoc}
243
   */
244
  public function flagErrors(FieldItemListInterface $items, ConstraintViolationListInterface $violations, array $form, FormStateInterface $form_state) {
245
    if ($violations->count() > 0) {
246
      /** @var \Symfony\Component\Validator\ConstraintViolation $violation */
247
      foreach ($violations as $offset => $violation) {
248
        // The value of the required field is checked through the "not null"
249
        // constraint, whose message is not very useful. We override it here for
250
        // better UX.
251
        if ($violation->getConstraint() instanceof NotNullConstraint) {
0 ignored issues
show
Bug introduced by
The class Drupal\Core\Validation\P...raint\NotNullConstraint does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
252
          $violations->set($offset, new ConstraintViolation(
253
            $this->t('@name field is required.', ['@name' => $items->getFieldDefinition()->getLabel()]),
254
            '',
255
            [],
256
            $violation->getRoot(),
257
            $violation->getPropertyPath(),
258
            $violation->getInvalidValue(),
259
            $violation->getPlural(),
260
            $violation->getCode(),
261
            $violation->getConstraint(),
262
            $violation->getCause()
263
          ));
264
        }
265
      }
266
    }
267
268
    parent::flagErrors($items, $violations, $form, $form_state);
269
  }
270
271
  /**
272
   * Returns a key used to store the previously loaded entity.
273
   *
274
   * @param \Drupal\Core\Field\FieldItemListInterface $items
275
   *   The field items.
276
   *
277
   * @return string
278
   *   A key for form state storage.
279
   */
280
  protected function getFormStateKey(FieldItemListInterface $items) {
281
    return $items->getEntity()->uuid() . ':' . $items->getFieldDefinition()->getName();
282
  }
283
284
  /**
285
   * {@inheritdoc}
286
   */
287
  public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
288
    $entity_type = $this->fieldDefinition->getFieldStorageDefinition()->getSetting('target_type');
289
    $entity_storage = $this->entityTypeManager->getStorage($entity_type);
290
291
    $ids = [];
292
    $entities = [];
293
294
    // Determine if we're submitting and if submit came from this widget.
295
    $is_relevant_submit = FALSE;
296
    if (($trigger = $form_state->getTriggeringElement())) {
297
      // Can be triggered by hidden target_id element or "Remove" button.
298
      if (end($trigger['#parents']) === 'target_id' || (end($trigger['#parents']) === 'remove_button')) {
299
        $is_relevant_submit = TRUE;
300
301
        // In case there are more instances of this widget on the same page we
302
        // need to check if submit came from this instance.
303
        $field_name_key = end($trigger['#parents']) === 'target_id' ? 2 : static::$deleteDepth + 1;
304
        $field_name_key = count($trigger['#parents']) - $field_name_key;
305
        $is_relevant_submit &= ($trigger['#parents'][$field_name_key] === $this->fieldDefinition->getName()) &&
306
          (array_slice($trigger['#parents'], 0, count($element['#field_parents'])) == $element['#field_parents']);
307
      }
308
    };
309
310
    if ($is_relevant_submit) {
311
      // Submit was triggered by hidden "target_id" element when entities were
312
      // added via entity browser.
313 View Code Duplication
      if (!empty($trigger['#ajax']['event']) && $trigger['#ajax']['event'] == 'entity_browser_value_updated') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
314
        $parents = $trigger['#parents'];
315
      }
316
      // Submit was triggered by one of the "Remove" buttons. We need to walk
317
      // few levels up to read value of "target_id" element.
318
      elseif ($trigger['#type'] == 'submit' && strpos($trigger['#name'], $this->fieldDefinition->getName() . '_remove_') === 0) {
319
        $parents = array_merge(array_slice($trigger['#parents'], 0, -static::$deleteDepth), ['target_id']);
320
      }
321
322
      if (isset($parents) && $value = $form_state->getValue($parents)) {
323
        $entities = EntityBrowserElement::processEntityIds($value);
324
      }
325
    }
326
    // IDs from a previous request might be saved in the form state.
327
    elseif ($form_state->has(['entity_browser_widget', $this->getFormStateKey($items)])) {
328
      $ids = $form_state->get(['entity_browser_widget', $this->getFormStateKey($items)]);
329
      $entities = $entity_storage->loadMultiple($ids);
330
    }
331
    // We are loading for for the first time so we need to load any existing
332
    // values that might already exist on the entity. Also, remove any leftover
333
    // data from removed entity references.
334
    else {
335
      foreach ($items as $item) {
336
        if (isset($item->target_id)) {
337
          $entity = $entity_storage->load($item->target_id);
338
          if (!empty($entity)) {
339
            $entities[$item->target_id] = $entity;
340
          }
341
        }
342
      }
343
      $ids = array_keys($entities);
344
    }
345
    $ids = array_filter($ids);
346
    // We store current entity IDs as we might need them in future requests. If
347
    // some other part of the form triggers an AJAX request with
348
    // #limit_validation_errors we won't have access to the value of the
349
    // target_id element and won't be able to build the form as a result of
350
    // that. This will cause missing submit (Remove, Edit, ...) elements, which
351
    // might result in unpredictable results.
352
    $form_state->set(['entity_browser_widget', $this->getFormStateKey($items)], $ids);
353
354
    $hidden_id = Html::getUniqueId('edit-' . $this->fieldDefinition->getName() . '-target-id');
355
    $details_id = Html::getUniqueId('edit-' . $this->fieldDefinition->getName());
356
357
    $element += [
358
      '#id' => $details_id,
359
      '#type' => 'details',
360
      '#open' => !empty($entities) || $this->getSetting('open'),
361
      '#required' => $this->fieldDefinition->isRequired(),
362
      // We are not using Entity browser's hidden element since we maintain
363
      // selected entities in it during entire process.
364
      'target_id' => [
365
        '#type' => 'hidden',
366
        '#id' => $hidden_id,
367
        // We need to repeat ID here as it is otherwise skipped when rendering.
368
        '#attributes' => ['id' => $hidden_id],
369
        '#default_value' => array_map(
370
          function (EntityInterface $item) { return $item->getEntityTypeId() . ':' . $item->id(); },
371
          $entities
372
        ),
373
        // #ajax is officially not supported for hidden elements but if we
374
        // specify event manually it works.
375
        '#ajax' => [
376
          'callback' => [get_class($this), 'updateWidgetCallback'],
377
          'wrapper' => $details_id,
378
          'event' => 'entity_browser_value_updated',
379
        ],
380
      ],
381
    ];
382
383
    $cardinality = $this->fieldDefinition->getFieldStorageDefinition()->getCardinality();
384
    if ($cardinality == FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED || count($ids) < $cardinality) {
385
      $element['entity_browser'] = [
386
        '#type' => 'entity_browser',
387
        '#entity_browser' => $this->getSetting('entity_browser'),
388
        '#cardinality' => $cardinality,
389
        '#entity_browser_validators' => ['entity_type' => ['type' => $entity_type]],
390
        '#custom_hidden_id' => $hidden_id,
391
        '#process' => [
392
          ['\Drupal\entity_browser\Element\EntityBrowserElement', 'processEntityBrowser'],
393
          [get_called_class(), 'processEntityBrowser'],
394
        ],
395
      ];
396
397
      $element['#attached']['library'][] = 'entity_browser/entity_reference';
398
    }
399
400
    $field_parents = $element['#field_parents'];
401
402
    $element['current'] = $this->displayCurrentSelection($details_id, $field_parents, $entities);
403
404
    return $element;
405
  }
406
407
  /**
408
   * Render API callback: Processes the entity browser element.
409
   */
410
  public static function processEntityBrowser(&$element, FormStateInterface $form_state, &$complete_form) {
411
    $uuid = key($element['#attached']['drupalSettings']['entity_browser']);
412
    $element['#attached']['drupalSettings']['entity_browser'][$uuid]['selector'] = '#' . $element['#custom_hidden_id'];
413
    return $element;
414
  }
415
416
  /**
417
   * {@inheritdoc}
418
   */
419
  public function massageFormValues(array $values, array $form, FormStateInterface $form_state) {
420
    $entities = empty($values['target_id']) ? [] : explode(' ', trim($values['target_id']));
421
    $return = [];
422
    foreach ($entities as $entity) {
423
      $return[]['target_id'] = explode(':', $entity)[1];
424
    }
425
426
    return $return;
427
  }
428
429
  /**
430
   * AJAX form callback.
431
   */
432
  public static function updateWidgetCallback(array &$form, FormStateInterface $form_state) {
433
    $trigger = $form_state->getTriggeringElement();
434
    // AJAX requests can be triggered by hidden "target_id" element when
435
    // entities are added or by one of the "Remove" buttons. Depending on that
436
    // we need to figure out where root of the widget is in the form structure
437
    // and use this information to return correct part of the form.
438 View Code Duplication
    if (!empty($trigger['#ajax']['event']) && $trigger['#ajax']['event'] == 'entity_browser_value_updated') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
439
      $parents = array_slice($trigger['#array_parents'], 0, -1);
440
    }
441
    elseif ($trigger['#type'] == 'submit' && strpos($trigger['#name'], '_remove_')) {
442
      $parents = array_slice($trigger['#array_parents'], 0, -static::$deleteDepth);
443
    }
444
445
    return NestedArray::getValue($form, $parents);
0 ignored issues
show
Bug introduced by
The variable $parents does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
446
  }
447
448
  /**
449
   * {@inheritdoc}
450
   */
451
  public function errorElement(array $element, ConstraintViolationInterface $violation, array $form, FormStateInterface $form_state) {
452
    if (($trigger = $form_state->getTriggeringElement())) {
453
      // Can be triggered by "Remove" button.
454
      if (end($trigger['#parents']) === 'remove_button') {
455
        return FALSE;
456
      }
457
    }
458
    return parent::errorElement($element, $violation, $form, $form_state);
459
  }
460
461
  /**
462
   * Submit callback for remove buttons.
463
   */
464
  public static function removeItemSubmit(&$form, FormStateInterface $form_state) {
465
    $triggering_element = $form_state->getTriggeringElement();
466
    if (!empty($triggering_element['#attributes']['data-entity-id'])) {
467
      $id = $triggering_element['#attributes']['data-entity-id'];
468
      $parents = array_slice($triggering_element['#parents'], 0, -static::$deleteDepth);
469
      $array_parents = array_slice($triggering_element['#array_parents'], 0, -static::$deleteDepth);
470
471
      // Find and remove correct entity.
472
      $values = explode(' ', $form_state->getValue(array_merge($parents, ['target_id'])));
473
      $values = array_filter(
474
        $values,
475
        function($item) use ($id) {
476
          return $item != $id;
477
        }
478
      );
479
      $values = implode(' ', $values);
480
481
      // Set new value for this widget.
482
      $target_id_element = &NestedArray::getValue($form, array_merge($array_parents, ['target_id']));
483
      $form_state->setValueForElement($target_id_element, $values);
484
      NestedArray::setValue($form_state->getUserInput(), $target_id_element['#parents'], $values);
485
486
      // Rebuild form.
487
      $form_state->setRebuild();
488
    }
489
  }
490
491
  /**
492
   * Builds the render array for displaying the current results.
493
   *
494
   * @param string $details_id
495
   *   The ID for the details element.
496
   * @param string[] $field_parents
497
   *   Field parents.
498
   * @param \Drupal\Core\Entity\ContentEntityInterface[] $entities
499
   *   Array of referenced entities.
500
   *
501
   * @return array
502
   *   The render array for the current selection.
503
   */
504
  protected function displayCurrentSelection($details_id, $field_parents, $entities) {
505
506
    $field_widget_display = $this->fieldDisplayManager->createInstance(
507
      $this->getSetting('field_widget_display'),
508
      $this->getSetting('field_widget_display_settings') + ['entity_type' => $this->fieldDefinition->getFieldStorageDefinition()->getSetting('target_type')]
509
    );
510
511
    return [
512
      '#theme_wrappers' => ['container'],
513
      '#attributes' => ['class' => ['entities-list']],
514
      'items' => array_map(
515
        function (ContentEntityInterface $entity) use ($field_widget_display, $details_id, $field_parents) {
516
          $display = $field_widget_display->view($entity);
517
          if (is_string($display)) {
518
            $display = ['#markup' => $display];
519
          }
520
          return [
521
            '#theme_wrappers' => ['container'],
522
            '#attributes' => [
523
              'class' => ['item-container', Html::getClass($field_widget_display->getPluginId())],
524
              'data-entity-id' => $entity->getEntityTypeId() . ':' . $entity->id(),
525
            ],
526
            'display' => $display,
527
            'remove_button' => [
528
              '#type' => 'submit',
529
              '#value' => $this->t('Remove'),
530
              '#ajax' => [
531
                'callback' => [get_class($this), 'updateWidgetCallback'],
532
                'wrapper' => $details_id,
533
              ],
534
              '#submit' => [[get_class($this), 'removeItemSubmit']],
535
              '#name' => $this->fieldDefinition->getName() . '_remove_' . $entity->id(),
536
              '#limit_validation_errors' => [array_merge($field_parents, [$this->fieldDefinition->getName()])],
537
              '#attributes' => ['data-entity-id' => $entity->getEntityTypeId() . ':' . $entity->id()],
538
              '#access' => (bool) $this->getSetting('field_widget_remove'),
539
            ],
540
            'edit_button' => [
541
              '#type' => 'submit',
542
              '#value' => $this->t('Edit'),
543
              '#ajax' => [
544
                'url' => Url::fromRoute(
545
                  'entity_browser.edit_form', [
546
                    'entity_type' => $entity->getEntityTypeId(),
547
                    'entity' => $entity->id(),
548
                  ]
549
                ),
550
              ],
551
              '#access' => (bool) $this->getSetting('field_widget_edit'),
552
            ],
553
          ];
554
        },
555
        $entities
556
      ),
557
    ];
558
  }
559
560
  /**
561
   * Gets data that should persist across Entity Browser renders.
562
   *
563
   * @return array
564
   *   Data that should persist after the Entity Browser is rendered.
565
   */
566
  protected function getPersistentData() {
567
    return [
568
      'validators' => [
569
        'entity_type' => ['type' => $this->fieldDefinition->getFieldStorageDefinition()->getSetting('target_type')],
570
      ],
571
    ];
572
  }
573
574
}
575