You are here

ds.module in Display Suite 8.2

Display Suite core functions.

File

ds.module
View source
<?php

/**
 * @file
 * Display Suite core functions.
 */
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\Xss;
use Drupal\Core\Entity\Display\EntityDisplayInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Field\FieldConfigInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FormatterInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Template\Attribute;
use Drupal\Core\Url;
use Drupal\ds\Ds;
use Drupal\ds\DsAttribute;
use Drupal\field\Entity\FieldConfig;
use Drupal\layout_plugin\Layout;

/**
 * Implements hook_help().
 */
function ds_help($route_name, RouteMatchInterface $route_match) {
  switch ($route_name) {
    case 'help.page.ds':
      $output = '';
      $output .= '<h3>' . t('About') . '</h3>';
      $output .= '<br/>' . t('The <a href=":link">Display Suite</a> module allows you to take full control over how your content is displayed using a drag and drop interface. Arrange your nodes, views, comments, user data etc. the way you want without having to work your way through dozens of template files.', [
        ':link' => 'https://www.drupal.org/project/ds',
      ]);
      $output .= '<br/>' . t('It allows you to apply theme templates to content type displays. It comes with predefined layouts such as "two columns", "three columns stacked", "three columns fluid" et cetera, but also lets you define your own.');
      $output .= '<br/>' . t('Display Suite allows you to create fields from tokens or blocks.  This gives you full control over the way content is displayed without having to maintain dozens of twig files.');
      $output .= '<br/>' . t('More documentation about Display Suite in Drupal 8 can be found in Drupal\'s <a href=":documentation">Community Documentation </a>.', [
        ':documentation' => 'https://www.drupal.org/node/2718943',
      ]);
      return $output;
  }
}

/**
 * Implements hook_theme().
 */
function ds_theme() {
  $theme_functions = array();
  $theme_functions['ds_entity_view'] = [
    'render element' => 'content',
  ];

  // Field templates.
  if (\Drupal::config('ds.settings')
    ->get('field_template')) {
    $field_layouts = \Drupal::service('plugin.manager.ds.field.layout')
      ->getDefinitions();
    foreach ($field_layouts as $key => $plugin) {
      if ($key != 'default') {
        $theme_functions['field__' . $plugin['theme']] = array(
          'render element' => 'elements',
          'template' => strtr($plugin['theme'], '_', '-'),
          'base hook' => 'field',
          'path' => drupal_get_path('module', $plugin['provider']) . '/templates',
        );
        if (!empty($plugin['path'])) {
          $theme_functions['field__' . $plugin['theme']]['file'] = $plugin['path'];
        }
      }
    }
  }
  return $theme_functions;
}

/**
 * Implements hook_theme_registry_alter().
 */
function ds_theme_registry_alter(&$theme_registry) {
  $layouts = Ds::getLayouts();
  $layout_theme_hooks = [];
  foreach ($layouts as $info) {
    if ($info['class'] == '\\Drupal\\ds\\Plugin\\DsLayout') {
      $layout_theme_hooks[] = $info['theme'];
    }
  }

  // Only add preprocess functions if entity exposes theme function, and this
  // layout is using the Display Suite layout class.
  foreach ($theme_registry as $theme_hook => $info) {
    if (in_array($theme_hook, $layout_theme_hooks) || !empty($info['base hook']) && in_array($info['base hook'], $layout_theme_hooks)) {
      $theme_registry[$theme_hook]['preprocess functions'][] = 'ds_preprocess_ds_layout';
    }
  }

  // Run field group preprocess before ds_entity_view.
  if (function_exists('field_group_build_entity_groups')) {
    array_unshift($theme_registry['ds_entity_view']['preprocess functions'], 'field_group_build_entity_groups');
  }

  // Remove ds_preprocess_field in case field templates is not enabled.
  if (!\Drupal::config('ds.settings')
    ->get('field_template')) {
    $key = array_search('ds_preprocess_field', $theme_registry['field']['preprocess functions']);
    if (!empty($key)) {
      unset($theme_registry['field']['preprocess functions'][$key]);
    }
  }
}

/**
 * Implements hook_form_alter().
 */
function ds_form_entity_view_display_edit_form_alter(&$form, FormStateInterface $form_state) {
  $form_state
    ->loadInclude('ds', 'inc', 'includes/field_ui');

  // Also load admin on behalf of DS extras when enabled.
  if (\Drupal::moduleHandler()
    ->moduleExists('ds_extras')) {
    $form_state
      ->loadInclude('ds_extras', 'inc', 'includes/admin');
  }
  ds_field_ui_fields_layouts($form, $form_state);
}

/**
 * Implements hook_module_implements_alter().
 */
