Completed
Pull Request — 8.x-1.x (#123)
by
unknown
02:27
created

EntityReference::settingsForm()   B

Complexity

Conditions 5
Paths 12

Size

Total Lines 63
Code Lines 43

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
c 4
b 0
f 0
dl 0
loc 63
rs 8.6499
cc 5
eloc 43
nc 12
nop 2

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
/**
4
 * @file
5
 * Contains \Drupal\entity_browser\Plugin\Field\FieldWidget\EntityReference.
6
 */
7
8
namespace Drupal\entity_browser\Plugin\Field\FieldWidget;
9
10
use Drupal\Component\Utility\Html;
11
use Drupal\Component\Utility\NestedArray;
12
use Drupal\Core\Entity\EntityManagerInterface;
13
use Drupal\Core\Field\FieldDefinitionInterface;
14
use Drupal\Core\Field\FieldItemListInterface;
15
use Drupal\Core\Field\FieldStorageDefinitionInterface;
16
use Drupal\Core\Field\WidgetBase;
17
use Drupal\Core\Form\FormStateInterface;
18
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
19
use Drupal\entity_browser\Events\Events;
20
use Drupal\entity_browser\Events\RegisterJSCallbacks;
21
use Drupal\entity_browser\FieldWidgetDisplayManager;
22
use Symfony\Component\DependencyInjection\ContainerInterface;
23
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
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 EntityReference extends WidgetBase implements ContainerFactoryPluginInterface {
39
40
  /**
41
   * Entity manager service
42
   *
43
   * @var \Drupal\Core\Entity\EntityManagerInterface
44
   */
45
  protected $entityManager;
46
47
  /**
48
   * Field widget display plugin manager.
49
   *
50
   * @var \Drupal\entity_browser\FieldWidgetDisplayManager
51
   */
52
  protected $fieldDisplayManager;
53
54
  /**
55
   * Constructs widget plugin.
56
   *
57
   * @param string $plugin_id
58
   *   The plugin_id for the plugin instance.
59
   * @param mixed $plugin_definition
60
   *   The plugin implementation definition.
61
   * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
62
   *   The definition of the field to which the widget is associated.
63
   * @param array $settings
64
   *   The widget settings.
65
   * @param array $third_party_settings
66
   *   Any third party settings.
67
   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
68
   *   Entity manager service.
69
   * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
70
   *   Event dispatcher.
71
   * @param \Drupal\entity_browser\FieldWidgetDisplayManager $field_display_manager
72
   *   Field widget display plugin manager.
73
   */
74
  public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, EntityManagerInterface $entity_manager, EventDispatcherInterface $event_dispatcher, FieldWidgetDisplayManager $field_display_manager) {
75
    parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings);
76
    $this->entityManager = $entity_manager;
77
    $this->fieldDisplayManager = $field_display_manager;
78
  }
79
80
  /**
81
   * {@inheritdoc}
82
   */
83
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
84
    return new static(
85
      $plugin_id,
86
      $plugin_definition,
87
      $configuration['field_definition'],
88
      $configuration['settings'],
89
      $configuration['third_party_settings'],
90
      $container->get('entity.manager'),
91
      $container->get('event_dispatcher'),
92
      $container->get('plugin.manager.entity_browser.field_widget_display')
93
    );
94
  }
95
96
  /**
97
   * {@inheritdoc}
98
   */
99
  public static function defaultSettings() {
100
    return array(
101
      'entity_browser' => NULL,
102
      'field_widget_display' => NULL,
103
      'field_widget_display_settings' => [],
104
    ) + parent::defaultSettings();
105
  }
106
107
  /**
108
   * {@inheritdoc}
109
   */
