Nuancier

Drupal propose par défaut deux thèmes aux utilisateurs. Le thème général du site qui peut être Bartik, Unami (Drupal Core), un thème de la communauté ou votre thème personnel. Puis il y a le thème de l'administration, qui est souvent le thème Seven présent dans le cœur de Drupal. Il est donc possible de demander à ce que tel ou tel thème soit utilisé lors du rendu. Voyons ça un peu plus précisément ...

Cas d'utilisation

Dans un projet Drupal, il est très fréquent que l'on définisse différents rôles concernant les utilisateurs du site. Lorsqu'un utilisateur à un rôle de correcteur, d'administrateur ... il a généralement accès à des pages bien spécifiques, avec des accès rapides à des outils pour lui faciliter le travail. Pour ce cas, il est intéressant d'utiliser un thème dédié car la structure peut être totalement différente du site, dit, "Grand public". Dans ce cas il est possible de changer le thème en fonction des pages sur lesquelles est l'utilisateur.

Lorsqu'un site possède des utilisateurs et qu'il offre à ceux-ci une personnalisation assez fine, on peux lui proposer des options lui demandant sous quelle structure il préfère voir le site. Ici on ne parle pas juste de quelques couleurs différentes mais bien d'une structure spécifique à chaque choix.

Drupal le fait déjà...

Pour savoir quel thème doit être utilisé en fonction de la page, le service 'theme.negotiator.admin_theme' check si l'utilisateur actuel possède les bons droits et si la route est une page d'administration. Dans le cas où ces tests sont positifs, ce service demande à Drupal d'utiliser le thème d'administration. (Configurable sur cette page https://www.monsite.fr/admin/appearance )

Donc pourquoi pas moi !

Déclaration du service

Qui dit service dit déclaration de service dans un module. Commençons par créer un module puis déclarons le fichier monmodule.services.yml avec le contenu suivant :

services:
  theme.negotiator.myodule:
    class: Drupal\mymodule\Theme\CustomNegotiator
    arguments: ['@current_user']
    tags:
      - { name: theme_negotiator, priority: 1 }

Les informations importantes présentes dans cet extrait sont :

  • arguments : Il n'y a aucune obligation de fournir des dépendances à notre classe, c'est juste plus pratique de lui passer '@current_user' si on doit vérifier les autorisations d’accès de l'utilisateur courant.
  • tags : Pour que notre classe soit bien appelée au bon moment, nous devons indiquer à Drupal que le service que nous déclarons sera utiliser pour choisir le thème à afficher et est donc un service du type theme_negotiator.

Création de la classe

Une fois le service déclaré, nous pouvons créer la classe CustomNegotiator qui devra être placée dans le dossier modules/custom/mymodule/src/Theme.

<?php

namespace Drupal\mymodule\Theme;

use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Theme\ThemeNegotiatorInterface;

/**
 * Sets the active theme on hospitality pages.
 */
class CustomNegotiator implements ThemeNegotiatorInterface {

  /**
   * The current user.
   *
   * @var \Drupal\Core\Session\AccountInterface
   */
  protected $user;


  /**
   * HospitalityTeamNegotiator constructor.
   *
   * @param \Drupal\Core\Session\AccountInterface $user
   */
  public function __construct(AccountInterface $user) {
    $this->user = $user;
  }

  /**
   * {@inheritdoc}
   */
  public function applies(RouteMatchInterface $route_match) {
    return (!empty($route_match->getRouteObject())
    &&$route_match->getRouteObject()->hasOption('_custom_param_on_route')
      && $this->user->hasPermission('custom permission page access'))
    );
  }

  /**
   * {@inheritdoc}
   */
  public function determineActiveTheme(RouteMatchInterface $route_match) {
    return 'alternative_theme';
  }

}

L'interface ThemeNegotiatorInterface met à disposition deux méthodes similaires aux méthodes de la classe applies() et build().

Nous avons ici une méthode, applies(), qui renvoie un boolean indiquant si cette classe est compétente pour juger du thème à utiliser.

Dans l'exemple ci-dessous, la classe peut définir le thème si la route en court de construction possède une option 'custom_param_on_route' et que l'utilisateur actuel possède un droit spécifique.

Si ces conditions sont remplies, alors la méthode determineActiveTheme() est appelé, et devra retourner le nom du thème qui sera utilisé. Dans l'exemple, le nom du thème est écrit en dur mais rien ne vous empêche de faire un formulaire dans le BO afin de lister les thèmes actifs pour qu'un administrateur puisse choisir utiliser pour notre fonctionnalité.

Choix du thème par route

Dans l'exemple ci-dessus on a utilisé une option présente sur la route pour savoir si notre classe été compétente pour gérer le choix de la classe ou non.

En modifiant, légèrement, le code ci-dessus il est possible de mettre le nom de thème en option dans la déclaration de la route puis faire retourner la valeur depuis la méthode determineActiveTheme() de notre classe CustomNegotiator. Ceci permettra aux développeur de choisir quel thème utiliser pour chaque route directement dans le fichier mymodule.routing.yml ou depuis un service RouteSubscriber.

Routes mymodule.routing.yml :

mymodule.random_page:
  path: '/random-page'
  defaults:
    _controller: '\Drupal\mymodule\Controller\MyModuleController::random'
    _title: 'Random Page'
  requirements:
    _role: 'administrator'
  options:
    _custom_theme: 'awesome_theme'


mymodule.boring_page:
  path: '/boring-page'
  defaults:
    _controller: '\Drupal\mymodule\Controller\MyModuleController::boring'
    _title: 'Boring Page'
  requirements:
    _permission: 'access content'
  options:
    _custom_theme: 'flat_theme'

Service mymodule.services.yml :

services:
  theme.negotiator.mymodule:
    class: Drupal\mymodule\Theme\CustomNegotiator
    tags:
      - { name: theme_negotiator, priority: 1 }

Classe CustomNegotiator.php :

<?php

namespace Drupal\mymodule\Theme;

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

/**
 * Sets the active theme on hospitality pages.
 */
class CustomNegotiator implements ThemeNegotiatorInterface {



  /**
   * {@inheritdoc}
   */
  public function applies(RouteMatchInterface $route_match) {
    return (!empty($route_match->getRouteObject())
    &&$route_match->getRouteObject()->hasOption('_custom_theme'))
    );
  }

  /**
   * {@inheritdoc}
   */
  public function determineActiveTheme(RouteMatchInterface $route_match) {
    return $route_match->getRouteObject()->getOption('_custom_theme');
  }

}

 

Avec ce code le thème utilisé pour l'url https://www.monsite.fr/random-page sera le thème awesome_theme tandis que le thème boring_theme sera affiché pour l'url https://www.monsite.fr/boring-page