Completed
Push — 8.x-1.x ( 795405...792375 )
by Janez
02:47
created

EntityReferenceBrowserWidget::summaryBase()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 28
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 1
Metric Value
cc 4
eloc 18
c 2
b 0
f 1
nc 4
nop 0
dl 0
loc 28
rs 8.5806
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
25
/**
26
 * Plugin implementation of the 'entity_reference' widget for entity browser.
27
 *
28
 * @FieldWidget(
29
 *   id = "entity_browser_entity_reference",
30
 *   label = @Translation("Entity browser"),
31
 *   description = @Translation("Uses entity browser to select entities."),
32
 *   multiple_values = TRUE,
33
 *   field_types = {
34
 *     "entity_reference"
35
 *   }
36
 * )
37
 */
38
class EntityReferenceBrowserWidget extends WidgetBase implements ContainerFactoryPluginInterface {
39
40
  /**
41
   * Entity type manager service.
42
   *
43
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
44
   */
45
  protected $entityTypeManager;
46
47
  /**
48
   * Field widget display plugin manager.
49
   *
50
   * @var \Drupal\entity_browser\FieldWidgetDisplayManager
51
   */
52
  protected $fieldDisplayManager;
53
54
  /**
55
   * The depth of the delete button.
56
   *
57
   * This property exists so it can be changed if subclasses.
58
   *
59
   * @var int
60
   */
61
  protected static $deleteDepth = 4;
62
63
  /**
64
   * Constructs widget plugin.
65
   *
66
   * @param string $plugin_id
67
   *   The plugin_id for the plugin instance.
68
   * @param mixed $plugin_definition
69
   *   The plugin implementation definition.
70
   * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
71
   *   The definition of the field to which the widget is associated.
72
   * @param array $settings
73
   *   The widget settings.
74
   * @param array $third_party_settings
75
   *   Any third party settings.
76
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
77
   *   Entity type manager service.
78
   * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
79
   *   Event dispatcher.
80
   * @param \Drupal\entity_browser\FieldWidgetDisplayManager $field_display_manager
81
   *   Field widget display plugin manager.
82
   */
83
  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) {
84
    parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings);
85
    $this->entityTypeManager = $entity_type_manager;
86
    $this->fieldDisplayManager = $field_display_manager;
87
  }
88
89
  /**
90
   * {@inheritdoc}
91
   */
92
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
93
    return new static(
94
      $plugin_id,
95
      $plugin_definition,
96
      $configuration['field_definition'],
97
      $configuration['settings'],
98
      $configuration['third_party_settings'],
99
      $container->get('entity_type.manager'),
100
      $container->get('event_dispatcher'),
101
      $container->get('plugin.manager.entity_browser.field_widget_display')
102
    );
103
  }
104
105
  /**
106
   * {@inheritdoc}
107
   */
108
  public static function defaultSettings() {
109
    return array(
110
      'entity_browser' => NULL,
111
      'open' => FALSE,
112
      'field_widget_display' => NULL,
113
      'field_widget_edit' => TRUE,
114
      'field_widget_remove' => TRUE,
115
      'field_widget_display_settings' => [],
116
      'selection_mode' => EntityBrowserElement::SELECTION_MODE_APPEND,
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' => $this->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' => $this->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' => $this->t('Display Edit button'),
163
      '#type' => 'checkbox',
164
      '#default_value' => $this->getSetting('field_widget_edit'),
165
    ];
166
167
    $element['field_widget_remove'] = [
168
      '#title' => $this->t('Display Remove button'),
169
      '#type' => 'checkbox',
170
      '#default_value' => $this->getSetting('field_widget_remove'),
171
    ];
172
173
    $element['open'] = [
174
      '#title' => $this->t('Show widget details as open by default'),
175
      '#type' => 'checkbox',
176
      '#default_value' => $this->getSetting('open'),
177
    ];
178
179
    $element['selection_mode'] = [
180
      '#title' => $this->t('Selection mode'),
181
      '#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.'),
182
      '#type' => 'select',
183
      '#options' => EntityBrowserElement::getSelectionModeOptions(),
184
      '#default_value' => $this->getSetting('selection_mode'),
185
    ];
