class OliveroHooks

Hook implementations for olivero.

Hierarchy

Expanded class hierarchy of OliveroHooks

File

core/themes/olivero/src/Hook/OliveroHooks.php, line 18

Namespace

Drupal\olivero\Hook
View source
class OliveroHooks {
  use StringTranslationTrait;
  
  /**
   * Implements hook_preprocess_HOOK() for page-title.
   */
  public function preprocessPageTitle(&$variables) : void {
    // Since the title and the shortcut link are both block level elements,
    // positioning them next to each other is much simpler with a wrapper div.
    if (!empty($variables['title_suffix']['add_or_remove_shortcut']) && $variables['title']) {
      // Add a wrapper div using the title_prefix and title_suffix render
      // elements.
      $variables['title_prefix']['shortcut_wrapper'] = [
        '#markup' => '<div class="shortcut-wrapper">',
        '#weight' => 100,
      ];
      $variables['title_suffix']['shortcut_wrapper'] = [
        '#markup' => '</div>',
        '#weight' => -99,
      ];
      // Make sure the shortcut link is the first item in title_suffix.
      $variables['title_suffix']['add_or_remove_shortcut']['#weight'] = -100;
    }
    // Unset shortcut link on front page.
    $variables['is_front'] = \Drupal::service('path.matcher')->isFrontPage();
    if ($variables['is_front'] === TRUE) {
      unset($variables['title_suffix']['add_or_remove_shortcut']);
    }
  }
  
  /**
   * Implements hook_preprocess_HOOK() for maintenance-page.
   */
  public function preprocessMaintenancePage(&$variables) : void {
    // By default, site_name is set to Drupal if no db connection is available
    // or during site installation. Setting site_name to an empty string makes
    // the site and update pages look cleaner.
    // @see \Drupal\Core\Theme\ThemePreprocess::preprocessMaintenancePage()
    if (!$variables['db_is_active']) {
      $variables['site_name'] = '';
    }
    // Olivero has custom styling for the maintenance page.
    $variables['#attached']['library'][] = 'olivero/maintenance-page';
  }
  
  /**
   * Implements hook_preprocess_HOOK() for node.
   */
  public function preprocessNode(&$variables) : void {
    // Remove the "Add new comment" link on teasers or when the comment form is
    // displayed on the page.
    if ($variables['view_mode'] === 'teaser' || !empty($variables['content']['comments']['comment_form'])) {
      unset($variables['content']['links']['comment']['#links']['comment-add']);
    }
    // Apply custom date formatter to "date" field.
    if (!empty($variables['date']) && !empty($variables['display_submitted']) && $variables['display_submitted'] === TRUE) {
      $variables['date'] = \Drupal::service('date.formatter')->format($variables['node']->getCreatedTime(), 'olivero_medium');
    }
    // Pass layout variable to template if content type is article in full view
    // mode. This is then used in the template to create a BEM style CSS class
    // to control the layout.
    if ($variables['node']->bundle() === 'article' && $variables['view_mode'] === 'full') {
      $variables['layout'] = 'content-narrow';
    }
  }
  
  /**
   * Implements hook_preprocess_HOOK() for block.
   */
  public function preprocessBlock(&$variables) : void {
    if (!empty($variables['elements']['#id'])) {
      /** @var \Drupal\block\BlockInterface $block */
      $block = \Drupal::entityTypeManager()->getStorage('block')
        ->load($variables['elements']['#id']);
      if ($block) {
        $region = $block->getRegion();
        if ($variables['base_plugin_id'] === 'system_menu_block') {
          $variables['content']['#attributes']['region'] = $region;
          if ($region === 'sidebar') {
            $variables['#attached']['library'][] = 'olivero/menu-sidebar';
          }
        }
        if ($variables['base_plugin_id'] === 'search_form_block') {
          if ($region === 'primary_menu') {
            $variables['#attached']['library'][] = 'olivero/search-narrow';
            $variables['content']['actions']['submit']['#theme_wrappers'] = [
              'input__submit__header_search',
            ];
          }
          elseif ($region === 'secondary_menu') {
            $variables['#attached']['library'][] = 'olivero/search-wide';
            $variables['content']['actions']['submit']['#theme_wrappers'] = [
              'input__submit__header_search',
            ];
          }
        }
      }
    }
    if ($variables['plugin_id'] === 'system_branding_block') {
      $site_branding_color = \Drupal::service(ThemeSettingsProvider::class)->getSetting('site_branding_bg_color');
      if ($site_branding_color && $site_branding_color !== 'default') {
        $variables['attributes']['class'][] = 'site-branding--bg-' . $site_branding_color;
      }
    }
    // Add a primary-nav class to main menu navigation block.
    if ($variables['plugin_id'] === 'system_menu_block:main') {
      $variables['attributes']['class'][] = 'primary-nav';
    }
  }
  
