Articles de l'utilisateur

Par Kgaut
Adhérent
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
Adhérent
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
Adhérent
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
Adhérent
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
Adhérent
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
Adhérent
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
Adhérent
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 !

 

Par Kgaut
Adhérent
Kevin Gautreau

Drupal 8 - Utiliser les redirections de Redirect sur une page dépubliée

Dans le cas d'une page dé-publiée, si en tant qu'anonyme on tombe sur une erreur 403. On peut vouloir rediriger cette page temporairement vers une autre via le module redirect, mais nativement ça n'est pas pris en compte.

Voici un petit EventSubscriber qui répond sur les erreurs 403, qui teste si une redirection existe pour le nœud courant, et dans ce cas redirige l'utilisateur.

Note : Un administrateur ayant la permission pour voir le contenu dé-publié ne sera pas redirigé, car aucune erreur 403 ne sera lancée et donc l'event subscriber ne sera pas appelé.

Note 2 : C'est un bout de code qui semble fonctionnel, fonctionne avec le multilingue mais qui est certainement perfectible. N'hésitez-pas à me faire vos remarques.

1 - Déclaration de l'event subscriber :

  1. #mon_module.services.yml
  2.  
  3.   mon_module.redirector:
  4.   class: Drupal\mon_module\EventSubscriber\Redirector
  5.   tags:
  6.   - { name: event_subscriber }

2 - Définition de l'event subscriber 

  1. # web/modules/custom/mon_module/src/EventSubscriber/Redirector.php
  2.  
  3. namespace Drupal\mon_module\EventSubscriber;
  4.  
  5. use Drupal\Core\EventSubscriber\HttpExceptionSubscriberBase;
  6. use Drupal\redirect\RedirectRepository;
  7. use Drupal\node\Entity\Node;
  8. use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
  9. use Symfony\Component\HttpFoundation\RedirectResponse;
  10.  
  11. class Redirector extends HttpExceptionSubscriberBase {
  12.  
  13. /**
  14.   * {@inheritdoc}
  15.   */
  16. protected function getHandledFormats() {
  17. return ['html'];
  18. }
  19.  
  20. public function on403(GetResponseForExceptionEvent $event) {
  21. if ($event->getRequest()->attributes->get('node') !== NULL) {
  22. $nid = \Drupal::routeMatch()->getRawParameter('node');
  23. $langcode = \Drupal::languageManager()->getCurrentLanguage()->getId();
  24. /** @var RedirectRepository $redirectRepository */
  25. $redirectRepository = \Drupal::service('redirect.repository');
  26. $node = Node::load($nid);
  27. if ($node !== NULL && $node->hasTranslation($langcode) && $translation = $node->getTranslation($langcode)) {
  28. if (!$translation->isPublished()) {
  29. $alias = \Drupal::service('path.alias_manager')->getAliasByPath('/node/' . $nid);
  30. $redirection = $redirectRepository->findMatchingRedirect($alias, [], $langcode);
  31. if($redirection) {
  32. $event->setResponse(new RedirectResponse($redirection->getRedirectUrl()->toString(), $redirection->getStatusCode()));
  33. }
  34. }
  35. }
  36. }
  37. }
  38.  
  39. }

 

Par Kgaut
Adhérent
Kevin Gautreau

Drupal 8 - Surcharger la classe de formulaire d'un terme de taxonomie

Hier, nous avons vu comment surcharger le formulaire de création / modification d'un nœud, voici aujourd'hui comment faire la même chose mais pour un terme de taxonomie.

Cela se passe encore en deux étapes.

1 - Altération du type d'entité

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

2 - Classe du formulaire

  1. # web/modules/mon_module/src/Entity/Form/CustomTermForm.php
  2.  
  3. namespace Drupal\mon_module\Entity\Form;
  4.  
  5. use Drupal\Core\Form\FormStateInterface;
  6. use Drupal\taxonomy\TermForm;
  7.  
  8. class CustomTermForm extends TermForm {
  9. public function form(array $form, FormStateInterface $form_state) {
  10. $form = parent::form($form, $form_state);
  11. /** @var \Drupal\taxonomy\Entity\Term $term */
  12. $term = $this->entity;
  13. if($term->getVocabularyId() === 'mon_vocabulaire') {
  14. // À vous de jouer
  15. }
  16. return $form;
  17. }
  18.  
  19. }

 

