EntityDisplayFormBase.php 36 KB
Newer Older
1
2
3
4
<?php

/**
 * @file
5
 * Contains \Drupal\field_ui\Form\EntityDisplayFormBase.
6
7
 */

8
namespace Drupal\field_ui\Form;
9

10
use Drupal\Component\Plugin\Factory\DefaultFactory;
11
use Drupal\Component\Plugin\PluginManagerBase;
12
use Drupal\Component\Utility\Html;
13
14
15
use Drupal\Core\Entity\EntityForm;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityWithPluginCollectionInterface;
16
use Drupal\Core\Field\FieldDefinitionInterface;
17
use Drupal\Core\Field\FieldTypePluginManagerInterface;
18
use Drupal\Core\Field\PluginSettingsInterface;
19
use Drupal\Core\Form\FormStateInterface;
20
use Drupal\Core\Render\Element;
21
22
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\field_ui\FieldUI;
23
24

/**
25
 * Base class for EntityDisplay edit forms.
26
 */
27
abstract class EntityDisplayFormBase extends EntityForm {
28

29
30
31
32
33
34
35
  /**
   * The display context. Either 'view' or 'form'.
   *
   * @var string
   */
  protected $displayContext;

36
37
38
39
40
41
42
43
44
45
46
47
48
49
  /**
   * The widget or formatter plugin manager.
   *
   * @var \Drupal\Component\Plugin\PluginManagerBase
   */
  protected $pluginManager;

  /**
   * A list of field types.
   *
   * @var array
   */
  protected $fieldTypes;

50
  /**
51
   * The entity being used by this form.
52
   *
53
   * @var \Drupal\Core\Entity\Display\EntityDisplayInterface
54
   */
55
  protected $entity;
56

57
  /**
58
   * Constructs a new EntityDisplayFormBase.
59
   *
60
   * @param \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_manager
61
   *   The field type manager.
62
63
64
   * @param \Drupal\Component\Plugin\PluginManagerBase $plugin_manager
   *   The widget or formatter plugin manager.
   */
65
  public function __construct(FieldTypePluginManagerInterface $field_type_manager, PluginManagerBase $plugin_manager) {
66
    $this->fieldTypes = $field_type_manager->getDefinitions();
67
    $this->pluginManager = $plugin_manager;
68
69
70
71
72
  }

  /**
   * {@inheritdoc}
   */
73
74
75
  public function getEntityFromRouteMatch(RouteMatchInterface $route_match, $entity_type_id) {
    $route_parameters = $route_match->getParameters()->all();

76
    return $this->getEntityDisplay($route_parameters['entity_type_id'], $route_parameters['bundle'], $route_parameters[$this->displayContext . '_mode_name']);
77
78
79
  }

