' . t('About') . ''; $output .= '
' . t('The Display Suite 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 .= '
' . 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 .= '
' . 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 .= '
' . t('More documentation about Display Suite in Drupal 8 can be found in Drupal\'s Community Documentation .', [':documentation' => 'https://www.drupal.org/node/2718943']); return $output; } } /** * Implements hook_theme(). */ function ds_theme() { $theme_functions = []; $theme_functions['ds_entity_view'] = [ 'render element' => 'content', ]; // Field templates. if (\Drupal::config('ds.settings')->get('field_template')) { $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') { $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', 'path' => $path, ]; 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 = []; /** @var \Drupal\Core\Layout\LayoutDefinition $info */ foreach ($layouts as $info) { if (is_a($info->getClass(), DsLayout::class, TRUE)) { $layout_theme_hooks[$info->getThemeHook()] = 'layout'; } } // 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 (array_key_exists($theme_hook, $layout_theme_hooks) || (!empty($info['base hook']) && array_key_exists($info['base hook'], $layout_theme_hooks))) { $theme_registry[$theme_hook]['preprocess functions'][] = 'ds_preprocess_ds_layout'; } } // 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; } } } } } // ------------------------------------------------------------------------ // Workaround to get theme suggestions working for templates using the // Display Suite class. It's borderline insane, but gets the job done. // // 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) // ------------------------------------------------------------------------ // Merge layout and field hooks. $all_ds_theme_hooks = $layout_theme_hooks + $field_theme_hooks; $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. $files = \Drupal::service('file_system')->scanDirectory($theme_path, $regex, ['key' => 'filename']); $patterns = array_keys($files); $implementations = []; foreach ($all_ds_theme_hooks as $hook => $base_hook) { // Ignored if not registered (which would be weird). if (!isset($theme_registry[$hook])) { continue; } $pattern = $info['pattern'] ?? ($hook . '__'); 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], 'base hook' => $base_hook, '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. // ------------------------------------------------------------------------ // 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']); } } /** * 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'); // 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]); } } } } } // Add Display Suite fields. $fields = Ds::getFields($entity_type); $field_values = !empty($configuration['fields']) ? $configuration['fields'] : []; 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']; } $field_instance = Ds::getFieldInstance($key, $field, $entity, $view_mode, $display, $build); $field_value = $field_instance->build(); $field_title = $field_instance->getTitle(); // 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. elseif (!empty($field_value)) { $build[$key] = [ '#theme' => 'field', '#field_type' => 'ds', '#title' => $field_title, '#weight' => $field_values[$key]['weight'] ?? $weight, '#label_display' => $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' => [(object) ['_attributes' => []]], '#is_multiple' => $field_instance->isMultiple(), '#access' => !($field_permissions && function_exists('ds_extras_ds_field_access')) || ds_extras_ds_field_access($key, $entity_type), '#formatter' => 'ds_field', ]; if ($field_instance->isMultiple()) { $build[$key] += $field_value; } else { $build[$key][0] = [$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']; if (empty($build['#ds_configuration'])) { return; } $configuration = $build['#ds_configuration']; 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] = []; // 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; if ($use_field_names) { $regions[$region_name][$field] = $build[$field]; } else { $regions[$region_name][$key] = $build[$field]; } } } } /* @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)); // Disable CSS files when needed. if ($build['#ds_configuration']['layout']['disable_css'] ?? FALSE) { $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']; $layout_settings += ['wrappers' => []]; // Fetch the entity type. $bundle = FALSE; $entity_type_id = NULL; if (isset($variables['content']['#entity_type'])) { $entity_type_id = $variables['content']['#entity_type']; } if (isset($variables['content']['#bundle'])) { $bundle = $variables['content']['#bundle']; } // Template layout. if (!isset($variables['attributes']['class'])) { $variables['attributes']['class'] = []; } // 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. 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. $classes = !empty($layout_settings['classes'][$region_name]) ? $layout_settings['classes'][$region_name] : []; if (!empty($classes)) { $variables[$region_name . '_attributes'] = new Attribute(['class' => $classes]); } else { $variables[$region_name . '_attributes'] = new Attribute(); } // 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())); } $variables[$region_name . '_wrapper'] = !empty($layout_settings['wrappers'][$region_name]) ? $layout_settings['wrappers'][$region_name] : 'div'; } } // Add a layout wrapper. $variables['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_id) && isset($variables['content']['#entity_type'])) { $replaced_attribute = \Drupal::service('token')->replace( $layout_attribute, [$variables['content']['#entity_type'] => $variables['content']['#' . $entity_type_id]], ['clear' => TRUE] ); } [$key, $attribute_value] = explode('|', $replaced_attribute); // Handle the class attribute as an array and others as strings. $key == 'class' ? $variables['attributes'][$key][] = $attribute_value : $variables['attributes'][$key] = $attribute_value; } } } // Add an onclick attribute on the wrapper. if (!empty($layout_settings['link_attribute'])) { $url = NULL; switch ($layout_settings['link_attribute']) { case 'content': if ($entity_type_id && $variables['content']['#' . $entity_type_id]->id() > 0) { $url = $variables['content']['#' . $entity_type_id]->toUrl(); } break; case 'custom': $url = $layout_settings['link_custom']; break; case 'tokens': if ($entity_type_id) { $url = \Drupal::service('token')->replace($layout_settings['link_custom'], [$entity_type_id => $variables['content']['#' . $entity_type_id]], ['clear' => TRUE]); } break; } if (!empty($url)) { if (is_string($url)) { $uri_parts = parse_url($url); if (empty($uri_parts['scheme'])) { $url = 'internal:/' . ltrim($url,'/'); } $url = Url::fromUri($url); } // Give the possibility to alter the url object. \Drupal::moduleHandler()->alter('ds_onclick_url', $url); $variables['attributes']['onclick'] = 'location.href=\'' . $url->toString() . '\''; } } if ($entity_type_id) { if (isset($variables['content']['#ds_configuration'])) { // Add theming-classes to template. $entity_classes = $variables['content']['#ds_configuration']['layout']['entity_classes'] ?? 'all_classes'; if ($entity_classes != 'no_classes') { if ($entity_classes == 'all_classes') { $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']); } elseif ($entity_classes == 'old_view_mode') { // Add (old style, uncleaned) 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_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; // 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 = [ 'entity' => NULL, 'entity_type' => $variables['content']['#entity_type'], 'bundle' => $variables['content']['#bundle'], 'view_mode' => $variables['content']['#view_mode'], ]; // 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]; } \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']) && isset($variables['content']['#entity_type'])) { $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']) && !empty($variables['content']['#ds_configuration']['layout']['id']) && $base_theme_hook != 'ds_entity_view') { $entity_id = $variables['content']['#' . $variables['content']['#entity_type']]->id(); $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; } } /** * 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 = [ $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'] = [ '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 = $variables['element']['#ds_view_mode'] ?? $variables['element']['#view_mode']; /* @var $entity_display EntityDisplayInterface */ $entity_display = Ds::getDisplay($entity_type, $bundle, $view_mode); // Check if this field is being rendered as a layout builder FieldBlock // @see Drupal\layout_builder\Plugin\Block\FieldBlock::build(); $is_layout_builder = (!empty($variables['element']['#third_party_settings']['layout_builder']['view_mode']) && \Drupal::config('ds.settings')->get('ft_layout_builder')); 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 = []; 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']; } } // 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'] = []; 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, [$entity_type => $variables['element']['#object']], ['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 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); // 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(); $is_layout_builder = (!empty($variables['element']['#third_party_settings']['layout_builder']['view_mode']) && \Drupal::config('ds.settings')->get('ft_layout_builder')); 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']; $field_theme_function = \Drupal::config('ds.settings')->get('ft_default'); $undo_default_field_template_fix = \Drupal::config('ds.settings')->get('ft_default_bc'); 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']; } } // 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. $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. $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 (!empty($field_theme_function)) { // 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; } // Or the template file(s). $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']; $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']; $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']; } // 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'; } if (is_array($context['instance'])) { $key = $context['instance']['field_name']; } else { $key = $context['instance']->getName(); } // Check if this is the layout builder field block form $is_layout_builder = ($form_state->getBuildInfo()['base_form_id'] == 'layout_builder_configure_block'); // Plugin settings. if ($is_layout_builder) { $field_settings = $context['formatter']->getThirdPartySetting('ds', 'ft'); } else { $plugin_settings = $form_state->get('plugin_settings'); $field_settings = $plugin_settings[$key]['ft'] ?? []; } // 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']; } elseif (isset($values['settings']['formatter']['third_party_settings']['ds']['ft']['id'])) { $field_function = $values['settings']['formatter']['third_party_settings']['ds']['ft']['id']; } else { $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'], ]; // Support layout builder field blocks if ($is_layout_builder) { $form['ft']['id']['#ajax'] = [ 'callback' => 'ds_layout_builder_multistep_js', 'wrapper' => 'formatter-settings-wrapper', ]; } else { $form['ft']['id']['#field_name'] = $key; $form['ft']['id']['#ajax'] = [ 'callback' => 'ds_field_ui_display_overview_multistep_js', 'wrapper' => 'field-display-overview-wrapper', 'effect' => 'fade', ]; } // Create field layout plugin instance. $config = $field_settings['settings'] ?? []; $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'] = [ '#type' => 'container', '#attributes' => [ 'class' => [ '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) { $trigger = $form_state->getTriggeringElement(); $field_name = $trigger['#field_name']; $element = &$form['fields'][$field_name]['plugin']; $element['#prefix'] = '
' . ($element['#prefix'] ?? ''); $element['#suffix'] = ($element['#suffix'] ?? '') . '
'; return $form['fields']; } /** * Ajax handler for changing conditions on the 'Manage display' screen. */ function ds_layout_builder_multistep_js($form, &$form_state) { return $form['settings']['formatter']['settings_wrapper']; } /** * 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 = []; // Don't process the form if on the DS field config form. if (isset($form_state->getBuildInfo()['form_id']) && $form_state->getBuildInfo()['form_id'] == 'ds_custom_field_form') { return $element; } // Don't process the form if it's a layout builder FieldBlock and field // template support is disabled if (isset($form_state->getBuildInfo()['base_form_id']) && $form_state->getBuildInfo()['base_form_id'] == 'layout_builder_configure_block' && !\Drupal::config('ds.settings')->get('ft_layout_builder')) { return $element; } // Don't process the form if there's no layout selected or if we are not on a // layout builder block. if (isset($form_state->getBuildInfo()['base_form_id']) && $form_state->getBuildInfo()['base_form_id'] != 'layout_builder_configure_block') { /* @var \Drupal\Core\Entity\EntityFormInterface $entity_form */ $entity_form = $form_state->getFormObject(); /* @var \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display */ $display = $entity_form->getEntity(); if (!$display->getThirdPartySetting('ds', 'layout')) { return $element; } } $field_info = $field_definition->getFieldStorageDefinition(); if ($field_info->getCardinality() != 1) { $name = $field_info->getName(); $settings = $form_state->get('plugin_settings'); $element['ds_limit'] = [ '#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 = [ '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')) { // Don't process the summary if there's no layout selected. We don't have // a form state here, so we try to load the display using the route params. $parameters = \Drupal::routeMatch()->getRawParameters(); $entity_type_id = $parameters->get('entity_type_id'); $entity_type = \Drupal::entityTypeManager()->getDefinition($entity_type_id); if (empty($parameters->get('bundle'))) { $bundle_key = $entity_type->getBundleEntityType(); $bundle = \Drupal::routeMatch()->getRawParameter($bundle_key); } else { $bundle = $parameters->get('bundle'); } $view_mode = $parameters->get('view_mode_name'); $display = \Drupal::entityTypeManager()->getStorage('entity_view_display')->load("$entity_type_id.$bundle.$view_mode"); if ($display instanceof EntityViewDisplayInterface) { if (!$display->getThirdPartySetting('ds', 'layout')) { return; } } // 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 = $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'] = [ 'group' => 'ds', ]; $hooks['ds_views_row_render_entity_alter'] = [ 'group' => 'ds', ]; $hooks['ds_views_view_mode_alter'] = [ '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'] = $variables['ds-config']['settings'] ?? []; } /** * Implements template_preprocess_ds_field_expert(). */ function template_preprocess_field__ds_field_expert(&$variables) { $variables['settings'] = $variables['ds-config']['settings'] ?? []; $vars = [ 'lbw' => 'label_attributes', 'ow' => 'wrapper_attributes', 'fis' => 'field_wrapper_attributes', 'fi' => 'field_item_wrapper_attributes', ]; foreach ($vars as $key => $variable_name) { $var_attributes = []; // 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, 2); $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'] = [ 'title' => t('Configure block'), 'url' => new Url('ds.manage_block_field_config', ['field_key' => $field['id']]), ]; } } /** * Implements hook_entity_type_alter(). */ function ds_entity_type_alter(array &$entity_types) { if (\Drupal::moduleHandler()->moduleExists('field_ui')) { /* @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'); } } } } // Undo field_layout changes to EntityViewDisplay. if (\Drupal::moduleHandler()->moduleExists('field_layout') && !\Drupal::moduleHandler()->moduleExists('layout_builder')) { $entity_types['entity_view_display']->setClass($entity_types['entity_view_display']->getOriginalClass()); if (\Drupal::moduleHandler()->moduleExists('field_ui')) { $entity_types['entity_view_display']->setFormClass('edit', 'Drupal\field_ui\Form\EntityViewDisplayEditForm'); } } } /** * Implements hook_plugin_filter_TYPE__CONSUMER_alter(). */ function ds_plugin_filter_layout__layout_builder_alter(array &$definitions, array $extra) { // Remove DS layouts from layout builder. $exclude_ds_layouts_layout_builder = \Drupal::config('ds.settings')->get('exclude_ds_layout_layout_builder'); /** @var \Drupal\Core\Layout\LayoutDefinition $definition */ foreach ($definitions as $key => $definition) { if ($definition->getProvider() == 'ds' && $exclude_ds_layouts_layout_builder) { unset($definitions[$key]); } } } /** * Implements hook_layout_alter(). */ function ds_layout_alter(&$definitions) { $use_old_images = \Drupal::config('ds.settings')->get('layout_icon_image_bc'); if ($use_old_images) { return; } /** @var \Drupal\Core\Layout\LayoutDefinition $definition */ foreach ($definitions as $key => $definition) { if ($definition->getProvider() == 'ds') { $definition->setIconPath(NULL); } } }