Par Kgaut
Adhérent
Kevin Gautreau

Drupal 8 - Surcharger la classe de formulaire d'un nœud

Pour modifier le formulaire de création d'un nœud sous drupal 8, on peut utiliser le bon vieux HOOK_form_alter(), mais on peut aussi faire quelque chose de plus « propre » en altérant le type d'entité pour redéfinir son formulaire.

Cela se passe en deux étapes.

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\NodeCustomForm::class);
  4. $entity_types['node']->setFormClass('edit', \Drupal\mon_module\Entity\Form\NodeCustomForm::class);
  5. }

2 - Classe du formulaire

  1. # web/modules/mon_module/src/Entity/Form/NodeCustomForm.php
  2.  
  3. namespace Drupal\mon_module\Entity\Form;
  4.  
  5. use Drupal\Core\Form\FormStateInterface;
  6. use Drupal\node\NodeForm;
  7.  
  8. class NodeCustomForm extends NodeForm {
  9.  
  10. public function form(array $form, FormStateInterface $form_state) {
  11. $form = parent::form($form, $form_state);
  12. // Faite votre magie ici
  13. /** @var \Drupal\node\Entity\Node $node */
  14. $node = $this->entity;
  15. return $form;
  16. }
  17.  
  18. }

Cette classe étant la classe normale, nous avons donc juste à appliquer nos modifications à l'intérieur.

Le problème de cette solution : on ne peut pas avoir deux modules qui surchargent la classe, à moins d'étendre la première dans la seconde.

Par Kgaut
Adhérent
Kevin Gautreau

Drupal 8 - rendre un formulaire dans un template

La form api de drupal est très puissante, mais pour jouer avec le markup html avec les #prefix et #suffix peut s'avérer rapidement complexe en plus d'être un peu sale au niveau du code.

Dans drupal 8 il est possible de facilement utiliser un template pour un formulaire. Pour cela il faut se baser sur le nom machine du formulaire.

Cela fonctionne pour nos formulaires custom, mais aussi pour les formulaires des modules tiers ou du core. Je vais ici utiliser un template pour le formulaire user_form qui correspond au formulaire de modification du compte utilisateur. Le nom machine de ce formulaire est user_form.

Définition du template :

Fichier : mon_module.module

  1. function mon_module_theme() {
  2. $theme = [];
  3. $theme['user_form'] = [
  4. 'render element' => 'form',
  5. ];
  6. return $theme;
  7. }

J'utilise comme clé de thème le nom machine du formulaire.