  /**
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
   * Get the regions needed to create the overview form.
   *
   * @return array
   *   Example usage:
   *   @code
   *     return array(
   *       'content' => array(
   *         // label for the region.
   *         'title' => $this->t('Content'),
   *         // Indicates if the region is visible in the UI.
   *         'invisible' => TRUE,
   *         // A message to indicate that there is nothing to be displayed in
   *         // the region.
   *         'message' => $this->t('No field is displayed.'),
   *       ),
   *     );
   *   @endcode
97
98
99
100
   */
  public function getRegions() {
    return array(
      'content' => array(
101
        'title' => $this->t('Content'),
102
        'invisible' => TRUE,
103
        'message' => $this->t('No field is displayed.')
104
105
      ),
      'hidden' => array(
106
        'title' => $this->t('Disabled', array(), array('context' => 'Plural')),
107
        'message' => $this->t('No field is hidden.')
108
109
110
111
      ),
    );
  }

112
113
114
115
116
117
118
119
120
121
122
123
124
125
  /**
   * Returns an associative array of all regions.
   *
   * @return array
   *   An array containing the region options.
   */
  public function getRegionOptions() {
    $options = array();
    foreach ($this->getRegions() as $region => $data) {
      $options[$region] = $data['title'];
    }
    return $options;
  }

126
127
128
129
130
131
132
133
  /**
   * Collects the definitions of fields whose display is configurable.
   *
   * @return \Drupal\Core\Field\FieldDefinitionInterface[]
   *   The array of field definitions
   */
  protected function getFieldDefinitions() {
    $context = $this->displayContext;
134
    return array_filter($this->entityManager->getFieldDefinitions($this->entity->getTargetEntityTypeId(), $this->entity->getTargetBundle()), function(FieldDefinitionInterface $field_definition) use ($context) {
135
136
137
138
      return $field_definition->isDisplayConfigurable($context);
    });
  }

139
140
141
  /**
   * {@inheritdoc}
   */
142
143
  public function form(array $form, FormStateInterface $form_state) {
    $form = parent::form($form, $form_state);
144

145
    $field_definitions = $this->getFieldDefinitions();
146
147
148
    $extra_fields = $this->getExtraFields();

    $form += array(
149
150
      '#entity_type' => $this->entity->getTargetEntityTypeId(),
      '#bundle' => $this->entity->getTargetBundle(),
151
      '#fields' => array_keys($field_definitions),
152
153
154
      '#extra' => array_keys($extra_fields),
    );

155
    if (empty($field_definitions) && empty($extra_fields) && $route_info = FieldUI::getOverviewRouteInfo($this->entity->getTargetEntityTypeId(), $this->entity->getTargetBundle())) {
156
      drupal_set_message($this->t('There are no fields yet added. You can add new fields on the <a href=":link">Manage fields</a> page.', array(':link' => $route_info->toString())), 'warning');
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
      return $form;
    }

    $table = array(
      '#type' => 'field_ui_table',
      '#pre_render' => array(array($this, 'tablePreRender')),
      '#tree' => TRUE,
      '#header' => $this->getTableHeader(),
      '#regions' => $this->getRegions(),
      '#attributes' => array(
        'class' => array('field-ui-overview'),
        'id' => 'field-display-overview',
      ),
      // Add Ajax wrapper.
      '#prefix' => '<div id="field-display-overview-wrapper">',
      '#suffix' => '</div>',
173
174
175
176
177
178
179
180
181
182
183
184
185
186
      '#tabledrag' => array(
        array(
          'action' => 'order',
          'relationship' => 'sibling',
          'group' => 'field-weight',
        ),
        array(
          'action' => 'match',
          'relationship' => 'parent',
          'group' => 'field-parent',
          'subgroup' => 'field-parent',
          'source' => 'field-name',
        ),
      ),
187
188
189
    );

    // Field rows.
190
    foreach ($field_definitions as $field_name => $field_definition) {
191
      $table[$field_name] = $this->buildFieldRow($field_definition, $form, $form_state);
192
193
194
195
    }

    // Non-field elements.
    foreach ($extra_fields as $field_id => $extra_field) {
196
      $table[$field_id] = $this->buildExtraFieldRow($field_id, $extra_field);
197
198
199
200
201
    }

    $form['fields'] = $table;

    // Custom display settings.
202
    if ($this->entity->getMode() == 'default') {
203
      // Only show the settings if there is at least one custom display mode.
204
205
206
207
      $display_mode_options = $this->getDisplayModeOptions();
      // Unset default option.
      unset($display_mode_options['default']);
      if ($display_mode_options) {
208
209
        $form['modes'] = array(
          '#type' => 'details',
210
          '#title' => $this->t('Custom display settings'),
211
        );
212
        // Prepare default values for the 'Custom display settings' checkboxes.
213
        $default = array();
214
215
        if ($display_statuses = array_filter($this->getDisplayStatuses())) {
          $default = array_keys(array_intersect_key($display_mode_options, $display_statuses));
216
217
218
        }
        $form['modes']['display_modes_custom'] = array(
          '#type' => 'checkboxes',
219
          '#title' => $this->t('Use custom display settings for the following modes'),
220
          '#options' => $display_mode_options,
221
222
223
224
225
226
227
228
229
230
231
232
233
234
          '#default_value' => $default,
        );
      }
    }

    // In overviews involving nested rows from contributed modules (i.e
    // field_group), the 'plugin type' selects can trigger a series of changes
    // in child rows. The #ajax behavior is therefore not attached directly to
    // the selects, but triggered by the client-side script through a hidden
    // #ajax 'Refresh' button. A hidden 'refresh_rows' input tracks the name of
    // affected rows.
    $form['refresh_rows'] = array('#type' => 'hidden');
    $form['refresh'] = array(
      '#type' => 'submit',
235
      '#value' => $this->t('Refresh'),
236
      '#op' => 'refresh_table',
237
      '#submit' => array('::multistepSubmit'),
238
      '#ajax' => array(
239
        'callback' => '::multistepAjax',
240
241
242
243
244
245
246
247
248
249
        'wrapper' => 'field-display-overview-wrapper',
        'effect' => 'fade',
        // The button stays hidden, so we hide the Ajax spinner too. Ad-hoc
        // spinners will be added manually by the client-side script.
        'progress' => 'none',
      ),
      '#attributes' => array('class' => array('visually-hidden'))
    );

    $form['actions'] = array('#type' => 'actions');
250
251
252
253
254
    $form['actions']['submit'] = array(
      '#type' => 'submit',
      '#button_type' => 'primary',
      '#value' => $this->t('Save'),
    );
255

256
    $form['#attached']['library'][] = 'field_ui/drupal.field_ui';
257
258
259
260
261
262
263

    return $form;
  }

