EntityDisplayFormBase.php 32.6 KB
Newer Older
1 2
<?php

3
namespace Drupal\field_ui\Form;
4

5
use Drupal\Component\Plugin\Factory\DefaultFactory;
6
use Drupal\Component\Plugin\PluginManagerBase;
7 8 9
use Drupal\Core\Entity\EntityForm;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityWithPluginCollectionInterface;
10
use Drupal\Core\Field\FieldDefinitionInterface;
11
use Drupal\Core\Field\FieldTypePluginManagerInterface;
12
use Drupal\Core\Field\PluginSettingsInterface;
13
use Drupal\Core\Form\FormStateInterface;
14
use Drupal\Core\Routing\RouteMatchInterface;
15
use Drupal\field_ui\Element\FieldUiTable;
16
use Drupal\field_ui\FieldUI;
17 18

/**
19
 * Base class for EntityDisplay edit forms.
20
 */
21
abstract class EntityDisplayFormBase extends EntityForm {
22

23 24 25 26 27 28 29
  /**
   * The display context. Either 'view' or 'form'.
   *
   * @var string
   */
  protected $displayContext;

30 31 32 33 34 35 36 37 38 39 40 41 42 43
  /**
   * The widget or formatter plugin manager.
   *
   * @var \Drupal\Component\Plugin\PluginManagerBase
   */
  protected $pluginManager;

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

44
  /**
45
   * The entity being used by this form.
46
   *
47
   * @var \Drupal\Core\Entity\Display\EntityDisplayInterface
48
   */
49
  protected $entity;
50

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

  /**
   * {@inheritdoc}
   */
67 68 69
  public function getEntityFromRouteMatch(RouteMatchInterface $route_match, $entity_type_id) {
    $route_parameters = $route_match->getParameters()->all();

70
    return $this->getEntityDisplay($route_parameters['entity_type_id'], $route_parameters['bundle'], $route_parameters[$this->displayContext . '_mode_name']);
71 72 73
  }

  /**
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
   * 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
91 92
   */
  public function getRegions() {
93 94
    return [
      'content' => [
95
        'title' => $this->t('Content'),
96
        'invisible' => TRUE,
97
        'message' => $this->t('No field is displayed.')
98 99 100
      ],
      'hidden' => [
        'title' => $this->t('Disabled', [], ['context' => 'Plural']),
101
        'message' => $this->t('No field is hidden.')
102 103
      ],
    ];
104 105
  }

106 107 108 109 110 111 112
  /**
   * Returns an associative array of all regions.
   *
   * @return array
   *   An array containing the region options.
   */
  public function getRegionOptions() {
113
    $options = [];
114 115 116 117 118 119
    foreach ($this->getRegions() as $region => $data) {
      $options[$region] = $data['title'];
    }
    return $options;
  }

120 121 122 123 124 125 126 127
  /**
   * 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;
128
    return array_filter($this->entityManager->getFieldDefinitions($this->entity->getTargetEntityTypeId(), $this->entity->getTargetBundle()), function (FieldDefinitionInterface $field_definition) use ($context) {
129 130 131 132
      return $field_definition->isDisplayConfigurable($context);
    });
  }

133 134 135
  /**
   * {@inheritdoc}
   */
136 137
  public function form(array $form, FormStateInterface $form_state) {
    $form = parent::form($form, $form_state);
138

139
    $field_definitions = $this->getFieldDefinitions();
140 141
    $extra_fields = $this->getExtraFields();

142
    $form += [
143 144
      '#entity_type' => $this->entity->getTargetEntityTypeId(),
      '#bundle' => $this->entity->getTargetBundle(),
145
      '#fields' => array_keys($field_definitions),
146
      '#extra' => array_keys($extra_fields),
147
    ];
148

149
    if (empty($field_definitions) && empty($extra_fields) && $route_info = FieldUI::getOverviewRouteInfo($this->entity->getTargetEntityTypeId(), $this->entity->getTargetBundle())) {
150
      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.', [':link' => $route_info->toString()]), 'warning');
151 152 153
      return $form;
    }

154
    $table = [
155 156 157
      '#type' => 'field_ui_table',
      '#header' => $this->getTableHeader(),
      '#regions' => $this->getRegions(),
158 159
      '#attributes' => [
        'class' => ['field-ui-overview'],
160
        'id' => 'field-display-overview',
161 162 163
      ],
      '#tabledrag' => [
        [
164 165 166
          'action' => 'order',
          'relationship' => 'sibling',
          'group' => 'field-weight',
167 168
        ],
        [
169 170 171 172 173
          'action' => 'match',
          'relationship' => 'parent',
          'group' => 'field-parent',
          'subgroup' => 'field-parent',
          'source' => 'field-name',
174 175
        ],
        [
176 177 178 179 180
          'action' => 'match',
          'relationship' => 'parent',
          'group' => 'field-region',
          'subgroup' => 'field-region',
          'source' => 'field-name',
181 182 183
        ],
      ],
    ];
184 185

    // Field rows.
186
    foreach ($field_definitions as $field_name => $field_definition) {
187
      $table[$field_name] = $this->buildFieldRow($field_definition, $form, $form_state);
188 189 190 191
    }

    // Non-field elements.
    foreach ($extra_fields as $field_id => $extra_field) {
192
      $table[$field_id] = $this->buildExtraFieldRow($field_id, $extra_field);
193 194 195 196 197
    }

    $form['fields'] = $table;

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

    // 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.
231 232
    $form['refresh_rows'] = ['#type' => 'hidden'];
    $form['refresh'] = [
233
      '#type' => 'submit',
234
      '#value' => $this->t('Refresh'),
235
      '#op' => 'refresh_table',
236 237
      '#submit' => ['::multistepSubmit'],
      '#ajax' => [
238
        'callback' => '::multistepAjax',
239 240 241 242 243
        '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',
244 245 246
      ],
      '#attributes' => ['class' => ['visually-hidden']]
    ];
247

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

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

    return $form;
  }

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

278 279 280 281 282 283
    // 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);
    }

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

