Completed
Push — 8.x-1.x ( 09a174...2d9790 )
by Janez
03:18
created

EntityReferenceBrowserWidget::formElement()   D

Complexity

Conditions 20
Paths 192

Size

Total Lines 116
Code Lines 64

Duplication

Lines 8
Ratio 6.9 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 20
eloc 64
c 2
b 0
f 0
nc 192
nop 5
dl 8
loc 116
rs 4.4507

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Drupal\entity_browser\Plugin\Field\FieldWidget;
4
5
use Symfony\Component\Validator\ConstraintViolationInterface;
6
use Drupal\Component\Utility\Html;
7
use Drupal\Component\Utility\NestedArray;
8
use Drupal\Core\Entity\ContentEntityInterface;
9
use Drupal\Core\Entity\EntityTypeManagerInterface;
10
use Drupal\Core\Field\FieldDefinitionInterface;
11
use Drupal\Core\Field\FieldItemListInterface;
12
use Drupal\Core\Field\FieldStorageDefinitionInterface;
13
use Drupal\Core\Field\WidgetBase;
14
use Drupal\Core\Form\FormStateInterface;
15
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
16
use Drupal\Core\Url;
17
use Drupal\Core\Validation\Plugin\Validation\Constraint\NotNullConstraint;
18
use Drupal\entity_browser\FieldWidgetDisplayManager;
19
use Symfony\Component\DependencyInjection\ContainerInterface;
20
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
21
use Symfony\Component\Validator\ConstraintViolation;
22
use Symfony\Component\Validator\ConstraintViolationListInterface;
23
24
/**
25
 * Plugin implementation of the 'entity_reference' widget for entity browser.
26
 *
27
 * @FieldWidget(
28
 *   id = "entity_browser_entity_reference",
29
 *   label = @Translation("Entity browser"),
30
 *   description = @Translation("Uses entity browser to select entities."),
31
 *   multiple_values = TRUE,
32
 *   field_types = {
33
 *     "entity_reference"
34
 *   }
35
 * )
36
 */
37
class EntityReferenceBrowserWidget extends WidgetBase implements ContainerFactoryPluginInterface {
38
39
  /**
40
   * Entity type manager service.
41
   *
42
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
43
   */
44
  protected $entityTypeManager;
45
46
  /**
47
   * Field widget display plugin manager.
48
   *
49
   * @var \Drupal\entity_browser\FieldWidgetDisplayManager
50
   */
51
  protected $fieldDisplayManager;
52
53
  /**
54
   * The depth of the delete button.
55
   *
56
   * This property exists so it can be changed if subclasses.
57
   *
58
   * @var int
59
   */
60
  protected static $deleteDepth = 4;
61
62
  /**
63
   * Constructs widget plugin.
64
   *
65
   * @param string $plugin_id
66
   *   The plugin_id for the plugin instance.
67
   * @param mixed $plugin_definition
68
   *   The plugin implementation definition.
69
   * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
70
   *   The definition of the field to which the widget is associated.
71
   * @param array $settings
72
   *   The widget settings.
73
   * @param array $third_party_settings
74
   *   Any third party settings.
75
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
76
   *   Entity type manager service.
77
   * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
78
   *   Event dispatcher.
79
   * @param \Drupal\entity_browser\FieldWidgetDisplayManager $field_display_manager
80
   *   Field widget display plugin manager.
81
   */
82
  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) {
83
    parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings);
84
    $this->entityTypeManager = $entity_type_manager;
85
    $this->fieldDisplayManager = $field_display_manager;
86
  }
87
88
  /**
89
   * {@inheritdoc}
90
   */
91
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
92
    return new static(
93
      $plugin_id,
94
      $plugin_definition,
95
      $configuration['field_definition'],
96
      $configuration['settings'],
97
      $configuration['third_party_settings'],
98
      $container->get('entity_type.manager'),
99
      $container->get('event_dispatcher'),
100
      $container->get('plugin.manager.entity_browser.field_widget_display')
101
    );
102
  }
103
104
  /**
105
   * {@inheritdoc}
106
   */
107
  public static function defaultSettings() {
108
    return array(
109
      'entity_browser' => NULL,
110
      'open' => FALSE,
111
      'field_widget_display' => NULL,
112
      'field_widget_edit' => TRUE,
113
      'field_widget_remove' => TRUE,
114
      'field_widget_display_settings' => [],
115
    ) + parent::defaultSettings();
116
  }
117
118
  /**
119
   * {@inheritdoc}
120
   */
