Drupal 8: Theme Negotiator — программное переключение тем

Небольшая заметка о том, как просто переключать темы на сайте для конкретных страниц или разделов.

30.08.2016
5 комментариев
4 мин.

Иногда бывают такие моменты, что есть необходимость создать для каких-то конкретных страниц, разделов или конкретной страницы. Целей то может быть и много, но задача тут одна. В 8-ке для этого есть theme negotiator. Это такой небольшой сервис который и позволяет определить системе какую тему подключать на той или иной странице, в условиях Drupal 8, на том или ином роутинге.

Тема, которая так или иначе может быть активирована (установлена), должна быть включена в Appearance

Первым делом давайте объявим наш theme negotiator. Это простенький класс всего с двумя необходимыми методами:

  1. applies(RouteMatchInterface $route_match) - метод который отвечает за условия выбора. Если он возвращает TRUE, тогда вызывается следующий метод, если false, то данный negotiator пропускается. По факту, вся логика выборки должна находиться здесь.
  2. determineActiveTheme(RouteMatchInterface $route_match) - метод вызываемый в случае если applies() вернул TRUE.

Давайте рассмотрим на примере, например, включив тему Stark для главной страницы. Первым делом надо включить (установить) данную тему в админке. Так как эта тема в ядре, то просто жмем install и идем далее.

Теперь нужно объявить наш класс. Он должен располагаться в /src/Theme. Назовем его StarkForFront и заполним следующим кодом.

Листинг /src/Theme/StarkForFront.php
<?php

namespace Drupal\dummy\Theme;

use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Theme\ThemeNegotiatorInterface;
use Drupal\Core\Path\PathMatcherInterface;

/**
 * Sets the Stark for frontpage.
 */
class StarkForFront implements ThemeNegotiatorInterface {

  /**
   * @var \Drupal\Core\Path\PathMatcherInterface
   */
  protected $pathMatcher;

  /**
   * StarkForFront constructor.
   * @param \Drupal\Core\Path\PathMatcherInterface $pathMatcher
   */
  public function __construct(PathMatcherInterface $pathMatcher) {
    $this->pathMatcher = $pathMatcher;
  }

  /**
   * {@inheritdoc}
   */
  public function applies(RouteMatchInterface $routeMatch) {
    return $this->pathMatcher->isFrontPage();
  }

  /**
   * {@inheritdoc}
   */
  public function determineActiveTheme(RouteMatchInterface $routeMatch) {
    # Машинное имя темы, которую необходимо активировать.
    return 'stark';
  }
}

И последним, вторым, шагом станет то, что мы данный класс добавим в сервисы. Для этого нужно добавить в корень модуля файл MYMODULE.services.yml и объявить свой сервис.

services:
  theme.negotiator.stark_for_front:
    class: Drupal\dummy\Theme\StarkForFront
    arguments: ['@path.matcher']
    tags:
      - { name: theme_negotiator, priority: -40 }

Как вы можете заметить, мы также можем указывать приоритет. Если сработают сразу два theme negotiator на одной странице, то приоритет отдастся тому, у которого будет больший приоритет. У theme negotiator из ядра приоритет -100, так что имейте это ввиду.

Ну и чтобы убедиться что всё сделали правильно, заходим на главную страницу сайта и смотрим на результат.

Stark на главной.

Не пугайтесь, это не развалившаяся тема, а Stark, так что всё правильно :)

Вот так вот просто можно переключать темы.

Ну и небольшая подсказка на последок.

Включение темы для определенного типа содержимого.
public function applies(RouteMatchInterface $route_match) {
  if ($route_match->getRouteName() == 'entity.node.canonical') {
    $node = $route_match->getParameter('node');
    if ($node->bundle() == 'NODE_TYPE') {
      return TRUE;
    }
  }
  return FALSE;
}

P.s. Спасибо drupby за подсказку, как оптимизировать то что было, код в статье чутка исправлен.

Drupal
Drupal 8
Темизация

Комментарии

Андрей   вт, 24/04/2018 - 17:24

А можно еще пример переключение между Мобильной и Десктопной темами на основе UserAgent'a?

Niklan   вт, 24/04/2018 - 18:36

В applies() получать \Drupal::request()->headers->get('User-Agent') и определять нужна мобильная или десктопная тема. Я думаю тут лучше подрубать что-то дополнительно со стороны. Например serbanghita/Mobile-Detect и при помощи него определять мобилка или нет.

Websash   вт, 19/06/2018 - 11:35

Полезная возможность, можно на основе некоторых условий выводить разный дизайн сайта, что можно использовать для повышения его юзабилити.

Andrey A   вс, 05/04/2020 - 12:08

Большое спасибо, Никита! Очень помогло.

Была задача вывести на отдельной странице (используя небольшой кастомный template) форму добавления перевода для ноды (например, такую /node/47/translations/add/en/da), чтобы можно было изменить внешний вид. Планировалось дать доступ для использования этой формы определенным ролям.

Хотел просто вытащить через \Drupal::formBuilder()->getForm(...). Оказалось, что работы с переводом используется стандартная форма добавления ноды, и через getForm вытаскивается она. И эта стандартная форма переопределяется в модуле "content_translation".

Долго бился, ковырял модуль content_translation - создать такую форму на кастомной странице не получалось.

Случайно наткнулся на эту статью и просто повесил свою тему на на уже существующую форму по существующему роуту для нужных ролей. А далее css, js и hook_form_alter сделали свое дело.

Но все равно было бы полезно знать, как можно было отобразить форму добавления (а также и редактирования) перевода ноды в кастомном темплейте. Буду благодарен за подсказку.

ikode   чт, 12/11/2020 - 09:49

А что нужно написать в applies(), чтобы срабатывало на то, когда в пути (адресной строке сайта) появлялось слово special?