Completed
Push — 8.x-1.x ( ffa2fa...56e091 )
by Janez
02:43
created

displayCurrentSelection()   B

Complexity

Conditions 4
Paths 1

Size

Total Lines 72
Code Lines 49

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 49
nc 1
nop 3
dl 0
loc 72
rs 8.7033
c 0
b 0
f 0

How to fix   Long Method   

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 Drupal\Core\Entity\EntityInterface;
6
use Drupal\entity_browser\Element\EntityBrowserElement;
7
use Symfony\Component\Validator\ConstraintViolationInterface;
8
use Drupal\Component\Utility\Html;
9
use Drupal\Component\Utility\NestedArray;
10
use Drupal\Core\Entity\ContentEntityInterface;
11
use Drupal\Core\Entity\EntityTypeManagerInterface;
12
use Drupal\Core\Field\FieldDefinitionInterface;
13
use Drupal\Core\Field\FieldItemListInterface;
14
use Drupal\Core\Field\WidgetBase;
15
use Drupal\Core\Form\FormStateInterface;
16
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
17
use Drupal\Core\Url;
18
use Drupal\Core\Validation\Plugin\Validation\Constraint\NotNullConstraint;
19
use Drupal\entity_browser\FieldWidgetDisplayManager;
20
use Symfony\Component\DependencyInjection\ContainerInterface;
21
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
22
use Symfony\Component\Validator\ConstraintViolation;
23
use Symfony\Component\Validator\ConstraintViolationListInterface;
24
use Drupal\Core\Extension\ModuleHandlerInterface;
25
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
   * The module handler interface.
66
   *
67
   * @var \Drupal\Core\Extension\ModuleHandlerInterface
68
   */
69
  protected $moduleHandler;
70
71
  /**
72
   * Constructs widget plugin.
73
   *
74
   * @param string $plugin_id
75
   *   The plugin_id for the plugin instance.
76
   * @param mixed $plugin_definition
77
   *   The plugin implementation definition.
78
   * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
79
   *   The definition of the field to which the widget is associated.
80
   * @param array $settings
81
   *   The widget settings.
82
   * @param array $third_party_settings
83
   *   Any third party settings.
84
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
85
   *   Entity type manager service.
86
   * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
87
   *   Event dispatcher.
88
   * @param \Drupal\entity_browser\FieldWidgetDisplayManager $field_display_manager
89
   *   Field widget display plugin manager.
90
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
91
   *   The module handler service.
92
   */
93
  public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, EntityTypeManagerInterface $entity_type_manager, EventDispatcherInterface $event_dispatcher, FieldWidgetDisplayManager $field_display_manager, ModuleHandlerInterface $module_handler) {
94
    parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings);
95
    $this->entityTypeManager = $entity_type_manager;
96
    $this->fieldDisplayManager = $field_display_manager;
97
    $this->moduleHandler = $module_handler;
98
  }
99
100
  /**
101
   * {@inheritdoc}
102
   */
103
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
104
    return new static(
105
      $plugin_id,
106
      $plugin_definition,
107
      $configuration['field_definition'],
108
      $configuration['settings'],
109
      $configuration['third_party_settings'],
110
      $container->get('entity_type.manager'),
111
      $container->get('event_dispatcher'),
112
      $container->get('plugin.manager.entity_browser.field_widget_display'),
113
      $container->get('module_handler')
114
    );
115
  }
116
117
  /**
118
   * {@inheritdoc}
119
   */
120
  public static function defaultSettings() {
121
    return array(
122
      'entity_browser' => NULL,
123
      'open' => FALSE,
124
      'field_widget_display' => NULL,
125
      'field_widget_edit' => TRUE,
126
      'field_widget_remove' => TRUE,
127
      'field_widget_display_settings' => [],
128
      'selection_mode' => EntityBrowserElement::SELECTION_MODE_APPEND,
129
    ) + parent::defaultSettings();
130
  }
131
132
  /**
133
   * {@inheritdoc}
134
   */