121
  public function settingsForm(array $form, FormStateInterface $form_state) {
122
    $element = parent::settingsForm($form, $form_state);
123
124
    $browsers = [];
125
    /** @var \Drupal\entity_browser\EntityBrowserInterface $browser */
126
    foreach ($this->entityTypeManager->getStorage('entity_browser')->loadMultiple() as $browser) {
127
      $browsers[$browser->id()] = $browser->label();
128
    }
129
130
    $element['entity_browser'] = [
131
      '#title' => t('Entity browser'),
132
      '#type' => 'select',
133
      '#default_value' => $this->getSetting('entity_browser'),
134
      '#options' => $browsers,
135
    ];
136
137
    $target_type = $this->fieldDefinition->getFieldStorageDefinition()->getSetting('target_type');
138
    $entity_type = $this->entityTypeManager->getStorage($target_type)->getEntityType();
139
140
    $displays = [];
141
    foreach ($this->fieldDisplayManager->getDefinitions() as $id => $definition) {
142
      if ($this->fieldDisplayManager->createInstance($id)->isApplicable($entity_type)) {
143
        $displays[$id] = $definition['label'];
144
      }
145
    }
146
147
    $id = Html::getUniqueId('field-' . $this->fieldDefinition->getName() . '-display-settings-wrapper');
148
    $element['field_widget_display'] = [
149
      '#title' => t('Entity display plugin'),
150
      '#type' => 'select',
151
      '#default_value' => $this->getSetting('field_widget_display'),
152
      '#options' => $displays,
153
      '#ajax' => [
154
        'callback' => array($this, 'updateSettingsAjax'),
155
        'wrapper' => $id,
156
      ],
157
    ];
158
159
    $element['field_widget_edit'] = [
160
      '#title' => t('Display Edit button'),
161
      '#type' => 'checkbox',
162
      '#default_value' => $this->getSetting('field_widget_edit'),
163
    ];
164
165
    $element['field_widget_remove'] = [
166
      '#title' => t('Display Remove button'),
167
      '#type' => 'checkbox',
168
      '#default_value' => $this->getSetting('field_widget_remove'),
169
    ];
170
171
    $element['open'] = [
172
      '#title' => t('Show widget details as open by default'),
173
      '#type' => 'checkbox',
174
      '#default_value' => $this->getSetting('open'),
175
    ];
176
177
    $element['field_widget_display_settings'] = [
178
      '#type' => 'fieldset',
179
      '#title' => t('Entity display plugin configuration'),
180
      '#tree' => TRUE,
181
      '#prefix' => '<div id="' . $id . '">',
182
      '#suffix' => '</div>',
183
    ];
184
185
    if ($this->getSetting('field_widget_display')) {
186
      $element['field_widget_display_settings'] += $this->fieldDisplayManager
187
        ->createInstance(
188
          $form_state->getValue(
189
            ['fields', $this->fieldDefinition->getName(), 'settings_edit_form', 'settings', 'field_widget_display'],
190
            $this->getSetting('field_widget_display')
191
          ),
192
          $form_state->getValue(
193
            ['fields', $this->fieldDefinition->getName(), 'settings_edit_form', 'settings', 'field_widget_display_settings'],
194
            $this->getSetting('field_widget_display_settings')
195
          ) + ['entity_type' => $this->fieldDefinition->getFieldStorageDefinition()->getSetting('target_type')]
196
        )
197
        ->settingsForm($form, $form_state);
198
    }
199
200
    return $element;
201
  }
202
203
  /**
204
   * Ajax callback that updates field widget display settings fieldset.
205
   */
206
  public function updateSettingsAjax(array $form, FormStateInterface $form_state) {
207
    return $form['fields'][$this->fieldDefinition->getName()]['plugin']['settings_edit_form']['settings']['field_widget_display_settings'];
208
  }
209
210
  /**
211
   * {@inheritdoc}
212
   */
213
  public function settingsSummary() {
214
    $summary = [];
215
    $entity_browser_id = $this->getSetting('entity_browser');
216
    $field_widget_display = $this->getSetting('field_widget_display');
217
218
    if (empty($entity_browser_id)) {
219
      return [t('No entity browser selected.')];
220
    }
221
    else {
222
      if ($browser = $this->entityTypeManager->getStorage('entity_browser')->load($entity_browser_id)) {
223
        $summary[] = t('Entity browser: @browser', ['@browser' => $browser->label()]);
224
      }
225
      else {
226
        drupal_set_message(t('Missing entity browser!'), 'error');
227
        return [t('Missing entity browser!')];
228
      }
229
    }
230
231
    if (!empty($field_widget_display)) {
232
      $plugin = $this->fieldDisplayManager->getDefinition($field_widget_display);
233
      $summary[] = t('Entity display: @name', ['@name' => $plugin['label']]);
234
    }
235
236
    return $summary;
237
  }