110
  public function settingsForm(array $form, FormStateInterface $form_state) {
111
    $element = parent::settingsForm($form, $form_state);
112
113
    $browsers = [];
114
    /** @var \Drupal\entity_browser\EntityBrowserInterface $browser */
115
    foreach ($this->entityManager->getStorage('entity_browser')->loadMultiple() as $browser) {
116
      $browsers[$browser->id()] = $browser->label();
117
    }
118
119
    $element['entity_browser'] = [
120
      '#title' => t('Entity browser'),
121
      '#type' => 'select',
122
      '#default_value' => $this->getSetting('entity_browser'),
123
      '#options' => $browsers,
124
    ];
125
126
    $target_type = $this->fieldDefinition->getFieldStorageDefinition()->getSetting('target_type');
127
    $entity_type = \Drupal::entityTypeManager()->getStorage($target_type)->getEntityType();
128
129
    $displays = [];
130
    foreach ($this->fieldDisplayManager->getDefinitions() as $id => $definition) {
131
      if ($this->fieldDisplayManager->createInstance($id)->isApplicable($entity_type)) {
132
        $displays[$id] = $definition['label'];
133
      }
134
    }
135
136
    $id = Html::getUniqueId('field-' . $this->fieldDefinition->getName() . '-display-settings-wrapper');
137
    $element['field_widget_display'] = [
138
      '#title' => t('Entity display plugin'),
139
      '#type' => 'select',
140
      '#default_value' => $this->getSetting('field_widget_display'),
141
      '#options' => $displays,
142
      '#ajax' => [
143
        'callback' => array($this, 'updateSettingsAjax'),
144
        'wrapper' => $id,
145
      ],
146
    ];
147
148
    $element['field_widget_display_settings'] = [
149
      '#type' => 'fieldset',
150
      '#title' => t('Entity display plugin configuration'),
151
      '#tree' => TRUE,
152
      '#prefix' => '<div id="' . $id . '">',
153
      '#suffix' => '</div>',
154
    ];
155
156
    if ($this->getSetting('field_widget_display')) {
157
      $element['field_widget_display_settings'] += $this->fieldDisplayManager
158
        ->createInstance(
159
          $form_state->getValue(
160
            ['fields', $this->fieldDefinition->getName(), 'settings_edit_form', 'settings', 'field_widget_display'],
161
            $this->getSetting('field_widget_display')
162
          ),
163
          $form_state->getValue(
164
            ['fields', $this->fieldDefinition->getName(), 'settings_edit_form', 'settings', 'field_widget_display_settings'],
165
            $this->getSetting('field_widget_display_settings')
166
          ) + ['entity_type' => $this->fieldDefinition->getFieldStorageDefinition()->getSetting('target_type')]
167
        )
168
        ->settingsForm($form, $form_state);
169
    }
170
171
    return $element;
172
  }
173
174
  /**
175
   * Ajax callback that updates field widget display settings fieldset.
176
   */
177
  public function updateSettingsAjax(array $form, FormStateInterface $form_state) {
178
    return $form['fields'][$this->fieldDefinition->getName()]['plugin']['settings_edit_form']['settings']['field_widget_display_settings'];
179
  }
180
181
    /**
182
   * {@inheritdoc}
183
   */
184
  public function settingsSummary() {
185
    $summary = [];
186
    $entity_browser_id = $this->getSetting('entity_browser');
187
    $field_widget_display = $this->getSetting('field_widget_display');
188
189
    if (empty($entity_browser_id)) {
190
      return [t('No entity browser selected.')];
191
    }
192
    else {
193
      $browser = $this->entityManager->getStorage('entity_browser')
194
        ->load($entity_browser_id);
195
      $summary[] = t('Entity browser: @browser', ['@browser' => $browser->label()]);
196
    }
197
198
    if (!empty($field_widget_display)) {
199
      $plugin = $this->fieldDisplayManager->getDefinition($field_widget_display);
200
      $summary[] = t('Entity display: @name', ['@name' => $plugin['label']]);
201
    }
202
203
    return $summary;
204
  }
205
206
  /**
207
   * {@inheritdoc}
208
   */
