Articles de l'utilisateur

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

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.

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

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/UrlStupidTargetBlankFilter.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_stupid_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 UrlStupidTargetBlankFilter 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.

Par Kgaut
Adhérent
Kevin Gautreau

Drupal 8 - Ajouter une restriction par ip sur une route

Dans le fichier MODULE.routing.yml on va utiliser le requirement « _custom_access »

  1. module.ma_methode:
  2.   path: 'mon-module/mon-chemin'
  3.   defaults:
  4.   _controller: '\Drupal\module\Controller\monController::maMethode'
  5.   requirements:
  6.   _custom_access: '\Drupal\module\Controller\monController::maMethodeAccess'

Que l'on va implémenter dans notre contrôleur, ici monController.php :

  1. public function maMethodeAccess() {
  2. /** @var Request $request */
  3. $request = \Drupal::request();
  4. $ipAutorisees = \Drupal::config('iamyourstory.checkcard_api')->get('allowed_ips')
  5. $ipAutorisees = explode(',', $ipAutorisees);
  6. $ipAutorisees = array_map('trim', $ipAutorisees);
  7. return AccessResult::allowedIf(\in_array($request->server->get('REMOTE_ADDR'), $ipAutorisees));
  8. }

à noter :

  • Les ip autorisées sont stockées en configuration via un formulaire de config, les ip sont séparées par une virgule, d'où le explode.
  • J'utilise ensuite la fonction array_map('trim', $ipAutorisees) pour supprimer les éventuels espaces. Si la personne avait saisi « 127.0.0.1, 192.168.0.2 », mon tableau sans le trim sera ['127.0.0.1', ' 192.168.0.2'] (espace au départ de la seconde ip.)
Par Kgaut
Adhérent
Kevin Gautreau

Drupal 8 - Entité - Champ de base Fichier (File)

Voici comment créer un champ « fichier » au sein d'un type d'entité custom dans drupal 8 :

  1. $fields['programme_pdf'] = BaseFieldDefinition::create('file')
  2. ->setLabel(t('Programme PDF'))
  3. ->setSetting('file_directory', 'formations/programme') // dossier d'upload
  4. ->setSetting('max_filesize', '10MB') // taille max du fichier
  5. ->setSetting('file_extensions', 'pdf') // extensions autorisées, à séparer par un espace
  6. ->setSetting('description_field', FALSE) // si on veut activer un champ « description »
  7. ->setDisplayOptions('form', [
  8. 'label' => 'hidden',
  9. 'type' => 'file_generic',
  10. 'weight' => 4,
  11. ])
  12. ->setDisplayConfigurable('form', TRUE)
  13. ->setDisplayConfigurable('view', TRUE);

 

Par Kgaut
Adhérent
Kevin Gautreau

Drupal 8 - Entitée & Views - Créer une relations inverse

Prenons deux types d'entités custom : « Bière » et « Brasserie » avec une relation 1-n entre les deux dans le sens :

  • Une bière provient d'une et d'une seule brasserie
  • Une brasserie peut proposer N bières

 Ainsi :

drupal-views-relations.png

En drupalisme, on aurait une propriété « entity_reference » au niveau de notre bière qui fera référence à la brasserie.

Dans views, si on fait un listing des bières, pas de soucis pour accéder au contenu de la brasserie depuis la bière, par contre l'inverse n'est pas possible.

Depuis un listing de brasserie, il n'est pas possible d’accéder aux bières de la brasserie.

Pour cela il faut utiliser la classe en charge de views_data, définie dans l'annotation de notre type d'entité brasserie :

drupal-entitee-annotation.png

Et voila le contenu de ce fichier

  1.  
  2. namespace Drupal\mon_module\Entity\ViewsData;
  3.  
  4. use Drupal\views\EntityViewsData;
  5.  
  6. class BrasserieViewsData extends EntityViewsData {
  7.  
  8. /**
  9.   * {@inheritdoc}
  10.   */
  11. public function getViewsData() {
  12. $data = parent::getViewsData();
  13. $data['brasserie']['bieres'] = [
  14. 'title' => t('Bieres'),
  15. 'help' => t('Lie la brasserie aux bières produites'),
  16. 'relationship' => [
  17. 'group' => t('bieres'), // Affiché en information dans la partie « relationship » de views
  18. 'label' => t('Actions de formation'), // Affiché en information dans la partie « relationship » de views
  19. 'base' => 'biere', // Table de base de l'entitée cible
  20. 'field table' => 'biere', // Table contenant le champ de l'entitée cible sur lequel on fera la jointure
  21. 'base field' => 'brasserie',// Champ de l'entité cible Champ sur lequel on fera la jointure
  22. 'relationship field' => 'brasserie_id', // Champ de l'entité source sur lequel on fera la jointure
  23. 'id' => 'standard',
  24. ],
  25. ];
  26. return $data;
  27. }
  28. }

