You are here

twigsuggest.module in Twig Template Suggester 8

Twig Template Suggester module hook implementations.

File

twigsuggest.module
View source
<?php

/**
 * @file
 * Twig Template Suggester module hook implementations.
 */
use Drupal\block\Entity\Block;

/**
 * Implements hook_theme_suggestions_HOOK() for block templates.
 *
 * * Suggest region-specific block templates.
 *
 * Note: To suggest block type (bundle) specific block templates, use Block Type
 * Templates module, https://www.drupal.org/project/block_type_templates
 *
 * @TODO https://www.drupal.org/project/twigsuggest/issues/3007447
 */
function twigsuggest_theme_suggestions_block(array $variables) {
  $suggestions = [];

  // Prevent PHP notices if contrib modules create blocks without this ID.
  if (isset($variables['elements']['#id']) || !empty($variables['elements']['#id'])) {
    if ($block = Block::load($variables['elements']['#id'])) {
      $suggestions[] = 'block__' . $block
        ->getRegion();
      $suggestions[] = 'block__' . $block
        ->getRegion() . '__' . $variables['elements']['#id'];
      if ($block
        ->get('settings') && ($provider = $block
        ->get('settings')['provider'])) {

        // I'm pretty sure core is already providing provider as a suggestion.
        // $suggestions[] = 'block__' . $provider;.
        $base_plugin = $variables['elements']['#base_plugin_id'];
        if ($base_plugin !== $provider) {
          $suggestions[] = 'block__' . $base_plugin;
        }
        $suggestions[] = 'block__' . $provider . '__' . $block
          ->getRegion();
        if ($variables['elements']['#base_plugin_id'] != $provider) {
          $suggestions[] = 'block__' . $base_plugin . '__' . $block
            ->getRegion();
        }
        if (isset($variables['elements']['content']['#menu_name'])) {
          $menu_name = str_replace("-", "_", $variables['elements']['content']['#menu_name']);
          $suggestions[] = 'block__' . $provider . '__' . $menu_name . '__' . $block
            ->getRegion();
          if ($base_plugin !== $provider) {
            $suggestions[] = 'block__' . $base_plugin . '__' . $menu_name . '__' . $block
              ->getRegion();
          }
        }
      }
    }
  }
  return $suggestions;

  // @TODO decide whether to prefix these suggestions.  Probably best to be
  // consistent but if i were to *not* prefix one, it would actually probably be
  // bundles (content type, block type, etc.).  Block Type Templates gets around
  // this by prefacing its suggestions more intelligibly with 'block-content'
  // (as types only applies to custom, content blocks) so giving it suggestions
  // like this: block--block-content-{{ machine-name-of-block-type }}.html.twig
  // but the code floating out there everywhere from Jeff Burnz (he recommends
  // Block Type Templates module over this earlier code snippet of his) does
  // preface with bundle:
  // if (isset($variables['elements']['content']['#block_content'])) {
  // array_splice($suggestions, 1, 0, 'block__bundle__' .
  // $variables['elements']['content']['#block_content']->bundle());
  // }
}

/**
 * Implements hook_theme_suggestions_HOOK_alter() for layout templates.
 *
 * Fix suggestion of entity type specific layout templates.
 */
function twigsuggest_theme_suggestions_layout_alter(array &$suggestions, array $variables) {

  // Sometimes this can be helpful but othertimes apparently not so this hook
  // is disabled by default but can be enabled in settings.php:
  // $config['twigsuggest.settings']['alternate_ds_suggestions'] = TRUE;.
  if (!\Drupal::config('twigsuggest.settings')
    ->get('alternate_ds_suggestions')) {
    return;
  }

  // Although this appears to be done by default it's actually broken, as seen
  // in this bug report: https://www.drupal.org/project/drupal/issues/2881195
  // Apparently using two underscores/dashes will make it work despite the
  // double-listing (layout--onecol is there twice) still happening.
  if (isset($variables['content']) && is_array($variables['content']) && isset($variables['content']['#ds_configuration']) && $variables['theme_hook_original'] != 'ds_entity_view') {
    $layout_id = $variables['content']['#ds_configuration']['layout']['id'];
    $layout_id_len = strlen($layout_id);
    foreach ($suggestions as $key => $suggestion) {
      if (strpos($suggestion, $layout_id) === 0) {
        $base_suggest = str_replace('_', '__', $layout_id);
        $suggestions[$key] = substr_replace($suggestion, $base_suggest, 0, $layout_id_len);
      }
    }
  }
}