135
  public function settingsForm(array $form, FormStateInterface $form_state) {
136
    $element = parent::settingsForm($form, $form_state);
137
138
    $browsers = [];
139
    /** @var \Drupal\entity_browser\EntityBrowserInterface $browser */
140
    foreach ($this->entityTypeManager->getStorage('entity_browser')->loadMultiple() as $browser) {
141
      $browsers[$browser->id()] = $browser->label();
142
    }
143
144
    $element['entity_browser'] = [
145
      '#title' => $this->t('Entity browser'),
146
      '#type' => 'select',
147
      '#default_value' => $this->getSetting('entity_browser'),
148
      '#options' => $browsers,
149
    ];
150
151
    $target_type = $this->fieldDefinition->getFieldStorageDefinition()->getSetting('target_type');
152
    $entity_type = $this->entityTypeManager->getStorage($target_type)->getEntityType();
153
154
    $displays = [];
155
    foreach ($this->fieldDisplayManager->getDefinitions() as $id => $definition) {
156
      if ($this->fieldDisplayManager->createInstance($id)->isApplicable($entity_type)) {
157
        $displays[$id] = $definition['label'];
158
      }
159
    }
160
161
    $id = Html::getUniqueId('field-' . $this->fieldDefinition->getName() . '-display-settings-wrapper');
162
    $element['field_widget_display'] = [
163
      '#title' => $this->t('Entity display plugin'),
164
      '#type' => 'select',
165
      '#default_value' => $this->getSetting('field_widget_display'),
166
      '#options' => $displays,
167
      '#ajax' => [
168
        'callback' => array($this, 'updateSettingsAjax'),
169
        'wrapper' => $id,
170
      ],
171
    ];
172
173
    $edit_button_access = TRUE;
174
    if ($entity_type->id() == 'file') {
175
      // For entities of type "file", it only makes sense to have the edit
176
      // button if the module "file_entity" is present.
177
      $edit_button_access = $this->moduleHandler->moduleExists('file_entity');
178
    }
179
    $element['field_widget_edit'] = [
180
      '#title' => $this->t('Display Edit button'),
181
      '#type' => 'checkbox',
182
      '#default_value' => $this->getSetting('field_widget_edit'),
183
      '#access' => $edit_button_access,
184
    ];
185
186
    $element['field_widget_remove'] = [
187
      '#title' => $this->t('Display Remove button'),
188
      '#type' => 'checkbox',
189
      '#default_value' => $this->getSetting('field_widget_remove'),
190
    ];
191
192
    $element['open'] = [
193
      '#title' => $this->t('Show widget details as open by default'),
194
      '#description' => $this->t('If marked, the fieldset container that wraps the browser on the entity form will be loaded initially expanded.'),
195
      '#type' => 'checkbox',
196
      '#default_value' => $this->getSetting('open'),
197
    ];
198
199
    $element['selection_mode'] = [
200
      '#title' => $this->t('Selection mode'),
201
      '#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.'),
202
      '#type' => 'select',
203
      '#options' => EntityBrowserElement::getSelectionModeOptions(),
204
      '#default_value' => $this->getSetting('selection_mode'),
205
    ];
206
207
    $element['field_widget_display_settings'] = [
208
      '#type' => 'fieldset',
209
      '#title' => $this->t('Entity display plugin configuration'),
210
      '#tree' => TRUE,
211
      '#prefix' => '<div id="' . $id . '">',
212
      '#suffix' => '</div>',
213
    ];
214
215
    if ($this->getSetting('field_widget_display')) {
216
      $element['field_widget_display_settings'] += $this->fieldDisplayManager
217
        ->createInstance(
218
          $form_state->getValue(
219
            ['fields', $this->fieldDefinition->getName(), 'settings_edit_form', 'settings', 'field_widget_display'],
220
            $this->getSetting('field_widget_display')
221
          ),
222
          $form_state->getValue(
223
            ['fields', $this->fieldDefinition->getName(), 'settings_edit_form', 'settings', 'field_widget_display_settings'],
224
            $this->getSetting('field_widget_display_settings')
225
          ) + ['entity_type' => $this->fieldDefinition->getFieldStorageDefinition()->getSetting('target_type')]
226
        )
227
        ->settingsForm($form, $form_state);
228
    }
229
230
    return $element;
231
  }
232
233
  /**
234
   * Ajax callback that updates field widget display settings fieldset.
235
   */