  /**
   * Implements hook_theme_suggestions_HOOK_alter() for menu.
   */
  public function themeSuggestionsMenuAlter(&$suggestions, array $variables) : void {
    if (isset($variables['attributes']['region'])) {
      $suggestions[] = 'menu__' . $variables['attributes']['region'];
    }
  }
  
  /**
   * Implements hook_preprocess_HOOK() for menu.
   */
  public function preprocessMenu(&$variables) : void {
    if (isset($variables['attributes']['region'])) {
      if ($variables['attributes']['region'] === 'sidebar') {
        $variables['attributes']['class'][] = 'menu--sidebar';
      }
      unset($variables['attributes']['region']);
    }
  }
  
  /**
   * Implements hook_theme_suggestions_HOOK_alter() for form templates.
   */
  public function themeSuggestionsFormAlter(array &$suggestions, array $variables) : void {
    if ($variables['element']['#form_id'] === 'search_block_form') {
      $suggestions[] = 'form__search_block_form';
    }
  }
  
  /**
   * Implements hook_form_alter() for adding classes and placeholder text to the search forms.
   */
  public function formAlter(&$form, FormStateInterface $form_state, $form_id) : void {
    if (isset($form['actions']['submit']) && count($form['actions']) <= 2) {
      $form['actions']['submit']['#attributes']['class'][] = 'button--primary';
    }
    switch ($form_id) {
      case 'search_block_form':
        // Add placeholder text to keys input.
        $form['keys']['#attributes']['placeholder'] = $this->t('Search by keyword or phrase.');
        // Add classes to the search form submit input.
        $form['actions']['submit']['#attributes']['class'][] = 'search-form__submit';
        break;

      case 'search_form':
        $form['basic']['keys']['#attributes']['placeholder'] = $this->t('Search by keyword or phrase.');
        $form['basic']['submit']['#attributes']['class'][] = 'button--primary';
        $form['advanced']['submit']['#attributes']['class'][] = 'button--primary';
        break;

    }
  }
  
  /**
   * Implements hook_theme_suggestions_HOOK_alter() for block().
   */
  public function themeSuggestionsBlockAlter(&$suggestions, array $variables) : void {
    if (!empty($variables['elements']['#id'])) {
      /** @var \Drupal\block\BlockInterface $block */
      $block = \Drupal::entityTypeManager()->getStorage('block')
        ->load($variables['elements']['#id']);
      if ($block) {
        // Add region-specific block theme suggestions.
        $region = $block->getRegion();
        $suggestions[] = 'block__' . $region;
        $suggestions[] = 'block__' . $region . '__plugin_id__' . $variables['elements']['#plugin_id'];
        $suggestions[] = 'block__' . $region . '__id__' . $variables['elements']['#id'];
      }
    }
  }
  
  /**
   * Implements hook_preprocess_HOOK() for menu-local-tasks.
   */
  public function preprocessMenuLocalTasks(&$variables) : void {
    foreach (Element::children($variables['primary']) as $key) {
      $variables['primary'][$key]['#level'] = 'primary';
    }
    foreach (Element::children($variables['secondary']) as $key) {
      $variables['secondary'][$key]['#level'] = 'secondary';
    }
  }
  
  /**
   * Implements hook_preprocess_HOOK() for form-element.
   */
  public function preprocessFormElement(&$variables) : void {
    if (in_array($variables['element']['#type'] ?? FALSE, [
      'checkbox',
      'radio',
    ], TRUE)) {
      $variables['attributes']['class'][] = 'form-type-boolean';
    }
    if (!empty($variables['description']['attributes'])) {
      $variables['description']['attributes']->addClass('form-item__description');
    }
    if ($variables['disabled']) {
      $variables['label']['#attributes']['class'][] = 'is-disabled';
    }
  }
  
  /**
   * Implements hook_preprocess_HOOK() for form-element-label.
   */
  public function preprocessFormElementLabel(&$variables) : void {
    $variables['attributes']['class'][] = 'form-item__label';
  }
  