function ds_module_implements_alter(&$implementations, $hook) {

  // node_entity_view_display_alter() disables all labels on all fields
  // when the view mode is 'search_index'. If you set display modes for
  // this view mode by hand, then the hook isn't needed. Since this
  // may be called dozens of times on some pages, it's worth disabling it.
  if ($hook == 'entity_view_display_alter') {
    unset($implementations['node']);
  }

  // Because it's possible to turn on/off field templates,
  // we'll unset hooks here so ds_preprocess_field() doesn't run.
  $ft_hooks = array(
    'form_ds_classes_form_alter',
    'form_field_ui_field_edit_form_alter',
  );
  if (!\Drupal::config('ds.settings')
    ->get('field_template') && in_array($hook, $ft_hooks)) {
    unset($implementations['ds']);
  }
}

/**
 * Implements hook_entity_view_alter().
 */
function ds_entity_view_alter(&$build, EntityInterface $entity, EntityDisplayInterface $display) {
  static $field_permissions = FALSE;
  static $loaded = FALSE;
  $entity_type = $entity
    ->getEntityTypeId();
  $bundle = $entity
    ->bundle();
  $view_mode = $display
    ->getMode();

  // Add extra metadata needed for contextual links.
  if (isset($build['#contextual_links'][$entity_type])) {
    $build['#contextual_links'][$entity_type]['metadata']['ds_bundle'] = $bundle;
    $build['#contextual_links'][$entity_type]['metadata']['ds_view_mode'] = $view_mode;
  }

  // If no layout is configured, stop executing.
  if (!$display
    ->getThirdPartySetting('ds', 'layout')) {
    return;
  }

  // If Display Suite is disabled, stop here.
  if (Ds::isDisabled()) {
    return;
  }

  // Load field permissions and layouts only once.
  if (!$loaded) {
    $loaded = TRUE;
    $field_permissions = \Drupal::config('ds_extras.settings')
      ->get('field_permissions');
  }

  // Get configuration.
  $configuration = $display
    ->getThirdPartySettings('ds');

  // 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_configuration'] = $configuration;
  $build['#entity'] = $entity;

  // Implement UI limit.
  $components = $display
    ->getComponents();
  foreach ($components as $field => $component) {
    if (isset($component['third_party_settings']['ds']) && !empty($component['third_party_settings']['ds']['ds_limit'])) {
      $limit = $component['third_party_settings']['ds']['ds_limit'];
      if (isset($build[$field]) && isset($build[$field]['#items'])) {
        if ($limit === 'delta' && isset($entity->ds_delta) && isset($entity->ds_delta[$field])) {
          $delta = $entity->ds_delta[$field];
          $filtered_elements = Element::children($build['field']);
          foreach ($filtered_elements as $filtered_element) {
            if ($filtered_element != $delta) {
              unset($build[$field][$filtered_element]);
            }
          }
        }
        else {
          $filtered_elements = Element::children($build[$field]);
          $filtered_elements = array_slice($filtered_elements, $limit);
          foreach ($filtered_elements as $filtered_element) {
            unset($build[$field][$filtered_element]);
          }
        }
      }
    }
  }

  // Add Display Suite fields.
  $fields = Ds::getFields($entity_type);
  $field_values = !empty($configuration['fields']) ? $configuration['fields'] : array();
  foreach ($configuration['regions'] as $region) {
    foreach ($region as $weight => $key) {

      // Ignore if this field is not a DS field, just pull it in from the
      // entity.
      if (!isset($fields[$key])) {
        continue;
      }
      $field = $fields[$key];
      if (isset($field_values[$key]['formatter'])) {
        $field['formatter'] = $field_values[$key]['formatter'];
      }
      if (isset($field_values[$key]['settings'])) {
        $field['settings'] = $field_values[$key]['settings'];
      }

      /* @var $field_instance \Drupal\ds\Plugin\DsField\DsFieldInterface */
      $field_instance = Ds::getFieldInstance($key, $field, $entity, $view_mode, $display, $build);
      $field_value = $field_instance
        ->build();
      $field_title = $field_instance
        ->getTitle();

      // Only allow non empty fields.
      if (!empty($field_value)) {
        $build[$key] = array(
          '#theme' => 'field',
          '#field_type' => 'ds',
          '#title' => $field_title,
          '#weight' => isset($field_values[$key]['weight']) ? $field_values[$key]['weight'] : $weight,
          '#label_display' => isset($field_values[$key]['label']) ? $field_values[$key]['label'] : 'inline',
          '#field_name' => $key,
          '#bundle' => $bundle,
          '#object' => $entity,
          '#entity_type' => $entity_type,
          '#view_mode' => '_custom',
          '#ds_view_mode' => $view_mode,
          '#items' => array(
            (object) array(
              '_attributes' => array(),
            ),
          ),
          '#is_multiple' => $field_instance
            ->isMultiple(),
          '#access' => $field_permissions && function_exists('ds_extras_ds_field_access') ? ds_extras_ds_field_access($key, $entity_type) : TRUE,
          '#formatter' => 'ds_field',
        );
        if ($field_instance
          ->isMultiple()) {
          $build[$key] += $field_value;
        }
        else {
          $build[$key][0] = array(
            $field_value,
          );
        }
      }
    }
  }

  // Defer to ds_entity_view theme hook to actually render the layout.
  $build['#theme'] = 'ds_entity_view';
}