236
  public function updateSettingsAjax(array $form, FormStateInterface $form_state) {
237
    return $form['fields'][$this->fieldDefinition->getName()]['plugin']['settings_edit_form']['settings']['field_widget_display_settings'];
238
  }
239
240
  /**
241
   * {@inheritdoc}
242
   */
243
  public function settingsSummary() {
244
    $summary = $this->summaryBase();
245
    $field_widget_display = $this->getSetting('field_widget_display');
246
247
    if (!empty($field_widget_display)) {
248
      $plugin = $this->fieldDisplayManager->getDefinition($field_widget_display);
249
      $summary[] = $this->t('Entity display: @name', ['@name' => $plugin['label']]);
250
    }
251
    return $summary;
252
  }
253
254
  /**
255
   * {@inheritdoc}
256
   */
257
  public function flagErrors(FieldItemListInterface $items, ConstraintViolationListInterface $violations, array $form, FormStateInterface $form_state) {
258
    if ($violations->count() > 0) {
259
      /** @var \Symfony\Component\Validator\ConstraintViolation $violation */
260
      foreach ($violations as $offset => $violation) {
261
        // The value of the required field is checked through the "not null"
262
        // constraint, whose message is not very useful. We override it here for
263
        // better UX.
264
        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...
265
          $violations->set($offset, new ConstraintViolation(
266
            $this->t('@name field is required.', ['@name' => $items->getFieldDefinition()->getLabel()]),
267
            '',
268
            [],
269
            $violation->getRoot(),
270
            $violation->getPropertyPath(),
271
            $violation->getInvalidValue(),
272
            $violation->getPlural(),
273
            $violation->getCode(),
274
            $violation->getConstraint(),
275
            $violation->getCause()
276
          ));
277
        }
278
      }
279
    }
280
281
    parent::flagErrors($items, $violations, $form, $form_state);
282
  }
283
284
  /**
285
   * Returns a key used to store the previously loaded entity.
286
   *
287
   * @param \Drupal\Core\Field\FieldItemListInterface $items
288
   *   The field items.
289
   *
290
   * @return string
291
   *   A key for form state storage.
292
   */
293
  protected function getFormStateKey(FieldItemListInterface $items) {
294
    return $items->getEntity()->uuid() . ':' . $items->getFieldDefinition()->getName();
295
  }
296
297
  /**
298
   * {@inheritdoc}
299
   */
300
  public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
301
    $entity_type = $this->fieldDefinition->getFieldStorageDefinition()->getSetting('target_type');
302
    $entities = $this->formElementEntities($items, $element, $form_state);
303
304
    // Get correct ordered list of entity IDs.
305
    $ids = array_map(
306
      function (EntityInterface $entity) {
307
        return $entity->id();
308
      },
309
      $entities
310
    );
311
312
    // We store current entity IDs as we might need them in future requests. If
313
    // some other part of the form triggers an AJAX request with
314
    // #limit_validation_errors we won't have access to the value of the
315
    // target_id element and won't be able to build the form as a result of
316
    // that. This will cause missing submit (Remove, Edit, ...) elements, which
317
    // might result in unpredictable results.
318
    $form_state->set(['entity_browser_widget', $this->getFormStateKey($items)], $ids);
319
320
    $hidden_id = Html::getUniqueId('edit-' . $this->fieldDefinition->getName() . '-target-id');
321
    $details_id = Html::getUniqueId('edit-' . $this->fieldDefinition->getName());
322
323
    $element += [
324
      '#id' => $details_id,
325
      '#type' => 'details',
326
      '#open' => !empty($entities) || $this->getSetting('open'),
327
      '#required' => $this->fieldDefinition->isRequired(),
328
      // We are not using Entity browser's hidden element since we maintain
329
      // selected entities in it during entire process.
330
      'target_id' => [
331
        '#type' => 'hidden',
332
        '#id' => $hidden_id,
333
        // We need to repeat ID here as it is otherwise skipped when rendering.
334
        '#attributes' => ['id' => $hidden_id],
335
        '#default_value' => implode(' ', array_map(
336
            function (EntityInterface $item) {
337
              return $item->getEntityTypeId() . ':' . $item->id();
338
            },
339
            $entities
340
        )),
341
        // #ajax is officially not supported for hidden elements but if we
342
        // specify event manually it works.
343
        '#ajax' => [
344
          'callback' => [get_class($this), 'updateWidgetCallback'],
345
          'wrapper' => $details_id,
346
          'event' => 'entity_browser_value_updated',
347
        ],
348
      ],
349
    ];