Et voila le travail !

views-relationship.png

Par Kgaut
Adhérent
Kevin Gautreau

Drupal - PSA-2018-001 - Patch de sécurité déployé le 28/03/2018

Mise à jour du 29 mars 2018 :

Patchez votre site

Si vous n'avez pas encore patché / mis à jour votre site, faite le ! Téléchargement des nouvelles versions ou des patchs à cette adresse : https://www.drupal.org/sa-core-2018-002.

Si vous utilisez composer, la commande pour mettre à jour le core est :

  1. composer update drupal/core

Si vous utilisez drush

  1. drush rf
  2. drush up

La faille

Il s'agit de variables get / post / cookies / pouvant contenir des « # » dans leurs noms et potentiellement déclencher des injections sql ? Je n'ai pas encore vu d'exploit mais les implications données sur la FAQ de la faille sont violente :

 

  • How difficult is it for the attacker to leverage the vulnerability? None (user visits page).
  • What privilege level is required for an exploit to be successful? None (all/anonymous users).
  • Does this vulnerability cause non-public data to be accessible? All non-public data is accessible.
  • Can this exploit allow system data (or data handled by the system) to be compromised? All data can be modified or deleted.
  • Does a known exploit exist? Theoretical or white-hat (no public exploit code or documentation on development exists)
  • What percentage of users are affected? Default or common module configurations are exploitable, but a config change can disable the exploit.

Source : https://groups.drupal.org/security/faq-2018-002

En tout cas le patch permet de filtrer ces tableaux et enlever toutes les éléments pouvant être dangereux.

Il est possible d'avoir une whitelist des paramètres à ne pas supprimer dans le cas où vous utilisez des noms de cookies, paramètres GET ou POST commençant par des « # ».

Pour cela, sous drupal 7, dans le fichier settings.php

  1. $conf['sanitize_input_whitelist'] = ['#clee_1', '#clee_2'];

Sous drupal 8, dans le fichier settings.php aussi :

  1. $settings['sanitize_input_whitelist'] = ['#clee_1', '#clee_2'];

Il y a aussi un système pour enregistrer à chaque fois que des paramètres sont supprimés.

Pour l'activer sous drupal 7 :

  1. $conf['sanitize_input_logging'] = TRUE;

Sous drupal 8 :

  1. $settings['sanitize_input_logging'] = TRUE;

Post original du 21/03/2018

Il vient d'être annoncé par l'équipe gérant la sécurité du CMS drupal qu'un gros correctif de sécurité pour drupal 7 et drupal 8 sera déployé mercredi 28/03/2018 entre 20h et 21h30 (heures Françaises). [Edit du 27/03/2018 : j'avais oublié le paramètre « heure d'été » dans mes conversions de timezones...]

On ne sait pas encore où se situe la faille. On sait seulement qu'elle semble (très) importante et qu'il est recommandé d'appliquer le patch immédiatement.

La dernière fois qu'un cas similaire s'est présenté, une fois le correctif disponible, les sites non patchés ont été très rapidement piratés. En effet : une fois que l'on a accès au patch, il est facile de trouver la faille corrigée et ainsi de l'exploiter sur des sites non protégés.

Si vous êtes développeur ou si vous gérez vos propres sites drupal, prévoyez une permanence mercredi soir pour vous occuper de ça.

Si vous avez un site drupal qui est géré par un tiers, assurez-vous qu'il soit au courant et qu'il s'en occupera au plus tôt.

Ce que l'on sait :

  1. Les versions 7 et 8 de drupal sont concernées. Potentiellement aussi la version 6 (pour plus d'informations spécifiques sur la version 6 : https://www.drupal.org/project/d6lts)
  2. Des patchs seront disponible en plus des nouvelles versions de drupal 7, 8.3, 8.4 et 8.5, la mise à disposition d'un patch permet une application du correctif plus rapide et moins risquée qu'une mise à jour complète d'un drupal.
  3. Le correctif ne nécessitera pas de mise à jour de la base de données.
  4. Il faudra aller vite, très vite : lors d'un précédent en 2015 (drupageddon) les premiers sites hackés l'avaient étés moins de 6h après la mise à disposition du patch.
  5. Le patch sera disponible à cette adresse : https://www.drupal.org/psa-2018-001 entre 20H et 21H30 ce mercredi.

 

Plus d'informations : https://www.drupal.org/psa-2018-001

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