Articles de l'utilisateur

Par kgaut
Kevin Gautreau

Drupal 8 & 9 - Menu - définir comme parent un élément de menu créé via l'UI

Dans le fichier mon_module.links.menu.yml, on peut facilement placer un élément de menu dans une arborescence via la clé parent.

mon_module.admin_mon_module_config_form:  title: 'Configuration Mon Module'  route_name: mon_module.admin_resalys_config_form  description: 'Configuration webservice de mon module'  parent: system.admin_config_services  weight: 10

Ici le parent fait référence à la clé d'un autre élément de menu.

Mais parfois le parent voulu a été créé via l'interface, et donc le parent doit être référencé via son UUID :

mon_module.admin.structure.settings:  title: 'Partner settings'  description: 'Configure Partner entities'  route_name: partner.settings  parent: 'menu_link_content:4bbda5ed-eeee-4eab-be01-e3d7349f4daa'

L'UUID est trouvable dans la base de données, ou bien en passant par l'onglet devel de l'élément de menu « parent » voulu :

Image

Drupal menu item uuid

Par kgaut
Kevin Gautreau

Drupal 8 & Drupal 9 - Utiliser des vues différentes selon les taxonomies

À partir de drupal 8, c'est une vue qui gère la page des contenus d'un terme de taxonomie.

Le soucis est que c'est la même vue pour l'ensemble des vocabulaires.

Pour le site courant je voulais une page différente pour les catégories de module (exemple avec la catégorie backoffice) de la page par défaut (exemple avec le tag drupal).

Pour cela il faut surcharger le contrôleur gérant les vues, et n'ayez pas peur c'est moins compliqué à faire qu'à dire.

Déclaration du service

Le service va altérer la route /taxonomy/term/% pour changer le contrôleur qui sera executé

modules/custom/mon_module/mon_module.services.yaml

services:  mon_module.route_subscriber:    class: Drupal\mon_module\Routing\TaxonomyListingViewsRoutingSubscriber    tags:      - { name: event_subscriber }

Définition du service

modules/custom/mon_module/src/Routing/TaxonomyListingViewsRoutingSubscriber.php

get('entity.taxonomy_term.canonical')) {      $route->setDefault('_controller', '\Drupal\mon_module\Controller\TaxonomyTermViewPageController::handle');    }  }   public static function getSubscribedEvents() {    $events = parent::getSubscribedEvents();    $events[RoutingEvents::ALTER] = array('onAlterRoutes', -180);    return $events;  } }

Surcharge du contrôleur

modules/custom/mon_module/src/Controller/TaxonomyTermViewPageController.php

getParameter('taxonomy_term');     $vid = $term->get('vid')->first()->getValue();    $vid = $vid['target_id'];     //on teste le vocabulaire    if ($vid === 'modules_categories') {      $view_id = 'front_modules';      $display_id = 'page_categories';    }     return parent::handle($view_id, $display_id, $route_match);  } }

 

Par kgaut
Kevin Gautreau

Drupal - Améliorer l'expérience d'administration

Note : Quand on débute avec drupal, le plus dur est de connaître l'ensemble de l'écosystème. Entre-autres, l'ensemble des modules tiers (contrib) qui existent. J'inaugure une nouvelle section sur ce site avec la présentation (rapide) de différents modules que je conseille. Cela se passe sur la page Modules Drupal. Je vais tenter aussi de temps en temps de mettre en avant des modules par thématique. c'est le cas ici.

Le backoffice de drupal est réputé pour être austère, même si cela s'est amélioré depuis les dernières version, ça n'est pas complètement faux !

Mais via quelques modules et thème il est possible d'améliorer grandement cette expérience.

Vous trouverez ci-dessous quelques modules que je conseille dans cet objectifs. N'hésitez-pas à ajouter les votre.

Adminimal

 Backoffice
 Thème

Le thème d'administration que j'utilise sur la plupart de mes sites. En en faisant un thème enfant évidement.

Lire la suite de Adminimal

 

Admin Toolbar

 Backoffice
 Indispensable

Admin Toolbar transforme la barre d'administration native de drupal (module toolbar) en menu déroulant. Oui ce module est indispensable.

À noter, ce module contient différents sous-modules :

Lire la suite de Admin Toolbar

 

Coffee

 Backoffice
 Indispensable

Le module Coffee permet aux administrateur de déclencher via un raccourci clavier (alt+k ou alt+d) un petit moteur de recherche qui indexe tous les éléments du menu d'administration.

Ainsi, plus besoin de chercher pendant des heures où est cachée la page de configuration de cet obscure module.

Lire la suite de Coffee

 

Field Group

 Backoffice

Permet de regrouper des champs dans des onglets sur les écrans de création de contenu.

Il est ainsi possible d'avoir des formulaires mieux « rangés »

Lire la suite de Field Group

 

Module filter

 Backoffice

Quand on commence à avoir une liste de module importante, la page par défaut n'est pas très utilisable.

Ce module permet de regrouper les modules par package dans des onglets et ajoute un champ de recherche javascript.

Lire la suite de Module filter

 

Environment Indicator

 Backoffice

Ce module tout simple permet de modifier la couleur de la barre d'administration en fonction d'un environnement (prod / preprod / staging / dev...)

La sélection de l’environnement courant peut se faire de plusieurs façon, le plus simple à mon sens est via le fichier settings.local.php :

Lire la suite de Environment Indicator

 

Node Edit Protection

 Backoffice
 Rédaction contenu
 Indispensable

Il vous est déjà arrivé de travailler sur un contenu pendant un certain temps sur votre site, et de quitter la fenêtre par erreur ou bien de faire précédent, et ainsi de perdre tout ce que vous aviez fait ? Moi oui...

Ce module ajoute une demande de confirmation javascript dans ces cas là ❤️

Lire la suite de Node Edit Protection

Par kgaut
Kevin Gautreau

Drupal 8 et Drupal 9 - Créer une archive Zip

Voici comment créer une archive zip et y ajouter des fichiers.

Pour commencer, nous aurons besoin d'injecter deux services, (ici je suis dans un formulaire, à adapter en fonction du contexte)

  public static function create(ContainerInterface $container) {    $instance = parent::create($container);    $instance->fileSystem = $container->get('file_system');    $instance->archiver = $container->get('plugin.manager.archiver');    return $instance;  }