  /**
   * Builds the table row structure for a single field.
   *
264
265
   * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
   *   The field definition.
266
267
   * @param array $form
   *   An associative array containing the structure of the form.
268
269
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The current state of the form.
270
271
272
273
   *
   * @return array
   *   A table row array.
   */
274
  protected function buildFieldRow(FieldDefinitionInterface $field_definition, array $form, FormStateInterface $form_state) {
275
    $field_name = $field_definition->getName();
276
    $display_options = $this->entity->getComponent($field_name);
277
    $label = $field_definition->getLabel();
278

279
280
281
282
283
284
    // Disable fields without any applicable plugins.
    if (empty($this->getApplicablePluginOptions($field_definition))) {
      $this->entity->removeComponent($field_name)->save();
      $display_options = $this->entity->getComponent($field_name);
    }

285
    $regions = array_keys($this->getRegions());
286
287
288
289
290
291
    $field_row = array(
      '#attributes' => array('class' => array('draggable', 'tabledrag-leaf')),
      '#row_type' => 'field',
      '#region_callback' => array($this, 'getRowRegion'),
      '#js_settings' => array(
        'rowHandler' => 'field',
292
        'defaultPlugin' => $this->getDefaultPlugin($field_definition->getType()),
293
294
      ),
      'human_name' => array(
295
        '#plain_text' => $label,
296
297
298
      ),
      'weight' => array(
        '#type' => 'textfield',
299
        '#title' => $this->t('Weight for @title', array('@title' => $label)),
300
301
302
303
304
305
306
307
        '#title_display' => 'invisible',
        '#default_value' => $display_options ? $display_options['weight'] : '0',
        '#size' => 3,
        '#attributes' => array('class' => array('field-weight')),
      ),
      'parent_wrapper' => array(
        'parent' => array(
          '#type' => 'select',
308
          '#title' => $this->t('Label display for @title', array('@title' => $label)),
309
          '#title_display' => 'invisible',
310
          '#options' => array_combine($regions, $regions),
311
          '#empty_value' => '',
312
          '#attributes' => array('class' => array('js-field-parent', 'field-parent')),
313
          '#parents' => array('fields', $field_name, 'parent'),
314
315
316
        ),
        'hidden_name' => array(
          '#type' => 'hidden',
317
          '#default_value' => $field_name,
318
319
320
321
322
323
324
325
          '#attributes' => array('class' => array('field-name')),
        ),
      ),
    );

    $field_row['plugin'] = array(
      'type' => array(
        '#type' => 'select',
326
        '#title' => $this->t('Plugin for @title', array('@title' => $label)),
327
        '#title_display' => 'invisible',
328
        '#options' => $this->getPluginOptions($field_definition),
329
        '#default_value' => $display_options ? $display_options['type'] : 'hidden',
330
        '#parents' => array('fields', $field_name, 'type'),
331
332
333
334
335
336
        '#attributes' => array('class' => array('field-plugin-type')),
      ),
      'settings_edit_form' => array(),
    );

    // Get the corresponding plugin object.
337
    $plugin = $this->entity->getRenderer($field_name);
338
339
340

    // Base button element for the various plugin settings actions.
    $base_button = array(
341
      '#submit' => array('::multistepSubmit'),
342
      '#ajax' => array(
343
        'callback' => '::multistepAjax',
344
345
346
        'wrapper' => 'field-display-overview-wrapper',
        'effect' => 'fade',
      ),
347
      '#field_name' => $field_name,
348
349
    );

350
    if ($form_state->get('plugin_settings_edit') == $field_name) {
351
352
353
354
355
356
357
      // We are currently editing this field's plugin settings. Display the
      // settings form and submit buttons.
      $field_row['plugin']['settings_edit_form'] = array();

      if ($plugin) {
        // Generate the settings form and allow other modules to alter it.
        $settings_form = $plugin->settingsForm($form, $form_state);
358
        $third_party_settings_form = $this->thirdPartySettingsForm($plugin, $field_definition, $form, $form_state);
359

360
        if ($settings_form || $third_party_settings_form) {
361
362
363
364
          $field_row['plugin']['#cell_attributes'] = array('colspan' => 3);
          $field_row['plugin']['settings_edit_form'] = array(
            '#type' => 'container',
            '#attributes' => array('class' => array('field-plugin-settings-edit-form')),
365
            '#parents' => array('fields', $field_name, 'settings_edit_form'),
366
            'label' => array(
367
              '#markup' => $this->t('Plugin settings'),
368
369
            ),
            'settings' => $settings_form,
370
            'third_party_settings' => $third_party_settings_form,
371
372
373
374
            'actions' => array(
              '#type' => 'actions',
              'save_settings' => $base_button + array(
                '#type' => 'submit',
375
                '#button_type' => 'primary',
376
                '#name' => $field_name . '_plugin_settings_update',
377
                '#value' => $this->t('Update'),
378
379
380
381
                '#op' => 'update',
              ),
              'cancel_settings' => $base_button + array(
                '#type' => 'submit',
382
                '#name' => $field_name . '_plugin_settings_cancel',
383
                '#value' => $this->t('Cancel'),
384
385
386
                '#op' => 'cancel',
                // Do not check errors for the 'Cancel' button, but make sure we
                // get the value of the 'plugin type' select.
387
                '#limit_validation_errors' => array(array('fields', $field_name, 'type')),
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
              ),
            ),
          );
          $field_row['#attributes']['class'][] = 'field-plugin-settings-editing';
        }
      }
    }
    else {
      $field_row['settings_summary'] = array();
      $field_row['settings_edit'] = array();

      if ($plugin) {
        // Display a summary of the current plugin settings, and (if the
        // summary is not empty) a button to edit them.
        $summary = $plugin->settingsSummary();

        // Allow other modules to alter the summary.
405
        $this->alterSettingsSummary($summary, $plugin, $field_definition);
406
407
408

        if (!empty($summary)) {
          $field_row['settings_summary'] = array(
409
410
411
            '#type' => 'inline_template',
            '#template' => '<div class="field-plugin-summary">{{ summary|safe_join("<br />") }}</div>',
            '#context' => array('summary' => $summary),
412
413
            '#cell_attributes' => array('class' => array('field-plugin-summary-cell')),
          );
414
        }
415
416

        // Check selected plugin settings to display edit link or not.
417
418
419
        $settings_form = $plugin->settingsForm($form, $form_state);
        $third_party_settings_form = $this->thirdPartySettingsForm($plugin, $field_definition, $form, $form_state);
        if (!empty($settings_form) || !empty($third_party_settings_form)) {
420
421
          $field_row['settings_edit'] = $base_button + array(
            '#type' => 'image_button',
422
            '#name' => $field_name . '_settings_edit',
423
            '#src' => 'core/misc/icons/787878/cog.svg',
424
            '#attributes' => array('class' => array('field-plugin-settings-edit'), 'alt' => $this->t('Edit')),
425
426
427
            '#op' => 'edit',
            // Do not check errors for the 'Edit' button, but make sure we get
            // the value of the 'plugin type' select.
428
            '#limit_validation_errors' => array(array('fields', $field_name, 'type')),
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
            '#prefix' => '<div class="field-plugin-settings-edit-wrapper">',
            '#suffix' => '</div>',
          );
        }
      }
    }

    return $field_row;
  }