209
  function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
210
    $entity_type = $this->fieldDefinition->getFieldStorageDefinition()->getSetting('target_type');
211
    $entity_storage = $this->entityManager->getStorage($entity_type);
212
    $field_widget_display = $this->fieldDisplayManager->createInstance(
213
      $this->getSetting('field_widget_display'),
214
      $this->getSetting('field_widget_display_settings') + ['entity_type' => $this->fieldDefinition->getFieldStorageDefinition()->getSetting('target_type')]
215
    );
216
217
    $ids = [];
218
    $entities = [];
219
    if (($trigger = $form_state->getTriggeringElement()) && in_array($this->fieldDefinition->getName(), $trigger['#parents'])) {
220
      // Submit was triggered by hidden "target_id" element when entities were
221
      // added via entity browser.
222 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...
223
        $parents = $trigger['#parents'];
224
      }
225
      // Submit was triggered by one of the "Remove" buttons. We need to walk
226
      // few levels up to read value of "target_id" element.
227
      elseif ($trigger['#type'] == 'submit' && strpos($trigger['#name'], $this->fieldDefinition->getName() . '_remove_') === 0) {
228
        $parents = array_merge(array_slice($trigger['#parents'], 0, -4), ['target_id']);
229
      }
230
231
      if (isset($parents) && $value = $form_state->getValue($parents)) {
232
        $ids = explode(' ', $value);
233
        $entities = $entity_storage->loadMultiple($ids);
234
      }
235
    }
236
    // We are loading for for the first time so we need to load any existing
237
    // values that might already exist on the entity. Also, remove any leftover
238
    // data from removed entity references.
239
    else {
240
      foreach ($items as $item) {
241
        $entity = $entity_storage->load($item->target_id);
242
        if (!empty($entity)) {
243
          $entities[$item->target_id] = $entity;
244
        }
245
      }
246
      $ids = array_keys($entities);
247
    }
248
    $ids = array_filter($ids);
249
250
    $hidden_id = Html::getUniqueId('edit-' . $this->fieldDefinition->getName() . '-target-id');
251
    $details_id = Html::getUniqueId('edit-' . $this->fieldDefinition->getName());
252
    /** @var \Drupal\entity_browser\EntityBrowserInterface $entity_browser */
253
    $entity_browser = $this->entityManager->getStorage('entity_browser')->load($this->getSetting('entity_browser'));
254
255
    $element += [
256
      '#id' => $details_id,
257
      '#type' => 'details',
258
      '#open' => !empty($ids),
259
      'target_id' => [
260
        '#type' => 'hidden',
261
        '#id' => $hidden_id,
262
        // We need to repeat ID here as it is otherwise skipped when rendering.
263
        '#attributes' => ['id' => $hidden_id],
264
        '#default_value' => $ids,
265
        // #ajax is officially not supported for hidden elements but if we
266
        // specify event manually it works.
267
        '#ajax' => [
268
          'callback' => [get_class($this), 'updateWidgetCallback'],
269
          'wrapper' => $details_id,
270
          'event' => 'entity_browser_value_updated',
271
        ],
272
      ],
273
    ];
274
275
    $cardinality = $this->fieldDefinition->getFieldStorageDefinition()->getCardinality();
276
    if ($cardinality == FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED || count($ids) < $cardinality) {
277
      $element['entity_browser'] = $entity_browser->getDisplay()->displayEntityBrowser();
278
      $element['#attached']['library'][] = 'entity_browser/entity_reference';
279
      $element['#attached']['drupalSettings']['entity_browser'] = [
280
        $entity_browser->getDisplay()->getUuid() => [
281
          'cardinality' => $this->fieldDefinition->getFieldStorageDefinition()->getCardinality(),
282
          'selector' => '#'.$element['target_id']['#attributes']['id'],
283
        ]
284
      ];
285
    }
286
287
    $field_parents = $element['#field_parents'];