Création de l'archive en tant que telle :

// $files contient un tableau de chemin de fichiers à ajouter à l'archive$files = ['private://monfichier1.pdf', 'private://monfichier2.pdf']; // Chemin où sera enregistré l'archive$archivePath = 'private://pdf-export/mon-archive.zip'; // On crée l'archive physiquement sur le disque, avec un contenu vide// FileSystemInterface::EXISTS_REPLACE indique que si le fichier existe déjà on le remplace, il existe d'autres options$this->fileSystem->saveData('', $archivePath, FileSystemInterface::EXISTS_REPLACE); //On récupère l'objet Zip pointant vers l'archive que nous venons de créer./** @var Zip $zip */$zip = $this->archiver->getInstance(['filepath' => $archivePath]); foreach ($files as $file)  {  $filepath = $this->fileSystem->realpath($file);  $zip->add($filepath, $filename);}

Et voila, cela fonctionne mais ça n'est pas forcément top... Pourquoi pas forcément top ? car toute l’arborescence des différents fichiers est recrée dans l'archive :

Image

Archive Zip

 Ça n'est pas forcement très gênant, mais dans mon cas je préférais avoir tous les fichiers à la racine de mon archive. Voila comment faire :

// $files contient un tableau de chemin de fichiers à ajouter à l'archive$files = ['private://monfichier1.pdf', 'private://monfichier2.pdf']; // Chemin où sera enregistré l'archive$archivePath = 'private://pdf-export/mon-archive.zip'; // On crée l'archive physiquement sur le disque, avec un contenu vide// FileSystemInterface::EXISTS_REPLACE indique que si le fichier existe déjà on le remplace, il existe d'autres options$this->fileSystem->saveData('', $archivePath, FileSystemInterface::EXISTS_REPLACE); //On récupère l'objet Zip pointant vers l'archive que nous venons de créer./** @var Zip $zip */$zip = $this->archiver->getInstance(['filepath' => $archivePath]); // $zip est juste un wrapper Drupal vers un objet \ZipArchive que l'on va récuprérer ici dans $zipArchive$zipArchive = $zip->getArchive(); foreach ($files as $file)  {  // on récupère le nom de fichier, sans le chemin  $filename = basename($file);  $filepath = $this->fileSystem->realpath($file);   // on ajoute le fichier à l'archive mais en indiquant comme chemin uniquement le nom du fichier, ainsi, plus de création de dossier  $zipArchive->addFile($filepath, $filename);}

 

Par kgaut
Kevin Gautreau

Drupal 8 et Drupal 9 - EntityQuery - faire une condition sur un champ d'une entité liée

Petite découverte datant d'il y a quelques mois, il est possible via une EntityQuery d'ajouter une condition sur un champ d'une entité liée.

Petit exemple :

J'ai un type d'entité game (match), lié à un type d'entité day (journée), lui même lié à un type d'entité league (compétition)

(Match : OM - ASSE, journée : Journée 1, Compétition : Ligue 1 2020-2021)

Image

MPD MesPronos

 

Si je souhaite récupérer tous les matchs d'une compétition donnée (mettons la 12,) je peux me baser sur l'attribut days.league : 

$query = \Drupal::entityQuery('game');$query->condition('day.entity:day.league', 12);$ids = $query->execute();

 

Mais on peut aussi aller plus loin et remonter encore d'un niveau pour faire une condition sur un attribut de la compétition de la journée des matchs. Pour ainsi récupérer l'ensemble des matchs des compétitions active (attribut leagues.status) :

$query = \Drupal::entityQuery('game');$query->condition('day.entity:day.league.entity:league.status', 'active');$ids = $query->execute();

Source : https://www.drupaleasy.com/quicktips/drupal-8-entity-query-across-throu…, partagé sur le slack de l'association Drupal France

Par kgaut
Kevin Gautreau

Les forks et merge-requests arrivent (enfin) sur drupal.org !

Depuis la migration des dépôts du code de Drupal ainsi que de l'ensemble des modules tiers sur Gitlab, nous sommes nombreux à attendre que l'ensemble des possibilités offertes par ce changement soient accessibles.

Une d'entre elle qui simplifiera grandement la proposition de modification de code par les néophytes est la possibilité de faire des « merge requests », à l'image de ce qui se fait sur une instance gitlab ou bien des pull-requests sur github.com.

Actuellement pour proposer une modification sur le core de Drupal ou l'un de ses modules tiers, il faut cloner le dépôt sur sa machine, faire les modifications, générer un patch et enfin proposer le patch. Procédure qui n'est pas inaccessible, mais qui n'est pas simple pour les nouveaux venus. Surtout quand on veut corriger juste un petit truc.

Le nouveau fonctionnement sera beaucoup plus simple. Il faudra commencer par créer un issue sur le projet que l'on souhaite amender :

Image

Drupal create issue

 

Note : retrouvez le ticket ici : https://www.drupal.org/project/apidae_tourisme/issues/3160451

Une fois cette issue créé, l'issue tracker de drupal proposera de créer un fork (copie) du dépôt. Fork qui sera dédié uniquement au traitement de ce ticket. Si deux personnes veulent proposer deux approches pour un même problème, alors deux forks peuvent être créés.

Image

Drupal issue fork

 

L'adresse du fork en question : https://git.drupalcode.org/issue/apidae_tourisme-3160451

Une fois créé, le fork sera clonable et/ou éditable en ligne directement via le web IDE de Gitlab :

Image

Gitlab web ide

 

Au commit, l'IDE nous proposera de directement créer une merge request, fonctionnalité qui est aussi possible depuis l'interface de Gitlab si l'on fait nos modifications en local et pas dans le web IDE.

Image

gitlab drupal merge request

 

Voici l'écran de création d'une merge request :

Image

Drupal merge request creation

 

Une fois créée, la MR est visible dans le ticket :

Image

Drupal issue merge request

 

Image

Drupal issue merge request 2

 

Ensuite l'écran d'une merge request permet de voir les modifications, de faire des commentaires, et d'accepter ou non la modification (voir en ligne : https://git.drupalcode.org/project/apidae_tourisme/-/merge_requests/1)

