Completed
Push — 8.x-1.x ( da8d51...96c705 )
by Janez
07:38 queued 03:01
created

EntityReferenceBrowserWidget::summaryBase()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 22
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 3
eloc 15
c 1
b 0
f 1
nc 3
nop 0
dl 0
loc 22
rs 9.2
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
      'selection_mode' => 'append',
118
    ) + parent::defaultSettings();
119
  }
120
121
  /**
122
   * {@inheritdoc}
123
   */
124
  public function settingsForm(array $form, FormStateInterface $form_state) {
125
    $element = parent::settingsForm($form, $form_state);
126
127
    $browsers = [];
128
    /** @var \Drupal\entity_browser\EntityBrowserInterface $browser */
129
    foreach ($this->entityTypeManager->getStorage('entity_browser')->loadMultiple() as $browser) {
130
      $browsers[$browser->id()] = $browser->label();
131
    }
132
133
    $element['entity_browser'] = [
134
      '#title' => t('Entity browser'),
135
      '#type' => 'select',
136
      '#default_value' => $this->getSetting('entity_browser'),
137
      '#options' => $browsers,
138
    ];
139
140
    $target_type = $this->fieldDefinition->getFieldStorageDefinition()->getSetting('target_type');
141
    $entity_type = $this->entityTypeManager->getStorage($target_type)->getEntityType();
142
143
    $displays = [];
144
    foreach ($this->fieldDisplayManager->getDefinitions() as $id => $definition) {
145
      if ($this->fieldDisplayManager->createInstance($id)->isApplicable($entity_type)) {
146
        $displays[$id] = $definition['label'];
147
      }
148
    }
149
150
    $id = Html::getUniqueId('field-' . $this->fieldDefinition->getName() . '-display-settings-wrapper');
151
    $element['field_widget_display'] = [
152
      '#title' => t('Entity display plugin'),
153
      '#type' => 'select',
154
      '#default_value' => $this->getSetting('field_widget_display'),
155
      '#options' => $displays,
156
      '#ajax' => [
157
        'callback' => array($this, 'updateSettingsAjax'),
158
        'wrapper' => $id,
159
      ],
160
    ];
161
162
    $element['field_widget_edit'] = [
163
      '#title' => t('Display Edit button'),
164
      '#type' => 'checkbox',
165
      '#default_value' => $this->getSetting('field_widget_edit'),
166
    ];
167
168
    $element['field_widget_remove'] = [
169
      '#title' => t('Display Remove button'),
170
      '#type' => 'checkbox',
171
      '#default_value' => $this->getSetting('field_widget_remove'),
172
    ];
173
174
    $element['open'] = [
175
      '#title' => t('Show widget details as open by default'),
176
      '#type' => 'checkbox',
177
      '#default_value' => $this->getSetting('open'),
178
    ];
179
180
    $element['selection_mode'] = [
181
      '#title' => t('Selection mode'),
182
      '#description' => t('Determines whether newly added entities are prepended on top of the list or appended to the end of it after they were selected.'),
183
      '#type' => 'select',
184
      '#options' => $this->selectionModeOptions(),
185
      '#default_value' => $this->getSetting('selection_mode'),
186
    ];
187
188
    $element['field_widget_display_settings'] = [
189
      '#type' => 'fieldset',
190
      '#title' => t('Entity display plugin configuration'),
191
      '#tree' => TRUE,
192
      '#prefix' => '<div id="' . $id . '">',
193
      '#suffix' => '</div>',
194
    ];
195
196
    if ($this->getSetting('field_widget_display')) {
197
      $element['field_widget_display_settings'] += $this->fieldDisplayManager
198
        ->createInstance(
199
          $form_state->getValue(
200
            ['fields', $this->fieldDefinition->getName(), 'settings_edit_form', 'settings', 'field_widget_display'],
201
            $this->getSetting('field_widget_display')
202
          ),
203
          $form_state->getValue(
204
            ['fields', $this->fieldDefinition->getName(), 'settings_edit_form', 'settings', 'field_widget_display_settings'],
205
            $this->getSetting('field_widget_display_settings')
206
          ) + ['entity_type' => $this->fieldDefinition->getFieldStorageDefinition()->getSetting('target_type')]
207
        )
208
        ->settingsForm($form, $form_state);
209
    }
210
211
    return $element;
212
  }
