Newer
Older
Kristof De Jaeger
committed
<?php
/**
Kristof De Jaeger
committed
*/
use Drupal\Component\Utility\Html;
Bram Goffings
committed
use Drupal\Component\Utility\Xss;
Ben Dougherty
committed
use Drupal\Core\Cache\CacheableMetadata;
Bram Goffings
committed
use Drupal\Core\Entity\Display\EntityDisplayInterface;
Kristof De Jaeger
committed
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Field\FieldConfigInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Field\FormatterInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Routing\RouteMatchInterface;
Kristof De Jaeger
committed
use Drupal\Core\Site\Settings;
Bram Goffings
committed
use Drupal\Core\Template\Attribute;
use Drupal\ds\DsAttribute;
Tim Plunkett
committed
use Drupal\ds\Plugin\DsLayout;
use Drupal\field\Entity\FieldConfig;
/**
* Implements hook_help().
*/
Martin Quested
committed
function ds_help($route_name, RouteMatchInterface $route_match) {
if ($route_name == 'help.page.ds') {
$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 entity 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;
Kristof De Jaeger
committed
/**
* Implements hook_theme().
*/
function ds_theme() {
$theme_functions = [];
Bram Goffings
committed
$theme_functions['ds_entity_view'] = [
'render element' => 'content',
];
if (\Drupal::config('ds.settings')->get('field_template')) {
Kristof De Jaeger
committed
$use_bc_templates = \Drupal::config('ds.settings')->get('ft_bc');
$field_layouts = \Drupal::service('plugin.manager.ds.field.layout')->getDefinitions();
foreach ($field_layouts as $key => $plugin) {
if ($key != 'default') {
Kristof De Jaeger
committed
$path = \Drupal::service('extension.list.module')->getPath($plugin['provider']) . '/templates';
if ($plugin['provider'] == 'ds' && $use_bc_templates) {
$path .= '/bc';
}
$theme_functions['field__' . $plugin['theme']] = [
'render element' => 'elements',
'template' => strtr($plugin['theme'], '_', '-'),
'base hook' => 'field',
Kristof De Jaeger
committed
'path' => $path,
if (!empty($plugin['path'])) {
$theme_functions['field__' . $plugin['theme']]['file'] = $plugin['path'];
}
}
}
}
return $theme_functions;
Kristof De Jaeger
committed
}
/**
Kristof De Jaeger
committed
*/
$layouts = Ds::getLayouts();
Bram Goffings
committed
$layout_theme_hooks = [];
/** @var \Drupal\Core\Layout\LayoutDefinition $info */
foreach ($layouts as $info) {
Kristof De Jaeger
committed
if (is_a($info->getClass(), DsLayout::class, TRUE)) {
Kristof De Jaeger
committed
$layout_theme_hooks[$info->getThemeHook()] = 'layout';
Bram Goffings
committed
}
}
// 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) {
Kristof De Jaeger
committed
if (array_key_exists($theme_hook, $layout_theme_hooks) || (!empty($info['base hook']) && array_key_exists($info['base hook'], $layout_theme_hooks))) {
Bram Goffings
committed
$theme_registry[$theme_hook]['preprocess functions'][] = 'ds_preprocess_ds_layout';
Kristof De Jaeger
committed
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
// Check preprocess functions for ds fields.
$field_theme_hooks = [];
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') {
// Get the hook name.
$hook_field_name = 'field__' . $plugin['theme'];
if (isset($theme_registry[$hook_field_name])) {
// Store for suggestions later.
$field_theme_hooks[$hook_field_name] = 'field';
// Variable holding the preprocess functions to add later.
$preprocess_functions_to_add = [];
// Overrides don't get the template_preprocess_field function.
if (!in_array('template_preprocess_field', $theme_registry[$hook_field_name]['preprocess functions'])) {
$preprocess_functions_to_add[] = 'template_preprocess_field';
}
// Overrides don't get the ds_preprocess_field function.
if (!in_array('ds_preprocess_field', $theme_registry[$hook_field_name]['preprocess functions'])) {
$preprocess_functions_to_add[] = 'ds_preprocess_field';
}
// We need to make sure the function runs asap as the dedicated field
// preprocess function might need ds information, e.g.
// template_preprocess_field__ds_field_expert().
// template_preprocess() will be available, so put them right after.
if (!empty($preprocess_functions_to_add)) {
$preprocess_functions = $theme_registry[$hook_field_name]['preprocess functions'];
array_splice($preprocess_functions, 1, 0, $preprocess_functions_to_add);
$theme_registry[$hook_field_name]['preprocess functions'] = $preprocess_functions;
}
}
}
}
}
Kristof De Jaeger
committed
// ------------------------------------------------------------------------
// Workaround to get theme suggestions working for templates using the
Kristof De Jaeger
committed
// Display Suite class. It's borderline insane, but gets the job done.
Kristof De Jaeger
committed
//
// Note that this currently only works for Twig, but I assume, there isn't
// any other engine out there yet for Drupal 8.
//
// Code based on drupal_find_theme_templates().
//
// @see
// - https://www.drupal.org/node/2862683 (core queue)
// - https://www.drupal.org/node/2802429 (DS queue)
// (and maybe others)
// ------------------------------------------------------------------------
Kristof De Jaeger
committed
// Merge layout and field hooks.
$all_ds_theme_hooks = $layout_theme_hooks + $field_theme_hooks;
Kristof De Jaeger
committed
$engine = \Drupal::theme()->getActiveTheme()->getEngine();
if ($engine == 'twig') {
$extension = '.html.twig';
$theme_path = \Drupal::theme()->getActiveTheme()->getPath();
// Escape the periods in the extension.
$regex = '/' . str_replace('.', '\.', $extension) . '$/';
// Get a listing of all template files in the path to search.
Kristof De Jaeger
committed
$files = \Drupal::service('file_system')->scanDirectory($theme_path, $regex, ['key' => 'filename']);
Kristof De Jaeger
committed
$patterns = array_keys($files);
$implementations = [];
Kristof De Jaeger
committed
foreach ($all_ds_theme_hooks as $hook => $base_hook) {
Kristof De Jaeger
committed
// Ignored if not registered (which would be weird).
if (!isset($theme_registry[$hook])) {
continue;
}
$pattern = $info['pattern'] ?? ($hook . '__');
Kristof De Jaeger
committed
if (!empty($pattern)) {
// Transform _ in pattern to - to match file naming scheme
// for the purposes of searching.
$pattern = strtr($pattern, '_', '-');
$matches = preg_grep('/^' . $pattern . '/', $patterns);
if ($matches) {
foreach ($matches as $match) {
$file = $match;
// Remove the extension from the filename.
$file = str_replace($extension, '', $file);
// Put the underscores back in for the hook name and register this
// pattern.
$info = $theme_registry[$hook];
$arg_name = isset($info['variables']) ? 'variables' : 'render element';
$new_hook = strtr($file, '-', '_');
$implementations[$new_hook] = [
'template' => $file,
'path' => dirname($files[$match]->uri),
$arg_name => $info[$arg_name],
Kristof De Jaeger
committed
'base hook' => $base_hook,
Kristof De Jaeger
committed
'type' => 'theme_engine',
'theme path' => $theme_path,
];
if (isset($theme_registry[$hook]['preprocess functions'])) {
$implementations[$new_hook]['preprocess functions'] = $theme_registry[$hook]['preprocess functions'];
}
}
}
}
}
if (!empty($implementations)) {
$theme_registry += $implementations;
}
}
// ------------------------------------------------------------------------
// End of workaround, hopefully we can kill this one day.
// ------------------------------------------------------------------------
Bram Goffings
committed
// 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]);
}
}
Kristof De Jaeger
committed
}
* 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');
Kristof De Jaeger
committed
// Also load admin on behalf of DS extras when enabled.
if (\Drupal::moduleHandler()->moduleExists('ds_extras')) {
$form_state->loadInclude('ds_extras', 'inc', 'includes/admin');
Kristof De Jaeger
committed
}
/**
* 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']);
}
function ds_entity_view_alter(&$build, EntityInterface $entity, EntityDisplayInterface $display) {
static $field_permissions = FALSE;
static $loaded = FALSE;
$entity_type = $entity->getEntityTypeId();
$bundle = $entity->bundle();
// 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.
Kristof De Jaeger
committed
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');
// Don't fatal on missing layout plugins.
$layout_id = $configuration['layout']['id'] ?? '';
if (!Ds::layoutExists($layout_id)) {
return;
}
// 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($build['#ds_delta']) && isset($build['#ds_delta'][$field])) {
// Get delta.
$delta = $build['#ds_delta'][$field];
// Remove caching for this entity as it otherwise won't work.
unset($build['#cache']);
$filtered_elements = Element::children($build[$field]);
foreach ($filtered_elements as $filtered_element) {
if ($filtered_element != $delta) {
unset($build[$field][$filtered_element]);
elseif (is_numeric($limit)) {
// Remove caching for this entity as it otherwise won't work.
unset($build['#cache']);
$filtered_elements = Element::children($build[$field]);
$filtered_elements = array_slice($filtered_elements, $limit);
foreach ($filtered_elements as $filtered_element) {
unset($build[$field][$filtered_element]);
}
}
}
}
$field_values = !empty($configuration['fields']) ? $configuration['fields'] : [];
Bram Goffings
committed
foreach ($configuration['regions'] as $region) {
David Snopek
committed
foreach ($region as $weight => $key) {
// Ignore if this field is not a DS field, just pull it in from the
// entity.
David Snopek
committed
if (!isset($fields[$key])) {
continue;
}
David Snopek
committed
$field = $fields[$key];
if (isset($field_values[$key]['formatter'])) {
$field['formatter'] = $field_values[$key]['formatter'];
}
David Snopek
committed
if (isset($field_values[$key]['settings'])) {
$field['settings'] = $field_values[$key]['settings'];
}
$field_instance = Ds::getFieldInstance($key, $field, $entity, $view_mode, $display, $build);
$field_value = $field_instance->build();
$field_title = $field_instance->getTitle();
David Snopek
committed
Ben Dougherty
committed
// If the field value is cache data then we presume the value was empty
// and we just have cache data as to why it's empty.
if ($field_value instanceof CacheableMetadata) {
CacheableMetadata::createFromRenderArray($build)
->merge($field_value)
->applyTo($build);
}
// Only allow non empty fields.
Ben Dougherty
committed
elseif (!empty($field_value)) {
$build[$key] = [
David Snopek
committed
'#theme' => 'field',
'#field_type' => 'ds',
'#title' => $field_title,
'#weight' => $field_values[$key]['weight'] ?? $weight,
'#label_display' => $field_values[$key]['label'] ?? 'inline',
David Snopek
committed
'#field_name' => $key,
'#bundle' => $bundle,
'#object' => $entity,
'#entity_type' => $entity_type,
'#view_mode' => '_custom',
'#ds_view_mode' => $view_mode,
'#items' => [(object) ['_attributes' => []]],
Bram Goffings
committed
'#is_multiple' => $field_instance->isMultiple(),
'#access' => !($field_permissions && function_exists('ds_extras_ds_field_access')) || ds_extras_ds_field_access($key, $entity_type),
Martin Quested
committed
'#formatter' => 'ds_field',
Bram Goffings
committed
if ($field_instance->isMultiple()) {
$build[$key] += $field_value;
}
else {
$build[$key][0] = [$field_value];
Bram Goffings
committed
}
David Snopek
committed
}
Bram Goffings
committed
// 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'];
Johan Nijdam
committed
if (empty($build['#ds_configuration'])) {
return;
}
Bram Goffings
committed
$configuration = $build['#ds_configuration'];
unset($build['#theme']);
Bram Goffings
committed
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']);
Bram Goffings
committed
// Create region variables based on the layout settings.
Florian Weber
committed
$use_field_names = \Drupal::config('ds.settings')->get('use_field_names');
$regions = [];
Bram Goffings
committed
foreach (array_keys($configuration['regions']) as $region_name) {
$regions[$region_name] = [];
Bram Goffings
committed
// 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;
}
// Always set weight.
$build[$field]['#weight'] = $key;
Florian Weber
committed
if ($use_field_names) {
$regions[$region_name][$field] = $build[$field];
Florian Weber
committed
}
else {
$regions[$region_name][$key] = $build[$field];
Florian Weber
committed
}
Bram Goffings
committed
}
}
}
/* @var \Drupal\Core\Layout\LayoutInterface $layout */
$layout = \Drupal::service('plugin.manager.core.layout')->createInstance($configuration['layout']['id'], $configuration['layout']['settings']);
$build = array_merge($build, $layout->build($regions));
Brecht Ceyssens
committed
// Disable CSS files when needed.
Kristof De Jaeger
committed
if ($build['#ds_configuration']['layout']['disable_css'] ?? FALSE) {
Brecht Ceyssens
committed
$library = $build['#ds_configuration']['layout']['library'];
$attached = $build['#attached']['library'];
$index = array_search($library, $attached);
unset($build['#attached']['library'][$index]);
}
Bram Goffings
committed
$variables['content'] = $build;
Kristof De Jaeger
committed
* This function is added in ds_theme_registry_alter().
Bram Goffings
committed
function ds_preprocess_ds_layout(&$variables) {
David Snopek
committed
$layout_settings = $variables['settings'];
Kristof De Jaeger
committed
$layout_settings += ['wrappers' => []];
David Snopek
committed
// Fetch the entity type.
Kristof De Jaeger
committed
$bundle = FALSE;
$entity_type_id = NULL;
David Snopek
committed
if (isset($variables['content']['#entity_type'])) {
Kristof De Jaeger
committed
$entity_type_id = $variables['content']['#entity_type'];
}
if (isset($variables['content']['#bundle'])) {
Kristof De Jaeger
committed
$bundle = $variables['content']['#bundle'];
David Snopek
committed
}
David Snopek
committed
// Template layout.
if (!isset($variables['attributes']['class'])) {
$variables['attributes']['class'] = [];
David Snopek
committed
}
Kristof De Jaeger
committed
David Snopek
committed
// 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;
}
}
David Snopek
committed
// Create region variables based on the layout settings.
Kristof De Jaeger
committed
if (!empty($layout_settings['wrappers']) && is_array($layout_settings['wrappers'])) {
foreach ($layout_settings['wrappers'] as $region_name => $wrapper) {
// The new way of doing stuff is creating an attributes object.
Kristof De Jaeger
committed
$classes = !empty($layout_settings['classes'][$region_name]) ? $layout_settings['classes'][$region_name] : [];
if (!empty($classes)) {
$variables[$region_name . '_attributes'] = new Attribute(['class' => $classes]);
Kristof De Jaeger
committed
}
else {
$variables[$region_name . '_attributes'] = new Attribute();
}
Kristof De Jaeger
committed
// Merge layout builder's region attributes on the manage display page.
if (!empty($variables['region_attributes'][$region_name])) {
$variables[$region_name . '_attributes'] = new Attribute(array_merge($variables[$region_name . '_attributes']->toArray(), $variables['region_attributes'][$region_name]->toArray()));
}
Kristof De Jaeger
committed
$variables[$region_name . '_wrapper'] = !empty($layout_settings['wrappers'][$region_name]) ? $layout_settings['wrappers'][$region_name] : 'div';
Bram Goffings
committed
}
David Snopek
committed
}
David Snopek
committed
// Add a layout wrapper.
$variables['outer_wrapper'] = $layout_settings['outer_wrapper'] ?? 'div';
David Snopek
committed
// 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) {
Kristof De Jaeger
committed
if (isset($entity_type_id) && isset($variables['content']['#entity_type'])) {
David Snopek
committed
$replaced_attribute = \Drupal::service('token')->replace(
$layout_attribute,
[$variables['content']['#entity_type'] => $variables['content']['#' . $entity_type_id]],
['clear' => TRUE]
David Snopek
committed
);
Kristof De Jaeger
committed
[$key, $attribute_value] = explode('|', $replaced_attribute);
Kristof De Jaeger
committed
// Handle the class attribute as an array and others as strings.
$key == 'class' ? $variables['attributes'][$key][] = $attribute_value : $variables['attributes'][$key] = $attribute_value;
e.zeedub
committed
}
David Snopek
committed
}
}
David Snopek
committed
// Add an onclick attribute on the wrapper.
if (!empty($layout_settings['link_attribute'])) {
Kristof De Jaeger
committed
$url = NULL;
David Snopek
committed
switch ($layout_settings['link_attribute']) {
case 'content':
Kristof De Jaeger
committed
if ($entity_type_id && $variables['content']['#' . $entity_type_id]->id() > 0) {
$url = $variables['content']['#' . $entity_type_id]->toUrl();
David Snopek
committed
}
break;
David Snopek
committed
case 'custom':
$url = $layout_settings['link_custom'];
break;
David Snopek
committed
case 'tokens':
Kristof De Jaeger
committed
if ($entity_type_id) {
$url = \Drupal::service('token')->replace($layout_settings['link_custom'], [$entity_type_id => $variables['content']['#' . $entity_type_id]], ['clear' => TRUE]);
David Snopek
committed
break;
}
David Snopek
committed
if (!empty($url)) {
Kristof De Jaeger
committed
if (is_string($url)) {
$uri_parts = parse_url($url);
if (empty($uri_parts['scheme'])) {
$url = 'internal:/' . ltrim($url,'/');
}
$url = Url::fromUri($url);
}
Kristof De Jaeger
committed
// Give the possibility to alter the url object.
\Drupal::moduleHandler()->alter('ds_onclick_url', $url);
David Snopek
committed
$variables['attributes']['onclick'] = 'location.href=\'' . $url->toString() . '\'';
}
}
Kristof De Jaeger
committed
if ($entity_type_id) {
David Snopek
committed
if (isset($variables['content']['#ds_configuration'])) {
Kristof De Jaeger
committed
// Add theming-classes to template.
Kristof De Jaeger
committed
$entity_classes = $variables['content']['#ds_configuration']['layout']['entity_classes'] ?? 'all_classes';
Kristof De Jaeger
committed
if ($entity_classes != 'no_classes') {
if ($entity_classes == 'all_classes') {
Kristof De Jaeger
committed
$variables['attributes']['class'][] = Html::cleanCssIdentifier($entity_type_id);
$variables['attributes']['class'][] = Html::cleanCssIdentifier($entity_type_id) . '--type-' . Html::cleanCssIdentifier($bundle);
$variables['attributes']['class'][] = Html::cleanCssIdentifier($entity_type_id) . '--view-mode-' . Html::cleanCssIdentifier($variables['content']['#view_mode']);
Kristof De Jaeger
committed
}
elseif ($entity_classes == 'old_view_mode') {
Kristof De Jaeger
committed
// Add (old style, uncleaned) view-mode-{name} to classes.
Kristof De Jaeger
committed
if (!in_array('view-mode-' . $variables['content']['#view_mode'], $variables['attributes']['class'])) {
$variables['attributes']['class'][] = 'view-mode-' . $variables['content']['#view_mode'];
}
}
Kristof De Jaeger
committed
}
Bram Goffings
committed
// RDF support.
Kristof De Jaeger
committed
if ($entity_type_id == 'node' && function_exists('rdf_preprocess_node')) {
$entity = $variables[$entity_type_id] ?? (isset($variables['content']['#' . $entity_type_id]) ? $variables['content']['#' . $entity_type_id] : '');
if ($entity) {
$variables['node'] = $entity;
Bram Goffings
committed
// 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.
Bram Goffings
committed
$variables['rendered_by_ds'] = TRUE;
David Snopek
committed
// Let other modules alter the ds array before rendering.
$context = [
'entity' => NULL,
'entity_type' => $variables['content']['#entity_type'],
'bundle' => $variables['content']['#bundle'],
'view_mode' => $variables['content']['#view_mode'],
Kristof De Jaeger
committed
// Special case for User entity, because $variables['user'] contains the
// currently logged in user.
if($entity_type_id !== 'user' && isset($variables[$entity_type_id])) {
$context['entity'] = $variables[$entity_type_id];
}
elseif (isset($variables['content']['#' . $entity_type_id])) {
$context['entity'] = $variables['content']['#' . $entity_type_id];
}
David Snopek
committed
\Drupal::moduleHandler()->alter('ds_pre_render', $variables['content'], $context, $variables);
David Snopek
committed
// Copy the regions from 'content' into the top-level.
foreach (Element::children($variables['content']) as $name) {
David Snopek
committed
$variables[$name] = $variables['content'][$name];
}
// Copy entity to top level to improve theme experience.
Kristof De Jaeger
committed
if (isset($variables['content']['#entity']) && isset($variables['content']['#entity_type'])) {
git
committed
$variables[$variables['content']['#entity_type']] = $variables['content']['#entity'];
}
Bram Goffings
committed
/**
* Implements hook_theme_suggestions_alter().
Bram Goffings
committed
*/
function ds_theme_suggestions_alter(&$suggestions, $variables, $base_theme_hook) {
Kristof De Jaeger
committed
if (isset($variables['content']) && is_array($variables['content']) && !empty($variables['content']['#ds_configuration']['layout']['id']) && $base_theme_hook != 'ds_entity_view') {
$entity_id = $variables['content']['#' . $variables['content']['#entity_type']]->id();
Kristof De Jaeger
committed
$layout_hook = $variables['content']['#ds_configuration']['layout']['id'];
if (!\Drupal::config('ds.settings')->get('layout_suggestion_bc')) {
$layout = \Drupal::service('plugin.manager.core.layout')->getDefinition($layout_hook);
if (isset($layout) && ($theme_hook = $layout->getThemeHook())) {
$layout_hook = $theme_hook;
}
}
$suggestions[] = $layout_hook . '__' . $variables['content']['#entity_type'];
$suggestions[] = $layout_hook . '__' . $variables['content']['#entity_type'] . '_' . $variables['content']['#view_mode'];
$suggestions[] = $layout_hook . '__' . $variables['content']['#entity_type'] . '_' . $variables['content']['#bundle'];
$suggestions[] = $layout_hook . '__' . $variables['content']['#entity_type'] . '_' . $variables['content']['#bundle'] . '_' . $variables['content']['#view_mode'];
$suggestions[] = $layout_hook . '__' . $variables['content']['#entity_type'] . '__' . $entity_id;
Bram Goffings
committed
}
}
/**
* Implements hook_contextual_links_view_alter().
*/
function ds_contextual_links_view_alter(&$element, $items) {
$def = \Drupal::service('entity_type.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 = [
$destination = \Drupal::destination()->getAsArray();
$url->setOption('query', $destination);
// When there is no bundle defined, return.
$element['#links']['manage-display'] = [
* 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 = $variables['element']['#ds_view_mode'] ?? $variables['element']['#view_mode'];
/* @var $entity_display EntityDisplayInterface */
$entity_display = Ds::getDisplay($entity_type, $bundle, $view_mode);
Chris Eastwood
committed
// Check if this field is being rendered as a layout builder FieldBlock
// @see Drupal\layout_builder\Plugin\Block\FieldBlock::build();
Kristof De Jaeger
committed
$is_layout_builder = (!empty($variables['element']['#third_party_settings']['layout_builder']['view_mode']) && \Drupal::config('ds.settings')->get('ft_layout_builder'));
Chris Eastwood
committed
if (($entity_display && $entity_display->getThirdPartySetting('ds', 'layout') || $is_layout_builder)) {
// Get the field name and field instance info - if available.
$field_name = $variables['element']['#field_name'];
$config = [];
Chris Eastwood
committed
if ($is_layout_builder && !empty($variables['element']['#third_party_settings']['ds']['ft'])) {
$field_settings = [];
$field_settings[$entity_type][$bundle][$view_mode][$field_name]['ft'] = $variables['element']['#third_party_settings']['ds']['ft'];
}
else {
static $field_settings = [];
if (!isset($field_settings[$entity_type][$bundle][$view_mode])) {
$f = [];
// 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'];
}
Chris Eastwood
committed
// 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');
}
Chris Eastwood
committed
$field_settings[$entity_type][$bundle][$view_mode] = $f;
}
// Check if this field has custom output settings.
$variables['ds-config'] = [];
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']);
if (isset($config['settings']['classes'])) {
foreach ($config['settings']['classes'] as $class_name) {
Bram Goffings
committed
if (isset($variables['element']['#object'])) {
$class_name = \Drupal::token()->replace(
$class_name,
[$entity_type => $variables['element']['#object']],
['clear' => TRUE]
Bram Goffings
committed
);
}
$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 field templates.
*/
function ds_theme_suggestions_field_alter(&$suggestions, $variables) {
$entity_type = $variables['element']['#entity_type'];
$bundle = $variables['element']['#bundle'];
$view_mode = $variables['element']['#ds_view_mode'] ?? $variables['element']['#view_mode'];
/* @var $entity_display EntityDisplayInterface */
$entity_display = Ds::getDisplay($entity_type, $bundle, $view_mode);
Chris Eastwood
committed
// Check if this field is being rendered as a layout builder FieldBlock and
// that layout builder field templates are enabled
// @see Drupal\layout_builder\Plugin\Block\FieldBlock::build();
Kristof De Jaeger
committed
$is_layout_builder = (!empty($variables['element']['#third_party_settings']['layout_builder']['view_mode']) && \Drupal::config('ds.settings')->get('ft_layout_builder'));
Chris Eastwood
committed
if ($entity_display && ($entity_display->getThirdPartySetting('ds', 'layout') || $is_layout_builder)) {
// Get the field name and field instance info - if available.
$field_name = $variables['element']['#field_name'];
Kristof De Jaeger
committed
$field_theme_function = \Drupal::config('ds.settings')->get('ft_default');
Kristof De Jaeger
committed
$undo_default_field_template_fix = \Drupal::config('ds.settings')->get('ft_default_bc');
Chris Eastwood
committed
if ($is_layout_builder && !empty($variables['element']['#third_party_settings']['ds']['ft'])) {
$field_settings[$entity_type][$bundle][$view_mode][$field_name]['ft'] = $variables['element']['#third_party_settings']['ds']['ft'];
}
else {
static $field_settings = [];
if (!isset($field_settings[$entity_type][$bundle][$view_mode])) {
$f = [];
// 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'];
}
}
Chris Eastwood
committed
// 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');
Chris Eastwood
committed
$field_settings[$entity_type][$bundle][$view_mode] = $f;
}
// Check if this field has custom output settings.
$config = [];
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.
// 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']);
$suggestions[] = 'field__' . $layout_instance->getThemeFunction();
$suggestion = 'field__' . $config['id'];
Kristof De Jaeger
committed
elseif (!empty($field_theme_function)) {
Kristof De Jaeger
committed
// Either it uses the function.
if ($undo_default_field_template_fix) {
$suggestions[] = 'field__theme_ds_field_' . $field_theme_function;
}
else {
$suggestions[] = 'field__ds_field_' . $field_theme_function;
}
Kristof De Jaeger
committed
$set_suggestion = TRUE;
if ($field_theme_function == 'default' && !$undo_default_field_template_fix) {
$set_suggestion = FALSE;
}
if ($set_suggestion) {
$suggestion = 'field__' . $field_theme_function;
}
if (!empty($suggestion)) {
$suggestions[] = $suggestion;
$suggestions[] = $suggestion . '__' . $field_name;
$suggestions[] = $suggestion . '__' . $variables['element']['#bundle'];
$suggestions[] = $suggestion . '__' . $variables['element']['#bundle'] . '__' . $variables['element']['#view_mode'];
$suggestions[] = $suggestion . '__' . $field_name . '__' . $variables['element']['#bundle'];
$suggestions[] = $suggestion . '__' . $field_name . '__' . $variables['element']['#bundle'] . '__' . $variables['element']['#view_mode'];
Bram Goffings
committed
$suggestions[] = $suggestion . '__' . $variables['element']['#entity_type'] . '__' . $field_name;
$suggestions[] = $suggestion . '__' . $variables['element']['#entity_type'] . '__' . $variables['element']['#bundle'];
$suggestions[] = $suggestion . '__' . $variables['element']['#entity_type'] . '__' . $variables['element']['#bundle'] . '__' . $variables['element']['#view_mode'];
Bram Goffings
committed
$suggestions[] = $suggestion . '__' . $variables['element']['#entity_type'] . '__' . $field_name . '__' . $variables['element']['#bundle'];
$suggestions[] = $suggestion . '__' . $variables['element']['#entity_type'] . '__' . $field_name . '__' . $variables['element']['#bundle'] . '__' . $variables['element']['#view_mode'];
Bram Goffings
committed
// 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();
Kristof De Jaeger
committed
$default_field_function = \Drupal::config('ds.settings')->get('ft_default');
if (empty($default_field_function)) {
Bram Goffings
committed
if (is_array($context['instance'])) {
$key = $context['instance']['field_name'];
}
else {
$key = $context['instance']->getName();
Bram Goffings
committed
}
Chris Eastwood
committed
// Check if this is the layout builder field block form
$is_layout_builder = ($form_state->getBuildInfo()['base_form_id'] == 'layout_builder_configure_block');
Kristof De Jaeger
committed
// Field (plugin) setting. If it's an array, this is a DS field, otherwise,
// it's a core base or field api field.
if (is_array($context['instance'])) {
Chris Eastwood
committed
$plugin_settings = $form_state->get('plugin_settings');
$field_settings = $plugin_settings[$key]['ft'] ?? [];
Chris Eastwood
committed
}
Kristof De Jaeger
committed
else {
$field_settings = $context['formatter']->getThirdPartySetting('ds', 'ft');
}
// In case with an ajax refresh we fetch the function from a different place.
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'];
}
Chris Eastwood
committed
elseif (isset($values['settings']['formatter']['third_party_settings']['ds']['ft']['id'])) {
$field_function = $values['settings']['formatter']['third_party_settings']['ds']['ft']['id'];
}
$field_function = $field_settings['id'] ?? $default_field_function;
if (!isset($functions[$field_function])) {
$field_function = $default_field_function;
}
$form['ft'] = [
'#weight' => 20,
$form['ft']['id'] = [
'#title' => t('Choose a Field Template'),
'#type' => 'select',
'#options' => $functions,
'#default_value' => $field_function,
'#submit' => ['ds_field_ui_display_overview_multistep_submit'],
Chris Eastwood
committed
];
// Support layout builder field blocks
if ($is_layout_builder) {
$form['ft']['id']['#ajax'] = [
'callback' => 'ds_layout_builder_multistep_js',
'wrapper' => 'formatter-settings-wrapper',
];
}
else {
Kristof De Jaeger
committed
$form['ft']['id']['#field_name'] = $key;
Chris Eastwood
committed
$form['ft']['id']['#ajax'] = [
'callback' => 'ds_field_ui_display_overview_multistep_js',
'wrapper' => 'field-display-overview-wrapper',
Kristof De Jaeger
committed
'effect' => 'fade',
Chris Eastwood
committed
];
}