350
351
    // Get configuration required to check entity browser availability.
352
    $cardinality = $this->fieldDefinition->getFieldStorageDefinition()->getCardinality();
353
    $selection_mode = $this->getSetting('selection_mode');
354
355
    // Enable entity browser if requirements for that are fulfilled.
356
    if (EntityBrowserElement::isEntityBrowserAvailable($selection_mode, $cardinality, count($ids))) {
357
      $element['entity_browser'] = [
358
        '#type' => 'entity_browser',
359
        '#entity_browser' => $this->getSetting('entity_browser'),
360
        '#cardinality' => $cardinality,
361
        '#selection_mode' => $selection_mode,
362
        '#default_value' => $entities,
363
        '#entity_browser_validators' => ['entity_type' => ['type' => $entity_type]],
364
        '#custom_hidden_id' => $hidden_id,
365
        '#process' => [
366
          ['\Drupal\entity_browser\Element\EntityBrowserElement', 'processEntityBrowser'],
367
          [get_called_class(), 'processEntityBrowser'],
368
        ],
369
      ];
370
371
      $element['#attached']['library'][] = 'entity_browser/entity_reference';
372
    }
373
374
    $field_parents = $element['#field_parents'];
375
376
    $element['current'] = $this->displayCurrentSelection($details_id, $field_parents, $entities);
377
378
    return $element;
379
  }
380
381
  /**
382
   * Render API callback: Processes the entity browser element.
383
   */
384
  public static function processEntityBrowser(&$element, FormStateInterface $form_state, &$complete_form) {
385
    $uuid = key($element['#attached']['drupalSettings']['entity_browser']);
386
    $element['#attached']['drupalSettings']['entity_browser'][$uuid]['selector'] = '#' . $element['#custom_hidden_id'];
387
    return $element;
388
  }
389
390
  /**
391
   * {@inheritdoc}
392
   */
393
  public function massageFormValues(array $values, array $form, FormStateInterface $form_state) {
394
    $entities = empty($values['target_id']) ? [] : explode(' ', trim($values['target_id']));
395
    $return = [];
396
    foreach ($entities as $entity) {
397
      $return[]['target_id'] = explode(':', $entity)[1];
398
    }
399
400
    return $return;
401
  }
402
403
  /**
404
   * AJAX form callback.
405
   */
406
  public static function updateWidgetCallback(array &$form, FormStateInterface $form_state) {
407
    $trigger = $form_state->getTriggeringElement();
408
    // AJAX requests can be triggered by hidden "target_id" element when
409
    // entities are added or by one of the "Remove" buttons. Depending on that
410
    // we need to figure out where root of the widget is in the form structure
411
    // and use this information to return correct part of the form.
412 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...
413
      $parents = array_slice($trigger['#array_parents'], 0, -1);
414
    }
415
    elseif ($trigger['#type'] == 'submit' && strpos($trigger['#name'], '_remove_')) {
416
      $parents = array_slice($trigger['#array_parents'], 0, -static::$deleteDepth);
417
    }
418
419
    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...
420
  }
421
422
  /**
423
   * {@inheritdoc}
424
   */
425
  public function errorElement(array $element, ConstraintViolationInterface $violation, array $form, FormStateInterface $form_state) {
426
    if (($trigger = $form_state->getTriggeringElement())) {
427
      // Can be triggered by "Remove" button.
428
      if (end($trigger['#parents']) === 'remove_button') {
429
        return FALSE;
430
      }
431
    }
432
    return parent::errorElement($element, $violation, $form, $form_state);
433
  }
434
435
  /**
436
   * Submit callback for remove buttons.
437
   */
