ebdbaed2c795afd38eaacedc70bd52d9e3e5df2f
[yaffs-website] / web / core / modules / field_ui / src / Form / EntityDisplayFormBase.php
1 <?php
2
3 namespace Drupal\field_ui\Form;
4
5 use Drupal\Component\Plugin\Factory\DefaultFactory;
6 use Drupal\Component\Plugin\PluginManagerBase;
7 use Drupal\Core\Entity\EntityForm;
8 use Drupal\Core\Entity\EntityInterface;
9 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    * The display context. Either 'view' or 'form'.
25    *
26    * @var string
27    */
28   protected $displayContext;
29
30   /**
31    * The widget or formatter plugin manager.
32    *
33    * @var \Drupal\Component\Plugin\PluginManagerBase
34    */
35   protected $pluginManager;
36
37   /**
38    * A list of field types.
39    *
40    * @var array
41    */
42   protected $fieldTypes;
43
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    * @param \Drupal\Component\Plugin\PluginManagerBase $plugin_manager
57    *   The widget or formatter plugin manager.
58    */
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    * {@inheritdoc}
66    */
67   public function getEntityFromRouteMatch(RouteMatchInterface $route_match, $entity_type_id) {
68     $route_parameters = $route_match->getParameters()->all();
69
70     return $this->getEntityDisplay($route_parameters['entity_type_id'], $route_parameters['bundle'], $route_parameters[$this->displayContext . '_mode_name']);
71   }
72
73   /**
74    * Get the regions needed to create the overview form.
75    *
76    * @return array
77    *   Example usage:
78    *   @code
79    *     return array(
80    *       'content' => array(
81    *         // label for the region.
82    *         'title' => $this->t('Content'),
83    *         // Indicates if the region is visible in the UI.
84    *         'invisible' => TRUE,
85    *         // A message to indicate that there is nothing to be displayed in
86    *         // the region.
87    *         'message' => $this->t('No field is displayed.'),
88    *       ),
89    *     );
90    *   @endcode
91    */
92   public function getRegions() {
93     return [
94       'content' => [
95         'title' => $this->t('Content'),
96         'invisible' => TRUE,
97         'message' => $this->t('No field is displayed.'),
98       ],
99       'hidden' => [
100         'title' => $this->t('Disabled', [], ['context' => 'Plural']),
101         'message' => $this->t('No field is hidden.'),
102       ],
103     ];
104   }
105
106   /**
107    * Returns an associative array of all regions.
108    *
109    * @return array
110    *   An array containing the region options.
111    */
112   public function getRegionOptions() {
113     $options = [];
114     foreach ($this->getRegions() as $region => $data) {
115       $options[$region] = $data['title'];
116     }
117     return $options;
118   }
119
120   /**
121    * Collects the definitions of fields whose display is configurable.
122    *
123    * @return \Drupal\Core\Field\FieldDefinitionInterface[]
124    *   The array of field definitions
125    */
126   protected function getFieldDefinitions() {
127     $context = $this->displayContext;
128     return array_filter($this->entityManager->getFieldDefinitions($this->entity->getTargetEntityTypeId(), $this->entity->getTargetBundle()), function (FieldDefinitionInterface $field_definition) use ($context) {
129       return $field_definition->isDisplayConfigurable($context);
130     });
131   }
132
133   /**
134    * {@inheritdoc}
135    */
136   public function form(array $form, FormStateInterface $form_state) {
137     $form = parent::form($form, $form_state);
138
139     $field_definitions = $this->getFieldDefinitions();
140     $extra_fields = $this->getExtraFields();
141
142     $form += [
143       '#entity_type' => $this->entity->getTargetEntityTypeId(),
144       '#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       $this->messenger()->addWarning($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()]));
151       return $form;
152     }
153
154     $table = [
155       '#type' => 'field_ui_table',
156       '#header' => $this->getTableHeader(),
157       '#regions' => $this->getRegions(),
158       '#attributes' => [
159         'class' => ['field-ui-overview'],
160         'id' => 'field-display-overview',
161       ],
162       '#tabledrag' => [
163         [
164           'action' => 'order',
165           'relationship' => 'sibling',
166           'group' => 'field-weight',
167         ],
168         [
169           'action' => 'match',
170           'relationship' => 'parent',
171           'group' => 'field-parent',
172           'subgroup' => 'field-parent',
173           'source' => 'field-name',
174         ],
175         [
176           'action' => 'match',
177           'relationship' => 'parent',
178           'group' => 'field-region',
179           'subgroup' => 'field-region',
180           '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     // Non-field elements.
191     foreach ($extra_fields as $field_id => $extra_field) {
192       $table[$field_id] = $this->buildExtraFieldRow($field_id, $extra_field);
193     }
194
195     $form['fields'] = $table;
196
197     // Custom display settings.
198     if ($this->entity->getMode() == 'default') {
199       // Only show the settings if there is at least one custom display mode.
200       $display_mode_options = $this->getDisplayModeOptions();
201       // Unset default option.
202       unset($display_mode_options['default']);
203       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         if ($enabled_displays = array_filter($this->getDisplayStatuses())) {
211           $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         // Provide link to manage display modes.
221         $form['modes']['display_modes_link'] = $this->getDisplayModesLink();
222       }
223     }
224
225     // In overviews involving nested rows from contributed modules (i.e
226     // field_group), the 'plugin type' selects can trigger a series of changes
227     // in child rows. The #ajax behavior is therefore not attached directly to
228     // the selects, but triggered by the client-side script through a hidden
229     // #ajax 'Refresh' button. A hidden 'refresh_rows' input tracks the name of
230     // affected rows.
231     $form['refresh_rows'] = ['#type' => 'hidden'];
232     $form['refresh'] = [
233       '#type' => 'submit',
234       '#value' => $this->t('Refresh'),
235       '#op' => 'refresh_table',
236       '#submit' => ['::multistepSubmit'],
237       '#ajax' => [
238         'callback' => '::multistepAjax',
239         'wrapper' => 'field-display-overview-wrapper',
240         'effect' => 'fade',
241         // The button stays hidden, so we hide the Ajax spinner too. Ad-hoc
242         // spinners will be added manually by the client-side script.
243         'progress' => 'none',
244       ],
245       '#attributes' => ['class' => ['visually-hidden']],
246     ];
247
248     $form['actions'] = ['#type' => 'actions'];
249     $form['actions']['submit'] = [
250       '#type' => 'submit',
251       '#button_type' => 'primary',
252       '#value' => $this->t('Save'),
253     ];
254
255     $form['#attached']['library'][] = 'field_ui/drupal.field_ui';
256
257     return $form;
258   }
259
260   /**
261    * Builds the table row structure for a single field.
262    *
263    * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
264    *   The field definition.
265    * @param array $form
266    *   An associative array containing the structure of the form.
267    * @param \Drupal\Core\Form\FormStateInterface $form_state
268    *   The current state of the form.
269    *
270    * @return array
271    *   A table row array.
272    */
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     // Disable fields without any applicable plugins.
279     if (empty($this->getApplicablePluginOptions($field_definition))) {
280       $this->entity->removeComponent($field_name)->save();
281       $display_options = $this->entity->getComponent($field_name);
282     }
283
284     $regions = array_keys($this->getRegions());
285     $field_row = [
286       '#attributes' => ['class' => ['draggable', 'tabledrag-leaf']],
287       '#row_type' => 'field',
288       '#region_callback' => [$this, 'getRowRegion'],
289       '#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         '#title_display' => 'invisible',
300         '#default_value' => $display_options ? $display_options['weight'] : '0',
301         '#size' => 3,
302         '#attributes' => ['class' => ['field-weight']],
303       ],
304       'parent_wrapper' => [
305         '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           '#attributes' => ['class' => ['js-field-parent', 'field-parent']],
312           '#parents' => ['fields', $field_name, 'parent'],
313         ],
314         'hidden_name' => [
315           '#type' => 'hidden',
316           '#default_value' => $field_name,
317           '#attributes' => ['class' => ['field-name']],
318         ],
319       ],
320       'region' => [
321         '#type' => 'select',
322         '#title' => $this->t('Region for @title', ['@title' => $label]),
323         '#title_display' => 'invisible',
324         '#options' => $this->getRegionOptions(),
325         '#default_value' => $display_options ? $display_options['region'] : 'hidden',
326         '#attributes' => ['class' => ['field-region']],
327       ],
328     ];
329
330     $field_row['plugin'] = [
331       '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         '#parents' => ['fields', $field_name, 'type'],
338         '#attributes' => ['class' => ['field-plugin-type']],
339       ],
340       'settings_edit_form' => [],
341     ];
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     $base_button = [
348       '#submit' => ['::multistepSubmit'],
349       '#ajax' => [
350         'callback' => '::multistepAjax',
351         'wrapper' => 'field-display-overview-wrapper',
352         'effect' => 'fade',
353       ],
354       '#field_name' => $field_name,
355     ];
356
357     if ($form_state->get('plugin_settings_edit') == $field_name) {
358       // We are currently editing this field's plugin settings. Display the
359       // settings form and submit buttons.
360       $field_row['plugin']['settings_edit_form'] = [];
361
362       if ($plugin) {
363         // Generate the settings form and allow other modules to alter it.
364         $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           $field_row['plugin']['#cell_attributes'] = ['colspan' => 3];
369           $field_row['plugin']['settings_edit_form'] = [
370             '#type' => 'container',
371             '#attributes' => ['class' => ['field-plugin-settings-edit-form']],
372             '#parents' => ['fields', $field_name, 'settings_edit_form'],
373             '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                 '#op' => 'cancel',
392                 // Do not check errors for the 'Cancel' button, but make sure we
393                 // get the value of the 'plugin type' select.
394                 '#limit_validation_errors' => [['fields', $field_name, 'type']],
395               ],
396             ],
397           ];
398           $field_row['#attributes']['class'][] = 'field-plugin-settings-editing';
399         }
400       }
401     }
402     else {
403       $field_row['settings_summary'] = [];
404       $field_row['settings_edit'] = [];
405
406       if ($plugin) {
407         // Display a summary of the current plugin settings, and (if the
408         // summary is not empty) a button to edit them.
409         $summary = $plugin->settingsSummary();
410
411         // 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             '#type' => 'inline_template',
417             '#template' => '<div class="field-plugin-summary">{{ summary|safe_join("<br />") }}</div>',
418             '#context' => ['summary' => $summary],
419             '#cell_attributes' => ['class' => ['field-plugin-summary-cell']],
420           ];
421         }
422
423         // Check selected plugin settings to display edit link or not.
424         $settings_form = $plugin->settingsForm($form, $form_state);
425         $third_party_settings_form = $this->thirdPartySettingsForm($plugin, $field_definition, $form, $form_state);
426         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             '#op' => 'edit',
433             // Do not check errors for the 'Edit' button, but make sure we get
434             // the value of the 'plugin type' select.
435             '#limit_validation_errors' => [['fields', $field_name, 'type']],
436             '#prefix' => '<div class="field-plugin-settings-edit-wrapper">',
437             '#suffix' => '</div>',
438           ];
439         }
440       }
441     }
442
443     return $field_row;
444   }
445
446   /**
447    * Builds the table row structure for a single extra field.
448    *
449    * @param string $field_id
450    *   The field ID.
451    * @param array $extra_field
452    *   The pseudo-field element.
453    *
454    * @return array
455    *   A table row array.
456    */
457   protected function buildExtraFieldRow($field_id, $extra_field) {
458     $display_options = $this->entity->getComponent($field_id);
459
460     $regions = array_keys($this->getRegions());
461     $extra_field_row = [
462       '#attributes' => ['class' => ['draggable', 'tabledrag-leaf']],
463       '#row_type' => 'extra_field',
464       '#region_callback' => [$this, 'getRowRegion'],
465       '#js_settings' => ['rowHandler' => 'field'],
466       '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         '#title_display' => 'invisible',
473         '#default_value' => $display_options ? $display_options['weight'] : 0,
474         '#size' => 3,
475         '#attributes' => ['class' => ['field-weight']],
476       ],
477       'parent_wrapper' => [
478         '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           '#attributes' => ['class' => ['js-field-parent', 'field-parent']],
485           '#parents' => ['fields', $field_id, 'parent'],
486         ],
487         'hidden_name' => [
488           '#type' => 'hidden',
489           '#default_value' => $field_id,
490           '#attributes' => ['class' => ['field-name']],
491         ],
492       ],
493       'region' => [
494         '#type' => 'select',
495         '#title' => $this->t('Region for @title', ['@title' => $extra_field['label']]),
496         '#title_display' => 'invisible',
497         '#options' => $this->getRegionOptions(),
498         '#default_value' => $display_options ? $display_options['region'] : 'hidden',
499         '#attributes' => ['class' => ['field-region']],
500       ],
501       'plugin' => [
502         'type' => [
503           '#type' => 'hidden',
504           '#value' => $display_options ? 'visible' : 'hidden',
505           '#parents' => ['fields', $field_id, 'type'],
506           '#attributes' => ['class' => ['field-plugin-type']],
507         ],
508       ],
509       'settings_summary' => [],
510       'settings_edit' => [],
511     ];
512
513     return $extra_field_row;
514   }
515
516   /**
517    * {@inheritdoc}
518    */
519   public function submitForm(array &$form, FormStateInterface $form_state) {
520     // If the main "Save" button was submitted while a field settings subform
521     // was being edited, update the new incoming settings when rebuilding the
522     // entity, just as if the subform's "Update" button had been submitted.
523     if ($edit_field = $form_state->get('plugin_settings_edit')) {
524       $form_state->set('plugin_settings_update', $edit_field);
525     }
526
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       $display_modes = $this->getDisplayModes();
533       $current_statuses = $this->getDisplayStatuses();
534
535       $statuses = [];
536       foreach ($form_values['display_modes_custom'] as $mode => $value) {
537         if (!empty($value) && empty($current_statuses[$mode])) {
538           // If no display exists for the newly enabled view mode, initialize
539           // it with those from the 'default' view mode, which were used so
540           // far.
541           if (!$this->entityManager->getStorage($this->entity->getEntityTypeId())->load($this->entity->getTargetEntityTypeId() . '.' . $this->entity->getTargetBundle() . '.' . $mode)) {
542             $display = $this->getEntityDisplay($this->entity->getTargetEntityTypeId(), $this->entity->getTargetBundle(), 'default')->createCopy($mode);
543             $display->save();
544           }
545
546           $display_mode_label = $display_modes[$mode]['label'];
547           $url = $this->getOverviewUrl($mode);
548           $this->messenger()->addStatus($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         $statuses[$mode] = !empty($value);
551       }
552
553       $this->saveDisplayStatuses($statuses);
554     }
555
556     $this->messenger()->addStatus($this->t('Your settings have been saved.'));
557   }
558
559   /**
560    * {@inheritdoc}
561    */
562   protected function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state) {
563     $form_values = $form_state->getValues();
564
565     if ($this->entity instanceof EntityWithPluginCollectionInterface) {
566       // Do not manually update values represented by plugin collections.
567       $form_values = array_diff_key($form_values, $this->entity->getPluginCollections());
568     }
569
570     // Collect data for 'regular' fields.
571     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       else {
578         $options = $entity->getComponent($field_name);
579
580         // Update field settings only if the submit handler told us to.
581         if ($form_state->get('plugin_settings_update') === $field_name) {
582           // Only store settings actually used by the selected plugin.
583           $default_settings = $this->pluginManager->getDefaultSettings($options['type']);
584           $options['settings'] = isset($values['settings_edit_form']['settings']) ? array_intersect_key($values['settings_edit_form']['settings'], $default_settings) : [];
585           $options['third_party_settings'] = isset($values['settings_edit_form']['third_party_settings']) ? $values['settings_edit_form']['third_party_settings'] : [];
586           $form_state->set('plugin_settings_update', NULL);
587         }
588
589         $options['type'] = $values['type'];
590         $options['weight'] = $values['weight'];
591         $options['region'] = $values['region'];
592         // Only formatters have configurable label visibility.
593         if (isset($values['label'])) {
594           $options['label'] = $values['label'];
595         }
596         $entity->setComponent($field_name, $options);
597       }
598     }
599
600     // Collect data for 'extra' fields.
601     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    * Form submission handler for multistep buttons.
616    */
617   public function multistepSubmit($form, FormStateInterface $form_state) {
618     $trigger = $form_state->getTriggeringElement();
619     $op = $trigger['#op'];
620
621     switch ($op) {
622       case 'edit':
623         // Store the field whose settings are currently being edited.
624         $field_name = $trigger['#field_name'];
625         $form_state->set('plugin_settings_edit', $field_name);
626         break;
627
628       case 'update':
629         // Set the field back to 'non edit' mode, and update $this->entity with
630         // the new settings fro the next rebuild.
631         $field_name = $trigger['#field_name'];
632         $form_state->set('plugin_settings_edit', NULL);
633         $form_state->set('plugin_settings_update', $field_name);
634         $this->entity = $this->buildEntity($form, $form_state);
635         break;
636
637       case 'cancel':
638         // Set the field back to 'non edit' mode.
639         $form_state->set('plugin_settings_edit', NULL);
640         break;
641
642       case 'refresh_table':
643         // If the currently edited field is one of the rows to be refreshed, set
644         // it back to 'non edit' mode.
645         $updated_rows = explode(' ', $form_state->getValue('refresh_rows'));
646         $plugin_settings_edit = $form_state->get('plugin_settings_edit');
647         if ($plugin_settings_edit && in_array($plugin_settings_edit, $updated_rows)) {
648           $form_state->set('plugin_settings_edit', NULL);
649         }
650         break;
651     }
652
653     $form_state->setRebuild();
654   }
655
656   /**
657    * Ajax handler for multistep buttons.
658    */
659   public function multistepAjax($form, FormStateInterface $form_state) {
660     $trigger = $form_state->getTriggeringElement();
661     $op = $trigger['#op'];
662
663     // Pick the elements that need to receive the ajax-new-content effect.
664     switch ($op) {
665       case 'edit':
666         $updated_rows = [$trigger['#field_name']];
667         $updated_columns = ['plugin'];
668         break;
669
670       case 'update':
671       case 'cancel':
672         $updated_rows = [$trigger['#field_name']];
673         $updated_columns = ['plugin', 'settings_summary', 'settings_edit'];
674         break;
675
676       case 'refresh_table':
677         $updated_rows = array_values(explode(' ', $form_state->getValue('refresh_rows')));
678         $updated_columns = ['settings_summary', 'settings_edit'];
679         break;
680     }
681
682     foreach ($updated_rows as $name) {
683       foreach ($updated_columns as $key) {
684         $element = &$form['fields'][$name][$key];
685         $element['#prefix'] = '<div class="ajax-new-content">' . (isset($element['#prefix']) ? $element['#prefix'] : '');
686         $element['#suffix'] = (isset($element['#suffix']) ? $element['#suffix'] : '') . '</div>';
687       }
688     }
689
690     // Return the whole table.
691     return $form['fields'];
692   }
693
694   /**
695    * Performs pre-render tasks on field_ui_table elements.
696    *
697    * @param array $elements
698    *   A structured array containing two sub-levels of elements. Properties
699    *   used:
700    *   - #tabledrag: The value is a list of $options arrays that are passed to
701    *     drupal_attach_tabledrag(). The HTML ID of the table is added to each
702    *     $options array.
703    *
704    * @return array
705    *
706    * @see \Drupal\Core\Render\RendererInterface::render()
707    * @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    * Determines the rendering order of an array representing a tree.
717    *
718    * 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    * Returns the extra fields of the entity type and bundle used by this form.
729    *
730    * @return array
731    *   An array of extra field info.
732    *
733    * @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    * Returns an entity display object to be used by this form.
743    *
744    * @param string $entity_type_id
745    *   The target entity type ID of the entity display.
746    * @param string $bundle
747    *   The target bundle of the entity display.
748    * @param string $mode
749    *   A view or form mode.
750    *
751    * @return \Drupal\Core\Entity\Display\EntityDisplayInterface
752    *   An entity display.
753    */
754   abstract protected function getEntityDisplay($entity_type_id, $bundle, $mode);
755
756   /**
757    * Returns an array of applicable widget or formatter options for a field.
758    *
759    * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
760    *   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     foreach ($options as $option => $label) {
769       $plugin_class = DefaultFactory::getPluginClass($option, $this->pluginManager->getDefinition($option));
770       if ($plugin_class::isApplicable($field_definition)) {
771         $applicable_options[$option] = $label;
772       }
773     }
774     return $applicable_options;
775   }
776
777   /**
778    * Returns the ID of the default widget or formatter plugin for a field type.
779    *
780    * @param string $field_type
781    *   The field type.
782    *
783    * @return string
784    *   The widget or formatter plugin ID.
785    */
786   abstract protected function getDefaultPlugin($field_type);
787
788   /**
789    * Returns the form or view modes used by this form.
790    *
791    * @return array
792    *   An array of form or view mode info.
793    */
794   abstract protected function getDisplayModes();
795
796   /**
797    * Returns an array of form or view mode options.
798    *
799    * @return array
800    *   An array of form or view mode options.
801    */
802   abstract protected function getDisplayModeOptions();
803
804   /**
805    * Returns a link to the form or view mode admin page.
806    *
807    * @return array
808    *   An array of a form element to be rendered as a link.
809    */
810   abstract protected function getDisplayModesLink();
811
812   /**
813    * Returns the region to which a row in the display overview belongs.
814    *
815    * @param array $row
816    *   The row element.
817    *
818    * @return string|null
819    *   The region name this row belongs to.
820    */
821   public function getRowRegion(&$row) {
822     $regions = $this->getRegions();
823     if (!isset($regions[$row['region']['#value']])) {
824       $row['region']['#value'] = 'hidden';
825     }
826     return $row['region']['#value'];
827   }
828
829   /**
830    * Returns entity (form) displays for the current entity display type.
831    *
832    * @return \Drupal\Core\Entity\Display\EntityDisplayInterface[]
833    *   An array holding entity displays or entity form displays.
834    */
835   protected function getDisplays() {
836     $load_ids = [];
837     $display_entity_type = $this->entity->getEntityTypeId();
838     $entity_type = $this->entityManager->getDefinition($display_entity_type);
839     $config_prefix = $entity_type->getConfigPrefix();
840     $ids = $this->configFactory()->listAll($config_prefix . '.' . $this->entity->getTargetEntityTypeId() . '.' . $this->entity->getTargetBundle() . '.');
841     foreach ($ids as $id) {
842       $config_id = str_replace($config_prefix . '.', '', $id);
843       list(,, $display_mode) = explode('.', $config_id);
844       if ($display_mode != 'default') {
845         $load_ids[] = $config_id;
846       }
847     }
848     return $this->entityManager->getStorage($display_entity_type)->loadMultiple($load_ids);
849   }
850
851   /**
852    * Returns form or view modes statuses for the bundle used by this form.
853    *
854    * @return array
855    *   An array of form or view mode statuses.
856    */
857   protected function getDisplayStatuses() {
858     $display_statuses = [];
859     $displays = $this->getDisplays();
860     foreach ($displays as $display) {
861       $display_statuses[$display->get('mode')] = $display->status();
862     }
863     return $display_statuses;
864   }
865
866   /**
867    * Saves the updated display mode statuses.
868    *
869    * @param array $display_statuses
870    *   An array holding updated form or view mode statuses.
871    */
872   protected function saveDisplayStatuses($display_statuses) {
873     $displays = $this->getDisplays();
874     foreach ($displays as $display) {
875       // Only update the display if the status is changing.
876       $new_status = $display_statuses[$display->get('mode')];
877       if ($new_status !== $display->status()) {
878         $display->set('status', $new_status);
879         $display->save();
880       }
881     }
882   }
883
884   /**
885    * Returns an array containing the table headers.
886    *
887    * @return array
888    *   The table header.
889    */
890   abstract protected function getTableHeader();
891
892   /**
893    * Returns the Url object for a specific entity (form) display edit form.
894    *
895    * @param string $mode
896    *   The form or view mode.
897    *
898    * @return \Drupal\Core\Url
899    *   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    * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
909    *   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    * @return array
916    *   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    * Alters the widget or formatter settings summary.
922    *
923    * @param array $summary
924    *   The widget or formatter settings summary.
925    * @param \Drupal\Core\Field\PluginSettingsInterface $plugin
926    *   The widget or formatter.
927    * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
928    *   The field definition.
929    */
930   abstract protected function alterSettingsSummary(array &$summary, PluginSettingsInterface $plugin, FieldDefinitionInterface $field_definition);
931
932 }