  /**
   * Builds the table row structure for a single extra field.
   *
   * @param string $field_id
   *   The field ID.
   * @param array $extra_field
   *   The pseudo-field element.
   *
   * @return array
   *   A table row array.
   */
450
451
  protected function buildExtraFieldRow($field_id, $extra_field) {
    $display_options = $this->entity->getComponent($field_id);
452

453
    $regions = array_keys($this->getRegions());
454
455
456
457
458
459
    $extra_field_row = array(
      '#attributes' => array('class' => array('draggable', 'tabledrag-leaf')),
      '#row_type' => 'extra_field',
      '#region_callback' => array($this, 'getRowRegion'),
      '#js_settings' => array('rowHandler' => 'field'),
      'human_name' => array(
460
        '#markup' => $extra_field['label'],
461
462
463
      ),
      'weight' => array(
        '#type' => 'textfield',
464
        '#title' => $this->t('Weight for @title', array('@title' => $extra_field['label'])),
465
466
467
468
469
470
471
472
        '#title_display' => 'invisible',
        '#default_value' => $display_options ? $display_options['weight'] : 0,
        '#size' => 3,
        '#attributes' => array('class' => array('field-weight')),
      ),
      'parent_wrapper' => array(
        'parent' => array(
          '#type' => 'select',
473
          '#title' => $this->t('Parents for @title', array('@title' => $extra_field['label'])),
474
          '#title_display' => 'invisible',
475
          '#options' => array_combine($regions, $regions),
476
          '#empty_value' => '',
477
          '#attributes' => array('class' => array('js-field-parent', 'field-parent')),
478
479
480
481
482
483
484
485
486
487
488
          '#parents' => array('fields', $field_id, 'parent'),
        ),
        'hidden_name' => array(
          '#type' => 'hidden',
          '#default_value' => $field_id,
          '#attributes' => array('class' => array('field-name')),
        ),
      ),
      'plugin' => array(
        'type' => array(
          '#type' => 'select',
489
          '#title' => $this->t('Visibility for @title', array('@title' => $extra_field['label'])),
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
          '#title_display' => 'invisible',
          '#options' => $this->getExtraFieldVisibilityOptions(),
          '#default_value' => $display_options ? 'visible' : 'hidden',
          '#parents' => array('fields', $field_id, 'type'),
          '#attributes' => array('class' => array('field-plugin-type')),
        ),
      ),
      'settings_summary' => array(),
      'settings_edit' => array(),
    );

    return $extra_field_row;
  }