213
214
  /**
215
   * Ajax callback that updates field widget display settings fieldset.
216
   */
217
  public function updateSettingsAjax(array $form, FormStateInterface $form_state) {
218
    return $form['fields'][$this->fieldDefinition->getName()]['plugin']['settings_edit_form']['settings']['field_widget_display_settings'];
219
  }
220
221
  /**
222
   * {@inheritdoc}
223
   */
224
  public function settingsSummary() {
225
    $summary = $this->summaryBase();
226
    $field_widget_display = $this->getSetting('field_widget_display');
227
228
    if (!empty($field_widget_display)) {
229
      $plugin = $this->fieldDisplayManager->getDefinition($field_widget_display);
230
      $summary[] = t('Entity display: @name', ['@name' => $plugin['label']]);
231
    }
232
    return $summary;
233
  }
234
235
  /**
236
   * {@inheritdoc}
237
   */
238
  public function flagErrors(FieldItemListInterface $items, ConstraintViolationListInterface $violations, array $form, FormStateInterface $form_state) {
239
    if ($violations->count() > 0) {
240
      /** @var \Symfony\Component\Validator\ConstraintViolation $violation */
241
      foreach ($violations as $offset => $violation) {
242
        // The value of the required field is checked through the "not null"
243
        // constraint, whose message is not very useful. We override it here for
244
        // better UX.
245
        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...
246
          $violations->set($offset, new ConstraintViolation(
247
            $this->t('@name field is required.', ['@name' => $items->getFieldDefinition()->getLabel()]),
248
            '',
249
            [],
250
            $violation->getRoot(),
251
            $violation->getPropertyPath(),
252
            $violation->getInvalidValue(),
253
            $violation->getPlural(),
254
            $violation->getCode(),
255
            $violation->getConstraint(),
256
            $violation->getCause()
257
          ));
258
        }
259
      }
260
    }
261
262
    parent::flagErrors($items, $violations, $form, $form_state);
263
  }
264
265
  /**
266
   * Returns a key used to store the previously loaded entity.
267
   *
268
   * @param \Drupal\Core\Field\FieldItemListInterface $items
269
   *   The field items.
270
   *
271
   * @return string
272
   *   A key for form state storage.
273
   */
274
  protected function getFormStateKey(FieldItemListInterface $items) {
275
    return $items->getEntity()->uuid() . ':' . $items->getFieldDefinition()->getName();
276
  }
277
278
  /**
279
   * {@inheritdoc}
280
   */
281
  public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
282
    $entity_type = $this->fieldDefinition->getFieldStorageDefinition()->getSetting('target_type');
283
    $entity_storage = $this->entityTypeManager->getStorage($entity_type);
284
285
    $ids = [];
286
    $entities = [];
287
288
    // Determine if we're submitting and if submit came from this widget.
289
    $is_relevant_submit = FALSE;
290
    if (($trigger = $form_state->getTriggeringElement())) {
291
      // Can be triggered by hidden target_id element or "Remove" button.
292
      if (end($trigger['#parents']) === 'target_id' || (end($trigger['#parents']) === 'remove_button')) {
293
        $is_relevant_submit = TRUE;
294
295
        // In case there are more instances of this widget on the same page we
296
        // need to check if submit came from this instance.
297
        $field_name_key = end($trigger['#parents']) === 'target_id' ? 2 : static::$deleteDepth + 1;
298
        $field_name_key = count($trigger['#parents']) - $field_name_key;
299
        $is_relevant_submit &= ($trigger['#parents'][$field_name_key] === $this->fieldDefinition->getName()) &&
300
          (array_slice($trigger['#parents'], 0, count($element['#field_parents'])) == $element['#field_parents']);
301
      }
302
    };
303
304
    if ($is_relevant_submit) {
305
      // Submit was triggered by hidden "target_id" element when entities were
306
      // added via entity browser.
307 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...
308
        $parents = $trigger['#parents'];
309
      }