288
289
    $element['current'] = [
290
      '#theme_wrappers' => ['container'],
291
      '#attributes' => ['class' => ['entities-list']],
292
      'items' => array_map(
293
        function($id) use ($entity_storage, $field_widget_display, $details_id, $field_parents, $entities) {
294
          $entity = $entities[$id];
295
296
          $display = $field_widget_display->view($entity);
297
          if (is_string($display)) {
298
            $display = ['#markup' => $display];
299
          }
300
301
          return [
302
            '#theme_wrappers' => ['container'],
303
            '#attributes' => [
304
              'class' => ['item-container'],
305
              'data-entity-id' => $entity->id()
306
            ],
307
            'display' => $display,
308
            'remove_button' => [
309
              '#type' => 'submit',
310
              '#value' => $this->t('Remove'),
311
              '#ajax' => [
312
                'callback' => [get_class($this), 'updateWidgetCallback'],
313
                'wrapper' => $details_id,
314
              ],
315
              '#submit' => [[get_class($this), 'removeItemSubmit']],
316
              '#name' => $this->fieldDefinition->getName() . '_remove_' . $id,
317
              '#limit_validation_errors' => [array_merge($field_parents, [$this->fieldDefinition->getName()])],
318
              '#attributes' => ['data-entity-id' => $id],
319
            ]
320
          ];
321
322
        },
323
        $ids
324
      ),
325
    ];
326
327
    return $element;
328
  }
329
330
  /**
331
   * {@inheritdoc}
332
   */
333
  public function massageFormValues(array $values, array $form, FormStateInterface $form_state) {
334
    $ids = empty($values['target_id']) ? [] : explode(' ', trim($values['target_id']));
335
    $return = [];
336
    foreach ($ids as $id) {
337
      $return[]['target_id'] = $id;
338
    }
339
340
    return $return;
341
  }
342
343
  /**
344
   * AJAX form callback.
345
   */
346
  public static function updateWidgetCallback(array &$form, FormStateInterface $form_state) {
347
    $trigger = $form_state->getTriggeringElement();
348
    // AJAX requests can be triggered by hidden "target_id" element when entities
349
    // are added or by one of the "Remove" buttons. Depending on that we need to
350
    // figure out where root of the widget is in the form structure and use this
351
    // information to return correct part of the form.
352 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...
353
      $parents = array_slice($trigger['#array_parents'], 0, -2);
354
    }
355
    elseif ($trigger['#type'] == 'submit' && strpos($trigger['#name'], '_remove_')) {
356
      $parents = array_slice($trigger['#array_parents'], 0, -4);
357
    }
358
359
    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...
360
  }
361
362
  /**
363
   * Submit callback for remove buttons.
364
   */
365
  public static function removeItemSubmit(&$form, FormStateInterface $form_state) {
366
    $triggering_element = $form_state->getTriggeringElement();
367
    if (!empty($triggering_element['#attributes']['data-entity-id'])) {
368
      $id = $triggering_element['#attributes']['data-entity-id'];
369
      $parents = array_slice($triggering_element['#parents'], 0, -4);
370
      $array_parents = array_slice($triggering_element['#array_parents'], 0, -4);
371
372
      // Find and remove correct entity.
373
      $values = explode(' ', $form_state->getValue(array_merge($parents, ['target_id'])));
374
      $values = array_filter(
375
        $values,
376
        function($item) use ($id) { return $item != $id; }
377
      );
378
      $values = implode(' ', $values);
379
380
      // Set new value for this widget.
381
      $target_id_element = &NestedArray::getValue($form, array_merge($array_parents, ['target_id']));
382
      $form_state->setValueForElement($target_id_element, $values);
383
      NestedArray::setValue($form_state->getUserInput(), $target_id_element['#parents'], $values);
384
385
      // Rebuild form.
386
      $form_state->setRebuild();
387
    }
388
  }
389
}
390