330 331
    $field_row['plugin'] = [
      'type' => [
332
        '#type' => 'select',
333
        '#title' => $this->t('Plugin for @title', ['@title' => $label]),
334
        '#title_display' => 'invisible',
335
        '#options' => $this->getApplicablePluginOptions($field_definition),
336
        '#default_value' => $display_options ? $display_options['type'] : 'hidden',
337 338 339 340 341
        '#parents' => ['fields', $field_name, 'type'],
        '#attributes' => ['class' => ['field-plugin-type']],
      ],
      'settings_edit_form' => [],
    ];
342 343

    // Get the corresponding plugin object.
344
    $plugin = $this->entity->getRenderer($field_name);
345 346

    // Base button element for the various plugin settings actions.
347 348 349
    $base_button = [
      '#submit' => ['::multistepSubmit'],
      '#ajax' => [
350
        'callback' => '::multistepAjax',
351 352
        'wrapper' => 'field-display-overview-wrapper',
        'effect' => 'fade',
353
      ],
354
      '#field_name' => $field_name,
355
    ];
356

357
    if ($form_state->get('plugin_settings_edit') == $field_name) {
358 359
      // We are currently editing this field's plugin settings. Display the
      // settings form and submit buttons.
360
      $field_row['plugin']['settings_edit_form'] = [];
361 362 363 364

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

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

      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.
412
        $this->alterSettingsSummary($summary, $plugin, $field_definition);
413 414

        if (!empty($summary)) {
415
          $field_row['settings_summary'] = [
416 417
            '#type' => 'inline_template',
            '#template' => '<div class="field-plugin-summary">{{ summary|safe_join("<br />") }}</div>',
418 419 420
            '#context' => ['summary' => $summary],
            '#cell_attributes' => ['class' => ['field-plugin-summary-cell']],
          ];
421
        }
422 423

        // Check selected plugin settings to display edit link or not.
424 425 426
        $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)) {
427
          $field_row['settings_edit'] = $base_button + [
428
            '#type' => 'image_button',
429
            '#name' => $field_name . '_settings_edit',
430
            '#src' => 'core/misc/icons/787878/cog.svg',
431
            '#attributes' => ['class' => ['field-plugin-settings-edit'], 'alt' => $this->t('Edit')],
432 433 434
            '#op' => 'edit',
            // Do not check errors for the 'Edit' button, but make sure we get
            // the value of the 'plugin type' select.
435
            '#limit_validation_errors' => [['fields', $field_name, 'type']],
436 437
            '#prefix' => '<div class="field-plugin-settings-edit-wrapper">',
            '#suffix' => '</div>',
438
          ];
439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456
        }
      }
    }

    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.
   */
457 458
  protected function buildExtraFieldRow($field_id, $extra_field) {
    $display_options = $this->entity->getComponent($field_id);
459

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

    return $extra_field_row;
  }

  /**
   * {@inheritdoc}
   */
519
  public function submitForm(array &$form, FormStateInterface $form_state) {
520 521 522 523 524 525 526
    // 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);
    }

527
    parent::submitForm($form, $form_state);
528
    $form_values = $form_state->getValues();
529 530

    // Handle the 'display modes' checkboxes if present.
531
    if ($this->entity->getMode() == 'default' && !empty($form_values['display_modes_custom'])) {
532 533 534
      $display_modes = $this->getDisplayModes();
      $current_statuses = $this->getDisplayStatuses();

535
      $statuses = [];
536 537 538 539 540
      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.
541 542
          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);