/**
 * Process entity view.
 */
function template_preprocess_ds_entity_view(&$variables) {
  $build = $variables['content'];
  $configuration = $build['#ds_configuration'];

  // Process the render array so we can reuse it.
  // Don't add/override the cache key if there aren't any.
  if (!empty($build['#cache']['keys'])) {
    $build['#cache']['keys'][0] = 'ds_entity_view';
  }
  unset($build['#theme']);
  unset($build['#pre_render']);
  unset($build['#sorted']);
  unset($build['#children']);
  unset($build['#render_children']);
  unset($build['#prefix']);
  unset($build['#suffix']);
  unset($build['#attached']['html_head_link']);

  // Create region variables based on the layout settings.
  $use_field_names = \Drupal::config('ds.settings')
    ->get('use_field_names');
  $regions = [];
  foreach (array_keys($configuration['regions']) as $region_name) {
    $regions[$region_name] = array();

    // Create the region content.
    if (!empty($configuration['regions'][$region_name])) {
      foreach ($configuration['regions'][$region_name] as $key => $field) {

        // Make sure the field exists.
        if (!isset($build[$field])) {
          continue;
        }
        if (!isset($build[$field]['#weight'])) {
          $build[$field]['#weight'] = $key;
        }
        if ($use_field_names) {
          $regions[$region_name][$field] = $build[$field];
        }
        else {
          $regions[$region_name][$key] = $build[$field];
        }
      }
    }
  }
  $layout = Layout::layoutPluginManager()
    ->createInstance($configuration['layout']['id'], $configuration['layout']['settings']);
  $build = array_merge($build, $layout
    ->build($regions));

  // Disable CSS files when needed.
  if ($build['#ds_configuration']['layout']['disable_css']) {
    $library = $build['#ds_configuration']['layout']['library'];
    $attached = $build['#attached']['library'];
    $index = array_search($library, $attached);
    unset($build['#attached']['library'][$index]);
  }
  $variables['content'] = $build;
}

/**
 * Process layout.
 *
 * This function is added in ds_theme_registry_alter().
 */