Je peux ensuite créer dans le dossier templates de mon thème le fichier user_form.html.twig :

  1. Test

  2. {{ form }}
  3. On peut rendre des champs « manuellement », mais il ne faut pas oublier de rendre le formulaire form à la fin : 

    1. Test

    2. {{ form.mon_champ_1 }}
  4. {{ form.mon_champ_2 }}
  • {{ form | without('mon_champ_1', 'mon_champ_2') }}
  •  

  • Par Kgaut
    Adhérent
    Kevin Gautreau

    Drupal 8 - Créer un Event, le lancer et l'intercepter

    Dans Drupal 8, le principe des hooks a été remplacé par un système d'évènement qui est lancé et peut être intercepté, cela se passe en 3 étapes :

    1. Création de l'évènement (classe étendant la classe Event)
    2. Lancer l'évènement
    3. Interception de l'évènement (Classe étendant l'interface EventSubscriberInterface)

    Pour cette évènement, nous allons prendre le contexte de mon site de pronostics sportifs. Je veux lancer un évènement quand un utilisateur fait ses pronostics

    Création de l'évènement

    Fichier : mon_module/src/Event/UserBetEvent.php

    1.  
    2. namespace Drupal\mon_module\Event;
    3.  
    4. use Drupal\mespronos\Entity\Day;
    5. use Drupal\user\UserInterface;
    6. use Symfony\Component\EventDispatcher\Event;
    7.  
    8. class UserBetEvent extends Event {
    9.  
    10. const EVENT_NAME = 'mespronos_user_bet';
    11.  
    12. /**
    13.   * The user account.
    14.   *
    15.   * @var \Drupal\user\UserInterface
    16.   */
    17. public $account;
    18.  
    19. /**
    20.   * The Day the user has bet on
    21.   *
    22.   * @var Day
    23.   */
    24. public $day;
    25.  
    26. public function __construct(UserInterface $account, Day $day) {
    27. $this->account = $account;
    28. $this->day = $day;
    29. }
    30.  
    31. }

    Cette classe est relativement simple, on défini les attributs que l'on veut rendre disponibles lors de l'interception de cet évènement, ici, l'objet User de l'utilisateur qui aura pronostiqué et la journée ($day) de compétition sur laquelle il aura fait ses pronostics.

    Dispatch de l'évènement

    Le code suivant est à placer là où vous souhaiter lancer l'évènement, dans mon cas il s'agit de la méthode submit de mon formulaire de pronostics :

    1. $event = new UserBetEvent($user, $day);
    2. $event_dispatcher = \Drupal::service('event_dispatcher');
    3. $event_dispatcher->dispatch(UserBetEvent::EVENT_NAME, $event);

    Évidement, il ne faut pas oublier de passer les paramètres que l'on a défini dans le constructeur de notre évènement (ici $user et $day)

    Interception de l'évènement

    Définition du subscriber

    Fichier : mon_autre_module/mon_autre_module.services.yml

    1. mon_autre_module.mespronos_user_bet:
    2.   class: 'Drupal\mon_autre_module\EventSubscriber\UserBetSubscriber'
    3.   tags:
    4.   - { name: 'event_subscriber' }

    Code du subscriber

    Fichier : mon_autre_module/src/EventSubscriber/UserBetSubscriber.php

    1.  
    2. namespace Drupal\mon_autre_module\EventSubscriber;
    3.  
    4. use Drupal\mespronos\Event\UserBetEvent;
    5. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
    6.  
    7. class UserBetSubscriber implements EventSubscriberInterface {
    8.  
    9. public function onBet(UserBetEvent $event) {
    10. // On peut récuperer les attributs de l'évènement
    11. $user = $event->account;
    12. $day = $event->day;
    13.  
    14. // À vous de faire ce que vous voulez ici
    15. }
    16.  
    17. public static function getSubscribedEvents() {
    18. // Définition du ou des évènements que l'on écoute et méthode à executer
    19. return [
    20. UserBetEvent::EVENT_NAME => 'onBet',
    21. ];
    22. }
    23.  
    24. }

     

    Par Kgaut
    Adhérent
    Kevin Gautreau

    Drupal 8 - rejouer une fonction d'update

    Drupal tient un registre des versions des modules installés, ces versions correspondent au dernier numéro du hook_update exécuté du module.

    Si par exemple vous venez de lancer le hook 8301 du module field_group et que vous souhaitez le relancer, alors il faudra passer le module en version 8300.

    Voila comment faire avec drush :

    1. drush ev "drupal_set_installed_schema_version('field_group', 8300)"

     

    Par Kgaut
    Adhérent
    Kevin Gautreau

    Drupal 8 - Exemple d'utilisation simple du cache

    Drupal 8 propose un système de cache très puissant et à plusieurs niveaux.

    Ici nous allons voir comment stocker simplement le résultat d'une requête en cache afin de ne pas avoir à la lancer la requête SQL à chaque appel.

    Commençons par définir notre « conteneur » de cache dans le fichier mon_module.services.yml :

    1. cache.mon_module:
    2.   class: Drupal\Core\Cache\CacheBackendInterface
    3.   tags:
    4.   - { name: cache.bin }
    5.   factory: cache_factory:get
    6.   arguments: [mon_module]

    mon_module sera le nom de notre conteneur de cache.

    Cela permettra de distinguer les données que nous mettrons dedans et de ne pas écraser d'autres caches d'autres modules. Si vous utilisez le cache en base de données (par défaut) vous verrez qu'une nouvelle table cache_mon_module a été créée.

    Ensuite voici un exemple de lecture du cache, et d'écriture si la donnée n'est pas présente :

    1. public function getVersion() {
    2. // On teste si la clé « database.version » est présente dans le conteneur de cache « mon_module »
    3. if ($results = \Drupal::cache('mon_module')->get('database.version')) {
    4. // Si c'est le cas, les données stockées sont dans l'attribut « data »
    5. return $results->data;
    6. }
    7. // Sinon on effectue la requête désirée
    8. $version = $this->connection->select('version', 'v')->fields('v', ['version'])->execute()->fetch();
    9. // Et on met la valeur en cache
    10. \Drupal::cache('mon_module')->set('database.version', $version->version);
    11.  
    12. return $version->version;
    13. }

    Il est possible aussi de donner date d'expiration afin que cette clé de cache ne soit plus valable une fois cette date passée :

    1. // cache valable 1 h (3600 secondes)
    2. \Drupal::cache('mon_module')->set('database.version', $version->version, date('U') + 3600);

     

    Par Kgaut
    Adhérent
    Kevin Gautreau

    Drupal - Drush - Appeler un script php et lui passer un argument

    Il est possible via drush d’exécuter un script php et de profiter de toute l'API de drupal pour effectuer des traitements (création / suppression de contenu, modification, import de traductions...)

    On utilise pour cela la commande drush php-script en lui passant le chemin vers le script relatif à la racine de drupal :

    1. # Exemple d'appel d'un script
    2. drush @alias php-script ../scripts/process/import-translations.php

    Mais il est aussi possible de passer des arguments à ce script :

    1. #Je passe ici le chemin vers le fichier à importer
    2. drush @alias php-script ../scripts/process/import-translations.php --file=../files/translations/imports/2019-05-14-translations.csv

    Et voici comment le récupérer dans notre script drush :

    1. # Récupération du paramètre file
    2. $file = drush_get_option('file');

    À noter que l'on peut aussi fournir une valeur par défaut :

    1. # ici, si --lang n'est pas passé lors de l'appel du script
    2. # alors $lang prendra la valeur « en »
    3.  
    4. $lang = drush_get_option('lang', 'en');

     

    Par Kgaut
    Adhérent
    Kevin Gautreau

    Drupal 8 - Se connecter à une base de données tierce

    Outre la base de données « classique » de drupal, il est aussi possible de se connecter à une autre base de données.

    Pour cela dans le fichier de settings il faut définir les identifiants :

    1. $databases['seconde_db'] = $databases['default'];
    2. $databases['seconde_db']['default']['host'] = 'HOST_SECONDE_DB';
    3. $databases['seconde_db']['default']['database'] = 'SECONDE_DB';
    4. $databases['seconde_db']['default']['username'] = 'USER_SECONDE_DB';
    5. $databases['seconde_db']['default']['password'] = 'PASSWORD_SECONDE_DB';

    Ensuite dans le code de notre drupal on peut sélectionner cette seconde base de données :

    1. # On sélectionne la base secondaire
    2. Database::setActiveConnection('seconde_db');

    Pour ensuite effectuer les requêtes que l'on souhaite, via la database API de drupal.

    Attention à la fin ne pas oublier de rebasculer sur la base de données par défaut afin de ne pas casser tout le drupal :

    1. # On bascule sur la base de données par défaut
    2. Database::setActiveConnection();

     

    Par Kgaut
    Adhérent
    Kevin Gautreau

    Optimiser les tâches lourdes de composer avec Drupal

    Une petite dépendance à ajouter à son composer.json qui permet d'économiser pas mal de ram lors des taches lourdes de composer (update notamment)

    1. composer require zaporylie/composer-drupal-optimizations:^1.1

    Simplement en supprimant des anciens tags des package de symfony, cela peut diviser par deux la mémoire vive nécessaire.

    Plus d'informations : https://github.com/zaporylie/composer-drupal-optimizations

     

    Par Kgaut
    Adhérent
    Kevin Gautreau

    Drupal 8 - Migrate - Aide mémoire

    Afficher la liste des destinations de migration :

    drupal debug<span class="sy0">:</span>plugin migrate<span class="sy0">.</span>destination

     

    Par Kgaut
    Adhérent
    Kevin Gautreau

    Drupal 8 - Créer un fichier avec le « résultat » d'un template

    Voici comment écrire un fichier dans drupal 8 :

    1. $sitemaps_path = 'public://sitemaps/';
    2. // création du dossier
    3. if(file_prepare_directory($sitemaps_path, FILE_CREATE_DIRECTORY)) {
    4. // écriture du fichier (s'il existe, on le remplace)
    5. file_save_data($content, $sitemaps_path . 'sitemap.xml', FILE_EXISTS_REPLACE);
    6. }
    7. else {
    8. \Drupal::logger('sitemap')->error(t('Problem creating the folder @folder', ['@folder' => $sitemaps_path]));
    9. }

    Imaginons que l'on veuille écrire dans un fichier le contenu d'un renderable array voici comment l'on définit $content :

    1. $datas = [
    2. '#theme' => 'xml_sitemap',
    3. '#urls' => [
    4. ['title' => 'test'],
    5. ['title' => 'test 2'],
    6. ],
    7. ];
    8.  
    9. // Ici si on ne peut pas utiliser l'injection de dépendance, on pourrait remplacer la ligne suivante par :
    10. // \Drupal::service('renderer')->renderPlain($datas);
    11. $content = $this->renderer->renderPlain($datas);

     

    Par Kgaut
    Adhérent
    Kevin Gautreau

    Drupal 8 - Template - Spécifier un thème spécifique

    Quand un template est appelé via un « reder array », le template va être cherché en priorité dans le thème actif, puis dans le module qui déclare ce template.

    Dans certains cas, cela peut poser problème : Si un template peut-être appelé dans un contexte back ou front, les thèmes sont la plupart du temps différents. La solution de feignant pourrait être de dupliquer ce template dans les deux thèmes. Mais c'est évidement pas une bonne solution.

    Dans la déclaration du template on peut spécifier où trouver le template en question :

    1. $themes['xml_sitemap'] = [
    2. '#template' => 'xml-sitemap',
    3. 'path' => drupal_get_path('theme', 'mon_theme_back') . '/templates',
    4. 'variables' => [
    5. 'urls' => [],
    6. ],
    7. ];

    Ainsi peut importe si mon template est appelé depuis le front ou depuis le back ça sera toujours le fichier :

    themes/custom/mon_theme_back/templates/xml-sitemap.html.twig qui sera utilisé.

    Par Kgaut
    Adhérent
    Kevin Gautreau

    Drupal 8 - Entité - Champ de base « texte long avec résumé »

    Voici comment ajouter un champ texte formaté avec résumé à un type d'entité :

    1. $fields['synospis'] = BaseFieldDefinition::create('text_with_summary')
    2. ->setLabel(t('Synopsis'))
    3. ->setSetting('text_processing', TRUE)
    4. ->setDisplayConfigurable('view', TRUE)
    5. ->setDisplayConfigurable('form', TRUE)
    6. ->setTranslatable(TRUE);

    À noter que j'ai maintenant pris l'habitude de ne plus configurer les options d'affichage en mode formulaire et front dans mon type d'entité, mais je le fais directement en backoffice du site.

    Voir des exemples d'affichages sur un texte long.

    Par Kgaut
    Adhérent
    Kevin Gautreau

    Drupal 7 - Créer un champ calculé pour Views

    (et oui des fois on doit retourner sous drupal 7)

    Voici comment créer un champ calculé pour un type d'entité (ici Node) qui sera accessible comme n'importe quel champ dans views.

    Dans mon_module.module :

    1. function mon_module_views_api($module = NULL, $api = NULL) {
    2. return ['api' => '3.0'];
    3. }

    Dans mon_module.views.inc : définition des champs

    1. function mon_module_views_data() {
    2. $data = array();
    3.  
    4. $data['node']['risk'] = [
    5. 'title' => t('Country latest risk'), // Titre visible dans views
    6. 'help' => t('Country latest risk description'), // Description visible dans views
    7. 'field' => [
    8. 'handler' => 'MonModuleLatestRisk', // Nom de la classe qui "rendra" notre champ calculé
    9. ],
    10. ];
    11.  
    12. return $data;
    13. }

    Dans mon_module.info : ne pas oublier de lister notre fichier qui contiendra notre classe

    files<span class="br0">[</span><span class="br0">]</span> <span class="sy0">=</span> MonModuleLatestRisk<span class="sy0">.</span>php

    Dans MonModuleLatestRisk.php : La logique de calcul du champ

    1. class MonModuleLatestRisk extends views_handler_field {
    2.  
    3. function render($values) {
    4. // Logique de "calcul" de notre champ
    5. // À noter que $values contient l'ensembles des champs sélectionnés (qu'ils soient exclus de l'affichage ou non) dans notre vue
    6. if(isset($values->field_field_country_scenarios[0]['raw'])) {
    7. return $values->field_field_country_scenarios[0]['raw']['entity']->field_description['und'][0]['value'];
    8. }
    9. return null;
    10. }
    11.  
    12. function query() {
    13. // laisser vide
    14. }
    15. }

     

    Par Kgaut
    Adhérent
    Kevin Gautreau

    Drupal 8 - Créer un filtre de texte

    À la demande d'un client je devais ajouter un attribut « target="_blank" » sur tous les liens sortant du site.

    J'ai pour cela créé un filtre de texte que j'ai appliqué à un format de texte.

    Voici le fichier mon_module/src/Plugin/Filter/UrlTargetBlankFilter.php

    1. namespace Drupal\mon_module\Plugin\Filter;
    2.  
    3. use Drupal\filter\FilterProcessResult;
    4. use Drupal\filter\Plugin\FilterBase;
    5.  
    6. /**
    7.  * @Filter(
    8.  * id = "url_target_blank_filter",
    9.  * title = @Translation("Url Target Blank"),
    10.  * description = @Translation("Add « target=_blank » to all urls"),
    11.  * type = Drupal\filter\Plugin\FilterInterface::TYPE_TRANSFORM_IRREVERSIBLE,
    12.  * )
    13.  */
    14. class UrlTargetBlankFilter extends FilterBase {
    15.  
    16. public function process($text, $langcode) {
    17. $regex = "/(\\b[^]*href=['\"]?http[^]+)>/is";
    18. $subst = "$1 target=\"_blank\" rel=\"noopener\">";
    19. $result = preg_replace($regex, $subst, $text);
    20. return new FilterProcessResult($result);
    21. }
    22. }

    À noter : j'ai aussi ajouté l'attribut rel="noopener" comme suggéré par Simon Georges.

    Par Kgaut
    Adhérent
    Kevin Gautreau

    Le programme du drupalcamp Paris 2019 est disponible

    Le drupalcamp est l'évènement Drupal en France rassemblant l'ensemble de la communauté francophone. L'évènement est organisé par une équipe de bénévole et est porté par l'association Drupal France et Francophonie.

    Cette année, le drupalcamp revient à Paris et se tiendra les 15, 16 et 17 février 2019 à l'espace St-Martin en plein cœur de Paris.

    Nous venons de terminer le programme des conférences, et il est consultable directement sur le site de l'évènement : https://paris2019.drupal.fr/programme.

    Cette années, trois tracks en parallèle : Business / Expertise / Découverte.

    Comme d'habitude aux drupalcamp, le dimanche sera consacré a des ateliers, des sprints de développement et de traduction.

    La billetterie est toujours ouverte, n'hésitez-pas à prendre vos places dès maintenant : https://paris2019.drupal.fr/billetterie.

    Pour vous tenir au courant sur l'évènement les infos principales sont données sur le compte twitter @drupalcampfr.

     

    Par Kgaut
    Adhérent
    Kevin Gautreau

    Drupal 8 - Supprimer toutes les entités d'un certain type

    Dans mon_module.install :

    1. /**
    2.  * Remove all products
    3.  */
    4. function mon_module_update_8001() {
    5. $toDelete = \Drupal::entityQuery('product')->execute();
    6. foreach ($toDelete as $item) {
    7. $item = \Drupal\clearblue\Entity\Product::load($item);
    8. $item->delete();
    9. }
    10. }

     

    Par Kgaut
    Adhérent
    Kevin Gautreau

    Drupal 8 - Supprimer un type d'entité

    Dans mon_module.install :

    1. /**
    2.  * Remove Product entity type
    3.  */
    4. function mon_module_update_8001() {
    5. $entity_type = 'product';
    6. $entity_update_manager = \Drupal::entityDefinitionUpdateManager();
    7. $entity_type = $entity_update_manager->getEntityType(entity_type);
    8. $entity_update_manager->uninstallEntityType($entity_type);
    9. }

     

    Par Kgaut
    Adhérent
    Kevin Gautreau

    Drupal 8 - Ajouter une propriété à un type d'entité config

    Voici comment altérer un type d'entité config pour lui ajouter une propriété.

    Dans le cas présent, nous allons modifier le type d'entité ConfigurableLanguage afin de lui ajouter un champ google_analytics, pour avoir par exemple un code de tracking différent pour chaque langue.

    Commençons par définir les nouveaux formulaire concernant pour l'édition et la création de langue :

    modules/custom/mon_module/mon_module.module

    1. function mon_module_entity_type_alter(array &$entity_types) {
    2. $entity_types['configurable_language']->setFormClass('add', \Drupal\mon_module\Entity\Form\LanguageCustomAddForm::class);
    3. $entity_types['configurable_language']->setFormClass('edit', \Drupal\mon_module\Entity\Form\LanguageCustomEditForm::class);
    4. }

     

    Ci dessous, le contenu des formulaires qui héritent directement des formulaires de base qu'ils supplantent.

    modules/custom/mon_module/src/Entity/Form/LanguageCustomAddForm.php

    1.  
    2. namespace Drupal\mon_module\Entity\Form;
    3.  
    4. use Drupal\Core\Form\FormStateInterface;
    5. use Drupal\language\Form\LanguageAddForm;
    6.  
    7. class LanguageCustomAddForm extends LanguageAddForm {
    8. use LanguageCustomTrait;
    9.  
    10. public function form(array $form, FormStateInterface $form_state) {
    11. $form = parent::form($form, $form_state);
    12. $this->getFormCustomFields($form);
    13. return $form;
    14. }
    15.  
    16. }

     

    modules/custom/mon_module/src/Entity/Form/LanguageCustomEditForm.php

    1.  
    2. namespace Drupal\mon_module\Entity\Form;
    3.  
    4. use Drupal\Core\Form\FormStateInterface;
    5. use Drupal\language\Form\LanguageEditForm;
    6.  
    7. class LanguageCustomEditForm extends LanguageEditForm {
    8. use LanguageCustomTrait;
    9.  
    10. public function form(array $form, FormStateInterface $form_state) {
    11. $form = parent::form($form, $form_state);
    12. $this->getFormCustomFields($form);
    13. return $form;
    14. }
    15.  
    16. }

     

    Enfin, afin d'éviter la répétition de code, je passe par un trait qui contient mes customisations.

    Ce trait est appelé dans les classes ci-dessous via l'appel : $this->getFormCustomFields($form);

    Ce trait, fait deux choses, dans la méthode getFormCustomFields() il altère le formulaire pour ajouter le champs qui nous intéresse, et via l'appel à la méthode customEntityBuilder(), l'enregistrement de ce champ est effectué.

    modules/custom/mon_module/src/Entity/Form/LanguageCustomTrait.php

    1.  
    2. namespace Drupal\mon_module\Entity\Form;
    3.  
    4. use Drupal\Core\Form\FormStateInterface;
    5. use Drupal\language\ConfigurableLanguageInterface;
    6.  
    7. trait LanguageCustomTrait {
    8.  
    9. public function getFormCustomFields(&$form) {
    10. /* @var $language \Drupal\language\ConfigurableLanguageInterface */
    11. $language = $this->entity;
    12.  
    13. $form['google_analytics'] = [
    14. '#title' => t('ID Google Analytics'),
    15. '#type' => 'textfield',
    16. '#default_value' => $language->getThirdPartySetting('mon_module', 'google_analytics')
    17. ];
    18. $form['#entity_builders'][] = '::customEntityBuilder';
    19. }
    20.  
    21. function customEntityBuilder($entity_type, ConfigurableLanguageInterface $language, &$form, FormStateInterface $form_state) {
    22. if ($form_state->getValue('google_analytics')) {
    23. $language->setThirdPartySetting('mon_module', 'google_analytics', $form_state->getValue('google_analytics'));
    24. return;
    25. }
    26. $language->unsetThirdPartySetting('mon_module', 'google_analytics');
    27. }
    28.  
    29. }

    évidement, pensez à modifier toutes les occurrences de mon_module par le nom machine de votre module.

    Merci à Alexandre Mallet aka @woprrr pour la piste

    Par Kgaut
    Adhérent
    Kevin Gautreau

    Drupal 8 - Afficher un noeud via le code

    Pour afficher avoir le renderable array d'un noeud dans le code, rien de plus simple via le service entity_type.manager :

    1. $node = Node::load(1);
    2. $node_rederable = \Drupal::service('entity_type.manager')->getViewBuilder('node')->view($node);

    Pour utiliser un autre view_mode :

    1. $node = Node::load(1);
    2. $node_rederable = \Drupal::service('entity_type.manager')->getViewBuilder('node')->view($node, 'embed');

    Évidement le plus propre et de passer par l'injection de dépendance, voici une version simplifiée de mon contrôleur :

    1.  
    2. namespace Drupal\mon_module\Controller;
    3.  
    4. use Drupal\Core\Controller\ControllerBase;
    5. use Drupal\Core\Entity\EntityTypeManager;
    6. use Drupal\node\Entity\Node;
    7. use Symfony\Component\DependencyInjection\ContainerInterface;
    8.  
    9. /**
    10.  * Class PageController.
    11.  */
    12. class PageController extends ControllerBase {
    13.  
    14. /**
    15.   * @var \Drupal\Core\Entity\EntityTypeManager
    16.   */
    17. protected $entityTypeManager;
    18.  
    19. public function __construct(EntityTypeManager $entityTypeManager) {
    20. $this->entityTypeManager = $entityTypeManager;
    21. }
    22.  
    23. public static function create(ContainerInterface $container) {
    24. return new static($container->get('entity_type.manager'));
    25. }
    26.  
    27. public function frontpage() {
    28. $node = Node::load(1);
    29. $page = $this->entityTypeManager->getViewBuilder('node')->view($node, 'embed');
    30.  
    31. return [
    32. 'page' => $page,
    33. ];
    34. }
    35. }

     

    Par Kgaut
    Adhérent
    Kevin Gautreau

    Drupal 8 - Afficher un menu dans le code

    Dans le contrôleur / Bloc...

    1. /** @var \Drupal\Core\Menu\MenuLinkTree $menu_tree_service */
    2. $menu_tree_service = \Drupal::service('menu.link_tree');
    3. $menu_parameters = new \Drupal\Core\Menu\MenuTreeParameters();
    4. $menu_parameters->setMaxDepth(1); // Profondeur du menu à afficher
    5. $menu_name = 'footer' // Nom machine du menu à afficher
    6. $menus = [
    7. 'footer' => $menu_tree_service->build($menu_tree_service->load($menu_name, $menu_parameters)),
    8. ];
    9. return [
    10. '#theme' => 'page-404',
    11. '#menus' => $menus,
    12. ];

    et tout simplement dans notre template :

    <span class="br0">{</span><span class="br0">{</span> <span class="re0">menus</span><span class="re1">.footer</span> <span class="br0">}</span><span class="br0">}</span>

     

    Par Kgaut
    Adhérent
    Kevin Gautreau

    Drupal 8 - Créer la traduction d'une chaine de caractère dans le code

    Dans un processus de déploiement, il est utile de gérer les traductions de chaînes de caractères dans le code, afin de pouvoir les déployer plus facilement.

    Exemple avec cette fonction d'update à adapter en fonction de vos besoins :

    1. /**
    2.  * Création d'une traduction
    3.  */
    4. function mespronos_tweaks_update_8005() {
    5. $chaine = 'Forgotten password';
    6. $traduction = 'Mot de passe oublié';
    7. $storage = \Drupal::service('locale.storage');
    8. $string = $storage->findString(['source' => $chaine]);
    9. if ($string === NULL) {
    10. $string = new \Drupal\locale\SourceString();
    11. $string->setString($chaine);
    12. $string->setStorage($storage);
    13. $string->save();
    14. }
    15.  
    16. $translation = $storage->createTranslation(array(
    17. 'lid' => $string->lid,
    18. 'language' => 'fr',
    19. 'translation' => $traduction,
    20. ))->save();
    21. }

    Pour un exemple réel, il sera plus pratique de passer par un tableau associatif (à deux dimensions si l'on veut importer plusieurs langues) qui sera parcouru par un ou deux foreach.

    Pages