186
187
    $element['field_widget_display_settings'] = [
188
      '#type' => 'fieldset',
189
      '#title' => $this->t('Entity display plugin configuration'),
190
      '#tree' => TRUE,
191
      '#prefix' => '<div id="' . $id . '">',
192
      '#suffix' => '</div>',
193
    ];
194
195
    if ($this->getSetting('field_widget_display')) {
196
      $element['field_widget_display_settings'] += $this->fieldDisplayManager
197
        ->createInstance(
198
          $form_state->getValue(
199
            ['fields', $this->fieldDefinition->getName(), 'settings_edit_form', 'settings', 'field_widget_display'],
200
            $this->getSetting('field_widget_display')
201
          ),
202
          $form_state->getValue(
203
            ['fields', $this->fieldDefinition->getName(), 'settings_edit_form', 'settings', 'field_widget_display_settings'],
204
            $this->getSetting('field_widget_display_settings')
205
          ) + ['entity_type' => $this->fieldDefinition->getFieldStorageDefinition()->getSetting('target_type')]
206
        )
207
        ->settingsForm($form, $form_state);
208
    }
209
210
    return $element;
211
  }
212
213
  /**
214
   * Ajax callback that updates field widget display settings fieldset.
215
   */
216
  public function updateSettingsAjax(array $form, FormStateInterface $form_state) {
217
    return $form['fields'][$this->fieldDefinition->getName()]['plugin']['settings_edit_form']['settings']['field_widget_display_settings'];
218
  }
219
220
  /**
221
   * {@inheritdoc}
222
   */
223
  public function settingsSummary() {
224
    $summary = $this->summaryBase();
225
    $field_widget_display = $this->getSetting('field_widget_display');
226
227
    if (!empty($field_widget_display)) {
228
      $plugin = $this->fieldDisplayManager->getDefinition($field_widget_display);
229
      $summary[] = $this->t('Entity display: @name', ['@name' => $plugin['label']]);
230
    }
231
    return $summary;
232
  }
233
234
  /**
235
   * {@inheritdoc}
236
   */
237
  public function flagErrors(FieldItemListInterface $items, ConstraintViolationListInterface $violations, array $form, FormStateInterface $form_state) {
238
    if ($violations->count() > 0) {
239
      /** @var \Symfony\Component\Validator\ConstraintViolation $violation */
240
      foreach ($violations as $offset => $violation) {
241
        // The value of the required field is checked through the "not null"
242
        // constraint, whose message is not very useful. We override it here for
243
        // better UX.
244
        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...
245
          $violations->set($offset, new ConstraintViolation(
246
            $this->t('@name field is required.', ['@name' => $items->getFieldDefinition()->getLabel()]),
247
            '',
248
            [],
249
            $violation->getRoot(),
250
            $violation->getPropertyPath(),
251
            $violation->getInvalidValue(),
252
            $violation->getPlural(),
253
            $violation->getCode(),
254
            $violation->getConstraint(),
255
            $violation->getCause()
256
          ));
257
        }
258
      }
259
    }
260
261
    parent::flagErrors($items, $violations, $form, $form_state);
262
  }
263
264
  /**
265
   * Returns a key used to store the previously loaded entity.
266
   *
267
   * @param \Drupal\Core\Field\FieldItemListInterface $items
268
   *   The field items.
269
   *
270
   * @return string
271
   *   A key for form state storage.
272
   */
273
  protected function getFormStateKey(FieldItemListInterface $items) {
274
    return $items->getEntity()->uuid() . ':' . $items->getFieldDefinition()->getName();
275
  }
276
277
  /**
278
   * {@inheritdoc}
279
   */