function ds_preprocess_ds_layout(&$variables) {
  $layout_settings = $variables['settings'];

  // Fetch the entity type.
  $bundle = FALSE;
  $entity_type = FALSE;
  if (isset($variables['content']['#entity_type'])) {
    $entity_type = $variables['content']['#entity_type'];
  }
  if (isset($variables['content']['#bundle'])) {
    $bundle = $variables['content']['#bundle'];
  }

  // Template layout.
  if (!isset($variables['attributes']['class'])) {
    $variables['attributes']['class'] = array();
  }

  // If the layout has wrapper class lets add it.
  if (!empty($layout_settings['classes']['layout_class'])) {
    foreach ($layout_settings['classes']['layout_class'] as $layout_class) {
      $variables['attributes']['class'][] = $layout_class;
    }
  }

  // Create region variables based on the layout settings.
  foreach ($layout_settings['wrappers'] as $region_name => $wrapper) {

    // @todo remove from D9, This is deprecated
    $variables[$region_name . '_classes'] = !empty($layout_settings['classes'][$region_name]) ? ' ' . implode(' ', $layout_settings['classes'][$region_name]) : '';

    // The new way of doing stuff is creating an attributes object.
    if (!empty($layout_settings['classes'][$region_name])) {
      $variables[$region_name . '_attributes'] = new Attribute([
        'class' => $layout_settings['classes'][$region_name],
      ]);
    }
    else {
      $variables[$region_name . '_attributes'] = new Attribute();
    }
    $variables[$region_name . '_wrapper'] = !empty($layout_settings['wrappers'][$region_name]) ? $layout_settings['wrappers'][$region_name] : 'div';
  }

  // Add a layout wrapper.
  $variables['outer_wrapper'] = isset($layout_settings['outer_wrapper']) ? $layout_settings['outer_wrapper'] : 'div';

  // Add custom attributes if any.
  if (!empty($layout_settings['attributes'])) {
    $layout_attributes = explode(',', $layout_settings['attributes']);
    foreach ($layout_attributes as $layout_attribute) {
      $replaced_attribute = $layout_attribute;
      if (strpos($layout_attribute, '|') !== FALSE) {
        if (isset($entity_type)) {
          $replaced_attribute = \Drupal::service('token')
            ->replace($layout_attribute, array(
            $variables['content']['#entity_type'] => $variables['content']['#' . $entity_type],
          ), array(
            'clear' => TRUE,
          ));
        }
        list($key, $attribute_value) = explode('|', $replaced_attribute);
        $variables['attributes'][$key] = $attribute_value;
      }
    }
  }

  // Add an onclick attribute on the wrapper.
  if (!empty($layout_settings['link_attribute'])) {
    $url = '';
    switch ($layout_settings['link_attribute']) {
      case 'content':
        if ($entity_type) {
          $url = $variables['content']['#' . $entity_type]
            ->toUrl()
            ->getInternalPath();
        }
        break;
      case 'custom':
        $url = $layout_settings['link_custom'];
        break;
      case 'tokens':
        if ($entity_type) {
          $url = \Drupal::service('token')
            ->replace($layout_settings['link_custom'], array(
            $layout_settings => $variables['content']['#' . $entity_type],
          ), array(
            'clear' => TRUE,
          ));
        }
        break;
    }
    if (!empty($url)) {
      $url = Url::fromUri('internal:/' . $url);
      $variables['attributes']['onclick'] = 'location.href=\'' . $url
        ->toString() . '\'';
    }
  }
  if ($entity_type) {
    if (isset($variables['content']['#ds_configuration'])) {

      // Add theming-classes to template.
      $entity_classes = !empty($variables['content']['#ds_configuration']['layout']['entity_classes']) ? $variables['content']['#ds_configuration']['layout']['entity_classes'] : 'old_view_mode';
      if ($entity_classes != 'no_classes') {
        if ($entity_classes == 'all_classes') {
          $variables['attributes']['class'][] = Html::cleanCssIdentifier($entity_type);
          $variables['attributes']['class'][] = Html::cleanCssIdentifier($entity_type) . '--type-' . Html::cleanCssIdentifier($bundle);
          $variables['attributes']['class'][] = Html::cleanCssIdentifier($entity_type) . '--view-mode-' . Html::cleanCssIdentifier($variables['content']['#view_mode']);
        }
        elseif ($entity_classes == 'old_view_mode') {

          // Add (old style, non cleaned) view-mode-{name} to classes.
          if (!in_array('view-mode-' . $variables['content']['#view_mode'], $variables['attributes']['class'])) {
            $variables['attributes']['class'][] = 'view-mode-' . $variables['content']['#view_mode'];
          }
        }
      }

      // RDF support
      if ($entity_type == 'node' && function_exists('rdf_preprocess_node')) {
        $entity = isset($variables[$entity_type]) ? $variables[$entity_type] : (isset($variables['content']['#' . $entity_type]) ? $variables['content']['#' . $entity_type] : '');
        if ($entity) {
          $variables['node'] = $entity;

          // We disable the date feature for now as it throws notices
          $variables['display_submitted'] = FALSE;
          rdf_preprocess_node($variables);
        }
      }

      // Let other modules know we have rendered.
      $variables['rendered_by_ds'] = TRUE;

      // Let other modules alter the ds array before rendering.
      $context = array(
        'entity' => isset($variables[$entity_type]) ? $variables[$entity_type] : (isset($variables['content']['#' . $entity_type]) ? $variables['content']['#' . $entity_type] : ''),
        'entity_type' => $variables['content']['#entity_type'],
        'bundle' => $variables['content']['#bundle'],
        'view_mode' => $variables['content']['#view_mode'],
      );
      \Drupal::moduleHandler()
        ->alter('ds_pre_render', $variables['content'], $context, $variables);
    }
  }

  // Copy the regions from 'content' into the top-level.
  foreach (Element::children($variables['content']) as $name) {
    $variables[$name] = $variables['content'][$name];
  }

  // Copy entity to top level to improve theme experience.
  if (isset($variables['content']['#entity'])) {
    $variables[$variables['content']['#entity_type']] = $variables['content']['#entity'];
  }
}

/**
 * Implements hook_theme_suggestions_alter().
 */
function ds_theme_suggestions_alter(&$suggestions, $variables, $base_theme_hook) {
  if (isset($variables['content']) && is_array($variables['content']) && isset($variables['content']['#ds_configuration']) && $base_theme_hook != 'ds_entity_view') {
    $layout_id = $variables['content']['#ds_configuration']['layout']['id'];
    $entity_id = $variables['content']['#' . $variables['content']['#entity_type']]
      ->id();
    $suggestions[] = $layout_id . '__' . $variables['content']['#entity_type'];
    $suggestions[] = $layout_id . '__' . $variables['content']['#entity_type'] . '_' . $variables['content']['#view_mode'];
    $suggestions[] = $layout_id . '__' . $variables['content']['#entity_type'] . '_' . $variables['content']['#bundle'];
    $suggestions[] = $layout_id . '__' . $variables['content']['#entity_type'] . '_' . $variables['content']['#bundle'] . '_' . $variables['content']['#view_mode'];
    $suggestions[] = $layout_id . '__' . $variables['content']['#entity_type'] . '__' . $entity_id;
  }
}

