You are here

field_ui.inc in Display Suite 8.4

Same filename and directory in other branches
  1. 8.2 includes/field_ui.inc
  2. 8.3 includes/field_ui.inc

Field UI functions for Display Suite.

File

includes/field_ui.inc
View source
<?php

/**
 * @file
 * Field UI functions for Display Suite.
 */
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\Core\Form\FormState;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Plugin\PluginFormInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Url;
use Drupal\ds\Ds;
use Drupal\ds\Plugin\DsField\DsFieldInterface;
use Drupal\field\FieldConfigInterface;
use Drupal\field_ui\FieldUI;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Link;
use Drupal\layout_builder\LayoutBuilderEnabledInterface;

/**
 * Adds the Display Suite fields and layouts to the form.
 */
function ds_field_ui_fields_layouts(&$form, FormStateInterface $form_state) {
  global $base_root, $base_path;

  // Get the entity_type, bundle and view mode.
  $entity_type = $form['#entity_type'];
  $bundle = $form['#bundle'];

  /* @var \Drupal\Core\Entity\EntityFormInterface $entity_form */
  $entity_form = $form_state
    ->getFormObject();

  /* @var \Drupal\Core\Entity\Display\EntityDisplayInterface $entity_display */
  $entity_display = $entity_form
    ->getEntity();
  $view_mode = $entity_display
    ->getMode();

  // Check layout builder.
  if ($entity_display instanceof LayoutBuilderEnabledInterface && $entity_display
    ->isLayoutBuilderEnabled()) {
    return;
  }

  // Create vertical tabs.
  ds_field_ui_create_vertical_tabs($form);

  // Add layout fieldset.
  _ds_field_ui_table_layouts($entity_type, $bundle, $view_mode, $form, $form_state);

  // Add/alter fields on the table, but only if a layout is selected.
  if (!empty($form['#ds_layout'])) {
    _ds_field_ui_fields($entity_type, $bundle, $view_mode, $form, $form_state);

    // Also alter core fields.
    _ds_field_ui_core_fields($form, $form_state);
  }

  // Special validate function for field group.
  if ($form_state
    ->has('no_field_group')) {
    array_unshift($form['#validate'], '_ds_field_group_field_ui_fix_notices');
  }

  // Attach js.
  $form['#attached']['library'][] = 'ds/admin';

  // Add process function to add the regions.
  $form['#process'][] = 'ds_field_ui_regions';

  // Add a destination so we can get back if layout has been changed.
  $form['ds_source'] = [
    '#type' => 'hidden',
    '#value' => $base_root . $base_path,
  ];
  $form['ds_destination'] = [
    '#type' => 'hidden',
    '#value' => \Drupal::destination()
      ->getAsArray(),
  ];
  $form['ds_entity_type'] = [
    '#type' => 'hidden',
    '#value' => $entity_type,
  ];
  $form['ds_bundle'] = [
    '#type' => 'hidden',
    '#value' => $bundle,
  ];
  $form['ds_view_mode'] = [
    '#type' => 'hidden',
    '#value' => $view_mode,
  ];
}

/**
 * Create vertical tabs.
 */
function ds_field_ui_create_vertical_tabs(&$form) {

  // Add additional settings vertical tab.
  if (!isset($form['additional_settings'])) {
    $form['additional_settings'] = [
      '#type' => 'vertical_tabs',
      '#theme_wrappers' => [
        'vertical_tabs',
      ],
      '#prefix' => '<div>',
      '#suffix' => '</div>',
      '#tree' => TRUE,
    ];
  }

  // @todo needs core permission
  $view_mode_admin_access = \Drupal::currentUser()
    ->hasPermission('admin_view_modes');
  if (isset($form['modes'])) {
    $form['modes']['#group'] = 'additional_settings';
    $form['modes']['#weight'] = -10;
    if ($view_mode_admin_access) {
      $url = Url::fromRoute('field_ui.display_mode');
      $form['modes']['view_modes_custom']['#description'] = Link::fromTextAndUrl(t('Manage display modes'), $url)
        ->toString();
    }
  }
}

/**
 * Add Regions to 'Manage fields' or 'Manage display' screen.
 *
 * @param array $form
 *   The form to add layout fieldset and extra Display Suite fields.
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 *   The current form state.
 *
 * @return array
 *   The altered form
 */
function ds_field_ui_regions(array $form, FormStateInterface $form_state) {

  // Get the entity_type, bundle and view mode.
  $entity_type = $form['#entity_type'];
  $bundle = $form['#bundle'];

  /* @var \Drupal\Core\Entity\EntityFormInterface $entity_form */
  $entity_form = $form_state
    ->getFormObject();

  /* @var \Drupal\Core\Entity\Display\EntityDisplayInterface $entity_display */
  $entity_display = $entity_form
    ->getEntity();
  $view_mode = $entity_display
    ->getMode();

  // Ignore field_group options.
  if ($form_state
    ->has('no_field_group')) {
    unset($form['fields']['_add_new_group']);
    $form['field_group']['#access'] = FALSE;
  }

  // Check layout.
  $layout = isset($form['#ds_layout']) ? $form['#ds_layout'] : FALSE;

  // Build an array which keys are the field names and
  // values are the region they are rendered in.
  $field_regions = [];

  // Change UI to add Region column if we have a layout.
  if ($layout) {
    foreach ($layout['regions'] as $region_name => $field_names) {
      foreach ($field_names as $field_name) {
        $field_regions[$field_name] = $region_name;
      }
    }
    $table =& $form['fields'];
    $table['#header'] = [
      t('Field'),
      t('Weight'),
      t('Parent'),
      t('Region'),
      t('Label'),
      t('Formatter'),
      t('Widget'),
      [
        'data' => t('Operations'),
        'colspan' => 2,
      ],
    ];
    $table['#regions'] = [];
    $region_options = [];
    foreach ($layout['region_names'] as $region_key => $region_info) {
      $region_options[$region_key] = $region_info['label'];
      $table['#regions'][$region_key] = [
        'title' => $region_info['label'],
        'message' => t('No fields are displayed in this region'),
      ];
    }

    // Let other modules alter the regions.
    $context = [
      'entity_type' => $entity_type,
      'bundle' => $bundle,
      'view_mode' => $view_mode,
    ];
    $region_info = [
      'region_options' => &$region_options,
      'table_regions' => &$table['#regions'],
    ];
    \Drupal::moduleHandler()
      ->alter('ds_layout_region', $context, $region_info);
    $region_options['hidden'] = t('Disabled');
    $table['#regions']['hidden'] = [
      'title' => t('Disabled'),
      'message' => t('No fields are hidden.'),
    ];
    $region = [
      '#type' => 'select',
      '#options' => $region_options,
      '#default_value' => 'hidden',
      '#attributes' => [
        'class' => [
          'field-region',
        ],
      ],
    ];

    // Update existing rows by changing rowHandler and adding regions.
    foreach (Element::children($table) as $name) {
      $row =& $table[$name];
      $row['#js_settings'] = [
        'rowHandler' => 'field',
      ];
      $row['#region_callback'] = 'ds_field_ui_row_region';

      // Remove hidden format.
      if (isset($row['plugin']['type']['#options']['hidden'])) {
        unset($row['plugin']['type']['#options']['hidden']);
      }

      // Add label class.
      if (isset($row['label'])) {
        if ($form_state
          ->has('plugin_settings')) {
          $plugin_settings = $form_state
            ->get('plugin_settings');
          if (isset($plugin_settings[$name]['ft']['settings']) && !empty($plugin_settings[$name]['ft']['settings']['lb'])) {
            $row['human_name']['#plain_text'] = $plugin_settings[$name]['ft']['settings']['lb'] . ' ' . t('(Original: @orig)', [
              '@orig' => $row['human_name']['#plain_text'],
            ]);
          }
        }
      }

      // Add region.
      // Core added regions to Field UI, which default to 'content'
      if (!isset($row['region'])) {
        $split = 7;
        $default = isset($field_regions[$name]) && isset($region_options[$field_regions[$name]]) ? $field_regions[$name] : 'hidden';
        $second = array_splice($row, $split);
        $row['region'] = $region;
        $row['region']['#default_value'] = $default;
        $row = array_merge($row, $second);
      }
      else {
        $region_default_value = $row['region']['#default_value'];
        $default = isset($field_regions[$name]) && isset($region_options[$field_regions[$name]]) ? $field_regions[$name] : 'hidden';
        if ($region_default_value == 'content' && !isset($region_options['content'])) {
          $default = key($region_options);
        }
        $row['region']['#options'] = $region_options;
        $row['region']['#default_value'] = $default;
      }
    }
  }
  return $form;
}