310
      // Submit was triggered by one of the "Remove" buttons. We need to walk
311
      // few levels up to read value of "target_id" element.
312
      elseif ($trigger['#type'] == 'submit' && strpos($trigger['#name'], $this->fieldDefinition->getName() . '_remove_') === 0) {
313
        $parents = array_merge(array_slice($trigger['#parents'], 0, -static::$deleteDepth), ['target_id']);
314
      }
315
316
      if (isset($parents) && $value = $form_state->getValue($parents)) {
317
        $entities = EntityBrowserElement::processEntityIds($value);
318
      }
319
    }
320
    // IDs from a previous request might be saved in the form state.
321
    elseif ($form_state->has(['entity_browser_widget', $this->getFormStateKey($items)])) {
322
      $ids = $form_state->get(['entity_browser_widget', $this->getFormStateKey($items)]);
323
      $entities = $entity_storage->loadMultiple($ids);
324
    }
325
    // We are loading for for the first time so we need to load any existing
326
    // values that might already exist on the entity. Also, remove any leftover
327
    // data from removed entity references.
328
    else {
329
      foreach ($items as $item) {
330
        if (isset($item->target_id)) {
331
          $entity = $entity_storage->load($item->target_id);
332
          if (!empty($entity)) {
333
            $entities[$item->target_id] = $entity;
334
          }
335
        }
336
      }
337
      $ids = array_keys($entities);
338
    }
339
    $ids = array_filter($ids);
340
    // We store current entity IDs as we might need them in future requests. If
341
    // some other part of the form triggers an AJAX request with
342
    // #limit_validation_errors we won't have access to the value of the
343
    // target_id element and won't be able to build the form as a result of
344
    // that. This will cause missing submit (Remove, Edit, ...) elements, which
345
    // might result in unpredictable results.
346
    $form_state->set(['entity_browser_widget', $this->getFormStateKey($items)], $ids);
347
348
    $hidden_id = Html::getUniqueId('edit-' . $this->fieldDefinition->getName() . '-target-id');
349
    $details_id = Html::getUniqueId('edit-' . $this->fieldDefinition->getName());
350
351
    $element += [
352
      '#id' => $details_id,
353
      '#type' => 'details',
354
      '#open' => !empty($entities) || $this->getSetting('open'),
355
      '#required' => $this->fieldDefinition->isRequired(),
356
      // We are not using Entity browser's hidden element since we maintain
357
      // selected entities in it during entire process.
358
      'target_id' => [
359
        '#type' => 'hidden',
360
        '#id' => $hidden_id,
361
        // We need to repeat ID here as it is otherwise skipped when rendering.
362
        '#attributes' => ['id' => $hidden_id],
363
        '#default_value' => array_map(
364
          function (EntityInterface $item) { return $item->getEntityTypeId() . ':' . $item->id(); },
365
          $entities
366
        ),
367
        // #ajax is officially not supported for hidden elements but if we
368
        // specify event manually it works.
369
        '#ajax' => [
370
          'callback' => [get_class($this), 'updateWidgetCallback'],
371
          'wrapper' => $details_id,
372
          'event' => 'entity_browser_value_updated',
373
        ],
374
      ],
375
    ];
376
377
    $cardinality = $this->fieldDefinition->getFieldStorageDefinition()->getCardinality();