/**
 * Implements hook_contextual_links_view_alter().
 */
function ds_contextual_links_view_alter(&$element, $items) {
  $def = \Drupal::service('entity.manager')
    ->getDefinitions();
  $entity_type = array_keys($element['#contextual_links'])[0];
  if (isset($def[$entity_type]) && $def[$entity_type]
    ->get('field_ui_base_route')) {
    if (!empty($entity_type) && \Drupal::moduleHandler()
      ->moduleExists('field_ui') && \Drupal::currentUser()
      ->hasPermission('administer node display')) {

      // This might not exist (especially in panels environments).
      if (!isset($element['#contextual_links'][$entity_type]['metadata']['ds_bundle'])) {
        return;
      }
      $bundle = $element['#contextual_links'][$entity_type]['metadata']['ds_bundle'];
      $view_mode = $element['#contextual_links'][$entity_type]['metadata']['ds_view_mode'];
      $route_name = "entity.entity_view_display.{$entity_type}.view_mode";
      $type = $def[$entity_type]
        ->getBundleEntityType();
      $route_params = array(
        $type => $bundle,
        'view_mode_name' => $view_mode,
      );
      $url = new Url($route_name, $route_params);
      $destination = \Drupal::destination()
        ->getAsArray();
      $url
        ->setOption('query', $destination);

      // When there is no bundle defined, return.
      if (!empty($bundle)) {
        $element['#links']['manage-display'] = array(
          'title' => t('Manage display'),
          'url' => $url,
        );
      }
    }
  }
}

/**
 * Implements hook_local_tasks_alter().
 */
function ds_local_tasks_alter(&$local_tasks) {
  if (!\Drupal::moduleHandler()
    ->moduleExists('contextual') || !\Drupal::moduleHandler()
    ->moduleExists('field_ui')) {
    unset($local_tasks['ds.manage_node_display']);
    unset($local_tasks['ds.manage_user_display']);
    unset($local_tasks['ds.manage_taxonomy_term_display']);
  }
}

/**
 * Implements hook_preprocess_field().
 */
function ds_preprocess_field(&$variables) {
  $entity_type = $variables['element']['#entity_type'];
  $bundle = $variables['element']['#bundle'];
  $view_mode = isset($variables['element']['#ds_view_mode']) ? $variables['element']['#ds_view_mode'] : $variables['element']['#view_mode'];

  /* @var $entity_display EntityDisplayInterface */
  $entity_display = Ds::getDisplay($entity_type, $bundle, $view_mode);
  if ($entity_display && $entity_display
    ->getThirdPartySetting('ds', 'layout')) {

    // Get the field name and field instance info - if available.
    $field_name = $variables['element']['#field_name'];
    $config = array();
    static $field_settings = array();
    if (!isset($field_settings[$entity_type][$bundle][$view_mode])) {
      $f = array();

      // Get third party settings for Core fields.
      foreach ($entity_display
        ->getComponents() as $key => $info) {
        if (!empty($info['third_party_settings']['ds']['ft'])) {
          $f[$key]['ft'] = $info['third_party_settings']['ds']['ft'];
        }
      }

      // Get third party settings for Display Suite fields.
      $ds_fields_third_party_settings = $entity_display
        ->getThirdPartySetting('ds', 'fields');
      if ($ds_fields_third_party_settings) {
        $f += $entity_display
          ->getThirdPartySetting('ds', 'fields');
      }
      $field_settings[$entity_type][$bundle][$view_mode] = $f;
    }

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

      // When dealing with a field template we need to massage to values before
      // printing to prevent layout issues.
      if (isset($config['id']) && $config['id'] != 'default' && !empty($variables['ds-config']['settings'])) {

        /* @var \Drupal\ds\Plugin\DsFieldTemplate\DsFieldTemplateInterface $layout_instance */
        $layout_instance = \Drupal::service('plugin.manager.ds.field.layout')
          ->createInstance($config['id']);
        if (isset($variables['element']['#object'])) {
          $layout_instance
            ->setEntity($variables['element']['#object']);
        }
        $layout_instance
          ->massageRenderValues($variables['ds-config']['settings'], $config['settings']);
      }
    }

    // CSS classes.
    if (isset($config['settings']['classes'])) {
      foreach ($config['settings']['classes'] as $class_name) {
        if (isset($variables['element']['#object'])) {
          $class_name = \Drupal::token()
            ->replace($class_name, array(
            $entity_type => $variables['element']['#object'],
          ), array(
            'clear' => TRUE,
          ));
        }
        $variables['attributes']['class'][] = $class_name;
      }
    }

    // Alter the label if configured.
    if (!$variables['label_hidden']) {
      if (!empty($config['settings']['lb'])) {
        $variables['label'] = t(Html::escape($config['settings']['lb']));
      }
    }
  }
}

