Completed
Pull Request — 8.x-1.x (#153)
by
unknown
05:05
created

MultiStepDisplay::executeJsCommand()   C

Complexity

Conditions 9
Paths 38

Size

Total Lines 72
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
eloc 35
nc 38
nop 1
dl 0
loc 72
rs 6.0413
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\EntityBrowser\SelectionDisplay;
4
5
use Drupal\Core\Ajax\AjaxResponse;
6
use Drupal\Core\Ajax\InvokeCommand;
7
use Drupal\Core\Ajax\ReplaceCommand;
8
use Drupal\Core\Entity\EntityTypeManagerInterface;
9
use Drupal\Core\Form\FormStateInterface;
10
use Drupal\entity_browser\FieldWidgetDisplayManager;
11
use Drupal\entity_browser\SelectionDisplayBase;
12
use Symfony\Component\DependencyInjection\ContainerInterface;
13
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
14
15
/**
16
 * Show current selection and delivers selected entities.
17
 *
18
 * @EntityBrowserSelectionDisplay(
19
 *   id = "multi_step_display",
20
 *   label = @Translation("Multi step selection display"),
21
 *   description = @Translation("Show current selection display and delivers selected entities."),
22
 *   acceptPreselection = TRUE,
23
 *   jsCommands = TRUE
24
 * )
25
 */
26
class MultiStepDisplay extends SelectionDisplayBase {
27
28
  /**
29
   * Field widget display plugin manager.
30
   *
31
   * @var \Drupal\entity_browser\FieldWidgetDisplayManager
32
   */
33
  protected $fieldDisplayManager;
34
35
  /**
36
   * Constructs widget plugin.
37
   *
38
   * @param array $configuration
39
   *   A configuration array containing information about the plugin instance.
40
   * @param string $plugin_id
41
   *   The plugin_id for the plugin instance.
42
   * @param mixed $plugin_definition
43
   *   The plugin implementation definition.
44
   * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $event_dispatcher
45
   *   Event dispatcher service.
46
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
47
   *   The entity type manager service.
48
   * @param \Drupal\entity_browser\FieldWidgetDisplayManager $field_display_manager
49
   *   Field widget display plugin manager.
50
   */
51
  public function __construct(array $configuration, $plugin_id, $plugin_definition, EventDispatcherInterface $event_dispatcher, EntityTypeManagerInterface $entity_type_manager, FieldWidgetDisplayManager $field_display_manager) {
52
    parent::__construct($configuration, $plugin_id, $plugin_definition, $event_dispatcher, $entity_type_manager);
53
    $this->fieldDisplayManager = $field_display_manager;
54
  }
55
56
  /**
57
   * {@inheritdoc}
58
   */
59
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
60
    return new static(
61
      $configuration,
62
      $plugin_id,
63
      $plugin_definition,
64
      $container->get('event_dispatcher'),
65
      $container->get('entity_type.manager'),
66
      $container->get('plugin.manager.entity_browser.field_widget_display')
67
    );
68
  }
69
70
  /**
71
   * {@inheritdoc}
72
   */
73
  public function defaultConfiguration() {
74
    return [
75
      'entity_type' => 'node',
76
      'display' => 'label',
77
      'display_settings' => [],
78
      'select_text' => 'Use selected',
79
      'selection_hidden' => 0,
80
    ] + parent::defaultConfiguration();
81
  }
82
83
  /**
84
   * {@inheritdoc}
85
   */
86
  public function getForm(array &$original_form, FormStateInterface $form_state) {
87
88
    // Check if trigger element is dedicated to handle front-end commands.
89
    if (($triggering_element = $form_state->getTriggeringElement()) && $triggering_element['#name'] === 'ajax_action_handler' && !empty($triggering_element['#value'])) {
90
      $this->executeJsCommand($form_state);
91
    }
92
93
    $selected_entities = $form_state->get(['entity_browser', 'selected_entities']);
94
95
    $form = [];
96
    $form['#attached']['library'][] = 'entity_browser/multi_step_display';
97
    $form['selected'] = [
98
      '#theme_wrappers' => ['container'],
99
      '#attributes' => ['class' => ['entities-list']],
100
      '#tree' => TRUE,
101
    ];
102
    if ($this->configuration['selection_hidden']) {
103
      $form['selected']['#attributes']['class'][] = 'hidden';
104
    }
105
    foreach ($selected_entities as $id => $entity) {
106
      $display_plugin = $this->fieldDisplayManager->createInstance(
107
        $this->configuration['display'],
108
        $this->configuration['display_settings'] + ['entity_type' => $this->configuration['entity_type']]
109
      );
110
      $display = $display_plugin->view($entity);
111
      if (is_string($display)) {
112
        $display = ['#markup' => $display];
113
      }
114
115
      $form['selected']['items_' . $entity->id() . '_' . $id] = [
116
        '#theme_wrappers' => ['container'],
117
        '#attributes' => [
118
          'class' => ['item-container'],
119
          'data-entity-id' => $entity->id(),
120
        ],
121
        'display' => $display,
122
        'remove_button' => [
123
          '#type' => 'submit',
124
          '#value' => $this->t('Remove'),
125
          '#submit' => [[get_class($this), 'removeItemSubmit']],
126
          '#name' => 'remove_' . $entity->id() . '_' . $id,
127
          '#attributes' => [
128
            'class' => ['entity-browser-remove-selected-entity'],
129
            'data-row-id' => $id,
130
            'data-remove-entity' => 'items_' . $entity->id(),
131
          ],
132
        ],
133
        'weight' => [
134
          '#type' => 'hidden',
135
          '#default_value' => $id,
136
          '#attributes' => ['class' => ['weight']],
137
        ],
138
      ];
139
    }
140
141
    // Add hidden element used to make execution of front-end commands.
142
    $form['ajax_action_handler'] = [
143
      '#type' => 'hidden',
144
      '#name' => 'ajax_action_handler',
145
      '#id' => 'ajax_action_handler',
146
      '#attributes' => ['id' => 'ajax_action_handler'],
147
      '#ajax' => [
148
        'callback' => [get_class($this), 'handleAjaxCommand'],
149
        'wrapper' => 'edit-selected',
150
        'event' => 'selection_display_action',
151
        'progress' => [
152
          'type' => 'fullscreen',
153
        ],
154
      ],
155
    ];
156
157
    $form['use_selected'] = [
158
      '#type' => 'submit',
159
      '#value' => $this->t($this->configuration['select_text']),
160
      '#name' => 'use_selected',
161
    ];
162
163
    $form['show_selection'] = [
164
      '#type' => 'button',
165
      '#value' => $this->t('Show selected'),
166
      '#attributes' => [
167
        'class' => ['entity-browser-show-selection'],
168
      ],
169
    ];
170
171
    return $form;
172
  }
173
174
  /**
175
   * Execute command generated by front-end.
176
   *
177
   * @param \Drupal\Core\Form\FormStateInterface $form_state
178
   *   Form state object.
179
   */
180
  protected function executeJsCommand(FormStateInterface $form_state) {
181
    $triggering_element = $form_state->getTriggeringElement();
182
183
    $commands = json_decode($triggering_element['#value'], TRUE);
184
185
    // Process Remove command.
186
    if (isset($commands['remove'])) {
187
      $entity_ids = $commands['remove'];
188
189
      // Remove weight of entity being removed.
190
      foreach ($entity_ids as $entity_info) {
191
        $entity_id_info = explode('_', $entity_info['entity_id']);
192
193
        $form_state->unsetValue([
194
          'selected',
195
          $entity_info['entity_id'],
196
        ]);
197
198
        // Remove entity itself.
199
        $selected_entities = &$form_state->get(['entity_browser', 'selected_entities']);
200
        unset($selected_entities[$entity_id_info[2]]);
201
      }
202
203
      static::saveNewOrder($form_state);
204
    }
205
206
    // Process Add command.
207
    if (isset($commands['add'])) {
208
      $entity_ids = $commands['add'];
209
210
      $entities_to_add = [];
211
      $added_entities = [];
212
213
      // Generate list of entities grouped by type, to speed up loadMultiple.
214
      foreach ($entity_ids as $entity_pair_info) {
215
        $entity_info = explode(':', $entity_pair_info['entity_id']);
216
217
        if (!isset($entities_to_add[$entity_info[0]])) {
218
          $entities_to_add[$entity_info[0]] = [];
219
        }
220
221
        $entities_to_add[$entity_info[0]][] = $entity_info[1];
222
      }
223
224
      // Load Entities and add into $added_entities, so that we have list of
225
      // entities with key - "type:id".
226
      foreach ($entities_to_add as $entity_type => $entity_type_ids) {
227
        $indexed_entities = $this->entityTypeManager->getStorage($entity_type)
228
          ->loadMultiple($entity_type_ids);
229
230
        foreach ($indexed_entities as $entity_id => $entity) {
231
          $added_entities[implode(':', [
232
            $entity_type,
233
            $entity_id,
234
          ])] = $entity;
235
        }
236
      }
237
238
      // Array is accessed as reference, so that changes are propagated.
239
      $selected_entities = &$form_state->get([
240
        'entity_browser',
241
        'selected_entities',
242
      ]);
243
244
      // Fill list of selected entities in correct order with loaded entities.
245
      // In this case, order is preserved and multiple entities with same ID
246
      // can be selected properly.
247
      foreach ($entity_ids as $entity_pair_info) {
248
        $selected_entities[] = $added_entities[$entity_pair_info['entity_id']];
249
      }
250
    }
251
  }
252
253
  /**
254
   * Handler to generate Ajax response, after command is executed.
255
   *
256
   * @param array $form
257
   *   Form.
258
   * @param \Drupal\Core\Form\FormStateInterface $form_state
259
   *   Form state object.
260
   *
261
   * @return \Drupal\Core\Ajax\AjaxResponse
262
   *   Return Ajax response with commands.
263
   */
264
  public static function handleAjaxCommand(array $form, FormStateInterface $form_state) {
265
    $ajax = new AjaxResponse();
266
267
    if (($triggering_element = $form_state->getTriggeringElement()) && $triggering_element['#name'] === 'ajax_action_handler' && !empty($triggering_element['#value'])) {
268
      $commands = json_decode($triggering_element['#value'], TRUE);
269
270
      // Entity IDs that are affected by this command.
271
      if (isset($commands['add'])) {
272
        $entity_ids = $commands['add'];
273
274
        $selected_entities = &$form_state->get([
275
          'entity_browser',
276
          'selected_entities',
277
        ]);
278
279
        // Get entities added by this command and generate JS commands for them.
280
        $selected_entity_keys = array_keys($selected_entities);
281
        $key_index = count($selected_entity_keys) - count($entity_ids);
282
        foreach ($entity_ids as $entity_pair_info) {
283
          $last_entity_id = $selected_entities[$selected_entity_keys[$key_index]]->id();
284
285
          $html = drupal_render($form['selection_display']['selected']['items_' . $last_entity_id . '_' . $selected_entity_keys[$key_index]]);
286
          $ajax->addCommand(
287
            new ReplaceCommand('div[id="' . $entity_pair_info['proxy_id'] . '"]', trim($html))
288
          );
289
290
          $key_index++;
291
        }
292
      }
293
294
      // Add Invoke command to trigger loading of entities that are queued
295
      // during execution of current Ajax request.
296
      $ajax->addCommand(
297
        new InvokeCommand('.entities-list', 'trigger', ['load-entities'])
298
      );
299
    }
300
301
    return $ajax;
302
  }
303
304
  /**
305
   * Submit callback for remove buttons.
306
   *
307
   * @param array $form
308
   *   Form.
309
   * @param \Drupal\Core\Form\FormStateInterface $form_state
310
   *   Form state.
311
   */
312
  public static function removeItemSubmit(array &$form, FormStateInterface $form_state) {
313
    $triggering_element = $form_state->getTriggeringElement();
314
315
    // Remove weight of entity being removed.
316
    $form_state->unsetValue([
317
      'selected',
318
      $triggering_element['#attributes']['data-remove-entity'] . '_' . $triggering_element['#attributes']['data-row-id'],
319
    ]);
320
321
    // Remove entity itself.
322
    $selected_entities = &$form_state->get(['entity_browser', 'selected_entities']);
323
    unset($selected_entities[$triggering_element['#attributes']['data-row-id']]);
324
325
    static::saveNewOrder($form_state);
326
    $form_state->setRebuild();
327
  }
328
329
  /**
330
   * {@inheritdoc}
331
   */
332
  public function submit(array &$form, FormStateInterface $form_state) {
333
    $this->saveNewOrder($form_state);
334
    if ($form_state->getTriggeringElement()['#name'] == 'use_selected') {
335
      $this->selectionDone($form_state);
336
    }
337
  }
338
339
  /**
340
   * Saves new ordering of entities based on weight.
341
   *
342
   * @param FormStateInterface $form_state
343
   *   Form state.
344
   */
345
  public static function saveNewOrder(FormStateInterface $form_state) {
346
    $selected = $form_state->getValue('selected');
347
    if (!empty($selected)) {
348
      $weights = array_column($selected, 'weight');
349
      $selected_entities = $form_state->get(['entity_browser', 'selected_entities']);
350
351
      // If we added new entities to the selection at this step we won't have
352
      // weights for them so we have to fake them.
353
      $diff_selected_size = count($selected_entities) - count($weights);
354
      if ($diff_selected_size > 0) {
355
        $max_weight = (max($weights) + 1);
356
        for ($new_weight = $max_weight; $new_weight < ($max_weight + $diff_selected_size); $new_weight++) {
357
          $weights[] = $new_weight;
358
        }
359
      }
360
361
      $ordered = array_combine($weights, $selected_entities);
362
      ksort($ordered);
363
      $form_state->set(['entity_browser', 'selected_entities'], $ordered);
364
    }
365
  }
366
367
  /**
368
   * {@inheritdoc}
369
   */
370
  public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
371
    $default_entity_type = $form_state->getValue('entity_type', $this->configuration['entity_type']);
372
    $default_display = $form_state->getValue('display', $this->configuration['display']);
373
    $default_display_settings = $form_state->getValue('display_settings', $this->configuration['display_settings']);
374
    $default_display_settings += ['entity_type' => $default_entity_type];
375
376
    if ($form_state->isRebuilding()) {
377
      $form['#prefix'] = '<div id="multi-step-form-wrapper">';
378
    } else {
379
      $form['#prefix'] .= '<div id="multi-step-form-wrapper">';
380
    }
381
    $form['#suffix'] = '</div>';
382
383
    $entity_types = [];
384
    foreach ($this->entityTypeManager->getDefinitions() as $entity_type_id => $entity_type) {
385
      /** @var \Drupal\Core\Entity\EntityTypeInterface $entity_type */
386
      $entity_types[$entity_type_id] = $entity_type->getLabel();
387
    }
388
    $form['entity_type'] = [
389
      '#type' => 'select',
390
      '#title' => $this->t('Entity type'),
391
      '#description' => $this->t("Entity browser itself does not need information about entity type being selected. It can actually select entities of different type. However, some of the display plugins need to know which entity type they are operating with. Display plugins that do not need this info will ignore this configuration value."),
392
      '#default_value' => $default_entity_type,
393
      '#options' => $entity_types,
394
      '#ajax' => [
395
        'callback' => [$this, 'updateSettingsAjax'],
396
        'wrapper' => 'multi-step-form-wrapper',
397
      ],
398
    ];
399
400
    $displays = [];
401
    foreach ($this->fieldDisplayManager->getDefinitions() as $display_plugin_id => $definition) {
402
      $entity_type = $this->entityTypeManager->getDefinition($default_entity_type);
403
      if ($this->fieldDisplayManager->createInstance($display_plugin_id)->isApplicable($entity_type)) {
404
        $displays[$display_plugin_id] = $definition['label'];
405
      }
406
    }
407
    $form['display'] = [
408
      '#title' => $this->t('Entity display plugin'),
409
      '#type' => 'select',
410
      '#default_value' => $default_display,
411
      '#options' => $displays,
412
      '#ajax' => [
413
        'callback' => [$this, 'updateSettingsAjax'],
414
        'wrapper' => 'multi-step-form-wrapper',
415
      ],
416
    ];
417
418
    $form['display_settings'] = [
419
      '#type' => 'container',
420
      '#title' => $this->t('Entity display plugin configuration'),
421
      '#tree' => TRUE,
422
    ];
423
    if ($default_display_settings) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $default_display_settings of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
424
      $display_plugin = $this->fieldDisplayManager
425
        ->createInstance($default_display, $default_display_settings);
426
427
      $form['display_settings'] += $display_plugin->settingsForm($form, $form_state);
428
    }
429
    $form['select_text'] = [
430
      '#type' => 'textfield',
431
      '#title' => $this->t('Select button text'),
432
      '#default_value' => $this->configuration['select_text'],
433
      '#description' => $this->t('Text to display on the entity browser select button.'),
434
    ];
435
436
    $form['selection_hidden'] = [
437
      '#type' => 'checkbox',
438
      '#title' => $this->t('Selection hidden by default'),
439
      '#default_value' => $this->configuration['selection_hidden'],
440
      '#description' => $this->t('Whether or not the selection should be hidden by default.'),
441
    ];
442
443
    return $form;
444
  }
445
446
  /**
447
   * Ajax callback that updates multi-step plugin configuration form on AJAX updates.
448
   */
449
  public function updateSettingsAjax(array $form, FormStateInterface $form_state) {
450
    return $form;
451
  }
452
453
}
454