378
    if ($cardinality == FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED || count($ids) < $cardinality) {
379
      $element['entity_browser'] = [
380
        '#type' => 'entity_browser',
381
        '#entity_browser' => $this->getSetting('entity_browser'),
382
        '#cardinality' => $cardinality,
383
        '#entity_browser_validators' => ['entity_type' => ['type' => $entity_type]],
384
        '#custom_hidden_id' => $hidden_id,
385
        '#selection_mode' => $this->getSetting('selection_mode'),
386
        '#process' => [
387
          ['\Drupal\entity_browser\Element\EntityBrowserElement', 'processEntityBrowser'],
388
          [get_called_class(), 'processEntityBrowser'],
389
        ],
390
      ];
391
392
      $element['#attached']['library'][] = 'entity_browser/entity_reference';
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
   * Render API callback: Processes the entity browser element.
404
   */
405
  public static function processEntityBrowser(&$element, FormStateInterface $form_state, &$complete_form) {
406
    $uuid = key($element['#attached']['drupalSettings']['entity_browser']);
407
    $element['#attached']['drupalSettings']['entity_browser'][$uuid]['selector'] = '#' . $element['#custom_hidden_id'];
408
    return $element;
409
  }
410
411
  /**
412
   * {@inheritdoc}
413
   */
414
  public function massageFormValues(array $values, array $form, FormStateInterface $form_state) {
415
    $entities = empty($values['target_id']) ? [] : explode(' ', trim($values['target_id']));
416
    $return = [];
417
    foreach ($entities as $entity) {
418
      $return[]['target_id'] = explode(':', $entity)[1];
419
    }
420
421
    return $return;
422
  }
423
424
  /**
425
   * AJAX form callback.
426
   */
427
  public static function updateWidgetCallback(array &$form, FormStateInterface $form_state) {
428
    $trigger = $form_state->getTriggeringElement();
429
    // AJAX requests can be triggered by hidden "target_id" element when
430
    // entities are added or by one of the "Remove" buttons. Depending on that
431
    // we need to figure out where root of the widget is in the form structure
432
    // and use this information to return correct part of the form.
433 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...
434
      $parents = array_slice($trigger['#array_parents'], 0, -1);
435
    }
436
    elseif ($trigger['#type'] == 'submit' && strpos($trigger['#name'], '_remove_')) {
437
      $parents = array_slice($trigger['#array_parents'], 0, -static::$deleteDepth);
438
    }
439
440
    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...
441
  }
442
443
  /**
444
   * {@inheritdoc}
445
   */
446
  public function errorElement(array $element, ConstraintViolationInterface $violation, array $form, FormStateInterface $form_state) {
447
    if (($trigger = $form_state->getTriggeringElement())) {
448
      // Can be triggered by "Remove" button.
449
      if (end($trigger['#parents']) === 'remove_button') {
450
        return FALSE;
451
      }
452
    }
453
    return parent::errorElement($element, $violation, $form, $form_state);
454
  }
455
456
  /**
457
   * Submit callback for remove buttons.
458
   */
459
  public static function removeItemSubmit(&$form, FormStateInterface $form_state) {
460
    $triggering_element = $form_state->getTriggeringElement();
461
    if (!empty($triggering_element['#attributes']['data-entity-id'])) {
462
      $id = $triggering_element['#attributes']['data-entity-id'];
463
      $parents = array_slice($triggering_element['#parents'], 0, -static::$deleteDepth);
464
      $array_parents = array_slice($triggering_element['#array_parents'], 0, -static::$deleteDepth);
465
466
      // Find and remove correct entity.
467
      $values = explode(' ', $form_state->getValue(array_merge($parents, ['target_id'])));
468
      $values = array_filter(
469
        $values,
470
        function($item) use ($id) {
471
          return $item != $id;
472
        }
473
      );
474
      $values = implode(' ', $values);
475
476
      // Set new value for this widget.
477
      $target_id_element = &NestedArray::getValue($form, array_merge($array_parents, ['target_id']));
478
      $form_state->setValueForElement($target_id_element, $values);
479
      NestedArray::setValue($form_state->getUserInput(), $target_id_element['#parents'], $values);
480
481
      // Rebuild form.
482
      $form_state->setRebuild();
483
    }
484
  }
485
486
  /**
487
   * Builds the render array for displaying the current results.
488
   *
489
   * @param string $details_id
490
   *   The ID for the details element.
491
   * @param string[] $field_parents
492
   *   Field parents.
493
   * @param \Drupal\Core\Entity\ContentEntityInterface[] $entities
494
   *   Array of referenced entities.
495
   *
496
   * @return array
497
   *   The render array for the current selection.
498
   */