438
  public static function removeItemSubmit(&$form, FormStateInterface $form_state) {
439
    $triggering_element = $form_state->getTriggeringElement();
440
    if (!empty($triggering_element['#attributes']['data-entity-id']) && isset($triggering_element['#attributes']['data-row-id'])) {
441
      $id = $triggering_element['#attributes']['data-entity-id'];
442
      $row_id = $triggering_element['#attributes']['data-row-id'];
443
      $parents = array_slice($triggering_element['#parents'], 0, -static::$deleteDepth);
444
      $array_parents = array_slice($triggering_element['#array_parents'], 0, -static::$deleteDepth);
445
446
      // Find and remove correct entity.
447
      $values = explode(' ', $form_state->getValue(array_merge($parents, ['target_id'])));
448
      foreach ($values as $index => $item) {
449
        if ($item == $id && $index == $row_id) {
450
          array_splice($values, $index, 1);
451
452
          break;
453
        }
454
      }
455
      $target_id_value = implode(' ', $values);
456
457
      // Set new value for this widget.
458
      $target_id_element = &NestedArray::getValue($form, array_merge($array_parents, ['target_id']));
459
      $form_state->setValueForElement($target_id_element, $target_id_value);
460
      NestedArray::setValue($form_state->getUserInput(), $target_id_element['#parents'], $target_id_value);
461
462
      // Rebuild form.
463
      $form_state->setRebuild();
464
    }
465
  }
466
467
  /**
468
   * Builds the render array for displaying the current results.
469
   *
470
   * @param string $details_id
471
   *   The ID for the details element.
472
   * @param string[] $field_parents
473
   *   Field parents.
474
   * @param \Drupal\Core\Entity\ContentEntityInterface[] $entities
475
   *   Array of referenced entities.
476
   *
477
   * @return array
478
   *   The render array for the current selection.
479
   */
480
  protected function displayCurrentSelection($details_id, $field_parents, $entities) {
481
482
    $field_widget_display = $this->fieldDisplayManager->createInstance(
483
      $this->getSetting('field_widget_display'),
484
      $this->getSetting('field_widget_display_settings') + ['entity_type' => $this->fieldDefinition->getFieldStorageDefinition()->getSetting('target_type')]
485
    );
486
487
    return [
488
      '#theme_wrappers' => ['container'],
489
      '#attributes' => ['class' => ['entities-list']],
490
      'items' => array_map(
491
        function (ContentEntityInterface $entity, $row_id) use ($field_widget_display, $details_id, $field_parents) {
492
          $display = $field_widget_display->view($entity);
493
          $edit_button_access = $this->getSetting('field_widget_edit');
494
          if ($entity->getEntityTypeId() == 'file') {
495
            // On file entities, the "edit" button shouldn't be visible unless
496
            // the module "file_entity" is present, which will allow them to be
497
            // edited on their own form.
498
            $edit_button_access &= $this->moduleHandler->moduleExists('file_entity');
499
          }
500
          if (is_string($display)) {
501
            $display = ['#markup' => $display];
502
          }
503
          return [
504
            '#theme_wrappers' => ['container'],
505
            '#attributes' => [
506
              'class' => ['item-container', Html::getClass($field_widget_display->getPluginId())],
507
              'data-entity-id' => $entity->getEntityTypeId() . ':' . $entity->id(),
508
              'data-row-id' => $row_id,
509
            ],
510
            'display' => $display,
511
            'remove_button' => [
512
              '#type' => 'submit',
513
              '#value' => $this->t('Remove'),
514
              '#ajax' => [
515
                'callback' => [get_class($this), 'updateWidgetCallback'],
516
                'wrapper' => $details_id,
517
              ],
518
              '#submit' => [[get_class($this), 'removeItemSubmit']],
519
              '#name' => $this->fieldDefinition->getName() . '_remove_' . $entity->id() . '_' . $row_id . '_' . md5(json_encode($field_parents)),
520
              '#limit_validation_errors' => [array_merge($field_parents, [$this->fieldDefinition->getName()])],
521
              '#attributes' => [
522
                'data-entity-id' => $entity->getEntityTypeId() . ':' . $entity->id(),
523
                'data-row-id' => $row_id,
524
              ],
525
              '#access' => (bool) $this->getSetting('field_widget_remove'),
526
            ],
527
            'edit_button' => [
528
              '#type' => 'submit',
529
              '#value' => $this->t('Edit'),
530
              '#ajax' => [
531
                'url' => Url::fromRoute(
532
                  'entity_browser.edit_form', [
533
                    'entity_type' => $entity->getEntityTypeId(),
534
                    'entity' => $entity->id(),
535
                  ]
536
                ),
537
                'options' => [
538
                  'query' => [
539
                    'details_id' => $details_id,
540
                  ],
541
                ],
542
              ],
543
              '#access' => $edit_button_access,
544
            ],
545
          ];
546
        },
547
        $entities,
548
        empty($entities) ? [] : range(0, count($entities) - 1)
549
      ),
550
    ];
551
  }
