Skip to content
ds.module 42.3 KiB
Newer Older
 * @file
Kristof De Jaeger's avatar
Kristof De Jaeger committed
 * Display Suite core functions.
use Drupal\Core\Template\Attribute;
Kristof De Jaeger's avatar
Kristof De Jaeger committed
use Drupal\Core\Entity\EntityInterface;
Kristof De Jaeger's avatar
Kristof De Jaeger committed
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Language\Language;
use Drupal\entity\Entity\EntityDisplay;
/**
 * Implements hook_permission().
 */
function ds_permission() {
  return array(
    'admin_display_suite' => array(
      'title' => t('Administer Display Suite'),
      'description' => t('General permission for Display Suite settings pages.')
Kristof De Jaeger's avatar
Kristof De Jaeger committed
    ),
  $items['admin/structure/ds'] = array(
    'description' => 'Manage layouts for entities and configure fields etc.',
Bram Goffings's avatar
Bram Goffings committed
  );

  // Layout overview.
  $items['admin/structure/ds/list'] = array(
    'title' => 'Display Suite',
    'route_name' => 'ds_list',
    'type' => MENU_SUGGESTED_ITEM,
  $items['admin/structure/ds/list'] = array(
    'route_name' => 'ds_list',
    'type' => MENU_SUGGESTED_ITEM,
  // Settings page
  $items['admin/structure/ds/settings'] = array(
    'title' => 'Settings',
    'route_name' => 'ds_admin_settings',
    'type' => MENU_SUGGESTED_ITEM,
  $items['admin/structure/ds/list/emergency'] = array(
    'type' => MENU_SUGGESTED_ITEM,
  // Change layout.
  $items['admin/structure/ds/change-layout'] = array(
    'title' => 'Change layout',
    'description' => 'Act on layout change to move fields elsewhere',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('ds_field_ui_layout_change'),
    'access arguments' => array('admin_display_suite'),
Kristof De Jaeger's avatar
Kristof De Jaeger committed
    'file' => 'includes/field_ui.inc',
    'type' => MENU_VISIBLE_IN_BREADCRUMB,
  );

  if (Drupal::moduleHandler()->moduleExists('contextual') && Drupal::moduleHandler()->moduleExists('field_ui')) {
    $items['node/%node/display'] = array(
      'title' => 'Manage display',
      'description' => 'Manage display of this content.',
      'page callback' => 'ds_contextual_page_tab',
      'page arguments' => array(1, 'node'),
Kristof De Jaeger's avatar
Kristof De Jaeger committed
      'file' => 'includes/contextual.inc',
      'access arguments' => array('administer content types'),
      'type' => MENU_LOCAL_TASK,
    );

    $items['user/%user/display'] = array(
      'title' => 'Manage display',
      'description' => 'Manage display of this user profile.',
      'page callback' => 'ds_contextual_page_tab',
      'page arguments' => array(1, 'user'),
Kristof De Jaeger's avatar
Kristof De Jaeger committed
      'file' => 'includes/contextual.inc',
      'access arguments' => array('administer users'),
      'type' => MENU_LOCAL_TASK,
    );

    if (Drupal::moduleHandler()->moduleExists('taxonomy')) {
      $items['taxonomy/term/%taxonomy_term/display'] = array(
        'title' => 'Manage display',
        'description' => 'Manage display of this term.',
        'page callback' => 'ds_contextual_page_tab',
        'page arguments' => array(2, 'taxonomy_term'),
        'access arguments' => array('administer taxonomy'),
        'type' => MENU_LOCAL_TASK,
        'weight' => 11,
Kristof De Jaeger's avatar
Kristof De Jaeger committed
        'file' => 'includes/contextual.inc',
/**
 * Implements hook_menu_alter().
 */
function ds_menu_alter(&$items) {
Kristof De Jaeger's avatar
Kristof De Jaeger committed
  module_load_include('inc', 'ds', 'includes/registry');
  return _ds_menu_alter($items);
}

/**
 * Implements hook_theme().
 */
function ds_theme() {
Kristof De Jaeger's avatar
Kristof De Jaeger committed
  module_load_include('inc', 'ds', 'includes/registry');
 * Implements hook_ds_layout_info().
function ds_ds_layout_info() {
Kristof De Jaeger's avatar
Kristof De Jaeger committed
  module_load_include('inc', 'ds', 'includes/registry');
  return _ds_ds_layout_info();
/**
 * Implements hook_node_type_update().
 */
function ds_node_type_update($info) {
  if (!empty($info->old_type) && $info->old_type != $info->type) {
Kristof De Jaeger's avatar
Kristof De Jaeger committed
    module_load_include('inc', 'ds', 'includes/registry');
Kristof De Jaeger's avatar
Kristof De Jaeger committed
  module_load_include('inc', 'ds', 'includes/registry');
Kristof De Jaeger's avatar
Kristof De Jaeger committed
 * Implements hook_theme_registry_alter().
Kristof De Jaeger's avatar
Kristof De Jaeger committed
function ds_theme_registry_alter(&$theme_registry) {
Kristof De Jaeger's avatar
Kristof De Jaeger committed
  module_load_include('inc', 'ds', 'includes/registry');
Kristof De Jaeger's avatar
Kristof De Jaeger committed
  _ds_theme_registry_alter($theme_registry);
 * Implements ds_entity_bundle_info_alter().
function ds_entity_bundle_info_alter(&$bundles) {
Kristof De Jaeger's avatar
Kristof De Jaeger committed
  module_load_include('inc', 'ds', 'includes/registry');
  _ds_entity_bundle_info_alter($bundles);
}

/**
 * Implements hook_entity_view_mode_info_alter().
 */
function ds_entity_view_mode_info_alter(&$view_modes) {
Kristof De Jaeger's avatar
Kristof De Jaeger committed
  module_load_include('inc', 'ds', 'includes/registry');
  _ds_entity_view_mode_info_alter($view_modes);
Kristof De Jaeger's avatar
Kristof De Jaeger committed
 * Implements hook_form_FORM_ID_alter().
Kristof De Jaeger's avatar
Kristof De Jaeger committed
function ds_form_field_ui_display_overview_form_alter(&$form, &$form_state) {
Kristof De Jaeger's avatar
Kristof De Jaeger committed
  form_load_include($form_state, 'inc', 'ds', 'includes/field_ui');
  // Also load admin on behalf of DS extras when enabled.
  if (Drupal::moduleHandler()->moduleExists('ds_extras')) {
Kristof De Jaeger's avatar
Kristof De Jaeger committed
    form_load_include($form_state, 'inc', 'ds_extras', 'includes/admin');
Kristof De Jaeger's avatar
Kristof De Jaeger committed
  ds_field_ui_fields_layouts($form, $form_state);
/**
 * Implements hook_module_implements_alter().
 */
function ds_module_implements_alter(&$implementations, $hook) {
  module_load_include('inc', 'ds', 'includes/registry');
  _ds_module_implements_alter($implementations, $hook);
/**
 * Implements hook_library_info().
 */
function ds_library_info() {
  $libraries['ds.admin.js'] = array(
    'title' => 'Display Suite settings',
    'version' => VERSION,
    'js' => array(
      drupal_get_path('module', 'ds') . '/js/ds.admin.js' => array(),
    ),
    'dependencies' => array(
      array('system', 'jquery'),
      array('system', 'drupal'),
      array('system', 'drupal.form'),
    ),
  );
  $libraries['ds.admin.css'] = array(
    'title' => 'Display Suite administration CSS',
    'version' => VERSION,
    'css' => array(
      drupal_get_path('module', 'ds') . '/css/ds.admin.css' => array(),
    ),
  );
 * Function to check if we go on with Display Suite.
 */
function ds_kill_switch() {
Kristof De Jaeger's avatar
Kristof De Jaeger committed
  // @todo move to state.
  if (Drupal::config('ds.settings')->get('disable')) {
    return TRUE;
  }
Kristof De Jaeger's avatar
Kristof De Jaeger committed
  return FALSE;
 */
function ds_get_layout_info() {
  static $layouts = FALSE;

  if (!$layouts) {
    $layouts = Drupal::moduleHandler()->invokeAll('ds_layout_info');

    // Give modules a chance to alter the layouts information.
    drupal_alter('ds_layout_info', $layouts);
    // Check that there is no 'content' region, but ignore panel layouts.
    // Because when entities are rendered, the field items are stored into a
    // 'content' key so fields would be overwritten before they're all moved.
    foreach ($layouts as $key => $info) {
      if (isset($info['regions']['content'])) {
        $errors[] = $key;
      }
    }
    if (!empty($errors)) {
      drupal_set_message(t('Following layouts have a "content" region key which is invalid: %layouts.', array('%layouts' => implode(', ', $errors))), 'error');
  }

  return $layouts;
}

/**
 * Get a layout for a given entity.
 *
 * @param $entity_type
 *   The name of the entity.
 * @param $bundle
 *   The name of the bundle.
 * @param $view_mode
 *   The name of the view mode.
 * @param $fallback
 *   Whether to fallback to default or not.
 * @return $layout
 *   Array of layout variables for the regions.
 */
function ds_get_layout($entity_type, $bundle, $view_mode, $fallback = TRUE) {
  static $layouts = array();

  $layout_key = $entity_type . '_' . $bundle . '_' . $view_mode;
  if (!isset($layouts[$layout_key])) {

    $bundles = entity_get_bundles();
    if ($view_mode != 'form') {
      $view_mode_settings = field_view_mode_settings($entity_type, $bundle);
      $overridden = (!empty($view_mode_settings[$view_mode]['status']) ? TRUE : FALSE);
    // Check if a layout is configured.
    if (isset($bundles[$entity_type][$bundle]['layouts'][$view_mode]) && ($overridden || $view_mode == 'default')) {
      $layouts[$layout_key] = $bundles[$entity_type][$bundle]['layouts'][$view_mode];
      $layouts[$layout_key]['view_mode'] = $view_mode;
    // In case $view_mode is not found, check if we have a default layout,
    // but only if the view mode settings aren't overridden for this view mode.
    if ($view_mode != 'default' && !$overridden && $fallback && isset($bundles[$entity_type][$bundle]['layouts']['default'])) {
      $layouts[$layout_key] = $bundles[$entity_type][$bundle]['layouts']['default'];
      $layouts[$layout_key]['view_mode'] = 'default';
    }

    // Register the false return value as well.
    if (!isset($layouts[$layout_key])) {
      $layouts[$layout_key] = FALSE;
    }
  }

  return $layouts[$layout_key];
}

Kristof De Jaeger's avatar
Kristof De Jaeger committed
 *
 * @param $entity_type
 *   The name of the entity.
 * @param $cache
 *   Whether we need to get the fields from cache or not.
 * @return
 *   Collection of fields.
 */
function ds_get_fields($entity_type, $cache = TRUE) {
Bram Goffings's avatar
Bram Goffings committed
  static $static_fields;
  if (!isset($static_fields[$entity_type])) {
    foreach (\Drupal::service('plugin.manager.ds')->getDefinitions() as $plugin_id => $plugin) {
Bram Goffings's avatar
Bram Goffings committed
      // Needed to get derivatives working
      $plugin['plugin_id'] = $plugin_id;
      $static_fields[$plugin['entity_type']][$plugin_id] = $plugin;
Bram Goffings's avatar
Bram Goffings committed
  return isset($static_fields[$entity_type]) ? $static_fields[$entity_type] : array();
 * Get the field settings.
 *
 * @param $entity_type
 *   The name of the entity.
 * @param $bundle
 *   The name of bundle (ie, page or story for node types, profile for users)
 *   The name of view mode.
function ds_get_field_settings($entity_type, $bundle, $view_mode, $default = TRUE) {
  static $field_settings = NULL;
  if (!isset($field_settings)) {
    if ($cache = cache()->get('ds_field_settings')) {
      $field_settings = $cache->data;
Kristof De Jaeger's avatar
Kristof De Jaeger committed
      $ds_field_settings = config_get_storage_names_with_prefix('ds.field_settings');
      foreach ($ds_field_settings as $config) {
        $field_setting = Drupal::config($config)->get();
Kristof De Jaeger's avatar
Kristof De Jaeger committed
        if (!isset($field_setting['settings'])) {
Kristof De Jaeger's avatar
Kristof De Jaeger committed
        foreach ($field_setting['settings'] as $field => $settings) {
          $field_settings[$field_setting['entity_type']][$field_setting['bundle']][$field_setting['view_mode']][$field] = $settings;
Bram Goffings's avatar
Bram Goffings committed
      cache()->set('ds_field_settings', $field_settings, CacheBackendInterface::CACHE_PERMANENT, array('ds_fields_info' => TRUE));
Bram Goffings's avatar
Bram Goffings committed
  return (isset($field_settings[$entity_type][$bundle][$view_mode])) ? $field_settings[$entity_type][$bundle][$view_mode] : (isset($field_settings[$entity_type][$bundle]['default']) && $default ? $field_settings[$entity_type][$bundle]['default'] : array());
/**
 * Get the value for a Display Suite field.
 *
 * @param $key
 *   The key of the field.
 * @param $field
 *   The configuration of a DS field.
 * @param $entity
 *   The current entity.
 * @param $view_mode
 *   The name of the view mode.
 * @param $build
 *   The current built of the entity.
 * @return $markup
 *   The markup of the field used for output.
 */
function ds_get_field_value($key, $field, $entity, $view_mode, $build = array()) {
  $field['entity'] = $entity;
  $field['entity_type'] = $entity->entityType();
  $field['bundle'] = $entity->bundle();
  $field['build'] = $build;
  $field_instance = drupal_container()->get('plugin.manager.ds')->createInstance($field['plugin_id']);

  // Special case preprocess fields as they don't need to be rendered
  if ($field_instance InstanceOf PreprocessPluginBase) {
    $entity->preprocess_fields[] = $key;
  }
  return $field_instance->render($field);
 * Implements hook_entity_view_alter().
function ds_entity_view_alter(&$build, EntityInterface $entity, EntityDisplay $display) {
  static $loaded_css = array();
Kristof De Jaeger's avatar
Kristof De Jaeger committed
  static $field_permissions = FALSE;
  static $loaded = FALSE;

  if (!$loaded) {
    $loaded = TRUE;
    $field_permissions = Drupal::config('ds.extras')->get('field_permissions');
Kristof De Jaeger's avatar
Kristof De Jaeger committed
  }
  // Global kill switch. In some edge cases, a view might
  // be inserted into the view of an entity, in which the
  // same entity is available as well. This is simply not
  // possible, so you can temporarily disable DS completely
  // by setting this variable, either from code or via
  // the UI through admin/structure/ds/
  if (ds_kill_switch()) {
  $entity_type = $entity->entityType();
  $bundle = $entity->bundle();
  $view_mode = $display->mode;
  // If no layout is configured, stop as well.
  if (!ds_get_layout($entity_type, $bundle, $view_mode)) {
  $layout = ds_get_layout($entity_type, $bundle, $view_mode);
  // Put #entity_type, #bundle and #layout on the build so we can
  // access it in ds_entity_variables().
  $build['#entity_type'] = $entity_type;
  $build['#bundle'] = $bundle;
  $build['#ds_layout'] = $layout;

  // Check on field/delta limit.
  if (isset($layout['settings']['limit'])) {
    foreach ($layout['settings']['limit'] as $field => $limit) {
      if (isset($build[$field])) {
        if ($limit === 'delta' && isset($entity->ds_delta) && isset($entity->ds_delta[$field])) {
          $delta = $entity->ds_delta[$field];
          foreach ($build[$field]['#items'] as $key => $item) {
            if ($key != $delta) {
              unset($build[$field][$key]);
            }
          }
        }
        else {
          $count = count($build[$field]['#items']);
          if ($count > $limit) {
            $build[$field]['#items'] = array_slice($build[$field]['#items'], 0, $limit);
          }
Kristof De Jaeger's avatar
Kristof De Jaeger committed
  // Add Display Suite fields.
  $fields = ds_get_fields($entity_type);
  $field_values = ds_get_field_settings($entity_type, $bundle, $view_mode);
Jochen Stals's avatar
Jochen Stals committed

Kristof De Jaeger's avatar
Kristof De Jaeger committed
    // Ignore if this field is not a DS field.
    if (!isset($fields[$key])) {
      continue;
    }

    if (isset($field_values[$key]['formatter'])) {
      $field['formatter'] = $field_values[$key]['formatter'];
    if (isset($field_values[$key]['plugin_settings'])) {
      $field['plugin_settings'] = $field_values[$key]['plugin_settings'];
    $field_value = ds_get_field_value($key, $field, $entity, $view_mode, $build);
    // Title label.
    if ($key == 'title' && $entity_type == 'node') {
      $node_type = node_type_load($entity->bundle());
      $field['title'] = function_exists('i18n_node_translate_type') ? i18n_node_translate_type($entity->type, 'title_label', $node_type->title_label) : $node_type->title_label;
    if (!empty($field_value) || (string) $field_value === '0') {
      $build[$key] = array(
        '#theme' => 'field',
        '#field_type' => 'ds',
        '#title' => $field['title'],
        '#weight' => isset($field_values[$key]['weight']) ? $field_values[$key]['weight'] : 0,
        '#label_display' => isset($field_values[$key]['label']) ? $field_values[$key]['label'] : 'inline',
        '#field_name' => $key,
        '#bundle' => $bundle,
        '#language' => $entity->langcode,
        '#object' => $entity,
        '#entity_type' => $entity_type,
        '#view_mode' => $view_mode,
        '#access' => ($field_permissions && function_exists('ds_extras_ds_field_access')) ? ds_extras_ds_field_access($key, $entity_type) : TRUE,
        '#items' => array(
Kristof De Jaeger's avatar
Kristof De Jaeger committed
          0 => array(
            'value' => $field_value,
Kristof De Jaeger's avatar
Kristof De Jaeger committed
          ),
        ),
        0 => array(
          '#markup' => $field_value,
        ),
      );
Kristof De Jaeger's avatar
Kristof De Jaeger committed
  // Add css file for this layout.
  if (isset($layout['css']) && !isset($loaded_css[$layout['path'] . '/' . $layout['layout'] . '.css'])) {
Kristof De Jaeger's avatar
Kristof De Jaeger committed
    // Register.
Kristof De Jaeger's avatar
Kristof De Jaeger committed
    $loaded_css[$layout['path'] . '/' . $layout['layout'] . '.css'] = TRUE;
Kristof De Jaeger's avatar
Kristof De Jaeger committed
    // Add to #attached.
    $build['#attached']['css'][] = $layout['path'] . '/' . $layout['layout'] . '.css';
 * Process layout.
 * This function is added in ds_theme_registry_alter().
function ds_preprocess_ds_layout(&$vars) {

  // Add extra metadata for contextual links.
  if (!empty($vars['title_suffix']['contextual_links']['#id']) && !empty($vars['elements']['#node'])) {
    $vars['title_suffix']['contextual_links']['#id'] .= $vars['elements']['#node']->bundle() . '-' . $vars['view_mode'];
  }

  if (isset($vars['elements']['#ds_layout'])) {
    $layout = $vars['elements']['#ds_layout'];
    $render_container = 'content';

Kristof De Jaeger's avatar
Kristof De Jaeger committed
    // Move any preprocess fields to render container.
    // Inconsitency in taxonomy term naming.
    $object = $vars['elements']['#entity_type'];
    if ($vars['elements']['#entity_type'] == 'taxonomy_term') {
      $object = 'term';
    }
    if (isset($vars[$object]->preprocess_fields)) {
      foreach ($vars[$object]->preprocess_fields as $field) {

        // Process RDF if the module is enabled before moving preprocess fields.
        if (Drupal::moduleHandler()->moduleExists('rdf')) {
          rdf_process($vars, $field);
          // Remove it so we can unset the field later on.
          unset($vars['rdf_template_variable_attributes_array'][$field]);
        // Move the field to content so it renders, remove it
        // because we don't need it anymore.
        if (isset($vars[$field])) {
          $vars[$render_container][$field] = array('#markup' => $vars[$field]);
          if (!isset($vars['preprocess_keep'])) {
            unset($vars[$field]);
          }
Nils Destoop's avatar
Nils Destoop committed
        }
    // Template layout.
    if (!isset($vars['attributes']['class'])) {
      $vars['attributes']['class'] = array();
Kristof De Jaeger's avatar
Kristof De Jaeger committed
    }
Kristof De Jaeger's avatar
Kristof De Jaeger committed

    // Add view-mode-{name} to classes.
    if (!in_array('view-mode-' . $vars['elements']['#view_mode'], $vars['attributes']['class'])) {
      $vars['attributes']['class'][] = 'view-mode-' . $vars['elements']['#view_mode'];
    $vars['theme_hook_suggestions'][] = $layout['layout'];
    $vars['theme_hook_suggestions'][] = $layout['layout'] . '__' . $vars['elements']['#entity_type'];
Kristof De Jaeger's avatar
Kristof De Jaeger committed
    $vars['theme_hook_suggestions'][] = $layout['layout'] . '__' . $vars['elements']['#entity_type'] . '_' . $vars['elements']['#view_mode'];
    $vars['theme_hook_suggestions'][] = $layout['layout'] . '__' . $vars['elements']['#entity_type'] . '_' . $vars['elements']['#bundle'];
    $vars['theme_hook_suggestions'][] = $layout['layout'] . '__' . $vars['elements']['#entity_type'] . '_' . $vars['elements']['#bundle'] . '_' . $vars['elements']['#view_mode'];

Kristof De Jaeger's avatar
Kristof De Jaeger committed
    // If the layout has wrapper class lets add it.
    if (isset($layout['settings']['classes']['layout_class'])) {
      foreach ($layout['settings']['classes']['layout_class'] as $layout_class) {
        $vars['attributes']['class'][] = $layout_class;
    // Create region variables based on the layout settings.
    foreach ($layout['regions'] as $region_name => $region) {
      // Create the region content.
      $layout_render_array[$region_name] = array();
      if (isset($layout['settings']['regions'][$region_name])) {
        foreach ($layout['settings']['regions'][$region_name] as $key => $field) {
          // Make sure the field exists.
          if (!isset($vars[$render_container][$field])) {
            continue;
          }
          if (!isset($vars[$render_container][$field]['#weight'])) {
            $vars[$render_container][$field]['#weight'] = $key;
          }
          $layout_render_array[$region_name][$key] = $vars[$render_container][$field];
      // Add extras classes to the region.
      if (empty($layout['flexible'])) {
        $vars[$region_name . '_classes'] = !empty($layout['settings']['classes'][$region_name]) ? ' ' . implode(' ', $layout['settings']['classes'][$region_name]) : '';
      // Add a wrapper to the region.
      if (empty($layout['flexible'])) {
        $vars[$region_name . '_wrapper'] = isset($layout['settings']['wrappers'][$region_name]) ? $layout['settings']['wrappers'][$region_name] : 'div';
      }
    // Let other modules know we have rendered.
    $vars['rendered_by_ds'] = TRUE;

    $vars['layout_wrapper'] = isset($layout['settings']['layout_wrapper']) ? $layout['settings']['layout_wrapper'] : 'div';
    // Add custom attributes if any.
    if (!empty($layout['settings']['layout_attributes'])) {
      $layout_attributes = explode(',', $layout['settings']['layout_attributes']);
      foreach ($layout_attributes as $layout_attribute) {
        list($key, $attribute_value) = explode('|', $layout_attribute);
        $vars['attributes'][$key] = $attribute_value;
    // Add an onclick attribute on the wrapper.
    if (!empty($layout['settings']['layout_link_attribute'])) {
      $url = '';
      switch ($layout['settings']['layout_link_attribute']) {
        case 'content':
          $uri = $vars[$object]->uri();
          $url = $uri['path'];
          break;
        case 'custom':
          $url = $layout['settings']['layout_link_custom'];
          break;
        case 'tokens':
          $url = token_replace($layout['settings']['layout_link_custom'], array($vars['elements']['#entity_type'] => $vars[$object]));
          break;
      }

      if (!empty($url)) {
        $vars['attributes']['onclick'] = 'location.href=\'' . url($url) . '\'';
      }
    }
    // Let other modules alter the ds array before creating the region variables.
Kristof De Jaeger's avatar
Kristof De Jaeger committed
    $context = array('entity' => isset($vars[$object]) ? $vars[$object] : (isset($vars['elements']['#' . $object]) ? $vars['elements']['#' . $object] : ''), 'entity_type' => $vars['elements']['#entity_type'], 'bundle' => $vars['elements']['#bundle'], 'view_mode' => $vars['elements']['#view_mode']);
    drupal_alter('ds_pre_render', $layout_render_array, $context);
    foreach ($layout_render_array as $region_name => $content) {
      $vars[$region_name] = drupal_render($content);
/**
 * Implements hook_contextual_links_view_alter().
 */
function ds_contextual_links_view_alter(&$element, $items) {
  // @todo how can we make this also work for every entity.
  if (isset($element['#contextual_links']['node']) && Drupal::moduleHandler()->moduleExists('field_ui') && Drupal::currentUser()->hasPermission('administer node display')) {
    $entity_type = 'node';
    list($bundle, $view_mode) = explode('-', key($element['#contextual_links']['node'][2]));

    // Get the manage display URI.
    $admin_path = Drupal::entityManager()->getAdminPath($entity_type, $bundle);

    // Check view mode settings.
    $view_mode_settings = field_view_mode_settings($entity_type, $bundle);
    $overridden = (!empty($view_mode_settings[$view_mode]['status']) ? TRUE : FALSE);
      $admin_path .= '/display';
    }
    else {
      $admin_path .= '/display/' . $view_mode;
    }

    $element['#links']['manage-display'] = array(
      'title' => t('Manage display'),
      'href' => $admin_path,
      'query' => drupal_get_destination(),
    );
  }
/**
 * Implements hook_form_FORM_ID_alter().
 */
function ds_form_field_ui_field_edit_form_alter(&$form, &$form_state, $form_id) {
  $theme_functions = Drupal::moduleHandler()->invokeAll('ds_field_theme_functions_info');
  $form['instance']['ds_field_template'] = array(
    '#type' => 'select',
    '#title' => t('Field Template'),
    '#default_value' => isset($form['#instance']['ds_field_template']) ? $form['#instance']['ds_field_template'] : '',
    '#options' => $theme_functions,
    '#description' => t('Choose the default HTML for this field.'),
    '#empty_option' => t('Use sitewide default'),
  );
}

/**
 * Implements hook_ds_field_theme_functions_info().
 */
function ds_ds_field_theme_functions_info() {
  return array(
    'theme_field' => t('Drupal default'),
    'theme_ds_field_reset' => t('Full Reset'),
    'theme_ds_field_minimal' => t('Minimal'),
    'theme_ds_field_expert' => t('Expert'),
  );
}

/**
 * Reset all HTML for the field.
 */
function theme_ds_field_reset($variables) {
  $output = '';

  // Render the label.
  if (!$variables['label_hidden']) {
    $output .= '<div class="label-' . $variables['element']['#label_display'] . '">' . $variables['label'];
    if (!Drupal::config('ds.settings')->get('ft-kill-colon')) {
      $output .= ':&nbsp;';
    }
    $output .= '</div>';
  }

  // Render the items.
  foreach ($variables['items'] as $delta => $item) {
    $output .= drupal_render($item);
  }

  return $output;
}

/**
 * Provide minimal HTML for the field.
 */
function theme_ds_field_minimal($variables) {
  $output = '';
  $config = $variables['ds-config'];
  $classes = isset($config['classes']) ? ' ' . $config['classes'] : '';

  // Add a simple wrapper.
  $output .= '<div class="field field-name-' . strtr($variables['element']['#field_name'], '_', '-') . $classes . '">';

  // Render the label.
  if (!$variables['label_hidden']) {
    $output .= '<div class="label-' . $variables['element']['#label_display'] . '">' . $variables['label'];
    if (!isset($config['lb-col'])) {
      $output .= ':&nbsp;';
    }
    $output .= '</div>';
  }

  // Render the items.
  foreach ($variables['items'] as $delta => $item) {
    $output .= drupal_render($item);
  }
  $output .="</div>";

  return $output;
}

/**
 * Custom output all HTML for the field.
 */
function theme_ds_field_expert($variables) {
  $output = '';

  $config = $variables['ds-config'];

  // Render the label if it's not hidden.
  if (!$variables['label_hidden']) {
    $label_wrapper = isset($config['lb-el']) ? $config['lb-el'] : 'div';
    $class = array('label-' . $variables['element']['#label_display']);
    if (!empty($config['lb-cl'])) $class[] = $config['lb-cl'];
    $class = !empty($class) ? ' class="' . implode(' ', $class) . '"' : '';
    $attributes = array();
    if (!empty($config['lb-at'])) $attributes[] = $config['lb-at'];
    if (!empty($config['lb-def-at'])) $attributes[] = $variables['title_attributes'];
    $attributes = (!empty($attributes)) ? ' ' . implode(' ', $attributes) : '';
    $output .= '<' . $label_wrapper . $class . $attributes . '>' . $variables['label'];
    if (empty($config['lb-col'])) $output .= ':&nbsp;';
    $output .= '</' . $label_wrapper . '>';
  }

  // Field items wrapper
  if (!empty($config['fis'])) {
    $class = (!empty($config['fis-cl'])) ? ' class="' . $config['fis-cl'] . '"' : '';
    $attributes = array();
    if (!empty($config['fis-at'])) $attributes[] = $config['fis-at'];
    if (!empty($config['fis-def-at'])) $attributes[] = $variables['content_attributes'];
    $attributes = (!empty($attributes)) ? ' ' . implode(' ', $attributes) : '';
    $output .= '<' . $config['fis-el'] . $class . $attributes . '>';
  }

  // Render items.
  foreach ($variables['items'] as $delta => $item) {
    // Field item wrapper.
    if (!empty($config['fi'])) {
      $class = array();
      if (!empty($config['fi-odd-even'])) {
        $class[] = $delta % 2 ? 'odd' : 'even';
      }
      if (!empty($config['fi-cl'])) {
        $class[] = $config['fi-cl'];
      }
      $class = !empty($class) ? ' class="'. implode(' ', $class)  .'"' : '';
      $attributes = array();
      if (!empty($config['fi-at'])) {
        $attributes[] = token_replace($config['fi-at'], array($variables['element']['#entity_type'] => $variables['element']['#object']), array('clear' => TRUE));
      }
      if (!empty($config['fi-def-at'])) {
        $attributes[] = $variables['item_attributes'][$delta];
      }
      $attributes = (!empty($attributes)) ? ' ' . implode(' ', $attributes) : '';
      $output .= '<' . $config['fi-el'] . $class . $attributes .'>';
    }

    // Render field content.
    $output .= drupal_render($item);

    // Closing field item wrapper.
    if (!empty($config['fi'])) {
      $output .= '</' . $config['fi-el'] . '>';
    }
  }

  // Closing field items wrapper.
  if (!empty($config['fis'])) {
    $output .= '</' . $config['fis-el'] . '>';
  }

  // Outer wrapper.
  if (isset($config['ow'])) {
    $class = array();
    if (!empty($config['ow-cl'])) $class[] = $config['ow-cl'];
    if (!empty($config['ow-def-cl'])) $class[] = $variables['attributes']['class'];
    $class = (!empty($class)) ? ' class="' . implode(' ', $class) . '"' : '';
    $attributes = array();
    if (!empty($config['ow-at'])) $attributes[] = $config['ow-at'];
    if (!empty($config['ow-def-at'])) $attributes[] = $variables['attributes'];
    $attributes = (!empty($attributes)) ? ' ' . implode(' ', $attributes) : '';
    $output = '<' . $config['ow-el'] . $class . $attributes . '>' . $output . '</' . $config['ow-el'] . '>';
  }

  return $output;
}

/**
 * Implements hook_ds_field_settings_alter().
 */
function ds_ds_field_settings_alter(&$field_settings, $form, &$form_state) {

  $fields = $form_state['values']['fields'];
  $default_field_function = Drupal::config('ds.settings')->get('ft-default');

  $wrappers = array(
    'ow' => t('Wrapper'),
    'fis' => t('Field items'),
    'fi' => t('Field item')
  );

  foreach ($fields as $key => $field) {

    // Make sure we need to save anything for this field.
    if (_ds_field_valid($key, $field, $form_state)) {
      continue;
    }

    // Get the values.
    $values = isset($form_state['plugin_settings'][$key]['ft']) ? $form_state['plugin_settings'][$key]['ft'] : array();
    if (empty($values)) {
      continue;
    }
    $field_settings[$key]['plugin_settings']['ft'] = array();

    // Theme output function.
    $function = isset($values['func']) ? $values['func'] : $default_field_function;
    if ($function != $default_field_function) {
      $field_settings[$key]['plugin_settings']['ft']['func'] = $function;
    }

    // Field classes.
    if ($function != 'theme_ds_field_expert' && $function != 'theme_ds_field_reset' && isset($values['classes'])) {
      $classes = is_array($values['classes']) ? implode(' ', $values['classes']) : $values['classes'];
      if (!empty($classes)) {
        $field_settings[$key]['plugin_settings']['ft']['classes'] = $classes;
      }
    }

    // Label.
    if (isset($form_state['values']['fields'][$key]['label']) && $form_state['values']['fields'][$key]['label'] != 'hidden') {
      if (!empty($values['lb'])) {
        $field_settings[$key]['plugin_settings']['ft']['lb'] = $values['lb'];
      }
      if (!(empty($values['lb-el'])) && $function == 'theme_ds_field_expert') {
        $field_settings[$key]['plugin_settings']['ft']['lb-el'] = check_plain($values['lb-el']);
      }
      if (!(empty($values['lb-cl'])) && $function == 'theme_ds_field_expert') {
        $field_settings[$key]['plugin_settings']['ft']['lb-cl'] = check_plain($values['lb-cl']);
      }
      if (!(empty($values['lb-at'])) && $function == 'theme_ds_field_expert') {
       $field_settings[$key]['plugin_settings']['ft']['lb-at'] = filter_xss($values['lb-at']);
      }
      if (!(empty($values['lb-def-at'])) && $function == 'theme_ds_field_expert') {
       $field_settings[$key]['plugin_settings']['ft']['lb-def-at'] = TRUE;
      }
      if (!(empty($values['lb-col']))) {
        $field_settings[$key]['plugin_settings']['ft']['lb-col'] = TRUE;
      }
    }

    // Custom field configuration.
    if ($function == 'theme_ds_field_expert') {
      foreach ($wrappers as $wrapper_key => $title) {
        if (!empty($values[$wrapper_key])) {
          // Enable.
          $field_settings[$key]['plugin_settings']['ft'][$wrapper_key] = TRUE;
          // Element.
          $field_settings[$key]['plugin_settings']['ft'][$wrapper_key . '-el'] = !(empty($values[$wrapper_key . '-el'])) ? check_plain($values[$wrapper_key . '-el']) : 'div';
          // Classes.
          $field_settings[$key]['plugin_settings']['ft'][$wrapper_key . '-cl'] = !(empty($values[$wrapper_key . '-cl'])) ? check_plain($values[$wrapper_key . '-cl']) : '';
          // Default Classes.
          if (in_array($wrapper_key, array('ow', 'lb'))) {
            $field_settings[$key]['plugin_settings']['ft'][$wrapper_key . '-def-cl'] = !(empty($values[$wrapper_key . '-def-cl'])) ? TRUE : FALSE;
          }
          // Attributes.
          $field_settings[$key]['plugin_settings']['ft'][$wrapper_key . '-at'] = !(empty($values[$wrapper_key . '-at'])) ? filter_xss($values[$wrapper_key . '-at']) : '';
          // Default attributes.
          $field_settings[$key]['plugin_settings']['ft'][$wrapper_key . '-def-at'] = !(empty($values[$wrapper_key . '-def-at'])) ? TRUE : FALSE;
          // Odd even class.
          if ($wrapper_key == 'fi') {
            $field_settings[$key]['plugin_settings']['ft'][$wrapper_key . '-odd-even'] = !(empty($values[$wrapper_key . '-odd-even'])) ? TRUE : FALSE;
          }
        }
      }
    }
  }
}

/**
 * Implements hook_preprocess_field().
 */
function ds_preprocess_field(&$variables) {
  // We need to be sure this field is in a layout which is rendered by DS.
  if (!ds_get_layout($variables['element']['#entity_type'], $variables['element']['#bundle'], $variables['element']['#view_mode'])) {
    return;
  }

  $entity_type = $variables['element']['#entity_type'];
  $bundle = $variables['element']['#bundle'];
  $view_mode = $variables['element']['#view_mode'];

  $config = array();
  static $field_settings = array();
  if (!isset($field_settings[$entity_type][$bundle][$view_mode])) {
    $layout = ds_get_layout($entity_type, $bundle, $view_mode);
    $field_settings[$entity_type][$bundle][$view_mode] = ds_get_field_settings($entity_type, $bundle, $view_mode);
  }

  // Get the field name and field instance info - if available.
  $field_name = $variables['element']['#field_name'];
  $field_instance_info = field_info_instance($entity_type, $field_name, $bundle);

  // Check if this field has custom output settings.
  $variables['ds-config'] = array();
  if (isset($field_settings[$entity_type][$bundle][$view_mode][$field_name]['plugin_settings']['ft'])) {
    $config = $field_settings[$entity_type][$bundle][$view_mode][$field_name]['plugin_settings']['ft'];