  /**
   * {@inheritdoc}
   */
507
  public function submitForm(array &$form, FormStateInterface $form_state) {
508
509
510
511
512
513
514
    // If the main "Save" button was submitted while a field settings subform
    // was being edited, update the new incoming settings when rebuilding the
    // entity, just as if the subform's "Update" button had been submitted.
    if ($edit_field = $form_state->get('plugin_settings_edit')) {
      $form_state->set('plugin_settings_update', $edit_field);
    }

515
    parent::submitForm($form, $form_state);
516
    $form_values = $form_state->getValues();
517
518

    // Handle the 'display modes' checkboxes if present.
519
    if ($this->entity->getMode() == 'default' && !empty($form_values['display_modes_custom'])) {
520
521
522
523
524
525
526
527
528
      $display_modes = $this->getDisplayModes();
      $current_statuses = $this->getDisplayStatuses();

      $statuses = array();
      foreach ($form_values['display_modes_custom'] as $mode => $value) {
        if (!empty($value) && empty($current_statuses[$mode])) {
          // If no display exists for the newly enabled view mode, initialize
          // it with those from the 'default' view mode, which were used so
          // far.
529
530
          if (!$this->entityManager->getStorage($this->entity->getEntityTypeId())->load($this->entity->getTargetEntityTypeId() . '.' . $this->entity->getTargetBundle() . '.' . $mode)) {
            $display = $this->getEntityDisplay($this->entity->getTargetEntityTypeId(), $this->entity->getTargetBundle(), 'default')->createCopy($mode);
531
532
533
534
535
            $display->save();
          }

          $display_mode_label = $display_modes[$mode]['label'];
          $url = $this->getOverviewUrl($mode);
536
          drupal_set_message($this->t('The %display_mode mode now uses custom display settings. You might want to <a href=":url">configure them</a>.', ['%display_mode' => $display_mode_label, ':url' => $url->toString()]));
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
        }
        $statuses[$mode] = !empty($value);
      }

      $this->saveDisplayStatuses($statuses);
    }

    drupal_set_message($this->t('Your settings have been saved.'));
  }

  /**
   * {@inheritdoc}
   */
  protected function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state) {
    $form_values = $form_state->getValues();

    if ($this->entity instanceof EntityWithPluginCollectionInterface) {
      // Do not manually update values represented by plugin collections.
      $form_values = array_diff_key($form_values, $this->entity->getPluginCollections());
    }
557
558
559
560
561
562

    // Collect data for 'regular' fields.
    foreach ($form['#fields'] as $field_name) {
      $values = $form_values['fields'][$field_name];

      if ($values['type'] == 'hidden') {
563
        $entity->removeComponent($field_name);
564
565
      }
      else {
566
567
568
569
570
571
        $options = $entity->getComponent($field_name);

        // Update field settings only if the submit handler told us to.
        if ($form_state->get('plugin_settings_update') === $field_name) {
          // Only store settings actually used by the selected plugin.
          $default_settings = $this->pluginManager->getDefaultSettings($options['type']);
572
          $options['settings'] = isset($values['settings_edit_form']['settings']) ? array_intersect_key($values['settings_edit_form']['settings'], $default_settings) : [];
573
574
          $options['third_party_settings'] = isset($values['settings_edit_form']['third_party_settings']) ? $values['settings_edit_form']['third_party_settings'] : [];
          $form_state->set('plugin_settings_update', NULL);
575
576
        }

577
578
        $options['type'] = $values['type'];
        $options['weight'] = $values['weight'];
579
580
        // Only formatters have configurable label visibility.
        if (isset($values['label'])) {
581
          $options['label'] = $values['label'];
582
        }
583
        $entity->setComponent($field_name, $options);
584
585
586
587
588
589
      }
    }

    // Collect data for 'extra' fields.
    foreach ($form['#extra'] as $name) {
      if ($form_values['fields'][$name]['type'] == 'hidden') {
590
        $entity->removeComponent($name);
591
592
      }
      else {
593
        $entity->setComponent($name, array(
594
595
596
597
598
599
600
601
602
          'weight' => $form_values['fields'][$name]['weight'],
        ));
      }
    }
  }

  /**
   * Form submission handler for multistep buttons.
   */