Image

Drupal merge request changes

 

Image

Drupal merge request screen

 

Ainsi on peut se passer complètement de la procédure de patch, même pour des grosses modifications ! Je trouve personnellement la procédure plus simple mais surtout plus accessible pour un·e néophyte.

Actuellement la fonctionnalité est encore en beta, il faut demander l'activation pour un ou plusieurs de ses projets personnels sur ce ticket : https://www.drupal.org/project/drupalorg/issues/3152637.

Si vous voulez jouer un peu et tester la procédure, n'hésitez pas à le faire sur le dépôt pour lequel j'ai activé la fonctionnalité, je ferai le ménage plus tard : https://www.drupal.org/project/apidae_tourisme/issues/3160451 (vous prouvez créer de nouveaux tickets si besoin en mettant « test » dans le titre.

Par kgaut
Kevin Gautreau

Drupal 8 et Drupal 9 - Créer une commande drush personnalisée

Voici un exemple rapide de commande drush appelant une méthode d'un service avec une option lors de l'appel.

À noter : Drupal\mon_module\Service\Resalys est un service existant, je souhaite utiliser la méthode syncResalys de ce service qui accepte un paramètre optionnel : $force

Déclaration de la commande

modules/mon_module/drush.services.yml

services:  mon_module.drush.resalys: # clé du fichier de commande, à définir vous même    class: Drupal\mon_module\Command\ResalysCommand # Namespace de la classe contenant la commande    arguments:      - '@mon_module.resalys' # Service à injecter dans notre commande    tags:      -  { name: drush.command }

Définition de la commande

modules/mon_module/src/Command/ResalysCommand.php

resalys = $resalys;  }   /**   * Sync resalys cache   *   * @command resalys:sync   * @usage drush resalys:sync   * @usage drush resalys:sync --force   *   * @param int[] $options   */  public function sync($options = ['force' => FALSE]) {    $force = (bool) $options['force'];    $this->resalys->syncResalys($force);  } }

Utilisation

drush resalys:syncdrush resalys:sync --force

 

Par kgaut
Kevin Gautreau

Drupal 8 et Drupal 9 - Attacher une librairie CSS ou JS à une vue

Voici comment inclure des fichiers CSS ou JS sur une page contenant une vue.

Pour rappel les librairies peuvent être définies dans un thème ou un module.

Utilisation du hook HOOK_views_pre_render.

Dans le .module de votre module :

function MON_MODULE_views_pre_render(\Drupal\views\ViewExecutable $view) {  if ($view->storage->id() === 'front_blog') {    $view->element['#attached']['library'][] = 'theme/swiper-library';    $view->element['#attached']['library'][] = 'theme/univers-js';  }}

 

Par kgaut
Kevin Gautreau

Drupal 8 & Drupal 9 - Entity Query - Ajouter une condition sur une colonne spécifique

Dans le cadre d'une EntityQuery, il peut être nécessaire parfois de faire une requête sur une colonne spécifique de notre table, autre que le traditionnel « value ».

Rien de bien compliqué, il faut alors le spécifier dans le nom du champ sur lequel on ajoute une condition.

Exemple normal :

// Condition sur la colonne « value » du champ « field type »$query->condition('field_type', 'FETE_ET_MANIFESTATION');

Pour spécifier la colonne, on concatène le nom de la colonne avec le nom du champ avec un point :

// Condition sur la colonne « dateDebut » du champ « field_apidae_dates »$query->condition('field_apidae_dates.dateDebut', $now->format('Y-m-d'), '>');

 

Par kgaut
Kevin Gautreau

Accélérer l'administration de son site drupal avec Makefile

Un Makefile est un fichier contenant des rules, (des fonctions) permettant d’exécuter une suite d'action. Beaucoup utilisé dans les langages compilés pour permettre de gérer la compilation d'un programme, c'est aussi utilisable dans le cadre d'un projet php.

J'ai découvert les Makefile après avoir commencé à utiliser docker pour mes projets web.

Quand on utilise docker pour le php les binaires php (drush, composer, drupal-console...) sont à exécuter dans le container php et non pas sur notre machine hôte.

Ce qui donne des commandes ressemblant à :

docker-compose exec php drush crdocker-compose exec php composer install

au lieu de simples

drush crcomposer install

ça n'est pas la mort, mais un développeur est feignant.

L'ensemble d'images docker docker4drupal propose un Makefile avec des rules permettant d’exécuter drush ou composer plus simplement :

make drush crmake composer install

C'est très pratique, mais on peut aller encore plus loin et écrire nos propres rules en les ajoutant à la suite du fichier. Voici par exemple une rules pour supprimer la base de données et la recréer :

## db-empty	: drop and recreate database.PHONY: db-emptydb-empty:	@docker-compose exec -T $(DB_HOST) mysql -u"$(DB_USER)" -p"$(DB_PASSWORD)" -e "DROP DATABASE IF EXISTS $(DB_NAME)"	@echo Database $(DB_NAME) dropped	@docker-compose exec -T $(DB_HOST) mysql -u"$(DB_USER)" -p"$(DB_PASSWORD)" -e "CREATE DATABASE $(DB_NAME)"

Un autre exemple pour afficher en continu le contenu du fichier drupal_debug.txt (voir : Drupal - Découvrez la fonction ddm, pour débuguer même en aveugle) :

## dd-tail	: show the tail of drupal-debug.txt file.PHONY: dd-taildd-tail:	tail -f $(LOCAL_TMP_PATH)/drupal_debug.txt

Vous voyez ici l'utilisation de variables, comme $(DB_HOST) qui sont définies dans le fichier .env :

Image

.env variables

À force d'ajouter des rules au fur et a mesure, j'ai finis par en avoir pas mal. J'ai fais un petit boulot de généralisation et tout est maintenant sur un dépôt github : https://github.com/kgaut/drupal-makefile.

L'installation est relativement rapide est simple :

Ajoutez le package à vos dépendances

composer require kgaut/drupal-makefile

Modifiez votre fichier .env et ajoutez les variables nécessaires présentées ici : https://github.com/kgaut/drupal-makefile/blob/master/.env.example

