0a91862b351ea1ed9bc85b9ac6e44d6b7b59bf2d
[yaffs-website] / 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       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       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         $form['modes']['display_modes_custom'] = [
214           '#type' => 'checkboxes',
215           '#title' => $this->t('Use custom display settings for the following @display_context modes', ['@display_context' => $this->displayContext]),
216           '#options' => $display_mode_options,
217           '#default_value' => $default,
218         ];
219         // Provide link to manage display modes.
220         $form['modes']['display_modes_link'] = $this->getDisplayModesLink();
221       }
222     }
223
224     // In overviews involving nested rows from contributed modules (i.e
225     // field_group), the 'plugin type' selects can trigger a series of changes
226     // in child rows. The #ajax behavior is therefore not attached directly to
227     // the selects, but triggered by the client-side script through a hidden
228     // #ajax 'Refresh' button. A hidden 'refresh_rows' input tracks the name of
229     // affected rows.
230     $form['refresh_rows'] = ['#type' => 'hidden'];
231     $form['refresh'] = [
232       '#type' => 'submit',
233       '#value' => $this->t('Refresh'),
234       '#op' => 'refresh_table',
235       '#submit' => ['::multistepSubmit'],
236       '#ajax' => [
237         'callback' => '::multistepAjax',
238         'wrapper' => 'field-display-overview-wrapper',
239         'effect' => 'fade',
240         // The button stays hidden, so we hide the Ajax spinner too. Ad-hoc
241         // spinners will be added manually by the client-side script.
242         'progress' => 'none',
243       ],
244       '#attributes' => ['class' => ['visually-hidden']]
245     ];
246
247     $form['actions'] = ['#type' => 'actions'];
248     $form['actions']['submit'] = [
249       '#type' => 'submit',
250       '#button_type' => 'primary',
251       '#value' => $this->t('Save'),
252     ];
253
254     $form['#attached']['library'][] = 'field_ui/drupal.field_ui';
255
256     return $form;
257   }
258
259   /**
260    * Builds the table row structure for a single field.
261    *
262    * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
263    *   The field definition.
264    * @param array $form
265    *   An associative array containing the structure of the form.
266    * @param \Drupal\Core\Form\FormStateInterface $form_state
267    *   The current state of the form.
268    *
269    * @return array
270    *   A table row array.
271    */
272   protected function buildFieldRow(FieldDefinitionInterface $field_definition, array $form, FormStateInterface $form_state) {
273     $field_name = $field_definition->getName();
274     $display_options = $this->entity->getComponent($field_name);
275     $label = $field_definition->getLabel();
276
277     // Disable fields without any applicable plugins.
278     if (empty($this->getApplicablePluginOptions($field_definition))) {
279       $this->entity->removeComponent($field_name)->save();
280       $display_options = $this->entity->getComponent($field_name);
281     }
282
283     $regions = array_keys($this->getRegions());
284     $field_row = [
285       '#attributes' => ['class' => ['draggable', 'tabledrag-leaf']],
286       '#row_type' => 'field',
287       '#region_callback' => [$this, 'getRowRegion'],
288       '#js_settings' => [
289         'rowHandler' => 'field',
290         'defaultPlugin' => $this->getDefaultPlugin($field_definition->getType()),
291       ],
292       'human_name' => [
293         '#plain_text' => $label,
294       ],
295       'weight' => [
296         '#type' => 'textfield',
297         '#title' => $this->t('Weight for @title', ['@title' => $label]),
298         '#title_display' => 'invisible',
299         '#default_value' => $display_options ? $display_options['weight'] : '0',
300         '#size' => 3,
301         '#attributes' => ['class' => ['field-weight']],
302       ],
303       'parent_wrapper' => [
304         'parent' => [
305           '#type' => 'select',
306           '#title' => $this->t('Label display for @title', ['@title' => $label]),
307           '#title_display' => 'invisible',
308           '#options' => array_combine($regions, $regions),
309           '#empty_value' => '',
310           '#attributes' => ['class' => ['js-field-parent', 'field-parent']],
311           '#parents' => ['fields', $field_name, 'parent'],
312         ],
313         'hidden_name' => [
314           '#type' => 'hidden',
315           '#default_value' => $field_name,
316           '#attributes' => ['class' => ['field-name']],
317         ],
318       ],
319       'region' => [
320         '#type' => 'select',
321         '#title' => $this->t('Region for @title', ['@title' => $label]),
322         '#title_display' => 'invisible',
323         '#options' => $this->getRegionOptions(),
324         '#default_value' => $display_options ? $display_options['region'] : 'hidden',
325         '#attributes' => ['class' => ['field-region']],
326       ],
327     ];
328
329     $field_row['plugin'] = [
330       'type' => [
331         '#type' => 'select',
332         '#title' => $this->t('Plugin for @title', ['@title' => $label]),
333         '#title_display' => 'invisible',
334         '#options' => $this->getApplicablePluginOptions($field_definition),
335         '#default_value' => $display_options ? $display_options['type'] : 'hidden',
336         '#parents' => ['fields', $field_name, 'type'],
337         '#attributes' => ['class' => ['field-plugin-type']],
338       ],
339       'settings_edit_form' => [],
340     ];
341
342     // Get the corresponding plugin object.
343     $plugin = $this->entity->getRenderer($field_name);
344
345     // Base button element for the various plugin settings actions.
346     $base_button = [
347       '#submit' => ['::multistepSubmit'],
348       '#ajax' => [
349         'callback' => '::multistepAjax',
350         'wrapper' => 'field-display-overview-wrapper',
351         'effect' => 'fade',
352       ],
353       '#field_name' => $field_name,
354     ];
355
356     if ($form_state->get('plugin_settings_edit') == $field_name) {
357       // We are currently editing this field's plugin settings. Display the
358       // settings form and submit buttons.
359       $field_row['plugin']['settings_edit_form'] = [];
360
361       if ($plugin) {
362         // Generate the settings form and allow other modules to alter it.
363         $settings_form = $plugin->settingsForm($form, $form_state);
364         $third_party_settings_form = $this->thirdPartySettingsForm($plugin, $field_definition, $form, $form_state);
365
366         if ($settings_form || $third_party_settings_form) {
367           $field_row['plugin']['#cell_attributes'] = ['colspan' => 3];
368           $field_row['plugin']['settings_edit_form'] = [
369             '#type' => 'container',
370             '#attributes' => ['class' => ['field-plugin-settings-edit-form']],
371             '#parents' => ['fields', $field_name, 'settings_edit_form'],
372             'label' => [
373               '#markup' => $this->t('Plugin settings'),
374             ],
375             'settings' => $settings_form,
376             'third_party_settings' => $third_party_settings_form,
377             'actions' => [
378               '#type' => 'actions',
379               'save_settings' => $base_button + [
380                 '#type' => 'submit',
381                 '#button_type' => 'primary',
382                 '#name' => $field_name . '_plugin_settings_update',
383                 '#value' => $this->t('Update'),
384                 '#op' => 'update',
385               ],
386               'cancel_settings' => $base_button + [
387                 '#type' => 'submit',
388                 '#name' => $field_name . '_plugin_settings_cancel',
389                 '#value' => $this->t('Cancel'),
390                 '#op' => 'cancel',
391                 // Do not check errors for the 'Cancel' button, but make sure we
392                 // get the value of the 'plugin type' select.
393                 '#limit_validation_errors' => [['fields', $field_name, 'type']],
394               ],
395             ],
396           ];
397           $field_row['#attributes']['class'][] = 'field-plugin-settings-editing';
398         }
399       }
400     }
401     else {
402       $field_row['settings_summary'] = [];
403       $field_row['settings_edit'] = [];
404
405       if ($plugin) {
406         // Display a summary of the current plugin settings, and (if the
407         // summary is not empty) a button to edit them.
408         $summary = $plugin->settingsSummary();
409
410         // Allow other modules to alter the summary.
411         $this->alterSettingsSummary($summary, $plugin, $field_definition);
412
413         if (!empty($summary)) {
414           $field_row['settings_summary'] = [
415             '#type' => 'inline_template',
416             '#template' => '<div class="field-plugin-summary">{{ summary|safe_join("<br />") }}</div>',
417             '#context' => ['summary' => $summary],
418             '#cell_attributes' => ['class' => ['field-plugin-summary-cell']],
419           ];
420         }
421
422         // Check selected plugin settings to display edit link or not.
423         $settings_form = $plugin->settingsForm($form, $form_state);
424         $third_party_settings_form = $this->thirdPartySettingsForm($plugin, $field_definition, $form, $form_state);
425         if (!empty($settings_form) || !empty($third_party_settings_form)) {
426           $field_row['settings_edit'] = $base_button + [
427             '#type' => 'image_button',
428             '#name' => $field_name . '_settings_edit',
429             '#src' => 'core/misc/icons/787878/cog.svg',
430             '#attributes' => ['class' => ['field-plugin-settings-edit'], 'alt' => $this->t('Edit')],
431             '#op' => 'edit',
432             // Do not check errors for the 'Edit' button, but make sure we get
433             // the value of the 'plugin type' select.
434             '#limit_validation_errors' => [['fields', $field_name, 'type']],
435             '#prefix' => '<div class="field-plugin-settings-edit-wrapper">',
436             '#suffix' => '</div>',
437           ];
438         }
439       }
440     }
441
442     return $field_row;
443   }
444
445   /**
446    * Builds the table row structure for a single extra field.
447    *
448    * @param string $field_id
449    *   The field ID.
450    * @param array $extra_field
451    *   The pseudo-field element.
452    *
453    * @return array
454    *   A table row array.
455    */
456   protected function buildExtraFieldRow($field_id, $extra_field) {
457     $display_options = $this->entity->getComponent($field_id);
458
459     $regions = array_keys($this->getRegions());
460     $extra_field_row = [
461       '#attributes' => ['class' => ['draggable', 'tabledrag-leaf']],
462       '#row_type' => 'extra_field',
463       '#region_callback' => [$this, 'getRowRegion'],
464       '#js_settings' => ['rowHandler' => 'field'],
465       'human_name' => [
466         '#markup' => $extra_field['label'],
467       ],
468       'weight' => [
469         '#type' => 'textfield',
470         '#title' => $this->t('Weight for @title', ['@title' => $extra_field['label']]),
471         '#title_display' => 'invisible',
472         '#default_value' => $display_options ? $display_options['weight'] : 0,
473         '#size' => 3,
474         '#attributes' => ['class' => ['field-weight']],
475       ],
476       'parent_wrapper' => [
477         'parent' => [
478           '#type' => 'select',
479           '#title' => $this->t('Parents for @title', ['@title' => $extra_field['label']]),
480           '#title_display' => 'invisible',
481           '#options' => array_combine($regions, $regions),
482           '#empty_value' => '',
483           '#attributes' => ['class' => ['js-field-parent', 'field-parent']],
484           '#parents' => ['fields', $field_id, 'parent'],
485         ],
486         'hidden_name' => [
487           '#type' => 'hidden',
488           '#default_value' => $field_id,
489           '#attributes' => ['class' => ['field-name']],
490         ],
491       ],
492       'region' => [
493         '#type' => 'select',
494         '#title' => $this->t('Region for @title', ['@title' => $extra_field['label']]),
495         '#title_display' => 'invisible',
496         '#options' => $this->getRegionOptions(),
497         '#default_value' => $display_options ? $display_options['region'] : 'hidden',
498         '#attributes' => ['class' => ['field-region']],
499       ],
500       'plugin' => [
501         'type' => [
502           '#type' => 'hidden',
503           '#value' => $display_options ? 'visible' : 'hidden',
504           '#parents' => ['fields', $field_id, 'type'],
505           '#attributes' => ['class' => ['field-plugin-type']],
506         ],
507       ],
508       'settings_summary' => [],
509       'settings_edit' => [],
510     ];
511
512     return $extra_field_row;
513   }
514
515   /**
516    * {@inheritdoc}
517    */
518   public function submitForm(array &$form, FormStateInterface $form_state) {
519     // If the main "Save" button was submitted while a field settings subform
520     // was being edited, update the new incoming settings when rebuilding the
521     // entity, just as if the subform's "Update" button had been submitted.
522     if ($edit_field = $form_state->get('plugin_settings_edit')) {
523       $form_state->set('plugin_settings_update', $edit_field);
524     }
525
526     parent::submitForm($form, $form_state);
527     $form_values = $form_state->getValues();
528
529     // Handle the 'display modes' checkboxes if present.
530     if ($this->entity->getMode() == 'default' && !empty($form_values['display_modes_custom'])) {
531       $display_modes = $this->getDisplayModes();
532       $current_statuses = $this->getDisplayStatuses();
533
534       $statuses = [];
535       foreach ($form_values['display_modes_custom'] as $mode => $value) {
536         if (!empty($value) && empty($current_statuses[$mode])) {
537           // If no display exists for the newly enabled view mode, initialize
538           // it with those from the 'default' view mode, which were used so
539           // far.
540           if (!$this->entityManager->getStorage($this->entity->getEntityTypeId())->load($this->entity->getTargetEntityTypeId() . '.' . $this->entity->getTargetBundle() . '.' . $mode)) {
541             $display = $this->getEntityDisplay($this->entity->getTargetEntityTypeId(), $this->entity->getTargetBundle(), 'default')->createCopy($mode);
542             $display->save();
543           }
544
545           $display_mode_label = $display_modes[$mode]['label'];
546           $url = $this->getOverviewUrl($mode);
547           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()]));
548         }
549         $statuses[$mode] = !empty($value);
550       }
551
552       $this->saveDisplayStatuses($statuses);
553     }
554
555     drupal_set_message($this->t('Your settings have been saved.'));
556   }
557
558   /**
559    * {@inheritdoc}
560    */
561   protected function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state) {
562     $form_values = $form_state->getValues();
563
564     if ($this->entity instanceof EntityWithPluginCollectionInterface) {
565       // Do not manually update values represented by plugin collections.
566       $form_values = array_diff_key($form_values, $this->entity->getPluginCollections());
567     }
568
569     // Collect data for 'regular' fields.
570     foreach ($form['#fields'] as $field_name) {
571       $values = $form_values['fields'][$field_name];
572
573       if ($values['region'] == 'hidden') {
574         $entity->removeComponent($field_name);
575       }
576       else {
577         $options = $entity->getComponent($field_name);
578
579         // Update field settings only if the submit handler told us to.
580         if ($form_state->get('plugin_settings_update') === $field_name) {
581           // Only store settings actually used by the selected plugin.
582           $default_settings = $this->pluginManager->getDefaultSettings($options['type']);
583           $options['settings'] = isset($values['settings_edit_form']['settings']) ? array_intersect_key($values['settings_edit_form']['settings'], $default_settings) : [];
584           $options['third_party_settings'] = isset($values['settings_edit_form']['third_party_settings']) ? $values['settings_edit_form']['third_party_settings'] : [];
585           $form_state->set('plugin_settings_update', NULL);
586         }
587
588         $options['type'] = $values['type'];
589         $options['weight'] = $values['weight'];
590         $options['region'] = $values['region'];
591         // Only formatters have configurable label visibility.
592         if (isset($values['label'])) {
593           $options['label'] = $values['label'];
594         }
595         $entity->setComponent($field_name, $options);
596       }
597     }
598
599     // Collect data for 'extra' fields.
600     foreach ($form['#extra'] as $name) {
601       if ($form_values['fields'][$name]['region'] == 'hidden') {
602         $entity->removeComponent($name);
603       }
604       else {
605         $entity->setComponent($name, [
606           'weight' => $form_values['fields'][$name]['weight'],
607           'region' => $form_values['fields'][$name]['region'],
608         ]);
609       }
610     }
611   }
612
613   /**
614    * Form submission handler for multistep buttons.
615    */
616   public function multistepSubmit($form, FormStateInterface $form_state) {
617     $trigger = $form_state->getTriggeringElement();
618     $op = $trigger['#op'];
619
620     switch ($op) {
621       case 'edit':
622         // Store the field whose settings are currently being edited.
623         $field_name = $trigger['#field_name'];
624         $form_state->set('plugin_settings_edit', $field_name);
625         break;
626
627       case 'update':
628         // Set the field back to 'non edit' mode, and update $this->entity with
629         // the new settings fro the next rebuild.
630         $field_name = $trigger['#field_name'];
631         $form_state->set('plugin_settings_edit', NULL);
632         $form_state->set('plugin_settings_update', $field_name);
633         $this->entity = $this->buildEntity($form, $form_state);
634         break;
635
636       case 'cancel':
637         // Set the field back to 'non edit' mode.
638         $form_state->set('plugin_settings_edit', NULL);
639         break;
640
641       case 'refresh_table':
642         // If the currently edited field is one of the rows to be refreshed, set
643         // it back to 'non edit' mode.
644         $updated_rows = explode(' ', $form_state->getValue('refresh_rows'));
645         $plugin_settings_edit = $form_state->get('plugin_settings_edit');
646         if ($plugin_settings_edit && in_array($plugin_settings_edit, $updated_rows)) {
647           $form_state->set('plugin_settings_edit', NULL);
648         }
649         break;
650     }
651
652     $form_state->setRebuild();
653   }
654
655   /**
656    * Ajax handler for multistep buttons.
657    */
658   public function multistepAjax($form, FormStateInterface $form_state) {
659     $trigger = $form_state->getTriggeringElement();
660     $op = $trigger['#op'];
661
662     // Pick the elements that need to receive the ajax-new-content effect.
663     switch ($op) {
664       case 'edit':
665         $updated_rows = [$trigger['#field_name']];
666         $updated_columns = ['plugin'];
667         break;
668
669       case 'update':
670       case 'cancel':
671         $updated_rows = [$trigger['#field_name']];
672         $updated_columns = ['plugin', 'settings_summary', 'settings_edit'];
673         break;
674
675       case 'refresh_table':
676         $updated_rows = array_values(explode(' ', $form_state->getValue('refresh_rows')));
677         $updated_columns = ['settings_summary', 'settings_edit'];
678         break;
679     }
680
681     foreach ($updated_rows as $name) {
682       foreach ($updated_columns as $key) {
683         $element = &$form['fields'][$name][$key];
684         $element['#prefix'] = '<div class="ajax-new-content">' . (isset($element['#prefix']) ? $element['#prefix'] : '');
685         $element['#suffix'] = (isset($element['#suffix']) ? $element['#suffix'] : '') . '</div>';
686       }
687     }
688
689     // Return the whole table.
690     return $form['fields'];
691   }
692
693   /**
694    * Performs pre-render tasks on field_ui_table elements.
695    *
696    * @param array $elements
697    *   A structured array containing two sub-levels of elements. Properties
698    *   used:
699    *   - #tabledrag: The value is a list of $options arrays that are passed to
700    *     drupal_attach_tabledrag(). The HTML ID of the table is added to each
701    *     $options array.
702    *
703    * @return array
704    *
705    * @see drupal_render()
706    * @see \Drupal\Core\Render\Element\Table::preRenderTable()
707    *
708    * @deprecated in Drupal 8.0.0, will be removed before Drupal 9.0.0.
709    */
710   public function tablePreRender($elements) {
711     return FieldUiTable::tablePreRender($elements);
712   }
713
714   /**
715    * Determines the rendering order of an array representing a tree.
716    *
717    * Callback for array_reduce() within
718    * \Drupal\field_ui\Form\EntityDisplayFormBase::tablePreRender().
719    *
720    * @deprecated in Drupal 8.0.0, will be removed before Drupal 9.0.0.
721    */
722   public function reduceOrder($array, $a) {
723     return FieldUiTable::reduceOrder($array, $a);
724   }
725
726   /**
727    * Returns the extra fields of the entity type and bundle used by this form.
728    *
729    * @return array
730    *   An array of extra field info.
731    *
732    * @see \Drupal\Core\Entity\EntityManagerInterface::getExtraFields()
733    */
734   protected function getExtraFields() {
735     $context = $this->displayContext == 'view' ? 'display' : $this->displayContext;
736     $extra_fields = $this->entityManager->getExtraFields($this->entity->getTargetEntityTypeId(), $this->entity->getTargetBundle());
737     return isset($extra_fields[$context]) ? $extra_fields[$context] : [];
738   }
739
740   /**
741    * Returns an entity display object to be used by this form.
742    *
743    * @param string $entity_type_id
744    *   The target entity type ID of the entity display.
745    * @param string $bundle
746    *   The target bundle of the entity display.
747    * @param string $mode
748    *   A view or form mode.
749    *
750    * @return \Drupal\Core\Entity\Display\EntityDisplayInterface
751    *   An entity display.
752    */
753   abstract protected function getEntityDisplay($entity_type_id, $bundle, $mode);
754
755   /**
756    * Returns an array of applicable widget or formatter options for a field.
757    *
758    * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
759    *   The field definition.
760    *
761    * @return array
762    *   An array of applicable widget or formatter options.
763    */
764   protected function getApplicablePluginOptions(FieldDefinitionInterface $field_definition) {
765     $options = $this->pluginManager->getOptions($field_definition->getType());
766     $applicable_options = [];
767     foreach ($options as $option => $label) {
768       $plugin_class = DefaultFactory::getPluginClass($option, $this->pluginManager->getDefinition($option));
769       if ($plugin_class::isApplicable($field_definition)) {
770         $applicable_options[$option] = $label;
771       }
772     }
773     return $applicable_options;
774   }
775
776   /**
777    * Returns the ID of the default widget or formatter plugin for a field type.
778    *
779    * @param string $field_type
780    *   The field type.
781    *
782    * @return string
783    *   The widget or formatter plugin ID.
784    */
785   abstract protected function getDefaultPlugin($field_type);
786
787   /**
788    * Returns the form or view modes used by this form.
789    *
790    * @return array
791    *   An array of form or view mode info.
792    */
793   abstract protected function getDisplayModes();
794
795   /**
796    * Returns an array of form or view mode options.
797    *
798    * @return array
799    *   An array of form or view mode options.
800    */
801   abstract protected function getDisplayModeOptions();
802
803   /**
804    * Returns a link to the form or view mode admin page.
805    *
806    * @return array
807    *   An array of a form element to be rendered as a link.
808    */
809   abstract protected function getDisplayModesLink();
810
811   /**
812    * Returns the region to which a row in the display overview belongs.
813    *
814    * @param array $row
815    *   The row element.
816    *
817    * @return string|null
818    *   The region name this row belongs to.
819    */
820   public function getRowRegion(&$row) {
821     $regions = $this->getRegions();
822     if (!isset($regions[$row['region']['#value']])) {
823       $row['region']['#value'] = 'hidden';
824     }
825     return $row['region']['#value'];
826   }
827
828   /**
829    * Returns entity (form) displays for the current entity display type.
830    *
831    * @return \Drupal\Core\Entity\Display\EntityDisplayInterface[]
832    *   An array holding entity displays or entity form displays.
833    */
834   protected function getDisplays() {
835     $load_ids = [];
836     $display_entity_type = $this->entity->getEntityTypeId();
837     $entity_type = $this->entityManager->getDefinition($display_entity_type);
838     $config_prefix = $entity_type->getConfigPrefix();
839     $ids = $this->configFactory()->listAll($config_prefix . '.' . $this->entity->getTargetEntityTypeId() . '.' . $this->entity->getTargetBundle() . '.');
840     foreach ($ids as $id) {
841       $config_id = str_replace($config_prefix . '.', '', $id);
842       list(,, $display_mode) = explode('.', $config_id);
843       if ($display_mode != 'default') {
844         $load_ids[] = $config_id;
845       }
846     }
847     return $this->entityManager->getStorage($display_entity_type)->loadMultiple($load_ids);
848   }
849
850   /**
851    * Returns form or view modes statuses for the bundle used by this form.
852    *
853    * @return array
854    *   An array of form or view mode statuses.
855    */
856   protected function getDisplayStatuses() {
857     $display_statuses = [];
858     $displays = $this->getDisplays();
859     foreach ($displays as $display) {
860       $display_statuses[$display->get('mode')] = $display->status();
861     }
862     return $display_statuses;
863   }
864
865   /**
866    * Saves the updated display mode statuses.
867    *
868    * @param array $display_statuses
869    *   An array holding updated form or view mode statuses.
870    */
871   protected function saveDisplayStatuses($display_statuses) {
872     $displays = $this->getDisplays();
873     foreach ($displays as $display) {
874       // Only update the display if the status is changing.
875       $new_status = $display_statuses[$display->get('mode')];
876       if ($new_status !== $display->status()) {
877         $display->set('status', $new_status);
878         $display->save();
879       }
880     }
881   }
882
883   /**
884    * Returns an array containing the table headers.
885    *
886    * @return array
887    *   The table header.
888    */
889   abstract protected function getTableHeader();
890
891   /**
892    * Returns the Url object for a specific entity (form) display edit form.
893    *
894    * @param string $mode
895    *   The form or view mode.
896    *
897    * @return \Drupal\Core\Url
898    *   A Url object for the overview route.
899    */
900   abstract protected function getOverviewUrl($mode);
901
902   /**
903    * Adds the widget or formatter third party settings forms.
904    *
905    * @param \Drupal\Core\Field\PluginSettingsInterface $plugin
906    *   The widget or formatter.
907    * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
908    *   The field definition.
909    * @param array $form
910    *   The (entire) configuration form array.
911    * @param \Drupal\Core\Form\FormStateInterface $form_state
912    *   The form state.
913    *
914    * @return array
915    *   The widget or formatter third party settings form.
916    */
917   abstract protected function thirdPartySettingsForm(PluginSettingsInterface $plugin, FieldDefinitionInterface $field_definition, array $form, FormStateInterface $form_state);
918
919   /**
920    * Alters the widget or formatter settings summary.
921    *
922    * @param array $summary
923    *   The widget or formatter settings summary.
924    * @param \Drupal\Core\Field\PluginSettingsInterface $plugin
925    *   The widget or formatter.
926    * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
927    *   The field definition.
928    */
929   abstract protected function alterSettingsSummary(array &$summary, PluginSettingsInterface $plugin, FieldDefinitionInterface $field_definition);
930
931 }