/**
 * Implements hook_theme_suggestions_HOOK_alter().
 *
 * The suggestion alters for for field templates.
 */
function ds_theme_suggestions_field_alter(&$suggestions, $variables) {
  $entity_type = $variables['element']['#entity_type'];
  $bundle = $variables['element']['#bundle'];
  $view_mode = isset($variables['element']['#ds_view_mode']) ? $variables['element']['#ds_view_mode'] : $variables['element']['#view_mode'];

  /* @var $entity_display EntityDisplayInterface */
  $entity_display = Ds::getDisplay($entity_type, $bundle, $view_mode);
  if ($entity_display && $entity_display
    ->getThirdPartySetting('ds', 'layout')) {

    // Get the field name and field instance info - if available.
    $field_name = $variables['element']['#field_name'];
    $field_theme_function = \Drupal::config('ds.settings')
      ->get('ft-default');
    static $field_settings = array();
    if (!isset($field_settings[$entity_type][$bundle][$view_mode])) {
      $f = array();

      // Get third party settings for Core fields.
      foreach ($entity_display
        ->getComponents() as $key => $info) {
        if (!empty($info['third_party_settings']['ds']['ft'])) {
          $f[$key]['ft'] = $info['third_party_settings']['ds']['ft'];
        }
      }

      // Get third party settings for Display Suite fields.
      $ds_fields_third_party_settings = $entity_display
        ->getThirdPartySetting('ds', 'fields');
      if ($ds_fields_third_party_settings) {
        $f += $entity_display
          ->getThirdPartySetting('ds', 'fields');
      }
      $field_settings[$entity_type][$bundle][$view_mode] = $f;
    }
    $field = FieldConfig::loadByName($entity_type, $bundle, $field_name);

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

    // Initialize suggestion name.
    $suggestion = '';

    // Determine the field template. In case it's something different.
    if (isset($config['id']) && $config['id'] != 'default') {
      $layout_instance = \Drupal::service('plugin.manager.ds.field.layout')
        ->createInstance($config['id']);

      // Either it uses the function.
      $suggestions[] = 'field__' . $layout_instance
        ->getThemeFunction();

      // Or the template file(s).
      $suggestion = 'field__' . $config['id'];
    }
    elseif ($field instanceof FieldConfigInterface && ($theme_function = $field
      ->getThirdPartySetting('ds', 'ds_field_template', '')) && !empty($theme_function)) {

      // Either it uses the function.
      $suggestions[] = 'field__theme_ds_field_' . $theme_function;

      // Or the template file(s).
      $suggestion = 'field__' . $theme_function;
    }
    elseif (!empty($field_theme_function)) {
      $suggestions[] = 'field__theme_ds_field_' . $field_theme_function;

      // Or the template file(s).
      $suggestion = 'field__' . $field_theme_function;
    }
    if (!empty($suggestion)) {
      $suggestions[] = $suggestion;
      $suggestions[] = $suggestion . '__' . $field_name;
      $suggestions[] = $suggestion . '__' . $variables['element']['#bundle'];
      $suggestions[] = $suggestion . '__' . $field_name . '__' . $variables['element']['#bundle'];
      $suggestions[] = $suggestion . '__' . $variables['element']['#entity_type'] . '__' . $field_name;
      $suggestions[] = $suggestion . '__' . $variables['element']['#entity_type'] . '__' . $variables['element']['#bundle'];
      $suggestions[] = $suggestion . '__' . $variables['element']['#entity_type'] . '__' . $field_name . '__' . $variables['element']['#bundle'];
    }

    // Custom DS fields name may contain colon separators or dashes; replace it
    // with "__" or "_" to ensure suggestions are compatible with file names on
    // all systems.
    foreach ($suggestions as $key => $suggestion) {
      $suggestions[$key] = str_replace([
        ':',
        '-',
      ], [
        '__',
        '_',
      ], $suggestion);
    }
  }
}

/**
 * Field template settings form.
 */