603
  public function multistepSubmit($form, FormStateInterface $form_state) {
604
    $trigger = $form_state->getTriggeringElement();
605
606
607
608
609
610
    $op = $trigger['#op'];

    switch ($op) {
      case 'edit':
        // Store the field whose settings are currently being edited.
        $field_name = $trigger['#field_name'];
611
        $form_state->set('plugin_settings_edit', $field_name);
612
613
614
        break;

      case 'update':
615
616
        // Set the field back to 'non edit' mode, and update $this->entity with
        // the new settings fro the next rebuild.
617
        $field_name = $trigger['#field_name'];
618
        $form_state->set('plugin_settings_edit', NULL);
619
620
        $form_state->set('plugin_settings_update', $field_name);
        $this->entity = $this->buildEntity($form, $form_state);
621
622
623
624
        break;

      case 'cancel':
        // Set the field back to 'non edit' mode.
625
        $form_state->set('plugin_settings_edit', NULL);
626
627
628
629
630
        break;

      case 'refresh_table':
        // If the currently edited field is one of the rows to be refreshed, set
        // it back to 'non edit' mode.
631
        $updated_rows = explode(' ', $form_state->getValue('refresh_rows'));
632
633
634
        $plugin_settings_edit = $form_state->get('plugin_settings_edit');
        if ($plugin_settings_edit && in_array($plugin_settings_edit, $updated_rows)) {
          $form_state->set('plugin_settings_edit', NULL);
635
636
637
638
        }
        break;
    }

639
    $form_state->setRebuild();
640
641
642
643
644
  }

  /**
   * Ajax handler for multistep buttons.
   */
645
  public function multistepAjax($form, FormStateInterface $form_state) {
646
    $trigger = $form_state->getTriggeringElement();
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
    $op = $trigger['#op'];

    // Pick the elements that need to receive the ajax-new-content effect.
    switch ($op) {
      case 'edit':
        $updated_rows = array($trigger['#field_name']);
        $updated_columns = array('plugin');
        break;

      case 'update':
      case 'cancel':
        $updated_rows = array($trigger['#field_name']);
        $updated_columns = array('plugin', 'settings_summary', 'settings_edit');
        break;

      case 'refresh_table':
663
        $updated_rows = array_values(explode(' ', $form_state->getValue('refresh_rows')));
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
        $updated_columns = array('settings_summary', 'settings_edit');
        break;
    }

    foreach ($updated_rows as $name) {
      foreach ($updated_columns as $key) {
        $element = &$form['fields'][$name][$key];
        $element['#prefix'] = '<div class="ajax-new-content">' . (isset($element['#prefix']) ? $element['#prefix'] : '');
        $element['#suffix'] = (isset($element['#suffix']) ? $element['#suffix'] : '') . '</div>';
      }
    }

    // Return the whole table.
    return $form['fields'];
  }

