Completed
Push — 8.x-1.x ( 8a7aec...a87349 )
by Janez
03:26
created

formElementEntities()   C

Complexity

Conditions 19
Paths 78

Size

Total Lines 76
Code Lines 39

Duplication

Lines 8
Ratio 10.53 %

Importance

Changes 0
Metric Value
cc 19
eloc 39
nc 78
nop 3
dl 8
loc 76
rs 5.222
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
namespace Drupal\entity_browser\Plugin\Field\FieldWidget;
4
5
use 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
    $entities = $this->formElementEntities($items, $element, $form_state);
283
284
    // Get correct ordered list of entity IDs.
285
    $ids = array_map(
286
      function (EntityInterface $entity) {
287
        return $entity->id();
288
      },
289
      $entities
290
    );
291
292
    // We store current entity IDs as we might need them in future requests. If
293
    // some other part of the form triggers an AJAX request with
294
    // #limit_validation_errors we won't have access to the value of the
295
    // target_id element and won't be able to build the form as a result of
296
    // that. This will cause missing submit (Remove, Edit, ...) elements, which
297
    // might result in unpredictable results.
298
    $form_state->set(['entity_browser_widget', $this->getFormStateKey($items)], $ids);
299
300
    $hidden_id = Html::getUniqueId('edit-' . $this->fieldDefinition->getName() . '-target-id');
301
    $details_id = Html::getUniqueId('edit-' . $this->fieldDefinition->getName());
302
303
    $element += [
304
      '#id' => $details_id,
305
      '#type' => 'details',
306
      '#open' => !empty($entities) || $this->getSetting('open'),
307
      '#required' => $this->fieldDefinition->isRequired(),
308
      // We are not using Entity browser's hidden element since we maintain
309
      // selected entities in it during entire process.
310
      'target_id' => [
311
        '#type' => 'hidden',
312
        '#id' => $hidden_id,
313
        // We need to repeat ID here as it is otherwise skipped when rendering.
314
        '#attributes' => ['id' => $hidden_id],
315
        '#default_value' => implode(' ', array_map(
316
            function (EntityInterface $item) {
317
              return $item->getEntityTypeId() . ':' . $item->id();
318
            },
319
            $entities
320
        )),
321
        // #ajax is officially not supported for hidden elements but if we
322
        // specify event manually it works.
323
        '#ajax' => [
324
          'callback' => [get_class($this), 'updateWidgetCallback'],
325
          'wrapper' => $details_id,
326
          'event' => 'entity_browser_value_updated',
327
        ],
328
      ],
329
    ];
330
331
    // Get configuration required to check entity browser availability.
332
    $cardinality = $this->fieldDefinition->getFieldStorageDefinition()->getCardinality();
333
    $selection_mode = $this->getSetting('selection_mode');
334
335
    // Enable entity browser if requirements for that are fulfilled.
336
    if (EntityBrowserElement::isEntityBrowserAvailable($selection_mode, $cardinality, count($ids))) {
337
      $element['entity_browser'] = [
338
        '#type' => 'entity_browser',
339
        '#entity_browser' => $this->getSetting('entity_browser'),
340
        '#cardinality' => $cardinality,
341
        '#selection_mode' => $selection_mode,
342
        '#default_value' => $entities,
343
        '#entity_browser_validators' => ['entity_type' => ['type' => $entity_type]],
344
        '#custom_hidden_id' => $hidden_id,
345
        '#process' => [
346
          ['\Drupal\entity_browser\Element\EntityBrowserElement', 'processEntityBrowser'],
347
          [get_called_class(), 'processEntityBrowser'],
348
        ],
349
      ];
350
351
      $element['#attached']['library'][] = 'entity_browser/entity_reference';
352
    }
353
354
    $field_parents = $element['#field_parents'];
355
356
    $element['current'] = $this->displayCurrentSelection($details_id, $field_parents, $entities);
357
358
    return $element;
359
  }
360
361
  /**
362
   * Render API callback: Processes the entity browser element.
363
   */
364
  public static function processEntityBrowser(&$element, FormStateInterface $form_state, &$complete_form) {
365
    $uuid = key($element['#attached']['drupalSettings']['entity_browser']);
366
    $element['#attached']['drupalSettings']['entity_browser'][$uuid]['selector'] = '#' . $element['#custom_hidden_id'];
367
    return $element;
368
  }