  /**
   * Implements hook_preprocess_HOOK() for input.
   */
  public function preprocessInput(&$variables) : void {
    if (!empty($variables['element']['#title_display']) && $variables['element']['#title_display'] === 'attribute' && !empty((string) $variables['element']['#title'])) {
      $variables['attributes']['title'] = (string) $variables['element']['#title'];
    }
    $type_api = $variables['element']['#type'];
    $type_html = $variables['attributes']['type'];
    $text_types_html = [
      'text',
      'email',
      'tel',
      'number',
      'search',
      'password',
      'date',
      'time',
      'file',
      'color',
      'datetime-local',
      'url',
      'month',
      'week',
    ];
    if (in_array($type_html, $text_types_html, TRUE)) {
      $variables['attributes']['class'][] = 'form-element';
      $variables['attributes']['class'][] = Html::getClass('form-element--type-' . $type_html);
      $variables['attributes']['class'][] = Html::getClass('form-element--api-' . $type_api);
      // This logic is functioning as expected, but there is nothing in the
      // theme that renders the result. As a result it can't currently be
      // covered by a functional test.
      if (!empty($variables['element']['#autocomplete_route_name'])) {
        $variables['autocomplete_message'] = $this->t('Loading…');
      }
    }
    if (in_array($type_html, [
      'checkbox',
      'radio',
    ], TRUE)) {
      $variables['attributes']['class'][] = 'form-boolean';
      $variables['attributes']['class'][] = Html::getClass('form-boolean--type-' . $type_html);
    }
  }
  
  /**
   * Implements hook_preprocess_HOOK() for textarea.
   */
  public function preprocessTextarea(&$variables) : void {
    $variables['attributes']['class'][] = 'form-element';
    $variables['attributes']['class'][] = 'form-element--type-textarea';
    $variables['attributes']['class'][] = 'form-element--api-textarea';
  }
  
  /**
   * Implements hook_preprocess_HOOK() for select.
   */
  public function preprocessSelect(&$variables) : void {
    $variables['attributes']['class'][] = 'form-element';
    $variables['attributes']['class'][] = $variables['element']['#multiple'] ? 'form-element--type-select-multiple' : 'form-element--type-select';
  }
  
  /**
   * Implements hook_preprocess_HOOK() for checkboxes.
   */
  public function preprocessCheckboxes(&$variables) : void {
    $variables['attributes']['class'][] = 'form-boolean-group';
  }
  
  /**
   * Implements hook_preprocess_HOOK() for radios.
   */
  public function preprocessRadios(&$variables) : void {
    $variables['attributes']['class'][] = 'form-boolean-group';
  }
  
  /**
   * Implements hook_preprocess_HOOK() for field.
   */
  public function preprocessField(&$variables) : void {
    $rich_field_types = [
      'text_with_summary',
      'text',
      'text_long',
    ];
    if (in_array($variables['field_type'], $rich_field_types, TRUE)) {
      $variables['attributes']['class'][] = 'text-content';
      $variables['#attached']['library'][] = 'olivero/olivero.table';
    }
    if ($variables['field_type'] == 'image' && $variables['element']['#view_mode'] == 'full' && !$variables["element"]["#is_multiple"] && $variables['field_name'] !== 'user_picture') {
      $variables['attributes']['class'][] = 'wide-content';
    }
  }
  
  /**
   * Implements hook_preprocess_HOOK() for field-multiple-value-form.
   */
  public function preprocessFieldMultipleValueForm(&$variables) : void {
    // Make disabled available for the template.
    $variables['disabled'] = !empty($variables['element']['#disabled']);
    if (!empty($variables['multiple'])) {
      // Add an additional CSS class for the field label table cell.
      // This repeats the logic of
      // \Drupal\Core\Field\FieldPreprocess::preprocessFieldMultipleValueForm()
      // without using '#prefix' and '#suffix' for the wrapper element.
      //
      // If the field is multiple, we don't have to check the existence of the
      // table header cell.
      $header_attributes = [
        'class' => [
          'form-item__label',
          'form-item__label--multiple-value-form',
        ],
      ];
      if (!empty($variables['element']['#required'])) {
        $header_attributes['class'][] = 'js-form-required';
        $header_attributes['class'][] = 'form-required';
      }
      // Using array_key_first() for addressing the first header cell would be
      // more elegant here, but we can rely on the related theme.inc preprocess.
      // @todo change this after https://www.drupal.org/node/3099026 has landed.
      $variables['table']['#header'][0]['data'] = [
        '#type' => 'html_tag',
        '#tag' => 'h4',
        '#value' => $variables['element']['#title'],
        '#attributes' => $header_attributes,
      ];
      if ($variables['disabled']) {
        $variables['table']['#attributes']['class'][] = 'tabledrag-disabled';
        $variables['table']['#attributes']['class'][] = 'js-tabledrag-disabled';
        // We will add the 'is-disabled' CSS class to the disabled table header
        // cells.
        $header_attributes['class'][] = 'is-disabled';
        foreach ($variables['table']['#header'] as &$cell) {
          if (is_array($cell) && isset($cell['data'])) {
            $cell = $cell + [
              'class' => [],
            ];
            $cell['class'][] = 'is-disabled';
          }
          else {
            // We have to modify the structure of this header cell.
            $cell = [
              'data' => $cell,
              'class' => [
                'is-disabled',
              ],
            ];
          }
        }
      }
    }
  }
  