680
681
682
683
684
685
686
687
688
689
  /**
   * Performs pre-render tasks on field_ui_table elements.
   *
   * @param array $elements
   *   A structured array containing two sub-levels of elements. Properties
   *   used:
   *   - #tabledrag: The value is a list of $options arrays that are passed to
   *     drupal_attach_tabledrag(). The HTML ID of the table is added to each
   *     $options array.
   *
690
691
   * @return array
   *
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
   * @see drupal_render()
   * @see \Drupal\Core\Render\Element\Table::preRenderTable()
   */
  public function tablePreRender($elements) {
    $js_settings = array();

    // For each region, build the tree structure from the weight and parenting
    // data contained in the flat form structure, to determine row order and
    // indentation.
    $regions = $elements['#regions'];
    $tree = array('' => array('name' => '', 'children' => array()));
    $trees = array_fill_keys(array_keys($regions), $tree);

    $parents = array();
    $children = Element::children($elements);
    $list = array_combine($children, $children);

    // Iterate on rows until we can build a known tree path for all of them.
    while ($list) {
      foreach ($list as $name) {
        $row = &$elements[$name];
        $parent = $row['parent_wrapper']['parent']['#value'];
        // Proceed if parent is known.
        if (empty($parent) || isset($parents[$parent])) {
          // Grab parent, and remove the row from the next iteration.
          $parents[$name] = $parent ? array_merge($parents[$parent], array($parent)) : array();
          unset($list[$name]);

          // Determine the region for the row.
          $region_name = call_user_func($row['#region_callback'], $row);

          // Add the element in the tree.
          $target = &$trees[$region_name][''];
          foreach ($parents[$name] as $key) {
            $target = &$target['children'][$key];
          }
          $target['children'][$name] = array('name' => $name, 'weight' => $row['weight']['#value']);

          // Add tabledrag indentation to the first row cell.
          if ($depth = count($parents[$name])) {
            $children = Element::children($row);
            $cell = current($children);
            $indentation = array(
              '#theme' => 'indentation',
              '#size' => $depth,
            );
            $row[$cell]['#prefix'] = drupal_render($indentation) . (isset($row[$cell]['#prefix']) ? $row[$cell]['#prefix'] : '');
          }

          // Add row id and associate JS settings.
          $id = Html::getClass($name);
          $row['#attributes']['id'] = $id;
          if (isset($row['#js_settings'])) {
            $row['#js_settings'] += array(
              'rowHandler' => $row['#row_type'],
              'name' => $name,
              'region' => $region_name,
            );
            $js_settings[$id] = $row['#js_settings'];
          }
        }
      }
    }
    // Determine rendering order from the tree structure.
    foreach ($regions as $region_name => $region) {
      $elements['#regions'][$region_name]['rows_order'] = array_reduce($trees[$region_name], array($this, 'reduceOrder'));
    }

    $elements['#attached']['drupalSettings']['fieldUIRowsData'] = $js_settings;

    // If the custom #tabledrag is set and there is a HTML ID, add the table's
    // HTML ID to the options and attach the behavior.
    // @see \Drupal\Core\Render\Element\Table::preRenderTable()
    if (!empty($elements['#tabledrag']) && isset($elements['#attributes']['id'])) {
      foreach ($elements['#tabledrag'] as $options) {
        $options['table_id'] = $elements['#attributes']['id'];
        drupal_attach_tabledrag($elements, $options);
      }
    }

    return $elements;
  }

  /**
   * Determines the rendering order of an array representing a tree.
   *
   * Callback for array_reduce() within
779
   * \Drupal\field_ui\Form\EntityDisplayFormBase::tablePreRender().
780
781
782
783
784
785
786
787
788
789
790
791
792
   */
  public function reduceOrder($array, $a) {
    $array = !isset($array) ? array() : $array;
    if ($a['name']) {
      $array[] = $a['name'];
    }
    if (!empty($a['children'])) {
      uasort($a['children'], array('Drupal\Component\Utility\SortArray', 'sortByWeightElement'));
      $array = array_merge($array, array_reduce($a['children'], array($this, 'reduceOrder')));
    }
    return $array;
  }

793
794
795
796
  /**
   * Returns the extra fields of the entity type and bundle used by this form.
   *
   * @return array
797
798
799
   *   An array of extra field info.
   *
   * @see \Drupal\Core\Entity\EntityManagerInterface::getExtraFields()
800
   */
801
  protected function getExtraFields() {
802
    $context = $this->displayContext == 'view' ? 'display' : $this->displayContext;
803
    $extra_fields = $this->entityManager->getExtraFields($this->entity->getTargetEntityTypeId(), $this->entity->getTargetBundle());
804
    return isset($extra_fields[$context]) ? $extra_fields[$context] : array();
805
  }
806

807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
  /**
   * Returns an entity display object to be used by this form.
   *
   * @param string $entity_type_id
   *   The target entity type ID of the entity display.
   * @param string $bundle
   *   The target bundle of the entity display.
   * @param string $mode
   *   A view or form mode.
   *
   * @return \Drupal\Core\Entity\Display\EntityDisplayInterface
   *   An entity display.
   */
  abstract protected function getEntityDisplay($entity_type_id, $bundle, $mode);

822
  /**
823
   * Returns an array of applicable widget or formatter options for a field.
824
   *
825
826
   * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
   *   The field definition.
827
828
   *
   * @return array
829
   *   An array of applicable widget or formatter options.
830
   */
831
  protected function getApplicablePluginOptions(FieldDefinitionInterface $field_definition) {
832
833
834
835
836
837
838
839
    $options = $this->pluginManager->getOptions($field_definition->getType());
    $applicable_options = array();
    foreach ($options as $option => $label) {
      $plugin_class = DefaultFactory::getPluginClass($option, $this->pluginManager->getDefinition($option));
      if ($plugin_class::isApplicable($field_definition)) {
        $applicable_options[$option] = $label;
      }
    }
840
841
842
843
844
845
846
847
848
849
850
851
852
853
    return $applicable_options;
  }

  /**
   * Returns an array of widget or formatter options for a field.
   *
   * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
   *   The field definition.
   *
   * @return array
   *   An array of widget or formatter options.
   */
  protected function getPluginOptions(FieldDefinitionInterface $field_definition) {
    $applicable_options = $this->getApplicablePluginOptions($field_definition);
854
    return $applicable_options + array('hidden' => '- ' . $this->t('Hidden') . ' -');
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
  }

  /**
   * Returns the ID of the default widget or formatter plugin for a field type.
   *
   * @param string $field_type
   *   The field type.
   *
   * @return string
   *   The widget or formatter plugin ID.
   */
  abstract protected function getDefaultPlugin($field_type);

  /**
   * Returns the form or view modes used by this form.
   *
   * @return array
   *   An array of form or view mode info.
   */
  abstract protected function getDisplayModes();

