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

MultiStepDisplay::handleAjaxCommand()   B

Complexity

Conditions 7
Paths 4

Size

Total Lines 56
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

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