280
  public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
281
    $entity_type = $this->fieldDefinition->getFieldStorageDefinition()->getSetting('target_type');
282
    $entity_storage = $this->entityTypeManager->getStorage($entity_type);
283
284
    $entities = [];
285
286
    // Determine if we're submitting and if submit came from this widget.
287
    $is_relevant_submit = FALSE;
288
    if (($trigger = $form_state->getTriggeringElement())) {
289
      // Can be triggered by hidden target_id element or "Remove" button.
290
      if (end($trigger['#parents']) === 'target_id' || (end($trigger['#parents']) === 'remove_button')) {
291
        $is_relevant_submit = TRUE;
292
293
        // In case there are more instances of this widget on the same page we
294
        // need to check if submit came from this instance.
295
        $field_name_key = end($trigger['#parents']) === 'target_id' ? 2 : static::$deleteDepth + 1;
296
        $field_name_key = count($trigger['#parents']) - $field_name_key;
297
        $is_relevant_submit &= ($trigger['#parents'][$field_name_key] === $this->fieldDefinition->getName()) &&
298
          (array_slice($trigger['#parents'], 0, count($element['#field_parents'])) == $element['#field_parents']);
299
      }
300
    };
301
302
    if ($is_relevant_submit) {
303
      // Submit was triggered by hidden "target_id" element when entities were
304
      // added via entity browser.
305 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...
306
        $parents = $trigger['#parents'];
307
      }
308
      // Submit was triggered by one of the "Remove" buttons. We need to walk
309
      // few levels up to read value of "target_id" element.
310
      elseif ($trigger['#type'] == 'submit' && strpos($trigger['#name'], $this->fieldDefinition->getName() . '_remove_') === 0) {
311
        $parents = array_merge(array_slice($trigger['#parents'], 0, -static::$deleteDepth), ['target_id']);
312
      }
313
314
      if (isset($parents) && $value = $form_state->getValue($parents)) {
315
        $entities = EntityBrowserElement::processEntityIds($value);
316
      }
317
    }
318
    // IDs from a previous request might be saved in the form state.
319
    elseif ($form_state->has(['entity_browser_widget', $this->getFormStateKey($items)])) {
320
      $stored_ids = $form_state->get(['entity_browser_widget', $this->getFormStateKey($items)]);
321
      $indexed_entities = $entity_storage->loadMultiple($stored_ids);
322
323
      // Selection can contain same entity multiple times. Since loadMultiple()
324
      // returns unique list of entities, it's necessary to recreate list of
325
      // entities in order to preserve selection of duplicated entities.
326
      foreach ($stored_ids as $entity_id) {
327
        if (isset($indexed_entities[$entity_id])) {
328
          $entities[] = $indexed_entities[$entity_id];
329
        }
330
      }
331
    }
332
    // We are loading for for the first time so we need to load any existing
333
    // values that might already exist on the entity. Also, remove any leftover
334
    // data from removed entity references.
335
    else {
336
      foreach ($items as $item) {
337
        if (isset($item->target_id)) {
338
          $entity = $entity_storage->load($item->target_id);
339
          if (!empty($entity)) {
340
            $entities[] = $entity;
341
          }
342
        }
343
      }
344
    }
345
346
    // Get correct ordered list of entity IDs.
347
    $ids = array_map(
348
      function (EntityInterface $entity) {
349
        return $entity->id();
350
      },
351
      $entities
352
    );
353
354
    // We store current entity IDs as we might need them in future requests. If
355
    // some other part of the form triggers an AJAX request with
356
    // #limit_validation_errors we won't have access to the value of the
357
    // target_id element and won't be able to build the form as a result of
358
    // that. This will cause missing submit (Remove, Edit, ...) elements, which
359
    // might result in unpredictable results.
360
    $form_state->set(['entity_browser_widget', $this->getFormStateKey($items)], $ids);