499
  protected function displayCurrentSelection($details_id, $field_parents, $entities) {
500
501
    $field_widget_display = $this->fieldDisplayManager->createInstance(
502
      $this->getSetting('field_widget_display'),
503
      $this->getSetting('field_widget_display_settings') + ['entity_type' => $this->fieldDefinition->getFieldStorageDefinition()->getSetting('target_type')]
504
    );
505
506
    return [
507
      '#theme_wrappers' => ['container'],
508
      '#attributes' => ['class' => ['entities-list']],
509
      'items' => array_map(
510
        function (ContentEntityInterface $entity) use ($field_widget_display, $details_id, $field_parents) {
511
          $display = $field_widget_display->view($entity);
512
          if (is_string($display)) {
513
            $display = ['#markup' => $display];
514
          }
515
          return [
516
            '#theme_wrappers' => ['container'],
517
            '#attributes' => [
518
              'class' => ['item-container', Html::getClass($field_widget_display->getPluginId())],
519
              'data-entity-id' => $entity->getEntityTypeId() . ':' . $entity->id(),
520
            ],
521
            'display' => $display,
522
            'remove_button' => [
523
              '#type' => 'submit',
524
              '#value' => $this->t('Remove'),
525
              '#ajax' => [
526
                'callback' => [get_class($this), 'updateWidgetCallback'],
527
                'wrapper' => $details_id,
528
              ],
529
              '#submit' => [[get_class($this), 'removeItemSubmit']],
530
              '#name' => $this->fieldDefinition->getName() . '_remove_' . $entity->id(),
531
              '#limit_validation_errors' => [array_merge($field_parents, [$this->fieldDefinition->getName()])],
532
              '#attributes' => ['data-entity-id' => $entity->getEntityTypeId() . ':' . $entity->id()],
533
              '#access' => (bool) $this->getSetting('field_widget_remove'),
534
            ],
535
            'edit_button' => [
536
              '#type' => 'submit',
537
              '#value' => $this->t('Edit'),
538
              '#ajax' => [
539
                'url' => Url::fromRoute(
540
                  'entity_browser.edit_form', [
541
                    'entity_type' => $entity->getEntityTypeId(),
542
                    'entity' => $entity->id(),
543
                  ]
544
                ),
545
              ],
546
              '#access' => (bool) $this->getSetting('field_widget_edit'),
547
            ],
548
          ];
549
        },
550
        $entities
551
      ),
552
    ];
553
  }
554
555
  /**
556
   * Gets data that should persist across Entity Browser renders.
557
   *
558
   * @return array
559
   *   Data that should persist after the Entity Browser is rendered.
560
   */
561
  protected function getPersistentData() {
562
    return [
563
      'validators' => [
564
        'entity_type' => ['type' => $this->fieldDefinition->getFieldStorageDefinition()->getSetting('target_type')],
565
      ],
566
    ];
567
  }
568
569
  /**
570
   * Gets options that define where newly added entities are inserted.
571
   *
572
   * @return array
573
   *   Mode labels indexed by key.
574
   */
575
  protected function selectionModeOptions() {
576
    return ['append' => t('Append'), 'prepend' => t('Prepend')];
577
  }
578
579
  /**
580
   * Provides base for settings summary shared by all EB widgets.
581
   *
582
   * @return array
583
   *   A short summary of the widget settings.
584
   */
585
  protected function summaryBase() {
586
    $summary = [];
587
    $entity_browser_id = $this->getSetting('entity_browser');
588
    if (empty($entity_browser_id)) {
589
      return [t('No entity browser selected.')];
590
    }
591
    else {
592
      if ($browser = $this->entityTypeManager->getStorage('entity_browser')->load($entity_browser_id)) {
593
        $summary[] = t('Entity browser: @browser', ['@browser' => $browser->label()]);
594
      }
595
      else {
596
        drupal_set_message(t('Missing entity browser!'), 'error');
597
        return [t('Missing entity browser!')];
598
      }
599
    }
600
601
    $summary[] = t(
602
      'Selection mode: @mode',
603
      ['@mode' => $this->selectionModeOptions()[$this->getSetting('selection_mode')]]
604
    );
605
    return $summary;
606
  }
607
608
}
609