369
370
  /**
371
   * {@inheritdoc}
372
   */
373
  public function massageFormValues(array $values, array $form, FormStateInterface $form_state) {
374
    $entities = empty($values['target_id']) ? [] : explode(' ', trim($values['target_id']));
375
    $return = [];
376
    foreach ($entities as $entity) {
377
      $return[]['target_id'] = explode(':', $entity)[1];
378
    }
379
380
    return $return;
381
  }
382
383
  /**
384
   * AJAX form callback.
385
   */
386
  public static function updateWidgetCallback(array &$form, FormStateInterface $form_state) {
387
    $trigger = $form_state->getTriggeringElement();
388
    // AJAX requests can be triggered by hidden "target_id" element when
389
    // entities are added or by one of the "Remove" buttons. Depending on that
390
    // we need to figure out where root of the widget is in the form structure
391
    // and use this information to return correct part of the form.
392 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...
393
      $parents = array_slice($trigger['#array_parents'], 0, -1);
394
    }
395
    elseif ($trigger['#type'] == 'submit' && strpos($trigger['#name'], '_remove_')) {
396
      $parents = array_slice($trigger['#array_parents'], 0, -static::$deleteDepth);
397
    }
398
399
    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...
400
  }
401
402
  /**
403
   * {@inheritdoc}
404
   */
405
  public function errorElement(array $element, ConstraintViolationInterface $violation, array $form, FormStateInterface $form_state) {
406
    if (($trigger = $form_state->getTriggeringElement())) {
407
      // Can be triggered by "Remove" button.
408
      if (end($trigger['#parents']) === 'remove_button') {
409
        return FALSE;
410
      }
411
    }
412
    return parent::errorElement($element, $violation, $form, $form_state);
413
  }
414
415
  /**
416
   * Submit callback for remove buttons.
417
   */
418
  public static function removeItemSubmit(&$form, FormStateInterface $form_state) {
419
    $triggering_element = $form_state->getTriggeringElement();
420
    if (!empty($triggering_element['#attributes']['data-entity-id']) && isset($triggering_element['#attributes']['data-row-id'])) {
421
      $id = $triggering_element['#attributes']['data-entity-id'];
422
      $row_id = $triggering_element['#attributes']['data-row-id'];
423
      $parents = array_slice($triggering_element['#parents'], 0, -static::$deleteDepth);
424
      $array_parents = array_slice($triggering_element['#array_parents'], 0, -static::$deleteDepth);
425
426
      // Find and remove correct entity.
427
      $values = explode(' ', $form_state->getValue(array_merge($parents, ['target_id'])));
428
      foreach ($values as $index => $item) {
429
        if ($item == $id && $index == $row_id) {
430
          array_splice($values, $index, 1);
431
432
          break;
433
        }
434
      }
435
      $target_id_value = implode(' ', $values);
436
437
      // Set new value for this widget.
438
      $target_id_element = &NestedArray::getValue($form, array_merge($array_parents, ['target_id']));
439
      $form_state->setValueForElement($target_id_element, $target_id_value);
440
      NestedArray::setValue($form_state->getUserInput(), $target_id_element['#parents'], $target_id_value);
441
442
      // Rebuild form.
443
      $form_state->setRebuild();
444
    }
445
  }
446
447
  /**
448
   * Builds the render array for displaying the current results.
449
   *
450
   * @param string $details_id
451
   *   The ID for the details element.
452
   * @param string[] $field_parents
453
   *   Field parents.
454
   * @param \Drupal\Core\Entity\ContentEntityInterface[] $entities
455
   *   Array of referenced entities.
456
   *
457
   * @return array
458
   *   The render array for the current selection.
459
   */