361
362
    $hidden_id = Html::getUniqueId('edit-' . $this->fieldDefinition->getName() . '-target-id');
363
    $details_id = Html::getUniqueId('edit-' . $this->fieldDefinition->getName());
364
365
    $element += [
366
      '#id' => $details_id,
367
      '#type' => 'details',
368
      '#open' => !empty($entities) || $this->getSetting('open'),
369
      '#required' => $this->fieldDefinition->isRequired(),
370
      // We are not using Entity browser's hidden element since we maintain
371
      // selected entities in it during entire process.
372
      'target_id' => [
373
        '#type' => 'hidden',
374
        '#id' => $hidden_id,
375
        // We need to repeat ID here as it is otherwise skipped when rendering.
376
        '#attributes' => ['id' => $hidden_id],
377
        '#default_value' => implode(' ', array_map(
378
            function (EntityInterface $item) {
379
              return $item->getEntityTypeId() . ':' . $item->id();
380
            },
381
            $entities
382
        )),
383
        // #ajax is officially not supported for hidden elements but if we
384
        // specify event manually it works.
385
        '#ajax' => [
386
          'callback' => [get_class($this), 'updateWidgetCallback'],
387
          'wrapper' => $details_id,
388
          'event' => 'entity_browser_value_updated',
389
        ],
390
      ],
391
    ];
392
393
    // Get configuration required to check entity browser availability.
394
    $cardinality = $this->fieldDefinition->getFieldStorageDefinition()->getCardinality();
395
    $selection_mode = $this->getSetting('selection_mode');
396
397
    // Enable entity browser if requirements for that are fulfilled.
398
    if (EntityBrowserElement::isEntityBrowserAvailable($selection_mode, $cardinality, count($ids))) {
399
      $element['entity_browser'] = [
400
        '#type' => 'entity_browser',
401
        '#entity_browser' => $this->getSetting('entity_browser'),
402
        '#cardinality' => $cardinality,
403
        '#selection_mode' => $selection_mode,
404
        '#default_value' => $entities,
405
        '#entity_browser_validators' => ['entity_type' => ['type' => $entity_type]],
406
        '#custom_hidden_id' => $hidden_id,
407
        '#process' => [
408
          ['\Drupal\entity_browser\Element\EntityBrowserElement', 'processEntityBrowser'],
409
          [get_called_class(), 'processEntityBrowser'],
410
        ],
411
      ];
412
413
      $element['#attached']['library'][] = 'entity_browser/entity_reference';
414
    }
415
416
    $field_parents = $element['#field_parents'];
417
418
    $element['current'] = $this->displayCurrentSelection($details_id, $field_parents, $entities);
419
420
    return $element;
421
  }
422
423
  /**
424
   * Render API callback: Processes the entity browser element.
425
   */
426
  public static function processEntityBrowser(&$element, FormStateInterface $form_state, &$complete_form) {
427
    $uuid = key($element['#attached']['drupalSettings']['entity_browser']);
428
    $element['#attached']['drupalSettings']['entity_browser'][$uuid]['selector'] = '#' . $element['#custom_hidden_id'];
429
    return $element;
430
  }
431
432
  /**
433
   * {@inheritdoc}
434
   */
435
  public function massageFormValues(array $values, array $form, FormStateInterface $form_state) {
436
    $entities = empty($values['target_id']) ? [] : explode(' ', trim($values['target_id']));
437
    $return = [];
438
    foreach ($entities as $entity) {
439
      $return[]['target_id'] = explode(':', $entity)[1];
440
    }
441
442
    return $return;
443
  }
444
445
  /**
446
   * AJAX form callback.
447
   */
448
  public static function updateWidgetCallback(array &$form, FormStateInterface $form_state) {
449
    $trigger = $form_state->getTriggeringElement();
450
    // AJAX requests can be triggered by hidden "target_id" element when
451
    // entities are added or by one of the "Remove" buttons. Depending on that
452
    // we need to figure out where root of the widget is in the form structure
453
    // and use this information to return correct part of the form.
454 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...
455
      $parents = array_slice($trigger['#array_parents'], 0, -1);
456
    }