Si vous n'avez pas d'environnement de prod ou de preprod, vous pouvez supprimer les variables correspondantes ou bien les garder pour plus tard.

Enfin, il faudra lier ce nouveau fichier makefile à votre fichier makefile principal, à la racine de votre projet, en ajoutant après la ligne include .env, la ligne suivante :

include vendor/kgaut/drupal-makefile/drupal.mk

Les différentes rules sont présentées ici : https://github.com/kgaut/drupal-makefile#availables-rules

Une que j'utilise régulièrement est : db-prod-import qui fait en fait appel à plein d'autres rules :

Suppression de la base de données local et récréation (via db-empty)
Récupération du dump de base de données le plus récent en prod (via db-prod-get)
Import de ce dump en local (db-import)
Vidage des cache, mises à jour de base de données, import de la configuration et affiche un lien de connexion en tant qu'utilisateur 1 (via db-post-import)

Évidement tout ça est très lié à mon organisation de projet, mais vous pouvez certainement en récupérer des idées ou de l'inspiration.

N'hésitez-pas à suggérer des modifications ou des améliorations !

Par kgaut
Kevin Gautreau

Drupal 8 & 9 - Hook Update - Tester si une table existe, la supprimer et la recréer

Pour un projet drupal, j'avais besoin de créer deux tables identiques dans une base de données séparée sur un serveur différent, afin de servir de cache pour un webservice.

Voici le code que j'ai utilisé pour créer ces tables avec un test si les tables existe avant de les récréer (si j'ai besoin de mettre à jour le modèle par exemple).

Définition du schéma de ma table dans le fichier MODULE.install :

# Le nom de cette fonction est personnel, je n'utilise pas le HOOK_schema() icifunction MODULE_schema_db_cache() {  $schema['proposals'] = [    'description' => 'distribProposal',    'fields' => [],    'primary key' => ['id'],    'indexes' => [      'id' => ['id'],      'room_type_code' => ['room_type_code'],    ],  ];   $schema['proposals']['fields']['id'] = [    'description' => 'id',    'type' => 'serial',    'not null' => TRUE,    'unsigned' => TRUE,  ];   $schema['proposals']['fields']['name'] = [    'type' => 'varchar',    'length' => 255,  ];  //Ajout des champs supplémentaires ici}

Création des tables via un hook_update() :