function ds_field_template_settings_form(array &$form, FormStateInterface &$form_state, array $context) {
  $functions = Ds::getFieldLayoutOptions();
  $default_field_function = \Drupal::config('ds.settings')
    ->get('ft-default');
  if (empty($default_field_function)) {
    $default_field_function = 'default';
  }

  // @todo fix me - and rename 'instance' to field :)
  if (is_array($context['instance'])) {
    $key = $context['instance']['field_name'];
  }
  else {
    $key = $context['instance']
      ->getName();
  }

  // Plugin settings.
  $plugin_settings = $form_state
    ->get('plugin_settings');
  $field_settings = isset($plugin_settings[$key]['ft']) ? $plugin_settings[$key]['ft'] : array();

  // In case with an ajax refresh we fetch the function from a different place.
  $values = $form_state
    ->getValues();
  if (isset($values['fields'][$key]['settings_edit_form']['settings']['ft']['id'])) {
    $field_function = $values['fields'][$key]['settings_edit_form']['settings']['ft']['id'];
  }
  elseif (isset($values['fields'][$key]['settings_edit_form']['third_party_settings']['ds']['ft']['id'])) {
    $field_function = $values['fields'][$key]['settings_edit_form']['third_party_settings']['ds']['ft']['id'];
  }
  else {
    $field_function = isset($field_settings['id']) ? $field_settings['id'] : $default_field_function;
  }
  if (!isset($functions[$field_function])) {
    $field_function = $default_field_function;
  }
  $form['ft'] = array(
    '#weight' => 20,
  );
  $form['ft']['id'] = array(
    '#title' => t('Choose a Field Template'),
    '#type' => 'select',
    '#options' => $functions,
    '#default_value' => $field_function,
    '#submit' => array(
      'ds_field_ui_display_overview_multistep_submit',
    ),
    '#ajax' => array(
      'callback' => 'ds_field_ui_display_overview_multistep_js',
      'wrapper' => 'field-display-overview-wrapper',
    ),
  );

  // Create field layout plugin instance.
  $config = isset($field_settings['settings']) ? $field_settings['settings'] : array();
  $field_layout_instance = \Drupal::service('plugin.manager.ds.field.layout')
    ->createInstance($field_function, $config);

  // Alter the form to add specific field layout settings.
  $form['ft']['settings'] = array(
    '#type' => 'container',
    '#attributes' => array(
      'class' => array(
        'ft-settings',
      ),
    ),
  );
  $field_layout_instance
    ->alterForm($form['ft']['settings']);
}

/**
 * Ajax handler for changing conditions on the 'Manage display' screen.
 */
function ds_field_ui_display_overview_multistep_js($form, &$form_state) {
  return $form['fields'];
}

/**
 * Submission handler for condition changes in field_ui_display_overview_form().
 */
function ds_field_ui_display_overview_multistep_submit($form, &$form_state) {
  $form_state['rebuild'] = TRUE;
}

/**
 * Implements hook_field_formatter_third_party_settings_form().
 */
function ds_field_formatter_third_party_settings_form(FormatterInterface $plugin, FieldDefinitionInterface $field_definition, $view_mode, $form, FormStateInterface $form_state) {
  $element = array();
  $field_info = $field_definition
    ->getFieldStorageDefinition();
  if (!empty($field_info) && $field_info
    ->getCardinality() != 1) {
    $name = $field_info
      ->getName();
    $settings = $form_state
      ->get('plugin_settings');
    $element['ds_limit'] = array(
      '#type' => 'textfield',
      '#title' => t('UI limit'),
      '#size' => 2,
      '#description' => t("Enter a number to limit the number of items or 'delta' to print a specific delta (usually configured in views or found in entity->ds_delta). Leave empty to display them all. Note that depending on the formatter settings, this option might not always work."),
      '#default_value' => !empty($settings[$name]['ds_limit']) ? $settings[$name]['ds_limit'] : '',
    );
  }
  if (\Drupal::config('ds.settings')
    ->get('field_template')) {
    $context = array(
      'instance' => $field_definition,
      'formatter' => $plugin,
    );
    ds_field_template_settings_form($element, $form_state, $context);
  }
  return $element;
}

/**
 * Implements hook_field_formatter_settings_summary_alter().
 */
function ds_field_formatter_settings_summary_alter(&$summary, $context) {
  if (\Drupal::config('ds.settings')
    ->get('field_template')) {

    // Field template summary.
    $functions = Ds::getFieldLayoutOptions();
    $default_field_function = \Drupal::config('ds.settings')
      ->get('ft-default');
    $field = $context['field_definition'];
    if (isset($context['form_state'])) {

      /* @var $form_state FormStateInterface */
      $form_state = $context['form_state'];
      $plugin_settings = $form_state
        ->get('plugin_settings');
      $field_function = isset($plugin_settings[$field
        ->getName()]['ft']['id']) ? $plugin_settings[$field
        ->getName()]['ft']['id'] : $default_field_function;
    }
    else {
      $plugin_settings = $context['formatter']
        ->getThirdPartySetting('ds', 'ft');
      $field_function = isset($plugin_settings['id']) ? $plugin_settings['id'] : $default_field_function;
    }
    if (!isset($functions[$field_function])) {
      $field_function = $default_field_function;
    }
    $summary[] = 'Field template: ' . Html::escape($field_function);
  }
}

/**
 * Implements hook_hook_info().
 */
function ds_hook_info() {
  $hooks['ds_views_row_render_entity'] = array(
    'group' => 'ds',
  );
  $hooks['ds_views_row_render_entity_alter'] = array(
    'group' => 'ds',
  );
  $hooks['ds_views_view_mode_alter'] = array(
    'group' => 'ds',
  );
  return $hooks;
}