552
553
  /**
554
   * Gets data that should persist across Entity Browser renders.
555
   *
556
   * @return array
557
   *   Data that should persist after the Entity Browser is rendered.
558
   */
559
  protected function getPersistentData() {
560
    return [
561
      'validators' => [
562
        'entity_type' => ['type' => $this->fieldDefinition->getFieldStorageDefinition()->getSetting('target_type')],
563
      ],
564
    ];
565
  }
566
567
  /**
568
   * Gets options that define where newly added entities are inserted.
569
   *
570
   * @return array
571
   *   Mode labels indexed by key.
572
   */
573
  protected function selectionModeOptions() {
574
    return ['append' => $this->t('Append'), 'prepend' => $this->t('Prepend')];
575
  }
576
577
  /**
578
   * Provides base for settings summary shared by all EB widgets.
579
   *
580
   * @return array
581
   *   A short summary of the widget settings.
582
   */
583
  protected function summaryBase() {
584
    $summary = [];
585
586
    $entity_browser_id = $this->getSetting('entity_browser');
587
    if (empty($entity_browser_id)) {
588
      return [$this->t('No entity browser selected.')];
589
    }
590
    else {
591
      if ($browser = $this->entityTypeManager->getStorage('entity_browser')->load($entity_browser_id)) {
592
        $summary[] = $this->t('Entity browser: @browser', ['@browser' => $browser->label()]);
593
      }
594
      else {
595
        drupal_set_message($this->t('Missing entity browser!'), 'error');
596
        return [$this->t('Missing entity browser!')];
597
      }
598
    }
599
600
    $selection_mode = $this->getSetting('selection_mode');
601
    $selection_mode_options = EntityBrowserElement::getSelectionModeOptions();
602
    if (isset($selection_mode_options[$selection_mode])) {
603
      $summary[] = $this->t('Selection mode: @selection_mode', ['@selection_mode' => $selection_mode_options[$selection_mode]]);
604
    }
605
    else {
606
      $summary[] = $this->t('Undefined selection mode.');
607
    }
608
609
    return $summary;
610
  }
611
612
  /**
613
   * Determines the entities used for the form element.
614
   *
615
   * @param \Drupal\Core\Field\FieldItemListInterface $items
616
   *   The field item to extract the entities from.
617
   * @param array $element
618
   *   The form element.
619
   * @param \Drupal\Core\Form\FormStateInterface $form_state
620
   *   The form state.
621
   *
622
   * @return \Drupal\Core\Entity\EntityInterface[]
623
   *   The list of entities for the form element.
624
   */