/**
 * Returns the region to which a row in the Field UI screen belongs.
 *
 * @param array $row
 *   The current row that is being rendered in the Field UI screen.
 *
 * @return string
 *   The region.
 */
function ds_field_ui_row_region(array $row) {
  return isset($row['region']['#value']) ? $row['region']['#value'] : 'hidden';
}

/**
 * Validate the layout settings on the Field UI.
 */
function ds_field_ui_layouts_validate($form, FormStateInterface $form_state) {

  // Determine layout variables.
  $layout = $form_state
    ->getValue('ds_layout');
  if ($layout === '_none') {
    $layout = '';
    $form_state
      ->setValue('ds_layout', '');
  }
  $old_layout = $form_state
    ->getValue('old_layout');
  $new_layout = $layout != $old_layout || empty($old_layout);

  // Only validate the layout settings if the layout hasn't changed.
  if (!$new_layout && !empty($layout)) {

    /* @var \Drupal\layout_plugin\Plugin\Layout\LayoutInterface $layout_plugin */
    $layout_plugin = \Drupal::service('plugin.manager.core.layout')
      ->createInstance($form_state
      ->getValue('ds_layout'), []);
    $layout_form = isset($form['layout_configuration']) ? $form['layout_configuration'] : [];
    foreach (Element::children($form) as $name) {
      if (!empty($form[$name]['#ds_layout_configuration'])) {
        $layout_form[$name] = $form[$name];
      }
    }
    if ($layout_plugin instanceof PluginFormInterface) {
      $layout_form_state = (new FormState())
        ->setValues($form_state
        ->getValue('layout_configuration', []));

      // TODO

      //$layout_plugin->validateConfigurationForm($layout_form, $layout_form_state);
    }
  }

  // Move the view modes so Field UI can handle them.
  if ($form_state
    ->hasValue('modes')) {
    $modes = $form_state
      ->getValue('modes');
    if (isset($modes['display_modes_custom'])) {
      $form_state
        ->setValue('display_modes_custom', $modes['display_modes_custom']);
    }
  }
}

/**
 * Save the layout settings from the 'Manage display' screen.
 */
function ds_field_ui_layouts_save($form, FormStateInterface $form_state) {
  $save_display = FALSE;

  // Get default values.
  $entity_type = $form['#entity_type'];

  /* @var \Drupal\Core\Entity\EntityFormInterface $entity_form */
  $entity_form = $form_state
    ->getFormObject();

  // Get the entity display.

  /* @var \Drupal\Core\Entity\Display\EntityDisplayInterface $display */
  $display = $entity_form
    ->getEntity();
  if ($display instanceof LayoutBuilderEnabledInterface && $display
    ->isLayoutBuilderEnabled()) {
    $display
      ->unsetThirdPartySetting('ds', 'layout');
    $display
      ->unsetThirdPartySetting('ds', 'regions');
    $display
      ->unsetThirdPartySetting('ds', 'fields');
    $display
      ->save();
    $form_state
      ->set('ignore_ds_fields', FALSE);
    return;
  }

  // Get view mode.
  $view_mode = $display
    ->getMode();

  // Determine layout variables.
  $layout = $form_state
    ->getValue('ds_layout');
  $disable_css = $form_state
    ->getValue('disable_css');
  $entity_classes = $form_state
    ->getValue('entity_classes');
  if (empty($entity_classes)) {
    $entity_classes = 'all_classes';
  }
  $old_layout = $form_state
    ->getValue('old_layout');
  $new_layout = $layout != $old_layout || empty($old_layout);
  $key = array_search('ds_field_ui_change_layout_submit', $form['actions']['submit']['#submit']);
  if ($key && !empty($old_layout)) {
    return;
  }
  $ds_layouts = Ds::getLayouts();

  // Save layout and add regions if necessary.
  $record = [];
  $record['layout'] = [
    'id' => $layout,
    'library' => !empty($layout) ? $ds_layouts[$layout]
      ->getLibrary() : FALSE,
    'disable_css' => $disable_css ? TRUE : FALSE,
    'entity_classes' => $entity_classes,
  ];
  $record['regions'] = [];

  // Remove old layout if necessary.
  if ($new_layout && !empty($old_layout) || empty($layout)) {
    $display
      ->unsetThirdPartySetting('ds', 'layout');
    $display
      ->unsetThirdPartySetting('ds', 'regions');

    // If layout is empty, reset all fields to content, if the region is not
    // set to hidden at the moment. This takes field groups into account as well
    // automatically.
    if (empty($layout)) {
      $form_state
        ->set('ignore_ds_fields', FALSE);
      $display
        ->unsetThirdPartySetting('ds', 'fields');

      // Check for Field Group form params. If it's available, reset the
      // available and default regions.
      $field_group_params = $form_state
        ->get('field_group_params');
      if (isset($field_group_params) && !empty($field_group_params->available_regions) && !empty($field_group_params->default_region)) {
        $field_group_params->available_regions = [
          'content',
          'hidden',
        ];
        $field_group_params->default_region = 'hidden';
        $form_state
          ->set('field_group_params', $field_group_params);
      }
      $fields = $form_state
        ->getValue('fields');
      if (!empty($fields)) {
        foreach (array_keys($fields) as $name) {
          if (isset($fields[$name]['region'])) {
            $reset_region = 'content';
            if ($fields[$name]['region'] == 'hidden') {
              $reset_region = 'hidden';
            }
            $fields[$name]['region'] = $reset_region;
          }
        }
      }
      $form_state
        ->setValue('fields', $fields);
    }
    $display
      ->save();
  }
  if ($new_layout && !empty($layout)) {
    $save_display = TRUE;

    // Move current visible fields into a default region, so
    // we keep their current settings.
    $layouts = Ds::getLayouts();

    /** @var \Drupal\Core\Layout\LayoutDefinition $sl */
    $sl = $layouts[$layout];

    // TODO use default region method?
    $first_region = key($sl
      ->getRegions());
    $record['layout']['settings']['classes'] = [];
    $record['layout']['settings']['wrappers'] = [];

    // Set default region values.
    foreach ($sl
      ->getRegions() as $region_name => $content) {
      $record['layout']['settings']['wrappers'][$region_name] = 'div';
    }
    $record['layout']['settings']['outer_wrapper'] = 'div';
    $record['layout']['settings']['attributes'] = '';
    $record['layout']['settings']['link_attribute'] = FALSE;
    $record['layout']['settings']['link_custom'] = '';
    $fields = _ds_sort_fields((array) $form_state
      ->getValue('fields'), 'weight');
    foreach ($fields as $field_key => $field) {

      // Ignore new fieldgroup, new field or existing field.
      if (in_array($field_key, [
        '_add_new_field',
        '_add_existing_field',
        '_add_new_group',
      ])) {
        continue;
      }

      // Can either be form or display.
      if (isset($field['type']) && $field['type'] != 'hidden') {
        $record['regions'][$first_region][] = $field_key;
      }
    }

    // In case this is the full node view mode and if the comment module
    // is enabled for this content type, add it as well.
    if ($entity_type == 'node' && $view_mode == 'full' && \Drupal::moduleHandler()
      ->moduleExists('comment')) {
      $record['regions'][$first_region][] = 'comments';
    }
  }
  elseif (!empty($layout)) {
    $save_display = TRUE;
    $fields = _ds_sort_fields((array) $form_state
      ->getValue('fields'), 'weight');
    foreach ($fields as $key => $field) {

      // Make sure to hide hidden fields.
      if ($field['region'] == 'hidden') {
        $form_state
          ->setValue([
          'fields',
          $key,
          'type',
        ], 'hidden');
        continue;
      }
      if (!isset($record['regions'][$field['region']])) {
        $record['regions'][$field['region']] = [];
      }
      $record['regions'][$field['region']][] = $key;
    }

    /* @var \Drupal\layout_plugin\Plugin\Layout\LayoutInterface $layout_plugin */
    $layout_plugin = \Drupal::service('plugin.manager.core.layout')
      ->createInstance($layout, []);
    $layout_form = isset($form['layout_configuration']) ? $form['layout_configuration'] : [];
    foreach (Element::children($form) as $name) {
      if (!empty($form[$name]['#ds_layout_configuration'])) {
        $layout_form[$name] = $form[$name];
      }
    }
    if ($layout_plugin instanceof PluginFormInterface) {
      $layout_form_state = (new FormState())
        ->setValues($form_state
        ->getValue('layout_configuration', []));
      $layout_plugin
        ->submitConfigurationForm($layout_form, $layout_form_state);
    }

    // Get the layout settings from the layout_plugin.
    $record['layout']['settings'] = $layout_plugin
      ->getConfiguration();

    // Let other modules alter the layout settings.
    \Drupal::moduleHandler()
      ->alter('ds_layout_settings', $record, $form_state);
  }

  // Save the configuration.
  if ($save_display) {

    // Let other modules alter the layout settings.
    \Drupal::moduleHandler()
      ->alter('ds_layout_settings', $record, $form_state);
    foreach (array_keys($record) as $key) {
      $display
        ->setThirdPartySetting('ds', $key, $record[$key]);
    }
    $display
      ->save();
  }
}