/**
 * Implements template_preprocess_ds_field_reset().
 */
function template_preprocess_field__ds_field_reset(&$variables) {
  $variables['show_colon'] = \Drupal::config('ds.settings')
    ->get('ft-show-colon');
}

/**
 * Implements template_preprocess_ds_field_minimal().
 */
function template_preprocess_field__ds_field_minimal(&$variables) {
  $variables['settings'] = isset($variables['ds-config']['settings']) ? $variables['ds-config']['settings'] : [];
}

/**
 * Implements template_preprocess_ds_field_expert().
 */
function template_preprocess_field__ds_field_expert(&$variables) {
  $variables['settings'] = isset($variables['ds-config']['settings']) ? $variables['ds-config']['settings'] : [];
  $vars = array(
    'lbw' => 'label_attributes',
    'ow' => 'wrapper_attributes',
    'fis' => 'field_wrapper_attributes',
    'fi' => 'field_item_wrapper_attributes',
  );
  foreach ($vars as $key => $variable_name) {
    $var_attributes = array();

    // Add classes.
    if (!empty($variables['settings'][$key . '-cl'])) {
      $var_attributes['class'] = explode(' ', $variables['settings'][$key . '-cl']);
    }

    // Add attributes.
    if (!empty($variables['settings'][$key . '-at'])) {
      $attributes = explode(' ', $variables['settings'][$key . '-at']);
      foreach ($attributes as $key => $attribute) {
        if (strpos($attribute, '=') !== FALSE) {
          $attribute_parts = explode('=', $attribute);
          $var_attributes[$attribute_parts[0]] = trim($attribute_parts[1], '\\"');
        }
      }
    }
    $variables[$variable_name] = new DsAttribute($var_attributes);
  }

  // In order to allow HTML we need to filter XSS the output of the
  // prefix/suffix.
  $variables['settings']['prefix'] = XSS::filterAdmin($variables['settings']['prefix']);
  $variables['settings']['suffix'] = XSS::filterAdmin($variables['settings']['suffix']);
}

/**
 * Implements hook_ds_field_operations_alter().
 */
function ds_ds_field_operations_alter(&$operations, $field) {
  if ($field['type'] == 'block') {
    $operations['config'] = array(
      'title' => t('Configure block'),
      'url' => new Url('ds.manage_block_field_config', array(
        'field_key' => $field['id'],
      )),
    );
  }
}

/**
 * Implements hook_entity_type_alter().
 */
function ds_entity_type_alter(array &$entity_types) {

  /* @var $entity_types \Drupal\Core\Entity\EntityTypeInterface[] */
  foreach ($entity_types as $entity_type) {
    $base_table = $entity_type
      ->getBaseTable();
    if ($entity_type
      ->get('field_ui_base_route') && !empty($base_table)) {
      if ($entity_type
        ->hasLinkTemplate('canonical')) {
        $entity_type
          ->setLinkTemplate('display', $entity_type
          ->getLinkTemplate('canonical') . '/manage_display');
      }
    }
  }
}

Functions

Namesort descending Description
ds_contextual_links_view_alter Implements hook_contextual_links_view_alter().
ds_ds_field_operations_alter Implements hook_ds_field_operations_alter().
ds_entity_type_alter Implements hook_entity_type_alter().
ds_entity_view_alter Implements hook_entity_view_alter().
ds_field_formatter_settings_summary_alter Implements hook_field_formatter_settings_summary_alter().
ds_field_formatter_third_party_settings_form Implements hook_field_formatter_third_party_settings_form().
ds_field_template_settings_form Field template settings form.
ds_field_ui_display_overview_multistep_js Ajax handler for changing conditions on the 'Manage display' screen.
ds_field_ui_display_overview_multistep_submit Submission handler for condition changes in field_ui_display_overview_form().
ds_form_entity_view_display_edit_form_alter Implements hook_form_alter().
ds_help Implements hook_help().
ds_hook_info Implements hook_hook_info().
ds_local_tasks_alter Implements hook_local_tasks_alter().
ds_module_implements_alter Implements hook_module_implements_alter().
ds_preprocess_ds_layout Process layout.
ds_preprocess_field Implements hook_preprocess_field().
ds_theme Implements hook_theme().
ds_theme_registry_alter Implements hook_theme_registry_alter().
ds_theme_suggestions_alter Implements hook_theme_suggestions_alter().
ds_theme_suggestions_field_alter Implements hook_theme_suggestions_HOOK_alter().
template_preprocess_ds_entity_view Process entity view.
template_preprocess_field__ds_field_expert Implements template_preprocess_ds_field_expert().
template_preprocess_field__ds_field_minimal Implements template_preprocess_ds_field_minimal().
template_preprocess_field__ds_field_reset Implements template_preprocess_ds_field_reset().