  /**
   * Implements hook_preprocess_HOOK() for menu-local-task.
   */
  public function preprocessMenuLocalTask(&$variables) : void {
    $variables['link']['#options']['attributes']['class'][] = 'tabs__link';
    $variables['link']['#options']['attributes']['class'][] = 'js-tabs-link';
    // Ensure is-active class is set when the tab is active. The generic active
    // link handler applies stricter comparison rules than what is necessary for
    // tabs.
    if (isset($variables['is_active']) && $variables['is_active'] === TRUE) {
      $variables['link']['#options']['attributes']['class'][] = 'is-active';
    }
    if (isset($variables['element']['#level'])) {
      $variables['level'] = $variables['element']['#level'];
    }
  }
  
  /**
   * Implements hook_preprocess_HOOK() for fieldset.
   */
  public function preprocessFieldset(&$variables) : void {
    $element = $variables['element'];
    $composite_types = [
      'checkboxes',
      'radios',
    ];
    if (!empty($element['#type']) && in_array($element['#type'], $composite_types) && !empty($variables['element']['#children_errors'])) {
      $variables['legend_span']['attributes']->addClass('has-error');
    }
    if (!empty($element['#disabled'])) {
      $variables['legend_span']['attributes']->addClass('is-disabled');
      if (!empty($variables['description']) && !empty($variables['description']['attributes'])) {
        $variables['description']['attributes']->addClass('is-disabled');
      }
    }
    // Remove 'container-inline' class from the main attributes and add a flag
    // instead.
    // @todo remove this after https://www.drupal.org/node/3059593 has been
    //   resolved.
    if (!empty($variables['attributes']['class'])) {
      $container_inline_key = array_search('container-inline', $variables['attributes']['class']);
      if ($container_inline_key !== FALSE) {
        unset($variables['attributes']['class'][$container_inline_key]);
        $variables['inline_items'] = TRUE;
      }
    }
  }
  
  /**
   * Implements hook_theme_suggestions_HOOK_alter().
   */
  public function themeSuggestionsUserAlter(&$suggestions, $variables) : void {
    $suggestions[] = 'user__' . $variables['elements']['#view_mode'];
  }
  
  /**
   * Implements hook_preprocess_HOOK().
   */
  public function preprocessFieldNodeCreated(&$variables) : void {
    foreach (Element::children($variables['items']) as $item) {
      unset($variables['items'][$item]['content']['#prefix']);
    }
  }
  
  /**
   * Implements hook_preprocess_HOOK() for setting classes.
   */
  public function preprocessFilterCaption(&$variables) : void {
    $variables['classes'] = isset($variables['classes']) && !empty($variables['classes']) ? $variables['classes'] . ' caption' : 'caption';
  }
  
  /**
   * Implements hook_form_FORM_ID_alter() for node_preview_form_select.
   */
  public function formNodePreviewFormSelectAlter(&$form, FormStateInterface $form_state, $form_id) : void {
    $form['backlink']['#options']['attributes']['class'][] = 'button';
    $form['backlink']['#options']['attributes']['class'][] = 'button--small';
    $form['backlink']['#options']['attributes']['class'][] = 'button--icon-back';
    $form['backlink']['#options']['attributes']['class'][] = 'button--primary';
    $form['view_mode']['#attributes']['class'][] = 'form-element--small';
  }
  