/**
 * Form validation handler for _ds_field_ui_fields().
 */
function ds_field_ui_fields_validate($form, FormStateInterface $form_state) {
  $fields = $form_state
    ->getValue('fields');
  foreach (Element::children($form['fields']) as $key) {
    if (isset($fields[$key]['settings_edit_form']['settings'])) {
      $settings = $fields[$key]['settings_edit_form']['settings'];
      if (!empty($settings)) {
        $plugin_settings = $form_state
          ->get('plugin_settings');
        $plugin_settings[$key] = $settings;
        $form_state
          ->set('plugin_settings', $plugin_settings);
      }
    }
    if (isset($fields[$key]['settings_edit_form']['third_party_settings']['ds'])) {
      $settings = $fields[$key]['settings_edit_form']['third_party_settings']['ds'];
      if (!empty($settings)) {
        $plugin_settings = $form_state
          ->get('plugin_settings');
        $plugin_settings[$key] = $settings;
        $form_state
          ->set('plugin_settings', $plugin_settings);
      }
    }
  }
}

/**
 * Save the field settings from the 'Manage display' screen.
 */
function ds_field_ui_fields_save($form, FormStateInterface $form_state) {
  if (empty($form['#ds_fields']) || $form_state
    ->has('ignore_ds_fields')) {
    return;
  }

  // Get the entity display.

  /* @var \Drupal\Core\Entity\EntityFormInterface $entity_form */
  $entity_form = $form_state
    ->getFormObject();

  /* @var \Drupal\Core\Entity\Display\EntityDisplayInterface $display */
  $display = $entity_form
    ->getEntity();
  $field_settings = [];

  // Fetch values from form_state.
  $all_field_values = $form_state
    ->getValue('fields');
  $all_plugin_settings = $form_state
    ->get('plugin_settings');

  // Save settings for each Display Suite field.
  $ds_fields = $form['#ds_fields'];
  foreach ($ds_fields as $field) {
    $field_values = $all_field_values[$field];

    // In case the region is hidden, do not save.
    if (isset($field_values['region']) && $field_values['region'] == 'hidden') {
      continue;
    }

    // Build settings.
    $settings = [];
    $settings['plugin_id'] = $field;
    $settings['weight'] = $field_values['weight'];
    $settings['label'] = $field_values['label'];
    $settings['formatter'] = $field_values['plugin']['type'];

    // Any formatter settings.
    if (isset($all_plugin_settings[$field]['settings'])) {
      $settings['settings'] = $all_plugin_settings[$field]['settings'];
    }
    elseif (isset($all_plugin_settings[$field])) {

      // When the settings for that fields aren't changed the structure is
      // different.
      // @todo figure out how we can fix this.
      $settings['settings'] = $all_plugin_settings[$field];
    }
    $field_settings[$field] = $settings;

    // Always unset the field template settings from the regular settings array.
    // @todo needs serious review.
    unset($field_settings[$field]['settings']['ft']);

    // Add field template plugin settings if provided.
    $values = isset($all_plugin_settings[$field]['ft']) ? $all_plugin_settings[$field]['ft'] : [];
    if (!empty($values)) {
      $field_settings[$field]['ft'] = $values;
      $default_field_function = \Drupal::config('ds.settings')
        ->get('ft-default');
      $function = isset($values['id']) ? $values['id'] : $default_field_function;
      $field_settings[$field]['ft']['id'] = $function;

      // It's important for schema validation to never save empty arrays.
      if (empty($field_settings[$field]['ft']['settings'])) {
        unset($field_settings[$field]['ft']['settings']);
      }
    }

    // Last but not least unset the settings if they are empty after moving the
    // field template settings.
    if (empty($field_settings[$field]['settings'])) {
      unset($field_settings[$field]['settings']);
    }
  }

  // Save the record.
  $display
    ->unsetThirdPartySetting('ds', 'fields');
  if (!empty($field_settings)) {
    $display
      ->setThirdPartySetting('ds', 'fields', $field_settings);
  }
  $display
    ->save();

  // Clear the ds_fields cache.
  Cache::invalidateTags([
    'ds_fields_info',
  ]);
}

/**
 * Clone a fields layout.
 */