238
239
  /**
240
   * {@inheritdoc}
241
   */
242
  public function flagErrors(FieldItemListInterface $items, ConstraintViolationListInterface $violations, array $form, FormStateInterface $form_state) {
243
    if ($violations->count() > 0) {
244
      /** @var \Symfony\Component\Validator\ConstraintViolation $violation */
245
      foreach ($violations as $offset => $violation) {
246
        // The value of the required field is checked through the "not null"
247
        // constraint, whose message is not very useful. We override it here for
248
        // better UX.
249
        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...
250
          $violations->set($offset, new ConstraintViolation(
251
            $this->t('@name field is required.', ['@name' => $items->getFieldDefinition()->getLabel()]),
252
            '',
253
            [],
254
            $violation->getRoot(),
255
            $violation->getPropertyPath(),
256
            $violation->getInvalidValue(),
257
            $violation->getPlural(),
258
            $violation->getCode(),
259
            $violation->getConstraint(),
260
            $violation->getCause()
261
          ));
262
        }
263
      }
264
    }
265
266
    parent::flagErrors($items, $violations, $form, $form_state);
267
  }
268
269
  /**
270
   * Returns a key used to store the previously loaded entity.
271
   *
272
   * @param \Drupal\Core\Field\FieldItemListInterface $items
273
   *   The field items.
274
   *
275
   * @return string
276
   *   A key for form state storage.
277
   */
278
  protected function getFormStateKey(FieldItemListInterface $items) {
279
    return $items->getEntity()->uuid() . ':' . $items->getFieldDefinition()->getName();
280
  }
281
282
  /**
283
   * {@inheritdoc}
284
   */
285
  public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
286
    $entity_type = $this->fieldDefinition->getFieldStorageDefinition()->getSetting('target_type');
287
    $entity_storage = $this->entityTypeManager->getStorage($entity_type);
288
289
    $ids = [];
290
    $entities = [];
291
292
    // Determine if we're submitting and if submit came from this widget.
293
    $is_relevant_submit = FALSE;
294
    if (($trigger = $form_state->getTriggeringElement())) {
295
      // Can be triggered by hidden target_id element or "Remove" button.
296
      if (end($trigger['#parents']) === 'target_id' || (end($trigger['#parents']) === 'remove_button')) {
297
        $is_relevant_submit = TRUE;
298
299
        // In case there are more instances of this widget on the same page we
300
        // need to check if submit came from this instance.
301
        $field_name_key = end($trigger['#parents']) === 'target_id' ? 2 : static::$deleteDepth + 1;
302
        $field_name_key = count($trigger['#parents']) - $field_name_key;
303
        $is_relevant_submit &= ($trigger['#parents'][$field_name_key] === $this->fieldDefinition->getName()) &&
304
          (array_slice($trigger['#parents'], 0, count($element['#field_parents'])) == $element['#field_parents']);
305
      }
306
    };
307
308
    if ($is_relevant_submit) {
309
      // Submit was triggered by hidden "target_id" element when entities were
310
      // added via entity browser.
311 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...
312
        $parents = $trigger['#parents'];
313
      }
314
      // Submit was triggered by one of the "Remove" buttons. We need to walk
315
      // few levels up to read value of "target_id" element.
316
      elseif ($trigger['#type'] == 'submit' && strpos($trigger['#name'], $this->fieldDefinition->getName() . '_remove_') === 0) {
317
        $parents = array_merge(array_slice($trigger['#parents'], 0, -static::$deleteDepth), ['target_id']);
318
      }
319
320
      if (isset($parents) && $value = $form_state->getValue($parents)) {
321
        $ids = explode(' ', $value);
322
        $entities = $entity_storage->loadMultiple($ids);
323
      }
324
    }
325
    // IDs from a previous request might be saved in the form state.
326
    elseif ($form_state->has(['entity_browser_widget', $this->getFormStateKey($items)])) {
327
      $ids = $form_state->get(['entity_browser_widget', $this->getFormStateKey($items)]);
328
      $entities = $entity_storage->loadMultiple($ids);
329
    }
330
    // We are loading for for the first time so we need to load any existing
331
    // values that might already exist on the entity. Also, remove any leftover
332
    // data from removed entity references.
333
    else {
334
      foreach ($items as $item) {
335
        if (isset($item->target_id)) {
336
          $entity = $entity_storage->load($item->target_id);
337
          if (!empty($entity)) {
338
            $entities[$item->target_id] = $entity;
339
          }
340
        }
341
      }
342
      $ids = array_keys($entities);
343
    }
