Completed
Push — 8.x-1.x ( 4eb492...a2fddb )
by Janez
05:45
created

EntityReference   A

Complexity

Total Complexity 35

Size/Duplication

Total Lines 344
Duplicated Lines 4.07 %

Coupling/Cohesion

Components 1
Dependencies 2

Importance

Changes 19
Bugs 1 Features 1
Metric Value
c 19
b 1
f 1
dl 14
loc 344
wmc 35
lcom 1
cbo 2
rs 9

10 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A create() 0 12 1
A defaultSettings() 0 7 1
A settingsForm() 0 58 4
A updateSettingsAjax() 0 3 1
A settingsSummary() 0 21 3
D formElement() 8 119 14
A massageFormValues() 0 9 3
B updateWidgetCallback() 6 15 5
B removeItemSubmit() 0 24 2

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

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 array $configuration
0 ignored issues
show
Bug introduced by
There is no parameter named $configuration. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
58
   *   A configuration array containing information about the plugin instance.
59
   * @param string $plugin_id
60
   *   The plugin_id for the plugin instance.
61
   * @param mixed $plugin_definition
62
   *   The plugin implementation definition.
63
   * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
64
   *   Event dispatcher service.
65
   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
66
   *   Entity manager service.
67
   * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
68
   *   Event dispatcher.
69
   * @param \Drupal\entity_browser\FieldWidgetDisplayManager $field_display_manager
70
   *   Field widget display plugin manager.
71
   */
72
  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) {
73
    parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings);
74
    $this->entityManager = $entity_manager;
75
    $this->fieldDisplayManager = $field_display_manager;
76
  }
77
78
  /**
79
   * {@inheritdoc}
80
   */
81
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
82
    return new static(
83
      $plugin_id,
84
      $plugin_definition,
85
      $configuration['field_definition'],
86
      $configuration['settings'],
87
      $configuration['third_party_settings'],
88
      $container->get('entity.manager'),
89
      $container->get('event_dispatcher'),
90
      $container->get('plugin.manager.entity_browser.field_widget_display')
91
    );
92
  }
93
94
  /**
95
   * {@inheritdoc}
96
   */
97
  public static function defaultSettings() {
98
    return array(
99
      'entity_browser' => NULL,
100
      'field_widget_display' => NULL,
101
      'field_widget_display_settings' => [],
102
    ) + parent::defaultSettings();
103
  }
104
105
  /**
106
   * {@inheritdoc}
107
   */
108
  public function settingsForm(array $form, FormStateInterface $form_state) {
109
    $element = parent::settingsForm($form, $form_state);
110
111
    $browsers = [];
112
    /** @var \Drupal\entity_browser\EntityBrowserInterface $browser */
113
    foreach ($this->entityManager->getStorage('entity_browser')->loadMultiple() as $browser) {
114
      $browsers[$browser->id()] = $browser->label();
115
    }
116
117
    $element['entity_browser'] = [
118
      '#title' => t('Entity browser'),
119
      '#type' => 'select',
120
      '#default_value' => $this->getSetting('entity_browser'),
121
      '#options' => $browsers,
122
    ];
123
124
    $displays = [];
125
    foreach ($this->fieldDisplayManager->getDefinitions() as $id => $definition) {
126
      $displays[$id] = $definition['label'];
127
    }
128
129
    $id = Html::getUniqueId('field-' . $this->fieldDefinition->getName() . '-display-settings-wrapper');
130
    $element['field_widget_display'] = [
131
      '#title' => t('Entity display plugin'),
132
      '#type' => 'select',
133
      '#default_value' => $this->getSetting('field_widget_display'),
134
      '#options' => $displays,
135
      '#ajax' => [
136
        'callback' => array($this, 'updateSettingsAjax'),
137
        'wrapper' => $id,
138
      ],
139
    ];
140
141
    $element['field_widget_display_settings'] = [
142
      '#type' => 'fieldset',
143
      '#title' => t('Entity display plugin configuration'),
144
      '#tree' => TRUE,
145
      '#prefix' => '<div id="' . $id . '">',
146
      '#suffix' => '</div>',
147
    ];
148
149
    if ($this->getSetting('field_widget_display')) {
150
      $element['field_widget_display_settings'] += $this->fieldDisplayManager
151
        ->createInstance(
152
          $form_state->getValue(
153
            ['fields', $this->fieldDefinition->getName(), 'settings_edit_form', 'settings', 'field_widget_display'],
154
            $this->getSetting('field_widget_display')
155
          ),
156
          $form_state->getValue(
157
            ['fields', $this->fieldDefinition->getName(), 'settings_edit_form', 'settings', 'field_widget_display_settings'],
158
            $this->getSetting('field_widget_display_settings')
159
          ) + ['entity_type' => $this->fieldDefinition->getFieldStorageDefinition()->getSetting('target_type')]
160
        )
161
        ->settingsForm($form, $form_state);
162
    }
163
164
    return $element;
165
  }