function ds_field_ui_layout_clone($form, FormStateInterface $form_state) {
  $clone = $form_state
    ->getValue('clone');
  list(, , $cv) = explode('.', $clone);
  $entity_type = $form['#entity_type'];
  $bundle = $form['#bundle'];

  /* @var \Drupal\Core\Entity\EntityFormInterface $entity_form */
  $entity_form = $form_state
    ->getFormObject();

  // Get the entity display.

  /* @var \Drupal\Core\Entity\Display\EntityDisplayInterface $old_display */
  $old_display = $entity_form
    ->getEntity();
  $view_mode = $old_display
    ->getMode();
  $old_display
    ->delete();

  /* @var \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display */
  $display = EntityViewDisplay::load($entity_type . '.' . $bundle . '.' . $cv);
  $clone_display = $display
    ->createCopy($view_mode);
  $clone_display
    ->save();

  // Show message.
  \Drupal::messenger()
    ->addMessage(t('The layout has been cloned.'));
}

/**
 * Creates a summary for the field format configuration summary.
 *
 * @param \Drupal\ds\Plugin\DsField\DsFieldInterface $plugin_instance
 *   An instance of the plugin.
 * @param array $settings
 *   The passed settings.
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 *   The form state of the summary.
 *
 * @return array
 *   The summary.
 */
function ds_field_settings_summary(DsFieldInterface $plugin_instance, array $settings, FormStateInterface $form_state) {

  // Create the form.
  $summary = $plugin_instance
    ->settingsSummary($settings);

  // Add field template summary.
  ds_field_formatter_settings_summary_alter($summary, [
    'field_definition' => $plugin_instance,
    'form_state' => $form_state,
  ]);
  if (empty($summary)) {
    return [];
  }
  return [
    '#type' => 'inline_template',
    '#template' => '<div class="field-plugin-summary">{{ summary|safe_join("<br />") }}</div>',
    '#context' => [
      'summary' => $summary,
    ],
    '#cell_attributes' => [
      'class' => [
        'field-plugin-summary-cell',
      ],
    ],
  ];
}

/**
 * Creates a form for Display Suite fields.
 *
 * @param \Drupal\ds\Plugin\DsField\DsFieldInterface $plugin_instance
 *   An instance of the plugin.
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 *   The form state of the form.
 *
 * @return mixed
 *   The altered form.
 */
function ds_field_settings_form(DsFieldInterface $plugin_instance, FormStateInterface $form_state) {

  // Create the form.
  $form = $plugin_instance
    ->settingsForm([], $form_state);

  // Add field template settings to every field if enabled.
  if (\Drupal::config('ds.settings')
    ->get('field_template')) {
    $context = [
      'instance' => [
        'entity_type' => $plugin_instance
          ->getEntityTypeId(),
        'bundle' => $plugin_instance
          ->bundle(),
        'field_name' => $plugin_instance
          ->getName(),
      ],
      'view_mode' => $plugin_instance
        ->viewMode(),
    ];
    ds_field_template_settings_form($form, $form_state, $context);
  }
  return $form;
}

/**
 * Add fake field group value in.
 *
 * @param array $form
 *   The actual form.
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 *   The form state of the form.
 */
function _ds_field_group_field_ui_fix_notices(array $form, FormStateInterface $form_state) {
  $field_group = [
    'group_name' => '',
    'label' => '',
  ];
  $fields = $form_state
    ->getValue('fields');
  $fields['_add_new_group'] = $field_group;
  $form_state
    ->setValue('fields', $fields);
}

/**
 * Add the layouts fieldset on the Field UI screen.
 *
 * @param string $entity_type
 *   The name of the entity type.
 * @param string $bundle
 *   The name of the bundle.
 * @param string $view_mode
 *   The name of the view_mode.
 * @param array $form
 *   A collection of form properties.
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 *   The form_state.
 */