344
    $ids = array_filter($ids);
345
    // We store current entity IDs as we might need them in future requests. If
346
    // some other part of the form triggers an AJAX request with
347
    // #limit_validation_errors we won't have access to the value of the
348
    // target_id element and won't be able to build the form as a result of
349
    // that. This will cause missing submit (Remove, Edit, ...) elements, which
350
    // might result in unpredictable results.
351
    $form_state->set(['entity_browser_widget', $this->getFormStateKey($items)], $ids);
352
353
    $hidden_id = Html::getUniqueId('edit-' . $this->fieldDefinition->getName() . '-target-id');
354
    $details_id = Html::getUniqueId('edit-' . $this->fieldDefinition->getName());
355
    /** @var \Drupal\entity_browser\EntityBrowserInterface $entity_browser */
356
    $entity_browser = $this->entityTypeManager->getStorage('entity_browser')->load($this->getSetting('entity_browser'));
357
358
    $element += [
359
      '#id' => $details_id,
360
      '#type' => 'details',
361
      '#open' => !empty($ids) || $this->getSetting('open'),
362
      '#required' => $this->fieldDefinition->isRequired(),
363
      'target_id' => [
364
        '#type' => 'hidden',
365
        '#id' => $hidden_id,
366
        // We need to repeat ID here as it is otherwise skipped when rendering.
367
        '#attributes' => ['id' => $hidden_id],
368
        '#default_value' => $ids,
369
        // #ajax is officially not supported for hidden elements but if we
370
        // specify event manually it works.
371
        '#ajax' => [
372
          'callback' => [get_class($this), 'updateWidgetCallback'],
373
          'wrapper' => $details_id,
374
          'event' => 'entity_browser_value_updated',
375
        ],
376
      ],
377
    ];
378
379
    $cardinality = $this->fieldDefinition->getFieldStorageDefinition()->getCardinality();
380
    if ($cardinality == FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED || count($ids) < $cardinality) {
381
      $entity_browser_uuid = sha1(implode('-', array_merge($form['#parents'], [$this->fieldDefinition->getName(), $delta])));
382
      $entity_browser_display = $entity_browser->getDisplay();
383
      $entity_browser_display->setUuid($entity_browser_uuid);
384
385
      $element['entity_browser'] = $entity_browser_display->displayEntityBrowser($form_state, $this->getPersistentData());
386
      $element['#attached']['library'][] = 'entity_browser/entity_reference';
387
      $element['#attached']['drupalSettings']['entity_browser'] = [
388
        $entity_browser->getDisplay()->getUuid() => [
389
          'cardinality' => $cardinality,
390
          'selector' => '#' . $element['target_id']['#attributes']['id'],
391
        ],
392
      ];
393
    }
394
395
    $field_parents = $element['#field_parents'];
396
397
    $element['current'] = $this->displayCurrentSelection($details_id, $field_parents, $entities);
398
399
    return $element;
400
  }
401
402
  /**
403
   * {@inheritdoc}
404
   */
405
  public function massageFormValues(array $values, array $form, FormStateInterface $form_state) {
406
    $ids = empty($values['target_id']) ? [] : explode(' ', trim($values['target_id']));
407
    $return = [];
408
    foreach ($ids as $id) {
409
      $return[]['target_id'] = $id;
410
    }
411
412
    return $return;
413
  }
414
415
  /**
416
   * AJAX form callback.
417
   */
418
  public static function updateWidgetCallback(array &$form, FormStateInterface $form_state) {
419
    $trigger = $form_state->getTriggeringElement();
420
    // AJAX requests can be triggered by hidden "target_id" element when
421
    // entities are added or by one of the "Remove" buttons. Depending on that
422
    // we need to figure out where root of the widget is in the form structure
423
    // and use this information to return correct part of the form.
424 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...
425
      $parents = array_slice($trigger['#array_parents'], 0, -1);
426
    }
427
    elseif ($trigger['#type'] == 'submit' && strpos($trigger['#name'], '_remove_')) {
428
      $parents = array_slice($trigger['#array_parents'], 0, -static::$deleteDepth);
429
    }
430
431
    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...
432
  }
433
434
  /**
435
   * {@inheritdoc}
436
   */
437
  public function errorElement(array $element, ConstraintViolationInterface $violation, array $form, FormStateInterface $form_state) {
438
    if (($trigger = $form_state->getTriggeringElement())) {
439
      // Can be triggered by "Remove" button.
440
      if (end($trigger['#parents']) === 'remove_button') {
441
        return FALSE;
442
      }
443
    }
444
    return parent::errorElement($element, $violation, $form, $form_state);
445
  }