457
    elseif ($trigger['#type'] == 'submit' && strpos($trigger['#name'], '_remove_')) {
458
      $parents = array_slice($trigger['#array_parents'], 0, -static::$deleteDepth);
459
    }
460
461
    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...
462
  }
463
464
  /**
465
   * {@inheritdoc}
466
   */
467
  public function errorElement(array $element, ConstraintViolationInterface $violation, array $form, FormStateInterface $form_state) {
468
    if (($trigger = $form_state->getTriggeringElement())) {
469
      // Can be triggered by "Remove" button.
470
      if (end($trigger['#parents']) === 'remove_button') {
471
        return FALSE;
472
      }
473
    }
474
    return parent::errorElement($element, $violation, $form, $form_state);
475
  }
476
477
  /**
478
   * Submit callback for remove buttons.
479
   */
480
  public static function removeItemSubmit(&$form, FormStateInterface $form_state) {
481
    $triggering_element = $form_state->getTriggeringElement();
482
    if (!empty($triggering_element['#attributes']['data-entity-id']) && isset($triggering_element['#attributes']['data-row-id'])) {
483
      $id = $triggering_element['#attributes']['data-entity-id'];
484
      $row_id = $triggering_element['#attributes']['data-row-id'];
485
      $parents = array_slice($triggering_element['#parents'], 0, -static::$deleteDepth);
486
      $array_parents = array_slice($triggering_element['#array_parents'], 0, -static::$deleteDepth);
487
488
      // Find and remove correct entity.
489
      $values = explode(' ', $form_state->getValue(array_merge($parents, ['target_id'])));
490
      foreach ($values as $index => $item) {
491
        if ($item == $id && $index == $row_id) {
492
          array_splice($values, $index, 1);
493
494
          break;
495
        }
496
      }
497
      $target_id_value = implode(' ', $values);
498
499
      // Set new value for this widget.
500
      $target_id_element = &NestedArray::getValue($form, array_merge($array_parents, ['target_id']));
501
      $form_state->setValueForElement($target_id_element, $target_id_value);
502
      NestedArray::setValue($form_state->getUserInput(), $target_id_element['#parents'], $target_id_value);
503
504
      // Rebuild form.
505
      $form_state->setRebuild();
506
    }
507
  }
508
509
  /**
510
   * Builds the render array for displaying the current results.
511
   *
512
   * @param string $details_id
513
   *   The ID for the details element.
514
   * @param string[] $field_parents
515
   *   Field parents.
516
   * @param \Drupal\Core\Entity\ContentEntityInterface[] $entities
517
   *   Array of referenced entities.
518
   *
519
   * @return array
520
   *   The render array for the current selection.
521
   */