function _ds_field_ui_table_layouts($entity_type, $bundle, $view_mode, array &$form, FormStateInterface $form_state) {
  $ds_layouts = Ds::getLayouts();
  $layout_options = [];
  unset($ds_layouts['layout_builder_blank']);

  /** @var \Drupal\Core\Layout\LayoutDefinition $layout_definition */
  foreach ($ds_layouts as $key => $layout_definition) {

    // Create new layout option group.
    $optgroup = $layout_definition
      ->getCategory() ?: t('Other');
    if ($optgroup instanceof TranslatableMarkup) {
      $optgroup = (string) $optgroup;
    }
    if (!isset($layout_options[$optgroup])) {
      $layout_options[$optgroup] = [];
    }

    // Stack the layout.
    $layout_options[$optgroup][$key] = $layout_definition
      ->getLabel();
  }

  // If there is only one $optgroup, move it to the root.
  if (count($layout_options) == 1) {
    $layout_options = reset($layout_options);
  }

  // Add layouts form.
  $form['ds_layouts'] = [
    '#type' => 'details',
    '#title' => t('Layout for @bundle in @view_mode', [
      '@bundle' => str_replace('_', ' ', $bundle),
      '@view_mode' => str_replace('_', ' ', $view_mode),
    ]),
    '#collapsible' => TRUE,
    '#group' => 'additional_settings',
    '#collapsed' => FALSE,
    '#weight' => -100,
  ];

  // @todo cleanup
  $layout = [];

  /* @var \Drupal\Core\Entity\EntityFormInterface $entity_form */
  $entity_form = $form_state
    ->getFormObject();

  /* @var \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display */
  $display = $entity_form
    ->getEntity();
  if ($display
    ->getThirdPartySetting('ds', 'layout')) {
    $layout_configuration = $display
      ->getThirdPartySetting('ds', 'layout');
    if (isset($ds_layouts[$layout_configuration['id']])) {
      $layout = (array) $ds_layouts[$layout_configuration['id']];
      $layout['layout'] = $layout_configuration['id'];
      $layout['disable_css'] = $layout_configuration['disable_css'];
      $layout['entity_classes'] = $layout_configuration['entity_classes'];
      $layout['settings'] = $layout_configuration['settings'] ?: [];
      $layout['regions'] = $display
        ->getThirdPartySetting('ds', 'regions');
      $layout['fields'] = $display
        ->getThirdPartySetting('ds', 'fields');
    }
    else {
      \Drupal::messenger()
        ->addMessage(t('The configured layout (@layout) is missing, please select a new one', [
        '@layout' => $layout_configuration['id'],
      ]), 'error');
    }
  }
  if (!empty($layout) && isset($layout['layout']) && isset($ds_layouts[$layout['layout']])) {
    $layout['region_names'] = $ds_layouts[$layout['layout']]
      ->getRegions();
    $form['#ds_layout'] = $layout;
  }

  // Load the layout preview form.
  $layout['layout_options'] = $layout_options;
  _ds_field_ui_table_layouts_preview($form, $form_state, $ds_layouts, $layout, $display);
  if (!empty($layout) && !empty($layout['settings'])) {

    /* @var \Drupal\Core\Layout\LayoutInterface $layout_plugin */
    $layout_plugin = \Drupal::service('plugin.manager.core.layout')
      ->createInstance($layout['layout'], $layout['settings'] ?: []);
    if ($layout_plugin instanceof PluginFormInterface) {
      $layout_configuration_form = $layout_plugin
        ->buildConfigurationForm([], $form_state);

      // Merge 'details' elements in 'additional_settings' group into the main
      // form.
      foreach (Element::children($layout_configuration_form) as $name) {
        $element = $layout_configuration_form[$name];
        if ($element['#type'] == 'details' && $element['#group'] == 'additional_settings') {
          $form[$name] = $layout_configuration_form[$name];
          unset($layout_configuration_form[$name]);
          $form[$name]['#ds_layout_configuration'] = TRUE;
          $form[$name]['#parents'] = [
            'layout_configuration',
            $name,
          ];
          $form[$name]['#tree'] = TRUE;
          if ($layout['layout'] === 'ds_reset') {
            $form[$name]['#access'] = FALSE;
          }
        }
      }

      // If anything is left, then we put it on it's own vertical tab.
      if (!Element::isEmpty($layout_configuration_form)) {
        $form['layout_configuration'] = array_merge($layout_configuration_form, [
          '#group' => 'additional_settings',
          '#type' => 'details',
          '#title' => t('Layout settings'),
          '#tree' => TRUE,
        ]);
      }
    }
  }
  else {

    // See if we can clone from another view mode.
    $options = [];
    $entity_displays = \Drupal::configFactory()
      ->listAll('core.entity_view_display.' . $entity_type . '.' . $bundle);
    if (!empty($entity_displays)) {
      $entity_type_info = \Drupal::entityTypeManager()
        ->getDefinition($entity_type);
      foreach ($entity_displays as $name) {
        $row = \Drupal::config($name)
          ->get();
        if ($row['mode'] === $view_mode) {
          continue;
        }
        if ($row['mode'] != 'default') {
          $view_mode_label = \Drupal::entityTypeManager()
            ->getStorage('entity_view_mode')
            ->load($entity_type . '.' . $row['mode'])
            ->label();
        }
        else {
          $view_mode_label = 'Default';
        }
        if ($row['targetEntityType'] === $entity_type && $row['bundle'] === $bundle) {
          if ($entity_type_info
            ->getBundleEntityType()) {
            $bundle_info = \Drupal::entityTypeManager()
              ->getStorage($entity_type_info
              ->getBundleEntityType())
              ->load($bundle);
            $options[$row['id']] = $entity_type_info
              ->getLabel() . ' > ' . $bundle_info
              ->label() . ' > ' . $view_mode_label;
          }
          else {
            $options[$row['id']] = $entity_type_info
              ->getLabel() . ' > ' . $view_mode_label;
          }
        }
      }
      if (!empty($options)) {
        natcasesort($options);

        // Clone from another layout.
        $form['ds_clone'] = [
          '#type' => 'details',
          '#group' => 'additional_settings',
          '#title' => t('Clone layout'),
          '#collapsible' => TRUE,
          '#collapsed' => TRUE,
        ];
        $form['ds_clone']['clone'] = [
          '#title' => t('Select an existing layout to clone.'),
          '#type' => 'select',
          '#options' => $options,
          '#weight' => 20,
        ];
        $form['ds_clone']['clone_submit'] = [
          '#type' => 'submit',
          '#value' => t('Clone layout'),
          '#submit' => [
            'ds_field_ui_layout_clone',
          ],
          '#weight' => 21,
        ];
      }
    }
  }
  $form['ds_layouts']['old_layout'] = [
    '#type' => 'value',
    '#value' => isset($layout['layout']) ? $layout['layout'] : 0,
  ];

  // Add validate and submit handlers. Layout needs be first so
  // we can reset the type key for Field API fields.
  $form['#validate'][] = 'ds_field_ui_layouts_validate';
  array_unshift($form['actions']['submit']['#submit'], 'ds_field_ui_fields_save');
  array_unshift($form['actions']['submit']['#submit'], 'ds_field_ui_layouts_save');
}

/**
 * Add the layout previews to the Field UI screen.
 *
 * @param array $form
 *   A collection of form properties.
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 *   The state of the form.
 * @param array $ds_layouts
 *   Collection of all the layouts.
 * @param array $layout
 *   Current selected layout.
 * @param \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display
 *   The entity display object.
 */