876
877
878
879
880
881
882
883
  /**
   * Returns an array of form or view mode options.
   *
   * @return array
   *   An array of form or view mode options.
   */
  abstract protected function getDisplayModeOptions();

884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
  /**
   * Returns the region to which a row in the display overview belongs.
   *
   * @param array $row
   *   The row element.
   *
   * @return string|null
   *   The region name this row belongs to.
   */
  public function getRowRegion($row) {
    switch ($row['#row_type']) {
      case 'field':
      case 'extra_field':
        return ($row['plugin']['type']['#value'] == 'hidden' ? 'hidden' : 'content');
    }
  }

  /**
   * Returns an array of visibility options for extra fields.
   *
   * @return array
   *   An array of visibility options.
   */
  protected function getExtraFieldVisibilityOptions() {
    return array(
909
910
      'visible' => $this->t('Visible'),
      'hidden' => '- ' . $this->t('Hidden') . ' -',
911
912
913
    );
  }

914
915
916
  /**
   * Returns entity (form) displays for the current entity display type.
   *
917
   * @return \Drupal\Core\Entity\Display\EntityDisplayInterface[]
918
919
920
921
   *   An array holding entity displays or entity form displays.
   */
  protected function getDisplays() {
    $load_ids = array();
922
    $display_entity_type = $this->entity->getEntityTypeId();
923
924
    $entity_type = $this->entityManager->getDefinition($display_entity_type);
    $config_prefix = $entity_type->getConfigPrefix();
925
    $ids = $this->configFactory()->listAll($config_prefix . '.' . $this->entity->getTargetEntityTypeId() . '.' . $this->entity->getTargetBundle() . '.');
926
927
928
929
930
931
932
    foreach ($ids as $id) {
      $config_id = str_replace($config_prefix . '.', '', $id);
      list(,, $display_mode) = explode('.', $config_id);
      if ($display_mode != 'default') {
        $load_ids[] = $config_id;
      }
    }
933
    return $this->entityManager->getStorage($display_entity_type)->loadMultiple($load_ids);
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
  }

  /**
   * Returns form or view modes statuses for the bundle used by this form.
   *
   * @return array
   *   An array of form or view mode statuses.
   */
  protected function getDisplayStatuses() {
    $display_statuses = array();
    $displays = $this->getDisplays();
    foreach ($displays as $display) {
      $display_statuses[$display->get('mode')] = $display->status();
    }
    return $display_statuses;
  }

  /**
   * Saves the updated display mode statuses.
   *
   * @param array $display_statuses
   *   An array holding updated form or view mode statuses.
   */
  protected function saveDisplayStatuses($display_statuses) {
    $displays = $this->getDisplays();
    foreach ($displays as $display) {
      $display->set('status', $display_statuses[$display->get('mode')]);
      $display->save();
    }
  }

965
966
967
968
969
970
971
972
973
  /**
   * Returns an array containing the table headers.
   *
   * @return array
   *   The table header.
   */
  abstract protected function getTableHeader();

  /**
974
   * Returns the Url object for a specific entity (form) display edit form.
975
976
977
978
   *
   * @param string $mode
   *   The form or view mode.
   *
979
980
   * @return \Drupal\Core\Url
   *   A Url object for the overview route.
981
   */
982
  abstract protected function getOverviewUrl($mode);
983
984

  /**
985
   * Adds the widget or formatter third party settings forms.
986
   *
987
   * @param \Drupal\Core\Field\PluginSettingsInterface $plugin
988
   *   The widget or formatter.
989
990
   * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
   *   The field definition.
991
   * @param array $form
992
   *   The (entire) configuration form array.
993
   * @param \Drupal\Core\Form\FormStateInterface $form_state
994
   *   The form state.
995
996
997
   *
   * @return array
   *   The widget or formatter third party settings form.
998
   */
999
  abstract protected function thirdPartySettingsForm(PluginSettingsInterface $plugin, FieldDefinitionInterface $field_definition, array $form, FormStateInterface $form_state);
1000
1001
1002
1003
1004
1005

  /**
   * Alters the widget or formatter settings summary.
   *
   * @param array $summary
   *   The widget or formatter settings summary.
1006
   * @param \Drupal\Core\Field\PluginSettingsInterface $plugin
1007
   *   The widget or formatter.
1008
1009
   * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
   *   The field definition.
1010
   */
1011
  abstract protected function alterSettingsSummary(array &$summary, PluginSettingsInterface $plugin, FieldDefinitionInterface $field_definition);
1012
1013

}