522
  protected function displayCurrentSelection($details_id, $field_parents, $entities) {
523
524
    $field_widget_display = $this->fieldDisplayManager->createInstance(
525
      $this->getSetting('field_widget_display'),
526
      $this->getSetting('field_widget_display_settings') + ['entity_type' => $this->fieldDefinition->getFieldStorageDefinition()->getSetting('target_type')]
527
    );
528
529
    return [
530
      '#theme_wrappers' => ['container'],
531
      '#attributes' => ['class' => ['entities-list']],
532
      'items' => array_map(
533
        function (ContentEntityInterface $entity, $row_id) use ($field_widget_display, $details_id, $field_parents) {
534
          $display = $field_widget_display->view($entity);
535
          if (is_string($display)) {
536
            $display = ['#markup' => $display];
537
          }
538
          return [
539
            '#theme_wrappers' => ['container'],
540
            '#attributes' => [
541
              'class' => ['item-container', Html::getClass($field_widget_display->getPluginId())],
542
              'data-entity-id' => $entity->getEntityTypeId() . ':' . $entity->id(),
543
              'data-row-id' => $row_id,
544
            ],
545
            'display' => $display,
546
            'remove_button' => [
547
              '#type' => 'submit',
548
              '#value' => $this->t('Remove'),
549
              '#ajax' => [
550
                'callback' => [get_class($this), 'updateWidgetCallback'],
551
                'wrapper' => $details_id,
552
              ],
553
              '#submit' => [[get_class($this), 'removeItemSubmit']],
554
              '#name' => $this->fieldDefinition->getName() . '_remove_' . $entity->id() . '_' . $row_id,
555
              '#limit_validation_errors' => [array_merge($field_parents, [$this->fieldDefinition->getName()])],
556
              '#attributes' => [
557
                'data-entity-id' => $entity->getEntityTypeId() . ':' . $entity->id(),
558
                'data-row-id' => $row_id,
559
              ],
560
              '#access' => (bool) $this->getSetting('field_widget_remove'),
561
            ],
562
            'edit_button' => [
563
              '#type' => 'submit',
564
              '#value' => $this->t('Edit'),
565
              '#ajax' => [
566
                'url' => Url::fromRoute(
567
                  'entity_browser.edit_form', [
568
                    'entity_type' => $entity->getEntityTypeId(),
569
                    'entity' => $entity->id(),
570
                  ]
571
                ),
572
                'options' => [
573
                  'query' => [
574
                    'details_id' => $details_id,
575
                  ],
576
                ],
577
              ],
578
              '#access' => (bool) $this->getSetting('field_widget_edit'),
579
            ],
580
          ];
581
        },
582
        $entities,
583
        empty($entities) ? [] : range(0, count($entities) - 1)
584
      ),
585
    ];
586
  }
587
588
  /**
589
   * Gets data that should persist across Entity Browser renders.
590
   *
591
   * @return array
592
   *   Data that should persist after the Entity Browser is rendered.
593
   */
594
  protected function getPersistentData() {
595
    return [
596
      'validators' => [
597
        'entity_type' => ['type' => $this->fieldDefinition->getFieldStorageDefinition()->getSetting('target_type')],
598
      ],
599
    ];
600
  }
601
602
  /**
603
   * Gets options that define where newly added entities are inserted.
604
   *
605
   * @return array
606
   *   Mode labels indexed by key.
607
   */
608
  protected function selectionModeOptions() {
609
    return ['append' => $this->t('Append'), 'prepend' => $this->t('Prepend')];
610
  }
611
612
  /**
613
   * Provides base for settings summary shared by all EB widgets.
614
   *
615
   * @return array
616
   *   A short summary of the widget settings.
617
   */
618
  protected function summaryBase() {
619
    $summary = [];
620
621
    $entity_browser_id = $this->getSetting('entity_browser');
622
    if (empty($entity_browser_id)) {
623
      return [$this->t('No entity browser selected.')];
624
    }
625
    else {
626
      if ($browser = $this->entityTypeManager->getStorage('entity_browser')->load($entity_browser_id)) {
627
        $summary[] = $this->t('Entity browser: @browser', ['@browser' => $browser->label()]);
628
      }
629
      else {
630
        drupal_set_message($this->t('Missing entity browser!'), 'error');
631
        return [$this->t('Missing entity browser!')];
632
      }
633
    }
634
635
    $selection_mode = $this->getSetting('selection_mode');
636
    $selection_mode_options = EntityBrowserElement::getSelectionModeOptions();
637
    if (isset($selection_mode_options[$selection_mode])) {
638
      $summary[] = $this->t('Selection mode: @selection_mode', ['@selection_mode' => $selection_mode_options[$selection_mode]]);
639
    }
640
    else {
641
      $summary[] = $this->t('Undefined selection mode.');
642
    }
643
644
    return $summary;
645
  }
646
647
}
648