625
  protected function formElementEntities(FieldItemListInterface $items, array $element, FormStateInterface $form_state) {
626
    $entities = [];
627
    $entity_type = $this->fieldDefinition->getFieldStorageDefinition()->getSetting('target_type');
628
    $entity_storage = $this->entityTypeManager->getStorage($entity_type);
629
630
    // Find IDs from target_id element (it stores selected entities in form).
631
    // This was added to help solve a really edge casey bug in IEF.
632
    if (($target_id_entities = $this->getEntitiesByTargetId($element, $form_state)) !== FALSE) {
633
      return $target_id_entities;
634
    }
635
636
    // Determine if we're submitting and if submit came from this widget.
637
    $is_relevant_submit = FALSE;
638
    if (($trigger = $form_state->getTriggeringElement())) {
639
      // Can be triggered by hidden target_id element or "Remove" button.
640
      if (end($trigger['#parents']) === 'target_id' || (end($trigger['#parents']) === 'remove_button')) {
641
        $is_relevant_submit = TRUE;
642
643
        // In case there are more instances of this widget on the same page we
644
        // need to check if submit came from this instance.
645
        $field_name_key = end($trigger['#parents']) === 'target_id' ? 2 : static::$deleteDepth + 1;
646
        $field_name_key = count($trigger['#parents']) - $field_name_key;
647
        $is_relevant_submit &= ($trigger['#parents'][$field_name_key] === $this->fieldDefinition->getName()) &&
648
          (array_slice($trigger['#parents'], 0, count($element['#field_parents'])) == $element['#field_parents']);
649
      }
650
    };
651
652
    if ($is_relevant_submit) {
653
      // Submit was triggered by hidden "target_id" element when entities were
654
      // added via entity browser.
655 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...
656
        $parents = $trigger['#parents'];
657
      }
658
      // Submit was triggered by one of the "Remove" buttons. We need to walk
659
      // few levels up to read value of "target_id" element.
660
      elseif ($trigger['#type'] == 'submit' && strpos($trigger['#name'], $this->fieldDefinition->getName() . '_remove_') === 0) {
661
        $parents = array_merge(array_slice($trigger['#parents'], 0, -static::$deleteDepth), ['target_id']);
662
      }
663
664
      if (isset($parents) && $value = $form_state->getValue($parents)) {
665
        $entities = EntityBrowserElement::processEntityIds($value);
666
        return $entities;
667
      }
668
      return $entities;
669
    }
670
    // IDs from a previous request might be saved in the form state.
671
    elseif ($form_state->has([
672
      'entity_browser_widget',
673
      $this->getFormStateKey($items),
674
    ])
675
    ) {
676
      $stored_ids = $form_state->get([
677
        'entity_browser_widget',
678
        $this->getFormStateKey($items),
679
      ]);
680
      $indexed_entities = $entity_storage->loadMultiple($stored_ids);
681
682
      // Selection can contain same entity multiple times. Since loadMultiple()
683
      // returns unique list of entities, it's necessary to recreate list of
684
      // entities in order to preserve selection of duplicated entities.
685
      foreach ($stored_ids as $entity_id) {
686
        if (isset($indexed_entities[$entity_id])) {
687
          $entities[] = $indexed_entities[$entity_id];
688
        }
689
      }
690
      return $entities;
691
    }
692
    // We are loading for for the first time so we need to load any existing
693
    // values that might already exist on the entity. Also, remove any leftover
694
    // data from removed entity references.
695
    else {
696
      foreach ($items as $item) {
697
        if (isset($item->target_id)) {
698
          $entity = $entity_storage->load($item->target_id);
699
          if (!empty($entity)) {
700
            $entities[] = $entity;
701
          }
702
        }
703
      }
704
      return $entities;
705
    }
706
  }
707
708
  /**
709
   * {@inheritdoc}
710
   */
711
  public function calculateDependencies() {
712
    $dependencies = parent::calculateDependencies();
713
714
    // If an entity browser is being used in this widget, add it as a config
715
    // dependency.
716
    if ($browser_name = $this->getSetting('entity_browser')) {
717
      $dependencies['config'][] = 'entity_browser.browser.' . $browser_name;
718
    }
719
720
    return $dependencies;
721
  }
722
723
  /**
724
   * Get selected elements from target_id element on form.
725
   *
726
   * @param array $element
727
   *   The form element.
728
   * @param \Drupal\Core\Form\FormStateInterface $form_state
729
   *   The form state.
730
   *
731
   * @return \Drupal\Core\Entity\EntityInterface[]|false
732
   *   Return list of entities if they are available or false.
733
   */
734
  protected function getEntitiesByTargetId(array $element, FormStateInterface $form_state) {
735
    $target_id_element_path = array_merge(
736
      $element['#field_parents'],
737
      [$this->fieldDefinition->getName(), 'target_id']
738
    );
739
740
    if (!NestedArray::keyExists($form_state->getUserInput(), $target_id_element_path)) {
741
      return FALSE;
742
    }
743
744
    // TODO Figure out how to avoid using raw user input.
745
    $current_user_input = NestedArray::getValue($form_state->getUserInput(), $target_id_element_path);
746
    if (!is_array($current_user_input)) {
747
      $entities = EntityBrowserElement::processEntityIds($current_user_input);
748
      return $entities;
749
    }
750
751
    return FALSE;
752
  }
753
754
}
755