/**
 * Implements hook_theme_suggestions_HOOK() for container templates.
 *
 * Add suggestions, as by default none are provided.
 */
function twigsuggest_theme_suggestions_container(array $variables) {
  $suggestions = [];
  $element = $variables['element'];

  // We cannot count on template_preprocess_container having run, so we copy
  // its logic here to provide templates for forms (has parents) or not forms.
  // Special handling for form elements.
  if (isset($element['#array_parents'])) {
    $suggestions[] = 'container__has_parent';
  }
  else {
    $suggestions[] = 'container__no_parent';
  }
  if (isset($element['#type']) && $element['#type'] != 'container') {
    $suggestions[] = 'container__' . $element['#type'];
  }
  if (isset($element['#type']) && $element['#type'] == 'container' && isset($element['children']['#type'])) {
    $suggestions[] = 'container__' . $element['children']['#type'];
  }
  if (isset($element['#type']) && $element['#type'] == 'view') {
    $suggestions[] = 'container__view__' . $element['#name'];
    $suggestions[] = 'container__view__' . $element['#name'] . '__' . $element['#display_id'];
  }
  elseif (isset($element['widget'][0]['#type']) && $element['widget'][0]['#type'] === 'managed_file') {
    $suggestions[] = 'container__file';
    $suggestions[] = 'container__file__' . $element['widget']['#field_name'];
  }

  // Additional module-specific container templates.
  if (isset($element['#group'])) {
    $suggestions[] = 'container__' . str_replace('-', '_', $element['#group']);
  }
  if (isset($element['#webform_key'])) {
    $suggestions[] = 'container__' . str_replace('-', '_', $element['#webform_key']);
  }
  return $suggestions;
}

/**
 * Implements hook_theme_suggestions_HOOK() for form element templates.
 *
 * Add suggestions, as by default none are provided.
 */
function twigsuggest_theme_suggestions_form_element(array $variables) {
  $suggestions = [];
  $element = $variables['element'];
  if (isset($element['#id'])) {
    $suggestions[] = 'form_element__' . str_replace('-', '_', $element['#id']);
  }
  if (isset($element['#type'])) {
    $suggestions[] = 'form_element__' . $element['#type'];
  }

  // Additional module-specific container templates.
  if (isset($element['#webform_id'])) {
    $suggestions[] = 'form_element__webform__' . str_replace('-', '_', $element['#webform_id']);
  }
  return $suggestions;
}

/**
 * Implements hook_theme_suggestions_HOOK() for form input element templates.
 */
function twigsuggest_theme_suggestions_input(array $variables) {
  $suggestions = [];
  $element = $variables['element'];
  if (isset($element['#id'])) {
    $suggestions[] = 'input__' . $element['#id'];
  }
  return $suggestions;
}

/**
 * Implements hook_theme_suggestions_HOOK() for user templates.
 *
 * Add template suggestions based on highest user role following the same
 * pattern as for nodes. @see https://www.drupal.org/node/2354645
 *
 * user--[role|uid]--[viewmode].html.twig
 */
function twigsuggest_theme_suggestions_user(array $variables) {
  $suggestions = [];
  $view_mode = $variables['elements']['#view_mode'];

  /** @var \Drupal\user\Entity\User $user */
  $user = $variables['elements']['#user'];
  $roles = $user
    ->getRoles();
  $highest_role = end($roles);
  $uid = $user
    ->id();
  $suggestions[] = 'user__' . $uid;
  $suggestions[] = 'user__' . $view_mode;
  $suggestions[] = 'user__' . $highest_role;
  $suggestions[] = 'user__' . $uid . '__' . $view_mode;
  $suggestions[] = 'user__' . $highest_role . '__' . $view_mode;
  return $suggestions;
}

/**
 * Implements hook_theme_suggestions_HOOK() for the html template.
 *
 * Add additional template suggestion based on node type.
 */