446
447
  /**
448
   * Submit callback for remove buttons.
449
   */
450
  public static function removeItemSubmit(&$form, FormStateInterface $form_state) {
451
    $triggering_element = $form_state->getTriggeringElement();
452
    if (!empty($triggering_element['#attributes']['data-entity-id'])) {
453
      $id = $triggering_element['#attributes']['data-entity-id'];
454
      $parents = array_slice($triggering_element['#parents'], 0, -static::$deleteDepth);
455
      $array_parents = array_slice($triggering_element['#array_parents'], 0, -static::$deleteDepth);
456
457
      // Find and remove correct entity.
458
      $values = explode(' ', $form_state->getValue(array_merge($parents, ['target_id'])));
459
      $values = array_filter(
460
        $values,
461
        function($item) use ($id) {
462
          return $item != $id;
463
        }
464
      );
465
      $values = implode(' ', $values);
466
467
      // Set new value for this widget.
468
      $target_id_element = &NestedArray::getValue($form, array_merge($array_parents, ['target_id']));
469
      $form_state->setValueForElement($target_id_element, $values);
470
      NestedArray::setValue($form_state->getUserInput(), $target_id_element['#parents'], $values);
471
472
      // Rebuild form.
473
      $form_state->setRebuild();
474
    }
475
  }
476
477
  /**
478
   * Builds the render array for displaying the current results.
479
   *
480
   * @param string $details_id
481
   *   The ID for the details element.
482
   * @param string[] $field_parents
483
   *   Field parents.
484
   * @param \Drupal\Core\Entity\ContentEntityInterface[] $entities
485
   *   Array of referenced entities.
486
   *
487
   * @return array
488
   *   The render array for the current selection.
489
   */
490
  protected function displayCurrentSelection($details_id, $field_parents, $entities) {
491
492
    $field_widget_display = $this->fieldDisplayManager->createInstance(
493
      $this->getSetting('field_widget_display'),
494
      $this->getSetting('field_widget_display_settings') + ['entity_type' => $this->fieldDefinition->getFieldStorageDefinition()->getSetting('target_type')]
495
    );
496
497
    return [
498
      '#theme_wrappers' => ['container'],
499
      '#attributes' => ['class' => ['entities-list']],
500
      'items' => array_map(
501
        function (ContentEntityInterface $entity) use ($field_widget_display, $details_id, $field_parents) {
502
          $display = $field_widget_display->view($entity);
503
          if (is_string($display)) {
504
            $display = ['#markup' => $display];
505
          }
506
          return [
507
            '#theme_wrappers' => ['container'],
508
            '#attributes' => [
509
              'class' => ['item-container', Html::getClass($field_widget_display->getPluginId())],
510
              'data-entity-id' => $entity->id(),
511
            ],
512
            'display' => $display,
513
            'remove_button' => [
514
              '#type' => 'submit',
515
              '#value' => $this->t('Remove'),
516
              '#ajax' => [
517
                'callback' => [get_class($this), 'updateWidgetCallback'],
518
                'wrapper' => $details_id,
519
              ],
520
              '#submit' => [[get_class($this), 'removeItemSubmit']],
521
              '#name' => $this->fieldDefinition->getName() . '_remove_' . $entity->id(),
522
              '#limit_validation_errors' => [array_merge($field_parents, [$this->fieldDefinition->getName()])],
523
              '#attributes' => ['data-entity-id' => $entity->id()],
524
              '#access' => (bool) $this->getSetting('field_widget_remove'),
525
            ],
526
            'edit_button' => [
527
              '#type' => 'submit',
528
              '#value' => $this->t('Edit'),
529
              '#ajax' => [
530
                'url' => Url::fromRoute(
531
                  'entity_browser.edit_form', [
532
                    'entity_type' => $entity->getEntityTypeId(),
533
                    'entity' => $entity->id(),
534
                  ]
535
                ),
536
              ],
537
              '#access' => (bool) $this->getSetting('field_widget_edit'),
538
            ],
539
          ];
540
        },
541
        $entities
542
      ),
543
    ];
544
  }
545
546
  /**
547
   * Gets data that should persist across Entity Browser renders.
548
   *
549
   * @return array
550
   *   Data that should persist after the Entity Browser is rendered.
551
   */
552
  protected function getPersistentData() {
553
    return [
554
      'validators' => [
555
        'entity_type' => ['type' => $this->fieldDefinition->getFieldStorageDefinition()->getSetting('target_type')],
556
        'cardinality' => ['cardinality' => $this->fieldDefinition->getFieldStorageDefinition()->getCardinality()],
557
      ],
558
    ];
559
  }
560
561
}
562