/** * DB cache ws - Creation tables */function MODULE_core_update_8005() {  try {    // Je passe sur une autre base de données définis dans mes settings     // ici : $databases['cache_ws']['default']    Database::setActiveConnection('cache_ws');    // Je récupère le schéma    $db = MODULE_schema_db_cache();    if($schema = Database::getConnection()->schema()) {      // Je veux avoir deux fois la même table afin de passer sur la seconde quand je met à jour la première      $tables = ['proposals_a', 'proposals_b'];      foreach ($tables as $table) {        // Si la table existe déjà je la supprime        if ($schema->tableExists($table)) {          $schema->dropTable($table);        }        // Puis je la recrée        $schema->createTable($table, $db['proposals']);      }    }  }  catch (Exception $e) {    // En cas de soucis je commence par basculer sur la base de donnée par défaut    Database::setActiveConnection();     // Puis je lance une erreur    throw new \Drupal\Core\Utility\UpdateException(t('vacanceole_core_update_8005 error : @message', ['@message' => $e->getMessage()]));  }    // Si tout s'est bien passé je repasse sur la base de données par défaut.  Database::setActiveConnection();  return t('Tables proposals_a and proposals_b successfully created');}

Pour bien faire je devrais ajouter l'installation de mes tables dans un hook install, en faisant appel à la fonction que je viens de définir :

function MODULE_install() {  MODULE_update_8005();}

 

Par kgaut
Kevin Gautreau

Drupal 8 - Modifier les onglets (local tasks)

Voici comment modifier un onglet Local Task dans le langage drupal, en utilisant le hook HOOK_local_tasks_alter:

function MONMODULE_local_tasks_alter(&$local_tasks) {  $local_tasks['entity.user.edit_form']['title'] = 'Modifier mes informations';}

La clé est définie dans la déclaration de l'élément :

Image

Drupal local task

 

Ici uniquement le titre est changé,il est possible de modifier les autres propriété, comme l'ordre (weight), la destination de l'élément...

Par kgaut
Kevin Gautreau

Drupal - Découvrez la fonction ddm, pour débuguer même en aveugle

Si vous développez avec drupal, vous connaissez peut-être les fonctions dump, dpm ou kint, (sinon, lisez ma rapide présentation de kint.)

Une autre fonction très pratique mais moins connue est ddm(), anciennement connue sous le nom de dd(), qui permet de dumper les paramètres dans un fichier, au lieu de les afficher dans la page. Cette fonction est donc très pratique dans les cas où l’on a aucun retour d’affiché (batch, hook_update, EventSubscriber...)

Note : Cette fonction vient du module devel, le module doit donc être installé et activé pour que la fonction soit disponible.

Le paramètre passé à la fonction est dumpé dans le fichier temporary://drupal-debug.txt, ainsi, sur mon site le wrapper temporary:// est configuré dans le dossier ../files/tmp. (Note : cette configuration se fait directement dans le fichier settings.php de drupal, elle était avant dans Configuration / Médias / Système de fichier)

Image

Configuration dossier tmp

Je peux donc afficher le contenu de ce fichier directement dans un éditeur de texte. À savoir que les tableaux et objets sont dumpé hiéarchiquement, ce qui est pratique mais qui peut rapidement alourdir ce fichier !

# Dans un de vos fichier php : ddm($variable);

Image

Sortie de drupal_debug.txt

On peut assi ajouter des labels à ce que l'on dump pour éviter de se perdre quand on dump plusieurs variables :

function kgaut_2020_preprocess_node(array &$variables) {  /** @var \Drupal\node\Entity\Node $node */  $node = $variables['elements']['#node'];   ddm('------------------');  ddm($node->getType(), 'Type de noeud');  ddm($node->getTitle(), 'Titre');  ddm($node->getCreatedTime(), 'Created');...}

Image

Sortie de ddm

Enfin, la fonction bach tail, on peut afficher en live ce qui est dumpé, dans un terminal, sans avoir à recharger le fichier, la commande à exécuter est ci-dessous :

# Chemin à adapter en fonction de votre organisation : tail -f /vhosts/kgaut.net/files/tmp/drupal_debug.txt

Évidement, cela ne vaut pas un "vrai" debuger comme xdebug ou autre, mais c'est mieux que rien !

Par kgaut
Kevin Gautreau

Drupal 9, modules, et versionnement sémantique

Avec l'arrivée prévue le 3 juin 2020 de drupal 9, il n'y a pas besoin de créer une branche 9.x-1.x spécifiquement pour drupal 9, vous pouvez facilement indiquer que votre module est compatible avec cette nouvelle version en ajoutant dans votre fichier mon_module.info.yml la clé suivante :

core_version_requirement: ^8 || ^9

La clé core est à conserver pour garder la compatibilité avec les versions inférieures à drupal 8.7.7 (oui c'est d'un pratique...) Exemple dans un de mes modules.

En outre, il en sera bientôt terminé des versions de module "8.x-1.0", le versionnement sémantique est adopté et est compatible à partir de drupal 8.8.3, C'est à dire que les nouvelles branches de vos projets seront nommées 2.x (au lieu de 8.x-2.x) et les tags (versions) pourront être sous la forme :

2.0.0-beta1
2.0.0
2.0.1
2.1.0

Si vous passez au versionnement sémantique, les numéros de versions doivent être incrémentés, ainsi, si actuellement vous avez actuellement une branche 8.x-2.x, conservez là pour toutes les versions de drupal inférieures à 8.8.3, mais vous pouvez dès maintenant créer une branche 3.x pour la compatibilité avec les versions 8.8.3+ et 9.0.0 de drupal.

Il est possible de spécifier qu'une branche ne sera compatible qu'avec drupal 9 dans le fichier info.yml :

core_version_requirement: ^9

Plus d'informations sur le versionnement sémantique

Plus d'informations sur les changements du fichier mon_module.info.yml

tl;dr

Si vous avez un module avec une branche 8.x-2.x :

Votre module est compatible avec drupal 9 : vous pouvez ajouter la clé "core_version_requirement: ^8 || ^9" à votre module.info.yaml (c'est ce que j'ai fais pour mon mini module popin : https://www.drupal.org/project/popin. Vous pourrez passer au versionnement sémantique pour la prochaine version majeure de votre module
Votre module nécessite de plus gros travaux pour être rendu compatible, vous pouvez garder la branche 8.x-2.x pour drupal 8 et démarrer une branche 3.x qui sera une branche de travail pour drupal 9.

Par kgaut
Kevin Gautreau

Installer et tester drupal 9 avec composer

La première version beta de Drupal 9 est sortie il y a quelques jours, et la version stable est prévue pour le 3 juin.

Si vous souhaitez tester dès aujourd'hui et par exemple vérifier si vos modules sont compatibles, voici comment installer la première beta avec composer :

Dans le dossier où vous souhaitez installer drupal :

composer create-project drupal/recommended-project:9.0.0-beta1 ./

Ensuite configurez votre serveur web pour pointer sur le dossier web et c'est parti :

Image

Drupal 9

Par kgaut
Kevin Gautreau

Module drupal 8 : Apidae Tourisme

Apidae tourisme est un « réseau d'informations touristiques » qui permet aux offices du tourisme, mairies... De créer des « lieux » (restaurants, hôtels...) ainsi que des évènements sur des cartes de manière collaborative.

Dans le cadre d'un ancien projet, j'avais du développer une intégration avec l'API de ce service et un site drupal. J'avais à l'époque développé ce module sur github afin de peut-être un jour le mettre à disposition de tous. Un drupalistos m'a signalé récèment qu'il était listé sur la page des intégrations disponible sur la doc développeur d'Apidae.

Rebelote pour un projet que je vais attaquer, du coup j'ai pris un peu de temps pour packager le module que j'avais développé à l'époque, le nettoyer un peu afin de le publier sur drupal.org. C'est chose faite aujourd'hui !

Le module permet à intervalle régulier de synchroniser les données venant d'Apidae et de créer / mettre à jours des nœuds en récupérant différents champs (noms, description, médias, téléphone, coordonnés GPS...)

Le module gère le multi-langue et on peut ainsi récupérer des traductions des fiches et générer des traductions des nœuds.

Actuellement seulement certains champs sont mappés, mais je compte rendre ça administrable dans un futur proche.

Je suis preneur de suggestions et n'hésitez-pas à me contacter si vous souhaitez de l'aide ou des informations dans sa mise en œuvre.

La page du module sur drupal.org : https://www.drupal.org/project/apidae_tourisme

Par kgaut
Kevin Gautreau

Drupal 8 - Views - Créer un filtre contextuel personnalisé

Les contextual filters ou filtres contextuels sont une fonctionnalité très utile du module views de drupal : il permettent, comme le nom l'indique de filtrer les éléments affichés en fonction d'un paramètre contextuel (lié à un nœud, à l'internaute, un paramètre GET...).

Certains sont fournis de base avec drupal mais ils ne conviennent pas forcement à tous les cas d'utilisation.

Dans le cas présent, le but est de filtrer des nœuds en fonction de la localité de l'utilisateur (techniquement un terme de taxonomie) afin que l'utilisateur ne visualise que les nœuds qui concerne sa commune. Les nœuds ont un champ « référence à un terme de taxonomie » multiple, même chose pour l'utilisateur mais simple pour lui. Donc un nœud peut concerner plusieurs communes alors qu'un utilisateur n'appartient qu'à une seule commune.

Création du filtre contextuel :

# web/modules/custom/mon_module/src/Plugin/views/argument_default/UserLocalite.phpisAnonymous()) {      return 'all';    }    $user = User::load($user->id());    // getLocalitesId() est une methode personnalisée de la classe User    // qui retourne l'id du terme de taxonomy de la localité    return $user->getLocalitesId();  }   // Ma classe implémentant CacheableDependencyInterface, on peut ainsi ajouter des paramètres   // de cache, si ça n'est pas nécessaire, il suffit de supprimer l’implémentation et les deux méthodes ci-dessous.  public function getCacheMaxAge() {    return Cache::PERMANENT;  }   public function getCacheContexts() {    return ['user:localite'];  }}

Il nous reste plus qu'à sélectionner le filtre dans la liste des filtre contextuels de drupal, en choisissant bien le champs sur lequel le filtre doit s'appliquer (dans mon cas : node.localites_cibles) :

Ajout filtre contextuel

 

Configuration du filtre contextuel

 

Par kgaut
Kevin Gautreau

Drupal 8 - Surcharger la classe de l'entité User

Sous Drupal 8 il est possible de surcharger la classe du type d'entité « User » afin de par exemple ajouter nos propres getters et setters ou de modifier l'affichage par défaut d'un nom d'utilisateur.

Pour cela il faut informer drupal qu'on va changer la classe de base pour le type d'entité User. Cela se passe dans le .module via le hook HOOK_entity_type_alter :

function monmodule_entity_type_alter(array &$entity_types) {  $entity_types['user']->setClass(\Drupal\monmodule\Entity\User::class);}

Et il faut ensuite définir la classe en elle même :

# web/modules/custom/monmodule/src/Entity/User.phplastname->value . ' ' . $this->firstname->value;  }   public function setDomain($domain_id) {    $this->set('field_domain_access', [$domain_id]);    return $this;  } }

Rien de foufou ici, j'ai surchargé la méthode « label », ainsi par exemple dans la listes des membres du site sera affiché en lieu et place de leur nom d'utilisateur, leurs nom et prénom. Il y a aussi un setter pour définir le domain de l'utilisateur.

Maintenant d'est que l'on chargera un utilisateur il sera de cette classe, et non plus de la classe de base.

Ceci est évidement possible pour l'ensemble des types d'entités (Noeuds, terme de taxonomy...)

Par kgaut
Kevin Gautreau

Drupal 8 - Surcharger la page de liste des termes de taxonomie

La page par défaut de liste des termes de taxonomie de drupal 8 n'est pas très flexible. Elle peut facilement être substituée à une vue mais dans ce cas on perd le « drag'n'drop » dans le cas d'une arborescence.

Pour garder ce comportement, mais pouvoir quand même ajouter des colonnes, il est possible de surcharger le formulaire gérant cette page.

Cela se passe en 3 étapes :

1. Déclaration du service d'alteration de la route

# mon_module.services.ymlservices:  mon_module.route_subscriber:    class: Drupal\mon_module\Routing\VocabularyOverviewRouteProvider    tags:      - { name: event_subscriber }

2. Altération de la route

get('entity.taxonomy_vocabulary.overview_form');    // Make a change for default taxonomy overview form.    $route->setDefault('_form', VocabularyOverviewTerms::class);    // Re-add the collection to override the existing route.    $collection->add('entity.taxonomy_vocabulary.overview_form', $route);  } }

3. Surcharge du formulaire

Note : Ici je ne modifie le formulaire que pour un seul vocabulaire (etudiant_promos) et j'ajoute une colonne « Domain ».

id() !== 'etudiant_promos') {      return $form;    }    // On ajoute l'entête du tableau    $form['terms']['#header']['domain'] = $this->t('Domain');     // On parcours les ligne pour ajouter notre colonne    foreach ($form['terms'] as $key => &$termRow) {      if(strpos($key, 'tid:') === 0) {        /** @var \Drupal\taxonomy\Entity\Term $term */        $term = Term::load($termRow['term']['tid']['#value']);        /** @var \Drupal\domain\Entity\Domain $domain */        $domain = $term->get('domain')->referencedEntities()[0];        $termRow['domain'] = ['#markup' => $domain->label()];      }    }    return $form;  } }

 

 

Par kgaut
Kevin Gautreau

Drupal 8 - créer des suggestions de templates pour des types d'entités personnalisés

Voici comment ajouter des suggestions de template à un type d'entité personnalisé en fonction du mode d'affichage (view_mode).

Ici mon module s’appelle « mon_module » et mon type d'entité personnalisé « resource » :

function monmodule_theme_suggestions_resource(array $variables) {  $suggestions = [];  $sanitized_view_mode = str_replace('.', '_', $variables['elements']['#view_mode']);  $suggestions[] = $variables['theme_hook_original'] . '__' . $sanitized_view_mode;  return $suggestions;}

Ainsi, si j'ai un mode d'affichage « teaser » je pourrais utiliser un template resource--teaser.html.twig à la place de celui par défaut : resource.html.twig

Vous pouvez évidement ajouter autant de suggestions de template que nécessaire, en fonction d'autres paramètres.

Par kgaut
Kevin Gautreau

Drupal 8 - Menu - Ajouter une classe à un élément de menu

Voici comment ajouter une classe css à un élément de menu défini dans un module.

mon_module.links.menu.yml

main.abonnez:  title: 'Abonnez-vous'  route_name: entity.node.canonical  route_parameters: { node: 6 }  menu_name: main  weight: 4  options:    attributes:      class:        - 'arrow'

la clé class étant un tableau, il est évidement possible d'ajouter autant de classes css que l'on veut.

Voir aussi :  Drupal 8 - Menu - Ajouter un élément de menu avec des paramètres GET

Par kgaut
Kevin Gautreau

Drupal 8 - Type d'entité personnalisé - supprimer un « basefield »

Voici comment supprimer le basefield « mon_champ_a_suppr » de mon type d'entité personnalisé « mon_type_entite ».

/** * Delete basefield mon_type_entite.mon_champ_a_suppr */function monmodule_update_8020() {  $update_manager = Drupal::service('entity.definition_update_manager');  $definition = $update_manager->getFieldStorageDefinition('mon_champ_a_suppr', 'mon_type_entite');  $update_manager->uninstallFieldStorageDefinition($definition);  return t('mon_type_entite : mon_champ_a_suppr was uninstalled');}

Lancez les mises à jours de base de données de drupal (drush updb par exemple) et hop, votre champ sera supprimé de votre base de données, évidement si votre type d'entité avait des enregistrements, les données concernants le champ supprimé seront perdues.

Certains diront qu'il est possible d'utiliser la commande drush entup, mais le mécanisme n'est plus présent depuis drupal 8.7. (mais si vraiment vous en avez besoin, vous pouvez regarder du côté du module Devel Entity Updates, à n'installer qu'en connaissance de cause !

Par kgaut
Kevin Gautreau

Drupal 8 - Ajouter un champ « alias d'url » (path) à un type d'entité personnalisé

Pour un type d'entité personnalisé, les alias peuvent se gérer à l'aide de motifs via le module pathauto.

Mais à l'instar des nœuds, parfois nous voulons pouvoir avoir la main sur l'alias directement.

Pour cela on peut ajouter un computed field à notre type d'entité afin de laisser la main au créateur du contenu de décider de l'alias.

Dans la méthode baseFieldDefinitions de notre type d'entité :

$fields['path'] = BaseFieldDefinition::create('path')  ->setLabel(t('URL alias'))  ->setDisplayConfigurable('form', TRUE)  ->setComputed(TRUE);

Ensuite il faut aller configurer l'affichage du formulaire afin d'ajouter ce champ :

Path Drupal 8

 

Par kgaut
Kevin Gautreau

Drupal 8 - Créer une page d'administration « overview » avec tous les sous-menus

Sur un site drupal 8, j'ai pour habitude de créer un élément de menu spécifique dans la barre d'administration qui comprend toutes les parties un peu métier du site.

La plupart du temps cette route renvoie vers une méthode « dashboard » d'un AdminController qui au mieux affiche quelques informations.

Voici comment transformer cette route, en listes des sous-menus, à la manière de la page « Configuration ».

Dans mon_module.routing.yml :

mon_module.admin.dashboard:  path: '/admin/mon_module/dashboard'  defaults:    _controller: '\Drupal\system\Controller\SystemController::overview'    link_id: 'mon_module.admin.dashboard'    _title: 'dashboard'  requirements:    _permission: 'access mon_module dashboard'

et voila le résultat :

Dashboard Overview

Pour information, voici comment ajouter cette route à la barre d'administration (dans mon cas en première place) :

dans le fichier mon_module.links.menu.yml :

mon_module.admin.dashboard:  title: 'Mon Module'  route_name: mon_module.admin.dashboard  description: 'Mon Module dashboard'  parent: system.admin  weight: -20

 

Par kgaut
Kevin Gautreau

Premier regard sur Claro, le futur thème d'administration de drupal 8 et 9

Le thème d'administration de drupal 8 est seven, comme son nom l'indique il date de la version 7 de drupal, en 2011. Il a reçu un petit coup de brosse lors de la sortie de drupal 8 améliorant entres autre les aspects « responsives » du thème.

Un nouveau thème d'administration « Claro » devrait faire son apparition dans la version 8.8 de drupal (prévue pour le 4 décembre 2019). Ce thème sera en mode « expérimental », désactivé par défaut, et il sera mentionné, qu'il est là à des fins de tests uniquement.

Mais on peut dès maintenant tester ce thème sur des version antérieures de drupal 8 et remonter les soucis que l'on rencontre.

Rien de bien révolutionnaire, un thème plus aéré, un peu plus clair, une augmentation des contrastes pour l'accessibilité. Une amélioration du rendu sur petits écrans.

 

Écran de rédaction de contenu :

En responsive :

C'est un bon début ! Le thème est un peu trop lumineux pour mes yeux fragiles de développeurs qui préfère les thèmes sombres.

J'ai pour habitude d'utiliser le thème adminimal pour l'administration, on verra ce que donne l'évolution de Claro.

Pour installer claro via composer :

composer require drupal/claro

Pour suivre la liste des taches pour stabiliser claro afin qu'il arrive dans le core de drupal : https://www.drupal.org/project/drupal/issues/3066007

La page du thème sur drupal.org : https://www.drupal.org/project/claro

 

Par kgaut
Kevin Gautreau

Drupal 8 et composer, résoudre le problème « Package type "drupal-console-library" is not supported »

Sur des projets j'ai depuis quelques jours, lors d'un composer install, l'erreur suivante arrive :

Package type "drupal-console-library" is not supported

Pas encore eu le temps de creuser la cause, mais une solution que j'ai trouvé est d'ajouter la gestion des « drupal-console-library » dans la section installer-paths de mon fichier composer.json en ajoutant la ligne suivante :

  1. "vendor/drupal/{$name}": ["type:drupal-console-library"],

Voici la section en entier :

  1. "installer-paths": {
  2. "web/core": ["type:drupal-core"],
  3. "vendor/drupal/{$name}": ["type:drupal-console-library"],
  4. "web/libraries/{$name}": ["type:drupal-library"],
  5. "web/modules/_contrib/{$name}": ["type:drupal-module"],
  6. "web/profiles/_contrib/{$name}": ["type:drupal-profile"],
  7. "web/themes/_contrib/{$name}": ["type:drupal-theme"],
  8. "drush/_contrib/{$name}": ["type:drupal-drush"]
  9. }

Si quelqu'un a une idée de la raison, je suis preneur !

Par kgaut
Kevin Gautreau

Drupal 8 - Rediriger sur le listing des noeuds après la création d'un contenu

Sur une commande expresse de Vincent, voici comment modifier les formulaires de création / modification de nœud pour être redirigé sur la page de listing des nœuds plutôt que sur le nœud en lui même lors de la création ou de la modification d'un contenu.

1 - Altération du type d'entité

  1. # mon_module.module
  2. function mon_module_entity_type_alter(array &$entity_types) {
  3. $entity_types['node']->setFormClass('default', Drupal\mon_module\Entity\Form\CustomNodeForm::class);
  4. $entity_types['node']->setFormClass('edit', Drupal\mon_module\Entity\Form\CustomNodeForm::class);
  5. }

2 - Classe du formulaire

  1. # web/modules/mon_module/src/Entity/Form/CustomNodeForm.php
  2.  
  3. namespace Drupal\mon_module\Entity\Form;
  4.  
  5. use Drupal\Core\Form\FormStateInterface;
  6. use Drupal\node\NodeForm;
  7.  
  8. class CustomNodeForm extends NodeForm {
  9.  
  10. public function save(array $form, FormStateInterface $form_state) {
  11. parent::save($form, $form_state);
  12. $form_state->setRedirect('view.content.page_1');
  13. }
  14.  
  15. }

 

Par kgaut
Kevin Gautreau

Drupal 8 - ajouter du css ou du javascript à un module

Voici comment attacher une librairie javascript ou un fichier css à un module custom dans drupal 8.

Dans les extraits de code ci-dessous, le nom machine du module sera « shoutbox ».

Déclaration de la librairie

  1. #shoutbox.libraries.yml
  2.  
  3. # nom de la librairie
  4. shoutbox :
  5.   js:
  6. # chemin vers le fichier relatif au module
  7.   js/shoutbox.js: {}
  8.   css:
  9.   theme :
  10. # chemin vers le fichier relatif au module
  11.   css/administration.css : {}

Voici comment attacher cette librairies dans un bloc, un preprocess de template ou un controller

  1. // suivant l'endroit ooù l'on attache la librairie, cela peut être $build, $variables...
  2. // la partie avant le slash définie le nom du module qui défini la librairie
  3. // la partie après le slash est le nom de la librairie choisie dans le fichier shoutbox.libraries.yml
  4.  
  5. $variables['#attached']['library'][] = 'shoutbox/shoutbox';

 

Par kgaut
Kevin Gautreau

Drupal 8 - Surcharger la classe de contrôle d'accès d'un type d'entité

Sous drupal 8, les types d'entités, comme les noeuds, viennent avec leur classe pour gérer le contrôle d'accès (création / modification / visualisation / suppression).

Il est possible de surcharger ces classes pour personnaliser plus finement ce contrôle.

Nous allons ici surcharger le contrôle d'accès pour un type d'entité « shoutbox », mais c'est le même principe pour les nodes.

  1. # mon_module.module
  2. function mon_module_entity_type_alter(array &$entity_types) {
  3. $entity_types['shoutbox']->setHandlerClass('access', \Drupal\mon_module\Entity\AccessControlHandler\CustomShoutboxAccessControlHandler::class);
  4. // Note : si on avait voulu surcharger le controle d'accès aux noeuds :
  5. // $entity_types['node']->setHandlerClass('access', \Drupal\mon_module\Entity\AccessControlHandler\CustomNodeAccessControlHandler::class);
  6. }

La classe en elle même, qui étant la classe de contrôle d'accès de base (définie dans l'annotation de notre type d'entité)

  1. # mon_module/src/Entity/AccessControlHandler/CustomShoutboxAccessControlHandler.php
  2.  
  3. namespace Drupal\mon_module\Entity\AccessControlHandler;
  4.  
  5. use Drupal\Core\Access\AccessResult;
  6. use Drupal\Core\Entity\EntityInterface;
  7. use Drupal\Core\Session\AccountInterface;
  8. use Drupal\shoutbox\Entity\AccessControlHandler\ShoutboxAccessControlHandler;
  9. use Drupal\shoutbox\Entity\Shoutbox;
  10. use Drupal\user\Entity\User;
  11.  
  12. class CustomShoutboxAccessControlHandler extends ShoutboxAccessControlHandler {
  13.  
  14. protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
  15. /** @var Shoutbox $entity */
  16. if($operation === 'view' && $this->testPerso()) {
  17. if (!$entity->isPublished()) {
  18. return AccessResult::allowedIfHasPermission($account, 'administer shoutbox');
  19. }
  20. if ($this->autreTest()) {
  21. return AccessResult::allowed();
  22. }
  23. return AccessResult::forbidden('Shoutbox privée');
  24. }
  25. return parent::checkAccess($entity, $operation, $account);
  26. }
  27.  
  28. }

Ici je ne fais un contrôle d'accès que sur l'opération « view » pour la visualisation, je délègue tout le reste à la classe mère.

Par kgaut
Kevin Gautreau

Intégrer Slack à son site drupal 8

Nous allons voir comment envoyer des messages sur un slack depuis un site.

Évidement, vous devez pour cela être administrateur du slack ou bien avoir des jetons d'intégration.

Installation du module d'API

Commençons par télécharger et activer le module slack qui fera le pont entre notre site et l'API de slack

  1. composer require drupal/slack
  2. drush en slack

Configuration

Rendez-vous sur la page de configuration du module : /admin/config/services/slack/config

Il nous faudra ici une « Webhook URL », qui s'obtient sur votre slack, dans la section Apps : https://MONSLACK.slack.com/apps, dans le recherche tapez « webhook » et dans l'auto-complétion selectionnez « Incoming WebHooks » :

Slack Incoming WebHooks

Sur l'écran suivant cliquez sur le bouton « Add Configuration »

Slack - add configuration

Sélectionnez ensuite le canal où les messages devront être postés :

Slack - Selection chan

Récupérez ensuite l'adresse « webhook URL » en renseignez-la sur votre site, sur l'écran de configuration de Slack, vous pouvez renseigner le nom et l'avatar qui apparaîtra pour chaque message :

slack - config

On teste si tout fonctionne bien via l'onglet « send test message »

Slack Test

On vérifie dans le slack :

slack test

\o/

Voila le module est configuré, on peut maintenant utiliser rules pour déclencher des actions. Mais on peut aussi le faire depuis le code.

Un exemple tout simple (qui aurait pu être fait via rules) : envoyer un message slack à chaque commentaire sur le site, j'ai utilisé pour cela le hook_comment_insert avec le service slack.slack_service :

  1. function kgaut_comment_insert(\Drupal\comment\Entity\Comment $comment) {
  2. /** @var \Drupal\slack\Slack $slack */
  3. $slack = \Drupal::service('slack.slack_service');
  4.  
  5. $node = $comment->getCommentedEntity();
  6. $author = $comment->getAuthorName();
  7. $email = $comment->getAuthorEmail();
  8. $body = strip_tags($comment->get('comment_body')->value);
  9. $description = t('*@username* (@email) vient de poster un commentaire sur *@post_title* : _@message_. @comment_url', [
  10. '@username' => $author,
  11. '@message' => $body,
  12. '@email' => $email,
  13. '@post_title' => $node->label(),
  14. '@comment_url' => $comment->toUrl('canonical', ['absolute' => TRUE])->toString(),
  15. ]);
  16. $slack->sendMessage($description);
  17. }

Et hop !

samarsh !

 

Pages