543 544 545 546 547
            $display->save();
          }

          $display_mode_label = $display_modes[$mode]['label'];
          $url = $this->getOverviewUrl($mode);
548
          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()]));
549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568
        }
        $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());
    }
569 570 571

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

574
      if ($values['region'] == 'hidden') {
575
        $entity->removeComponent($field_name);
576
      }
577 578 579 580 581 582 583 584 585 586 587
      else {
        $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']);
          $options['settings'] = isset($values['settings_edit_form']['settings']) ? array_intersect_key($values['settings_edit_form']['settings'], $default_settings) : [];
          $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);
        }
588

589
        $options['type'] = $values['type'];
590
        $options['weight'] = $values['weight'];
591
        $options['region'] = $values['region'];
592 593 594 595 596
        // Only formatters have configurable label visibility.
        if (isset($values['label'])) {
          $options['label'] = $values['label'];
        }
        $entity->setComponent($field_name, $options);
597 598 599
      }
    }

600 601
    // Collect data for 'extra' fields.
    foreach ($form['#extra'] as $name) {
602
      if ($form_values['fields'][$name]['region'] == 'hidden') {
603
        $entity->removeComponent($name);
604 605
      }
      else {
606
        $entity->setComponent($name, [
607
          'weight' => $form_values['fields'][$name]['weight'],
608
          'region' => $form_values['fields'][$name]['region'],
609
        ]);
610 611 612 613 614 615 616
      }
    }
  }

  /**
   * Form submission handler for multistep buttons.
   */
617
  public function multistepSubmit($form, FormStateInterface $form_state) {
618
    $trigger = $form_state->getTriggeringElement();
619 620 621 622 623 624
    $op = $trigger['#op'];

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

      case 'update':
629 630
        // Set the field back to 'non edit' mode, and update $this->entity with
        // the new settings fro the next rebuild.
631
        $field_name = $trigger['#field_name'];
632
        $form_state->set('plugin_settings_edit', NULL);
633 634
        $form_state->set('plugin_settings_update', $field_name);
        $this->entity = $this->buildEntity($form, $form_state);
635 636 637 638
        break;

      case 'cancel':
        // Set the field back to 'non edit' mode.
639
        $form_state->set('plugin_settings_edit', NULL);
640 641 642 643 644
        break;

      case 'refresh_table':
        // If the currently edited field is one of the rows to be refreshed, set
        // it back to 'non edit' mode.
645
        $updated_rows = explode(' ', $form_state->getValue('refresh_rows'));
646 647 648
        $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);
649 650 651 652
        }
        break;
    }

653
    $form_state->setRebuild();
654 655 656 657 658
  }

  /**
   * Ajax handler for multistep buttons.
   */
659
  public function multistepAjax($form, FormStateInterface $form_state) {
660
    $trigger = $form_state->getTriggeringElement();
661 662 663 664 665
    $op = $trigger['#op'];

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

      case 'update':
      case 'cancel':
672 673
        $updated_rows = [$trigger['#field_name']];
        $updated_columns = ['plugin', 'settings_summary', 'settings_edit'];
674 675 676
        break;

      case 'refresh_table':
677
        $updated_rows = array_values(explode(' ', $form_state->getValue('refresh_rows')));
678
        $updated_columns = ['settings_summary', 'settings_edit'];
679 680 681 682 683 684 685 686 687 688 689 690 691 692 693
        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'];
  }

694 695 696 697 698 699 700 701 702 703
  /**
   * 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.
   *
704 705
   * @return array
   *
706 707
   * @see drupal_render()
   * @see \Drupal\Core\Render\Element\Table::preRenderTable()
708 709
   *
   * @deprecated in Drupal 8.0.0, will be removed before Drupal 9.0.0.
710 711
   */
  public function tablePreRender($elements) {
712
    return FieldUiTable::tablePreRender($elements);
713 714 715 716 717 718
  }

  /**
   * Determines the rendering order of an array representing a tree.
   *
   * Callback for array_reduce() within
719
   * \Drupal\field_ui\Form\EntityDisplayFormBase::tablePreRender().
720 721
   *
   * @deprecated in Drupal 8.0.0, will be removed before Drupal 9.0.0.
722 723
   */
  public function reduceOrder($array, $a) {
724
    return FieldUiTable::reduceOrder($array, $a);
725 726
  }

727 728 729 730
  /**
   * Returns the extra fields of the entity type and bundle used by this form.
   *
   * @return array
731 732 733
   *   An array of extra field info.
   *
   * @see \Drupal\Core\Entity\EntityManagerInterface::getExtraFields()
734
   */
735
  protected function getExtraFields() {
736
    $context = $this->displayContext == 'view' ? 'display' : $this->displayContext;
737
    $extra_fields = $this->entityManager->getExtraFields($this->entity->getTargetEntityTypeId(), $this->entity->getTargetBundle());
738
    return isset($extra_fields[$context]) ? $extra_fields[$context] : [];
739
  }