function _ds_field_ui_table_layouts_preview(array &$form, FormStateInterface $form_state, array $ds_layouts, array $layout, EntityViewDisplayInterface $display) {
  $layout_string = '';
  $form['ds_layouts']['ds_layout'] = [
    '#type' => 'select',
    '#title' => t('Select a layout'),
    '#options' => $layout['layout_options'],
    '#default_value' => isset($layout['layout']) ? $layout['layout'] : NULL,
    '#empty_value' => '_none',
    '#weight' => -1,
    '#ajax' => [
      'callback' => 'ds_field_ui_table_layouts_preview_callback',
      'wrapper' => 'ds_layout_wrapper',
    ],
  ];
  if (!isset($layout['layout'])) {
    $form['ds_layouts']['ds_layout']['#description'] = t("A layout must be selected to enable Display Suite functionality.");
  }
  $form['ds_layouts']['preview'] = [
    '#type' => 'container',
    '#prefix' => '<div id="ds_layout_wrapper">',
    '#suffix' => '</div>',
    '#weight' => -3,
  ];
  if (isset($layout['layout']) || $form_state
    ->hasValue('ds_layout')) {
    $layout_string = $form_state
      ->hasValue('ds_layout') ? $form_state
      ->getValue('ds_layout') : $layout['layout'];
  }
  $suggestions_array = [];
  if (!empty($layout_string)) {
    if (isset($ds_layouts[$layout_string])) {

      /** @var \Drupal\Core\Layout\LayoutDefinition $chosen_layout */
      $chosen_layout = $ds_layouts[$layout_string];
      $selected = '<strong>' . $chosen_layout
        ->getLabel() . '</strong>';
      $selected .= '<br/>' . t('The default template can be found in %path', [
        '%path' => $chosen_layout
          ->getPath() . '/' . $chosen_layout
          ->getTemplate(),
      ]);
      $suggestions_array[0] = $chosen_layout
        ->getTemplate() . '--' . $display
        ->getTargetEntityTypeId();
      $suggestions_array[2] = $chosen_layout
        ->getTemplate() . '--' . $display
        ->getTargetEntityTypeId() . '-' . $display
        ->getTargetBundle();
      $suggestions_array[4] = $chosen_layout
        ->getTemplate() . '--' . $display
        ->getTargetEntityTypeId() . '--{id}';
      if ($display
        ->getMode() != 'default') {
        $suggestions_array[1] = $chosen_layout
          ->getTemplate() . '--' . $display
          ->getTargetEntityTypeId() . '-' . $display
          ->getMode();
        $suggestions_array[3] = $chosen_layout
          ->getTemplate() . '--' . $display
          ->getTargetEntityTypeId() . '-' . $display
          ->getTargetBundle() . '-' . $display
          ->getMode();
      }
    }
    ksort($suggestions_array);

    // Append correct extension.
    foreach ($suggestions_array as $key => $value) {
      $suggestion_replace = strtr($suggestions_array[$key], '_', '-');
      $suggestions_array[$key] = $suggestion_replace . '.html.twig';
    }
    if ($form_state
      ->hasValue('ds_layout') || !empty($layout) && isset($layout['regions']) && !empty($chosen_layout)) {
      $fallback_image = drupal_get_path('module', 'ds') . '/images/preview.png';
      $current_layout = $form_state
        ->hasValue('ds_layout') && (!isset($layout->layout) || $form_state
        ->getValue('ds_layout') != $layout->layout) ? t('Current layout (after save)') : t('Current layout');
      $image = $chosen_layout
        ->getIconPath();
      if (empty($image)) {
        $image = $fallback_image;
      }
      $form['ds_layouts']['preview']['title'] = [
        '#markup' => '<div class="ds-layout-preview-title">' . $current_layout . '</div>',
      ];
      $form['ds_layouts']['preview']['image'] = [
        '#markup' => '<div class="ds-layout-preview-image"><img src="' . base_path() . $image . '"/></div>',
      ];
      $form['ds_layouts']['preview']['info'] = [
        '#type' => 'container',
        '#attributes' => [
          'class' => [
            'ds-layout-preview-suggestion',
          ],
        ],
      ];
      $form['ds_layouts']['preview']['info']['suggestions'] = [
        '#markup' => '<p>' . $selected . '</p><p>' . t('Template suggestions') . ':' . '</p>',
      ];
      $form['ds_layouts']['preview']['info']['suggestions_list'] = [
        '#theme' => 'item_list',
        '#items' => $suggestions_array,
      ];
      if (!empty($chosen_layout
        ->getLibrary())) {
        $disable_css = FALSE;
        if (isset($layout['disable_css'])) {
          $disable_css = $layout['disable_css'];
        }
        if ($form_state
          ->hasValue('disable_css') && $disable_css !== $form_state
          ->getValue('disable_css')) {
          $disable_css = $form_state
            ->getValue('disable_css');
        }
        $form['ds_layouts']['preview']['info']['settings']['disable_css'] = array(
          '#type' => 'checkbox',
          '#title' => t('Disable layout CSS styles'),
          '#default_value' => $disable_css,
        );
      }
      $entity_classes = 'all_classes';
      if (isset($layout['entity_classes'])) {
        $entity_classes = $layout['entity_classes'];
      }
      if ($form_state
        ->hasValue('entity_classes') && $entity_classes !== $form_state
        ->getValue('entity_classes')) {
        $entity_classes = $form_state
          ->getValue('entity_classes');
      }

      // Default classes.
      $form['ds_layouts']['preview']['info']['settings']['entity_classes'] = [
        '#type' => 'select',
        '#title' => t('Entity classes'),
        '#options' => [
          'all_classes' => t('Entity, bundle and view mode'),
          'no_classes' => t('No classes'),
          'old_view_mode' => t('View mode (deprecated)'),
        ],
        '#default_value' => $entity_classes,
      ];
      $form['ds_layouts']['preview']['clear'] = [
        '#markup' => '<div class="ds-after-suggestion"></div>',
      ];
    }
    if (!isset($layout['layout']) || $layout_string != $layout['layout']) {

      // Get admin path.
      $route = FieldUI::getOverviewRouteInfo($display
        ->getTargetEntityTypeId(), $display
        ->getTargetBundle());
      $route_parameters = $route
        ->getRouteParameters();
      $route_parameters['view_mode_name'] = $display
        ->getMode();
      $options = $route
        ->getOptions();
      $route_name = 'entity.entity_view_display.' . $display
        ->getTargetEntityTypeId() . '.view_mode';
      $admin_path = \Drupal::service('url_generator')
        ->generateFromRoute($route_name, $route_parameters, $options);
      $destination_url = '';

      // If regions aren't set we don't have to move fields.
      if (isset($layout['regions'])) {
        $route_name = 'ds.change_layout';
        $route_parameters = [
          'entity_type' => $display
            ->getTargetEntityTypeId(),
          'bundle' => $display
            ->getTargetBundle(),
          'display_mode' => $display
            ->getMode(),
          'new_layout' => $layout_string,
        ];
        $options = [];
        $destination_url = $admin_path;
      }
      $form['layout_changed_url'] = [
        '#type' => 'value',
        '#value' => [
          'route_name' => $route_name,
          'route_parameters' => $route_parameters,
          'destination_url' => $destination_url,
          'options' => $options,
        ],
      ];
      array_unshift($form['actions']['submit']['#submit'], 'ds_field_ui_change_layout_submit');
    }
  }
}

/**
 * Ajax callback for _ds_field_ui_table_layouts_preview().
 */
function ds_field_ui_table_layouts_preview_callback($form, FormStateInterface $form_state) {
  return $form['ds_layouts']['preview'];
}

/**
 * Form submission handler for _ds_field_ui_table_layouts_preview().
 */
function ds_field_ui_change_layout_submit($form, FormStateInterface $form_state) {

  // Remove original destination.
  \Drupal::request()->query
    ->remove('destination');
  $destination = $form_state
    ->getValue('layout_changed_url');
  $redirectUrl = new Url($destination['route_name'], $destination['route_parameters'], $destination['options']);
  if (!empty($destination['destination_url'])) {
    $redirectUrl
      ->setOption('query', [
      'destination' => $destination['destination_url'],
    ]);
  }
  $form_state
    ->setRedirectUrl($redirectUrl);
}

/**
 * Add the fields to the Field UI form.
 *
 * @param string $entity_type
 *   The name of the entity type.
 * @param string $bundle
 *   The name of the bundle.
 * @param string $view_mode
 *   The name of the view_mode.
 * @param array $form
 *   A collection of form properties.
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 *   A collection of form_state properties.
 */