  /**
   * Implements hook_preprocess_HOOK() for comment.
   */
  public function preprocessComment(&$variables) : void {
    // Getting the node creation time stamp from the comment object.
    $date = $variables['comment']->getCreatedTime();
    // Formatting "created" as "X days ago".
    $variables['created'] = $this->t('@time ago', [
      '@time' => \Drupal::service('date.formatter')->formatInterval(\Drupal::time()->getRequestTime() - $date),
    ]);
  }
  
  /**
   * Implements hook_preprocess_HOOK() for field--comment.
   */
  public function preprocessFieldComment(&$variables) : void {
    // Add a comment_count.
    $variables['comment_count'] = count(array_filter($variables['comments'], 'is_numeric', ARRAY_FILTER_USE_KEY));
    // Add user.compact to field-comment if profile's avatar of current user
    // exist.
    $user = \Drupal::currentUser();
    if ($user->isAuthenticated() && $user instanceof UserInterface) {
      if ($user->hasField('user_picture') && !$user->get('user_picture')
        ->isEmpty()) {
        $variables['user_picture'] = \Drupal::entityTypeManager()->getViewBuilder('user')
          ->view($user, 'compact');
      }
      $variables['#cache']['contexts'][] = 'user';
    }
  }
  
  /**
   * Implements hook_element_info_alter().
   */
  public function elementInfoAlter(&$info) : void {
    if (array_key_exists('text_format', $info)) {
      $info['text_format']['#pre_render'][] = [
        OliveroPreRender::class,
        'textFormat',
      ];
    }
    if (isset($info['status_messages'])) {
      $info['status_messages']['#pre_render'][] = [
        OliveroPreRender::class,
        'messagePlaceholder',
      ];
    }
  }
  
  /**
   * Implements hook_preprocess_HOOK() for text-format-wrapper.
   *
   * @todo Remove when https://www.drupal.org/node/3016343 is fixed.
   */
  public function preprocessTextFormatWrapper(&$variables) : void {
    $description_attributes = [];
    if (!empty($variables['attributes']['id'])) {
      $description_attributes['id'] = $variables['attributes']['aria-describedby'] = $variables['attributes']['id'];
      unset($variables['attributes']['id']);
    }
    $variables['description_attributes'] = new Attribute($description_attributes);
  }
  
  /**
   * Implements hook_preprocess_search_result().
   */
  public function preprocessSearchResult(&$variables) : void {
    // Apply custom date formatter to "date" field.
    if (!empty($variables['result']['date'])) {
      $variables['info_date'] = \Drupal::service('date.formatter')->format($variables['result']['node']->getCreatedTime(), 'olivero_medium');
    }
  }
  
  /**
   * Implements hook_preprocess_item_list__search_results().
   */
  public function preprocessItemListSearchResults(&$variables) : void {
    if (isset($variables['empty'])) {
      $variables['empty']['#attributes']['class'][] = 'empty-search-results-text';
      $variables['empty']['#attached']['library'][] = 'olivero/search-results';
    }
  }
  
  /**
   * Implements hook_preprocess_links__comment().
   */
  public function preprocessLinksComment(&$variables) : void {
    foreach ($variables['links'] as &$link) {
      $link['link']['#options']['attributes']['class'][] = 'comment__links-link';
    }
  }
  
  /**
   * Implements hook_preprocess_table().
   */
  public function preprocessTable(&$variables) : void {
    // Mark the whole table and the first cells if rows are draggable.
    if (!empty($variables['rows'])) {
      $draggable_row_found = FALSE;
      foreach ($variables['rows'] as &$row) {
        /** @var \Drupal\Core\Template\Attribute $row['attributes'] */
        if (!empty($row['attributes']) && $row['attributes']->hasClass('draggable')) {
          if (!$draggable_row_found) {
            $variables['attributes']['class'][] = 'draggable-table';
            $draggable_row_found = TRUE;
          }
        }
      }
    }
    $variables['#attached']['library'][] = 'olivero/olivero.table';
  }
  
  /**
   * Implements hook_preprocess_HOOK() for views-view-table.
   */
  public function preprocessViewsViewTable(&$variables) : void {
    $variables['#attached']['library'][] = 'olivero/olivero.table';
  }
  
  /**
   * Implements hook_form_FORM_ID_alter() for views_exposed_form.
   */
  public function formViewsExposedFormAlter(&$form) : void {
    $form['#attributes']['class'][] = 'form--inline';
  }

}

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.