function twigsuggest_theme_suggestions_html(array $variables) {
  $suggestions = [];

  /** @var \Drupal\node\Entity\Node $node */
  if ($node = \Drupal::service('twigsuggest.helper_functions')
    ->getCurrentNode()) {
    $suggestions[] = 'html__node__' . $node
      ->getType();
  }
  return $suggestions;
}

/**
 * Implements hook_theme_suggestions_HOOK() for page templates.
 *
 * Add additional template suggestion based on node type.
 */
function twigsuggest_theme_suggestions_page(array $variables) {
  $suggestions = [];

  /** @var \Drupal\node\Entity\Node $node */
  if ($node = \Drupal::service('twigsuggest.helper_functions')
    ->getCurrentNode()) {
    $suggestions[] = 'page__node__' . $node
      ->getType();
  }
  return $suggestions;
}

/**
 * Implements hook_theme_suggestions_HOOK() for field templates.
 */
function twigsuggest_theme_suggestions_field(array $variables) {
  $suggestions = [];
  $element = $variables['element'];
  $field_name = $element['#field_name'];
  $view_mode = $element['#view_mode'];
  $entity_type = $element['#entity_type'];
  $bundle = $element['#bundle'];
  $suggestions[] = 'field__' . $field_name . '__' . $view_mode;
  $suggestions[] = 'field__' . $entity_type . '__' . $field_name . '__' . $view_mode;
  $suggestions[] = 'field__' . $entity_type . '__' . $bundle . '__' . $field_name . '__' . $view_mode;
  return $suggestions;
}

/**
 * Implements hook_theme_suggestions_HOOK_alter() for field templates.
 *
 * Add entity reference target type template suggestion.
 *
 * @see https://git.drupalcode.org/project/adaptivetheme/-/blob/8.x-3.x/at_core/includes/suggestions.inc
 */
function twigsuggest_theme_suggestions_field_alter(array &$suggestions, array $variables) {

  // Add the entity reference type as a field template suggestion.
  if (isset($variables['element']['#items']) && is_object($variables['element']['#items'])) {
    $target_type = $variables['element']['#items']
      ->getSetting('target_type') ?: NULL;
    if ($target_type !== NULL) {
      array_splice($suggestions, 1, 0, 'field__entity_reference_type__' . $target_type);
    }
  }
}

/**
 * Implements hook_theme_suggestions_HOOK() for taxonomy terms.
 *
 * Currently Drupal core's taxonomy term module only provides:
 *  * $suggestions[] = 'taxonomy_term__' . $term->bundle();
 *  * $suggestions[] = 'taxonomy_term__' . $term->id() . $term->bundle();
 *
 * This is a very basic template suggestion that should be in core:
 * https://www.drupal.org/project/drupal/issues/2767243
 */
function twigsuggest_theme_suggestions_taxonomy_term(array $variables) {
  $term = $variables['elements']['#taxonomy_term'];

  // We allow dots in view modes?!  But keeping this from discarded core patch.
  $sanitized_view_mode = strtr($variables['elements']['#view_mode'], '.', '_');
  $suggestions[] = 'taxonomy_term__' . $term
    ->bundle() . '__' . $sanitized_view_mode;
  $suggestions[] = 'taxonomy_term__' . $sanitized_view_mode;
  $suggestions[] = 'taxonomy_term__' . $term
    ->id() . '__' . $term
    ->bundle() . '__' . $sanitized_view_mode;
  $suggestions[] = 'taxonomy_term__' . $term
    ->id() . '__' . $sanitized_view_mode;
  return $suggestions;
}

/**
 * Implements hook_theme_suggestions_HOOK_alter() for block templates.
 *
 * Remove duplicate template suggestions for blocks.
 */
function twigsuggest_theme_suggestions_block_alter(array &$suggestions, array $variables) {
  $suggestions = array_unique($suggestions);
}

/**
 * Implements hook_preprocess() for all templates.
 *
 * It's a variable themers should be able to rely on.  Now from any template we
 * can do things like <img src="{{ base_path ~ directory }}/images/icon.svg" />
 */
function twigsuggest_preprocess(&$variables, $hook) {
  $variables['base_path'] = base_path();
}