function _ds_field_ui_fields($entity_type, $bundle, $view_mode, array &$form, FormStateInterface $form_state) {

  // Do not add the fields if there is no layout.
  if (!isset($form['#ds_layout'])) {
    return;
  }

  // Get the fields and put them on the form.
  $fields = Ds::getFields($entity_type);

  // Get field settings.
  $field_settings = $form['#ds_layout']['fields'];
  $form['#field_settings'] = $field_settings;
  if (empty($form['fields'])) {

    // EntityDisplayFormBase doesn't add the fields table and field_ui library
    // when there are no fields.  Since DS needs those, we add them.
    $form['#attached']['library'][] = 'field_ui/drupal.field_ui';
    $form['fields'] = [
      '#type' => 'field_ui_table',
      '#weight' => -101,
      '#header' => [
        t('Field'),
        t('Weight'),
        t('Parent'),
        t('Region'),
        t('Label'),
        [
          'data' => t('Format'),
          'colspan' => 3,
        ],
      ],
      '#regions' => [],
      '#attributes' => [
        'class' => [
          'field-ui-overview',
        ],
        'id' => 'field-display-overview',
      ],
      '#tabledrag' => [
        [
          'action' => 'order',
          'relationship' => 'sibling',
          'group' => 'field-weight',
        ],
        [
          'action' => 'match',
          'relationship' => 'parent',
          'group' => 'field-parent',
          'subgroup' => 'field-parent',
          'source' => 'field-name',
        ],
        [
          'action' => 'match',
          'relationship' => 'parent',
          'group' => 'field-region',
          'subgroup' => 'field-region',
          'source' => 'field-name',
        ],
      ],
    ];

    // Check for the 'There are no fields yet added. You can add new fields on
    // the Manage fields page.' warning message. We really don't need it.
    $warnings = \Drupal::messenger()
      ->messagesByType(MessengerInterface::TYPE_WARNING);
    if (count($warnings) == 1) {
      \Drupal::messenger()
        ->deleteByType(MessengerInterface::TYPE_WARNING);
    }

    // In overviews involving nested rows from contributed modules (i.e
    // field_group), the 'plugin type' selects can trigger a series of changes
    // in child rows. The #ajax behavior is therefore not attached directly to
    // the selects, but triggered by the client-side script through a hidden
    // #ajax 'Refresh' button. A hidden 'refresh_rows' input tracks the name of
    // affected rows.
    $form['refresh_rows'] = [
      '#type' => 'hidden',
    ];
    $form['refresh'] = [
      '#type' => 'submit',
      '#value' => t('Refresh'),
      '#op' => 'refresh_table',
      '#submit' => [
        '::multistepSubmit',
      ],
      '#ajax' => [
        'callback' => '::multistepAjax',
        'wrapper' => 'field-display-overview-wrapper',
        'effect' => 'fade',
        // The button stays hidden, so we hide the Ajax spinner too. Ad-hoc
        // spinners will be added manually by the client-side script.
        'progress' => 'none',
      ],
      '#attributes' => [
        'class' => [
          'visually-hidden',
        ],
      ],
    ];
  }
  $table =& $form['fields'];
  $form['#ds_fields'] = [];
  $field_label_options = [
    'above' => t('Above'),
    'inline' => t('Inline'),
    'hidden' => t('- Hidden -'),
  ];
  \Drupal::moduleHandler()
    ->alter('ds_label_options', $field_label_options);

  /* @var \Drupal\Core\Entity\EntityFormInterface $entity_form */
  $entity_form = $form_state
    ->getFormObject();

  /* @var \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display */
  $display = $entity_form
    ->getEntity();
  $parent_options = [];
  if (function_exists('field_group_field_ui_form_params')) {
    $field_group_params = field_group_field_ui_form_params($form, $display);
    foreach ($field_group_params->groups as $name => $group) {
      $parent_options[$name] = $group->label;
    }
    $parent_options['_add_new_group'] = t('Add new group');
  }
  foreach ($fields as $key => $field) {
    if (isset($field_settings[$key]['formatter'])) {
      $field['formatter'] = $field_settings[$key]['formatter'];
    }
    $configuration = [
      'field' => $field,
      'field_name' => $key,
      'entity_type' => $entity_type,
      'bundle' => $bundle,
      'view_mode' => $view_mode,
    ];

    // Check if we can display this field here.

    /* @var $plugin_instance DsFieldInterface */
    $plugin_instance = \Drupal::service('plugin.manager.ds')
      ->createInstance($field['plugin_id'], $configuration);
    if (!$plugin_instance
      ->isAllowed()) {
      continue;
    }

    // Don't filter out fields when $displays is empty.
    if (!empty($displays)) {
      $continue = TRUE;
      foreach ($displays as $limitation) {
        list($limit_bundle, $limit_view_mode) = explode('|', $limitation);
        if ($limit_bundle == '*' || $limit_bundle == $bundle) {
          if ($limit_view_mode == '*' || $limit_view_mode == $view_mode) {
            $continue = FALSE;
          }
        }
      }
      if ($continue) {
        continue;
      }
    }
    $form['#ds_fields'][] = $key;

    // Fetch saved plugin settings.
    $form_state_plugin_settings = $form_state
      ->get('plugin_settings');

    // Check on formatter settings.
    $plugin_settings = [];
    if (isset($form_state_plugin_settings[$key])) {
      $plugin_settings = $form_state_plugin_settings[$key];
    }
    elseif (isset($field_settings[$key]['settings']) || isset($field_settings[$key]['ft']) || isset($field_settings[$key]['ds_limit'])) {
      if (isset($field_settings[$key]['settings'])) {
        $plugin_settings = $field_settings[$key]['settings'];
      }
      if (isset($field_settings[$key]['ft'])) {
        $plugin_settings['ft'] = $field_settings[$key]['ft'];
      }
      if (isset($field_settings[$key]['ds_limit'])) {
        $plugin_settings['ds_limit'] = $field_settings[$key]['ds_limit'];
      }
      $form_state_plugin_settings[$key] = $plugin_settings;
    }
    $plugin_instance
      ->setConfiguration($plugin_settings);

    // Save fetched plugin settings.
    $form_state
      ->set('plugin_settings', $form_state_plugin_settings);
    $hidden = [
      'hidden' => t('- Hidden -'),
    ];

    // Get the formatters from the field instance.
    $formatters = $plugin_instance
      ->formatters();

    // This should be temporary. Don't want to copy stuff from the object to
    // the field each ajax refresh.
    if (!empty($formatters)) {
      $formatters = $hidden + $formatters;
    }
    else {
      $formatters = $hidden + [
        'default' => t('Default'),
      ];
    }
    $table[$key] = [
      '#row_type' => 'field',
      '#js_settings' => [
        'field',
      ],
      '#region_callback' => 'field_ui_display_overview_row_region',
      '#attributes' => [
        'class' => [
          'draggable',
          'tabledrag-leaf',
        ],
      ],
      'human_name' => [
        '#plain_text' => $field['title'],
      ],
      'weight' => [
        '#type' => 'textfield',
        '#default_value' => isset($field_settings[$key]['weight']) ? $field_settings[$key]['weight'] : 0,
        '#size' => 3,
        '#attributes' => [
          'class' => [
            'field-weight',
          ],
        ],
      ],
      'parent_wrapper' => [
        'parent' => [
          '#type' => 'select',
          '#empty_value' => '',
          '#options' => $parent_options,
          '#default_value' => isset($field_group_params->parents[$key]) ? $field_group_params->parents[$key] : '',
          '#attributes' => [
            'class' => [
              'field-parent',
            ],
          ],
          '#parents' => [
            'fields',
            $key,
            'parent',
          ],
        ],
        'hidden_name' => [
          '#type' => 'hidden',
          '#default_value' => $key,
          '#attributes' => [
            'class' => [
              'field-name',
            ],
          ],
        ],
      ],
      'label' => [
        '#type' => 'select',
        '#options' => $field_label_options,
        '#default_value' => isset($field_settings[$key]['label']) ? $field_settings[$key]['label'] : 'hidden',
      ],
      'plugin' => [
        'type' => [
          '#type' => 'select',
          '#options' => $formatters,
          '#default_value' => isset($field_settings[$key]['formatter']) ? $field_settings[$key]['formatter'] : 'hidden',
          '#attributes' => [
            'class' => [
              'field-plugin-type',
            ],
          ],
        ],
      ],
      'settings_summary' => [],
      'settings_edit' => [],
    ];
    if ($form_state
      ->get('plugin_settings_edit') == $key) {
      $table[$key]['settings_summary']['#attributes']['colspan'] = 2;
      $settings_form = ds_field_settings_form($plugin_instance, $form_state);
      ds_field_row_form_format_construct($table, $key, $settings_form, $form_state);
    }
    else {

      // After saving, the settings are updated here as well. First we create
      // the element for the table cell.
      $summary = ds_field_settings_summary($plugin_instance, $plugin_settings, $form_state);
      if (!empty($summary)) {
        $table[$key]['settings_summary'] = $summary;
        ds_field_row_form_format_summary_construct($table, $key, $form_state);
      }
    }
  }
}

/**
 * Alter the core field on the Field UI form.
 *
 * @param array $form
 *   A collection of form properties.
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 *   A collection of form_state properties.
 */