166
167
  /**
168
   * Ajax callback that updates field widget display settings fieldset.
169
   */
170
  public function updateSettingsAjax(array $form, FormStateInterface $form_state) {
171
    return $form['fields'][$this->fieldDefinition->getName()]['plugin']['settings_edit_form']['settings']['field_widget_display_settings'];
172
  }
173
174
    /**
175
   * {@inheritdoc}
176
   */
177
  public function settingsSummary() {
178
    $summary = [];
179
    $entity_browser_id = $this->getSetting('entity_browser');
180
    $field_widget_display = $this->getSetting('field_widget_display');
181
182
    if (empty($entity_browser_id)) {
183
      return [t('No entity browser selected.')];
184
    }
185
    else {
186
      $browser = $this->entityManager->getStorage('entity_browser')
187
        ->load($entity_browser_id);
188
      $summary[] = t('Entity browser: @browser', ['@browser' => $browser->label()]);
189
    }
190
191
    if (!empty($field_widget_display)) {
192
      $plugin = $this->fieldDisplayManager->getDefinition($field_widget_display);
193
      $summary[] = t('Entity display: @name', ['@name' => $plugin['label']]);
194
    }
195
196
    return $summary;
197
  }
198
199
  /**
200
   * {@inheritdoc}
201
   */
202
  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...
203
    $entity_type = $this->fieldDefinition->getFieldStorageDefinition()->getSetting('target_type');
204
    $entity_storage = $this->entityManager->getStorage($entity_type);
205
    $field_widget_display = $this->fieldDisplayManager->createInstance(
206
      $this->getSetting('field_widget_display'),
207
      $this->getSetting('field_widget_display_settings') + ['entity_type' => $this->fieldDefinition->getFieldStorageDefinition()->getSetting('target_type')]
208
    );
209
210
    $ids = [];
211
    if (($trigger = $form_state->getTriggeringElement()) && in_array($this->fieldDefinition->getName(), $trigger['#parents'])) {
212
      // Submit was triggered by hidden "target_id" element when entities were
213
      // added via entity browser.
214 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...
215
        $parents = $trigger['#parents'];
216
      }
217
      // Submit was triggered by one of the "Remove" buttons. We need to walk
218
      // few levels up to read value of "target_id" element.
219
      elseif ($trigger['#type'] == 'submit' && strpos($trigger['#name'], $this->fieldDefinition->getName() . '_remove_') === 0) {
220
        $parents = array_merge(array_slice($trigger['#parents'], 0, -4), ['target_id']);
221
      }
222
223
      if (isset($parents) && $value = $form_state->getValue($parents)) {
224
        $ids = explode(' ', $value);
225
        $entities = $entity_storage->loadMultiple($ids);
226
      }
227
    }
228
    // We are loading for for the first time so we need to load any existing
229
    // values that might already exist on the entity. Also, remove any leftover
230
    // data from removed entity references.
231
    else {
232
      foreach ($items as $item) {
233
        $entity = $entity_storage->load($item->target_id);
234
        if (!empty($entity)) {
235
          $entities[$item->target_id] = $entity;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$entities was never initialized. Although not strictly required by PHP, it is generally a good practice to add $entities = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

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