460
  protected function displayCurrentSelection($details_id, $field_parents, $entities) {
461
462
    $field_widget_display = $this->fieldDisplayManager->createInstance(
463
      $this->getSetting('field_widget_display'),
464
      $this->getSetting('field_widget_display_settings') + ['entity_type' => $this->fieldDefinition->getFieldStorageDefinition()->getSetting('target_type')]
465
    );
466
467
    return [
468
      '#theme_wrappers' => ['container'],
469
      '#attributes' => ['class' => ['entities-list']],
470
      'items' => array_map(
471
        function (ContentEntityInterface $entity, $row_id) use ($field_widget_display, $details_id, $field_parents) {
472
          $display = $field_widget_display->view($entity);
473
          if (is_string($display)) {
474
            $display = ['#markup' => $display];
475
          }
476
          return [
477
            '#theme_wrappers' => ['container'],
478
            '#attributes' => [
479
              'class' => ['item-container', Html::getClass($field_widget_display->getPluginId())],
480
              'data-entity-id' => $entity->getEntityTypeId() . ':' . $entity->id(),
481
              'data-row-id' => $row_id,
482
            ],
483
            'display' => $display,
484
            'remove_button' => [
485
              '#type' => 'submit',
486
              '#value' => $this->t('Remove'),
487
              '#ajax' => [
488
                'callback' => [get_class($this), 'updateWidgetCallback'],
489
                'wrapper' => $details_id,
490
              ],
491
              '#submit' => [[get_class($this), 'removeItemSubmit']],
492
              '#name' => $this->fieldDefinition->getName() . '_remove_' . $entity->id() . '_' . $row_id,
493
              '#limit_validation_errors' => [array_merge($field_parents, [$this->fieldDefinition->getName()])],
494
              '#attributes' => [
495
                'data-entity-id' => $entity->getEntityTypeId() . ':' . $entity->id(),
496
                'data-row-id' => $row_id,
497
              ],
498
              '#access' => (bool) $this->getSetting('field_widget_remove'),
499
            ],
500
            'edit_button' => [
501
              '#type' => 'submit',
502
              '#value' => $this->t('Edit'),
503
              '#ajax' => [
504
                'url' => Url::fromRoute(
505
                  'entity_browser.edit_form', [
506
                    'entity_type' => $entity->getEntityTypeId(),
507
                    'entity' => $entity->id(),
508
                  ]
509
                ),
510
                'options' => [
511
                  'query' => [
512
                    'details_id' => $details_id,
513
                  ],
514
                ],
515
              ],
516
              '#access' => (bool) $this->getSetting('field_widget_edit'),
517
            ],
518
          ];
519
        },
520
        $entities,
521
        empty($entities) ? [] : range(0, count($entities) - 1)
522
      ),
523
    ];
524
  }
525
526
  /**
527
   * Gets data that should persist across Entity Browser renders.
528
   *
529
   * @return array
530
   *   Data that should persist after the Entity Browser is rendered.
531
   */
532
  protected function getPersistentData() {
533
    return [
534
      'validators' => [
535
        'entity_type' => ['type' => $this->fieldDefinition->getFieldStorageDefinition()->getSetting('target_type')],
536
      ],
537
    ];
538
  }
539
540
  /**
541
   * Gets options that define where newly added entities are inserted.
542
   *
543
   * @return array
544
   *   Mode labels indexed by key.
545
   */
546
  protected function selectionModeOptions() {
547
    return ['append' => $this->t('Append'), 'prepend' => $this->t('Prepend')];
548
  }
549
550
  /**
551
   * Provides base for settings summary shared by all EB widgets.
552
   *
553
   * @return array
554
   *   A short summary of the widget settings.
555
   */
556
  protected function summaryBase() {
557
    $summary = [];
558
559
    $entity_browser_id = $this->getSetting('entity_browser');
560
    if (empty($entity_browser_id)) {
561
      return [$this->t('No entity browser selected.')];
562
    }
563
    else {
564
      if ($browser = $this->entityTypeManager->getStorage('entity_browser')->load($entity_browser_id)) {
565
        $summary[] = $this->t('Entity browser: @browser', ['@browser' => $browser->label()]);
566
      }
567
      else {
568
        drupal_set_message($this->t('Missing entity browser!'), 'error');
569
        return [$this->t('Missing entity browser!')];
570
      }
571
    }
572
573
    $selection_mode = $this->getSetting('selection_mode');
574
    $selection_mode_options = EntityBrowserElement::getSelectionModeOptions();
575
    if (isset($selection_mode_options[$selection_mode])) {
576
      $summary[] = $this->t('Selection mode: @selection_mode', ['@selection_mode' => $selection_mode_options[$selection_mode]]);
577
    }
578
    else {
579
      $summary[] = $this->t('Undefined selection mode.');
580
    }
581
582
    return $summary;
583
  }