function _ds_field_ui_core_fields(array &$form, FormStateInterface $form_state) {
  $entity_type = $form['#entity_type'];
  $bundle = $form['#bundle'];

  /* @var \Drupal\Core\Entity\EntityFormInterface $entity_form */
  $entity_form = $form_state
    ->getFormObject();

  /* @var EntityViewDisplay $entity_display */
  $entity_display = $entity_form
    ->getEntity();

  // Gather type information.
  $instances = \Drupal::service('entity_field.manager')
    ->getFieldDefinitions($entity_type, $bundle);
  $table =& $form['fields'];

  // Get all persisted values for fields and plugin settings.
  $form_state_plugin_settings = $form_state
    ->get('plugin_settings');

  // Field rows.
  foreach ($instances as $key => $instance) {
    if ($instance instanceof FieldConfigInterface) {
      $settings = $entity_display
        ->getComponent($key);
      if (empty($settings)) {
        continue;
      }
      if (isset($form_state_plugin_settings[$key])) {
        $settings = array_merge($settings, $form_state_plugin_settings[$key]);
      }

      // Import field settings and merge with Field API settings.
      if (!isset($form_state_plugin_settings[$key])) {
        $form_state_plugin_settings[$key] = isset($settings['third_party_settings']['ds']) ? $settings['third_party_settings']['ds'] : [];
      }
      if ($form_state
        ->get('plugin_settings_edit') == $key) {
        $table[$key]['plugin']['settings_edit_form']['actions']['save_settings']['#validate'] = [
          'ds_field_ui_fields_validate',
        ];
        $table[$key]['plugin']['settings_edit_form']['actions']['cancel_settings']['#validate'] = [
          'ds_field_ui_fields_validate',
        ];
      }
      else {
        if (!empty($table[$key]['settings_summary'])) {
          ds_field_row_form_format_summary_construct($table, $key, $form_state);
        }
      }
    }
  }

  // Set updated plugin settings.
  $form_state
    ->set('plugin_settings', $form_state_plugin_settings);
}

/**
 * Helper function for building the formatter settings.
 */
function ds_field_row_form_format_construct(&$table, $key, $settings_form, FormStateInterface $form_state) {
  $build_info = $form_state
    ->getBuildInfo();
  $base_button = [
    '#submit' => [
      [
        $build_info['callback_object'],
        'multistepSubmit',
      ],
    ],
    '#validate' => [
      'ds_field_ui_fields_validate',
    ],
    '#ajax' => [
      'callback' => [
        $build_info['callback_object'],
        'multistepAjax',
      ],
      'wrapper' => 'field-display-overview-wrapper',
      'effect' => 'fade',
    ],
    '#field_name' => $key,
  ];
  $table[$key]['plugin']['settings_edit'] = [
    '#type' => 'container',
    '#attributes' => [
      'class' => [
        'field-plugin-settings-edit-form',
      ],
    ],
    '#parents' => [
      'fields',
      $key,
      'settings_edit_form',
    ],
    '#weight' => -5,
    // Create a settings form where hooks can pick in.
    'settings' => $settings_form,
    'actions' => [
      '#type' => 'actions',
      'save_settings' => $base_button + [
        '#type' => 'submit',
        '#name' => $key . '_plugin_settings_update',
        '#value' => t('Update'),
        '#op' => 'update',
      ],
      'cancel_settings' => $base_button + [
        '#type' => 'submit',
        '#name' => $key . '_plugin_settings_cancel',
        '#value' => t('Cancel'),
        '#op' => 'cancel',
        // Do not check errors for the 'Cancel' button.
        '#limit_validation_errors' => [],
      ],
    ],
  ];
  $table[$key]['#attributes']['class'][] = 'field-plugin-settings-editing';
  $table[$key]['plugin']['type']['#attributes']['class'] = [
    'visually-hidden',
  ];
}

/**
 * Helper function for formatter summary settings.
 */
function ds_field_row_form_format_summary_construct(&$table, $key, FormStateInterface $form_state) {
  $build_info = $form_state
    ->getBuildInfo();
  $base_button = [
    '#submit' => [
      [
        $build_info['callback_object'],
        'multistepSubmit',
      ],
    ],
    '#ajax' => [
      'callback' => [
        $build_info['callback_object'],
        'multistepAjax',
      ],
      'wrapper' => 'field-display-overview-wrapper',
      'effect' => 'fade',
    ],
    '#field_name' => $key,
  ];

  // Add the configure button.
  $table[$key]['settings_edit'] = $base_button + [
    '#type' => 'image_button',
    '#name' => $key . '_plugin_settings_edit',
    '#src' => 'core/misc/icons/787878/cog.svg',
    '#attributes' => [
      'class' => [
        'field-plugin-settings-edit',
      ],
      'alt' => t('Edit'),
    ],
    '#op' => 'edit',
    // Do not check errors for the 'Edit' button, but make sure we get
    // the value of the 'plugin type' select.
    '#limit_validation_errors' => [
      [
        'fields',
        $key,
        'type',
      ],
    ],
    '#prefix' => '<div class="field-plugin-settings-edit-wrapper">',
    '#suffix' => '</div>',
  ];
}

/**
 * Utility function to sort a multidimensional array by a value in a sub-array.
 *
 * @param array $a
 *   The array to sort.
 * @param string $subkey
 *   The subkey to sort by.
 *
 * @return array
 *   The sorted array.
 */
function _ds_sort_fields(array $a, $subkey) {
  $c = [];
  $b = [];
  foreach ($a as $k => $v) {
    if (isset($v[$subkey])) {
      $b[$k] = $v[$subkey];
    }
  }
  asort($b);
  foreach ($b as $key => $val) {
    $c[$key] = $a[$key];
  }
  return $c;
}

Functions

Namesort descending Description
ds_field_row_form_format_construct Helper function for building the formatter settings.
ds_field_row_form_format_summary_construct Helper function for formatter summary settings.
ds_field_settings_form Creates a form for Display Suite fields.
ds_field_settings_summary Creates a summary for the field format configuration summary.
ds_field_ui_change_layout_submit Form submission handler for _ds_field_ui_table_layouts_preview().
ds_field_ui_create_vertical_tabs Create vertical tabs.
ds_field_ui_fields_layouts Adds the Display Suite fields and layouts to the form.
ds_field_ui_fields_save Save the field settings from the 'Manage display' screen.
ds_field_ui_fields_validate Form validation handler for _ds_field_ui_fields().
ds_field_ui_layouts_save Save the layout settings from the 'Manage display' screen.
ds_field_ui_layouts_validate Validate the layout settings on the Field UI.
ds_field_ui_layout_clone Clone a fields layout.
ds_field_ui_regions Add Regions to 'Manage fields' or 'Manage display' screen.
ds_field_ui_row_region Returns the region to which a row in the Field UI screen belongs.
ds_field_ui_table_layouts_preview_callback Ajax callback for _ds_field_ui_table_layouts_preview().
_ds_field_group_field_ui_fix_notices Add fake field group value in.
_ds_field_ui_core_fields Alter the core field on the Field UI form.
_ds_field_ui_fields Add the fields to the Field UI form.
_ds_field_ui_table_layouts Add the layouts fieldset on the Field UI screen.
_ds_field_ui_table_layouts_preview Add the layout previews to the Field UI screen.
_ds_sort_fields Utility function to sort a multidimensional array by a value in a sub-array.