740

741 742 743 744 745 746 747 748 749 750 751 752 753 754 755
  /**
   * 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);

756
  /**
757
   * Returns an array of applicable widget or formatter options for a field.
758
   *
759 760
   * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
   *   The field definition.
761 762
   *
   * @return array
763
   *   An array of applicable widget or formatter options.
764
   */
765
  protected function getApplicablePluginOptions(FieldDefinitionInterface $field_definition) {
766
    $options = $this->pluginManager->getOptions($field_definition->getType());
767
    $applicable_options = [];
768 769 770 771 772 773
    foreach ($options as $option => $label) {
      $plugin_class = DefaultFactory::getPluginClass($option, $this->pluginManager->getDefinition($option));
      if ($plugin_class::isApplicable($field_definition)) {
        $applicable_options[$option] = $label;
      }
    }
774 775 776
    return $applicable_options;
  }

777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795
  /**
   * 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();

796 797 798 799 800 801 802 803
  /**
   * Returns an array of form or view mode options.
   *
   * @return array
   *   An array of form or view mode options.
   */
  abstract protected function getDisplayModeOptions();

804 805 806 807 808 809 810 811
  /**
   * Returns a link to the form or view mode admin page.
   *
   * @return array
   *   An array of a form element to be rendered as a link.
   */
  abstract protected function getDisplayModesLink();

812 813 814 815 816 817 818 819 820
  /**
   * 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.
   */
821 822 823 824
  public function getRowRegion(&$row) {
    $regions = $this->getRegions();
    if (!isset($regions[$row['region']['#value']])) {
      $row['region']['#value'] = 'hidden';
825
    }
826
    return $row['region']['#value'];
827 828
  }

829 830 831
  /**
   * Returns entity (form) displays for the current entity display type.
   *
832
   * @return \Drupal\Core\Entity\Display\EntityDisplayInterface[]
833 834 835
   *   An array holding entity displays or entity form displays.
   */
  protected function getDisplays() {
836
    $load_ids = [];
837
    $display_entity_type = $this->entity->getEntityTypeId();
838 839
    $entity_type = $this->entityManager->getDefinition($display_entity_type);
    $config_prefix = $entity_type->getConfigPrefix();
840
    $ids = $this->configFactory()->listAll($config_prefix . '.' . $this->entity->getTargetEntityTypeId() . '.' . $this->entity->getTargetBundle() . '.');
841 842 843 844 845 846 847
    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;
      }
    }
848
    return $this->entityManager->getStorage($display_entity_type)->loadMultiple($load_ids);
849 850 851 852 853 854 855 856 857
  }

  /**
   * 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() {
858
    $display_statuses = [];
859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874
    $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) {
875 876 877 878 879 880
      // Only update the display if the status is changing.
      $new_status = $display_statuses[$display->get('mode')];
      if ($new_status !== $display->status()) {
        $display->set('status', $new_status);
        $display->save();
      }
881 882 883
    }
  }

884 885 886 887 888 889 890 891 892
  /**
   * Returns an array containing the table headers.
   *
   * @return array
   *   The table header.
   */
  abstract protected function getTableHeader();

  /**
893
   * Returns the Url object for a specific entity (form) display edit form.
894 895 896 897
   *
   * @param string $mode
   *   The form or view mode.
   *
898 899
   * @return \Drupal\Core\Url
   *   A Url object for the overview route.
900
   */
901
  abstract protected function getOverviewUrl($mode);
902 903

  /**
904
   * Adds the widget or formatter third party settings forms.
905
   *
906
   * @param \Drupal\Core\Field\PluginSettingsInterface $plugin
907
   *   The widget or formatter.
908 909
   * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
   *   The field definition.
910
   * @param array $form
911
   *   The (entire) configuration form array.
912
   * @param \Drupal\Core\Form\FormStateInterface $form_state
913
   *   The form state.
914 915 916
   *
   * @return array
   *   The widget or formatter third party settings form.
917
   */
918
  abstract protected function thirdPartySettingsForm(PluginSettingsInterface $plugin, FieldDefinitionInterface $field_definition, array $form, FormStateInterface $form_state);
919 920 921 922 923 924

  /**
   * Alters the widget or formatter settings summary.
   *
   * @param array $summary
   *   The widget or formatter settings summary.
925
   * @param \Drupal\Core\Field\PluginSettingsInterface $plugin
926
   *   The widget or formatter.
927 928
   * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
   *   The field definition.
929
   */
930
  abstract protected function alterSettingsSummary(array &$summary, PluginSettingsInterface $plugin, FieldDefinitionInterface $field_definition);
931 932

}