584
585
  /**
586
   * Determines the entities used for the form element.
587
   *
588
   * @param \Drupal\Core\Field\FieldItemListInterface $items
589
   *   The field item to extract the entities from.
590
   * @param array $element
591
   *   The form element.
592
   * @param \Drupal\Core\Form\FormStateInterface $form_state
593
   *   The form state.
594
   *
595
   * @return \Drupal\Core\Entity\EntityInterface[]
596
   *   The list of entities for the form element.
597
   */
598
  protected function formElementEntities(FieldItemListInterface $items, array $element, FormStateInterface $form_state) {
599
    $entities = [];
600
    $entity_type = $this->fieldDefinition->getFieldStorageDefinition()->getSetting('target_type');
601
    $entity_storage = $this->entityTypeManager->getStorage($entity_type);
602
603
    // Determine if we're submitting and if submit came from this widget.
604
    $is_relevant_submit = FALSE;
605
    if (($trigger = $form_state->getTriggeringElement())) {
606
      // Can be triggered by hidden target_id element or "Remove" button.
607
      if (end($trigger['#parents']) === 'target_id' || (end($trigger['#parents']) === 'remove_button')) {
608
        $is_relevant_submit = TRUE;
609
610
        // In case there are more instances of this widget on the same page we
611
        // need to check if submit came from this instance.
612
        $field_name_key = end($trigger['#parents']) === 'target_id' ? 2 : static::$deleteDepth + 1;
613
        $field_name_key = count($trigger['#parents']) - $field_name_key;
614
        $is_relevant_submit &= ($trigger['#parents'][$field_name_key] === $this->fieldDefinition->getName()) &&
615
          (array_slice($trigger['#parents'], 0, count($element['#field_parents'])) == $element['#field_parents']);
616
      }
617
    };
618
619
    if ($is_relevant_submit) {
620
      // Submit was triggered by hidden "target_id" element when entities were
621
      // added via entity browser.
622 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...
623
        $parents = $trigger['#parents'];
624
      }
625
      // Submit was triggered by one of the "Remove" buttons. We need to walk
626
      // few levels up to read value of "target_id" element.
627
      elseif ($trigger['#type'] == 'submit' && strpos($trigger['#name'], $this->fieldDefinition->getName() . '_remove_') === 0) {
628
        $parents = array_merge(array_slice($trigger['#parents'], 0, -static::$deleteDepth), ['target_id']);
629
      }
630
631
      if (isset($parents) && $value = $form_state->getValue($parents)) {
632
        $entities = EntityBrowserElement::processEntityIds($value);
633
        return $entities;
634
      }
635
      return $entities;
636
    }
637
    // IDs from a previous request might be saved in the form state.
638
    elseif ($form_state->has([
639
      'entity_browser_widget',
640
      $this->getFormStateKey($items)
641
    ])
642
    ) {
643
      $stored_ids = $form_state->get([
644
        'entity_browser_widget',
645
        $this->getFormStateKey($items)
646
      ]);
647
      $indexed_entities = $entity_storage->loadMultiple($stored_ids);
648
649
      // Selection can contain same entity multiple times. Since loadMultiple()
650
      // returns unique list of entities, it's necessary to recreate list of
651
      // entities in order to preserve selection of duplicated entities.
652
      foreach ($stored_ids as $entity_id) {
653
        if (isset($indexed_entities[$entity_id])) {
654
          $entities[] = $indexed_entities[$entity_id];
655
        }
656
      }
657
      return $entities;
658
    }
659
    // We are loading for for the first time so we need to load any existing
660
    // values that might already exist on the entity. Also, remove any leftover
661
    // data from removed entity references.
662
    else {
663
      foreach ($items as $item) {
664
        if (isset($item->target_id)) {
665
          $entity = $entity_storage->load($item->target_id);
666
          if (!empty($entity)) {
667
            $entities[] = $entity;
668
          }
669
        }
670
      }
671
      return $entities;
672
    }
673
  }
674
675
}
676