Planète

Par kgaut
Kevin Gautreau

Drupal 8 & Drupal 9 - Lancer le téléchargement d'un fichier depuis un formulaire

Voici comment depuis la soumission d'un formulaire déclencher le téléchargement d'un fichier via une BinaryFileResponse :

      /** @var \Drupal\file\Entity\File $menuFile */
      $menuFile = $menu->get('field_file_menu')->entity; // Ici récupération du fichier à télécharger
      $headers = [
        'Content-Type' => $menuFile->getMimeType(),
        'Content-Disposition' => 'attachment;filename="' . $menuFile->getFilename() . '"',
      ];
      $response = new BinaryFileResponse($menuFile->getFileUri(), 200, $headers, true);
      $form_state->setResponse($response);

 

Par kgaut
Kevin Gautreau

Drupal 8 & Drupal 9 - Redirection après la création ou l'édition d'un contenu

Voici comment rediriger un utilisateur après la création ou la modification d'un noeud

function mon_module_form_alter(&$form, FormStateInterface $form_state, $form_id) {
  switch ($form_id) {
    case 'node_menu_patient_form':
    case 'node_menu_patient_edit_form':
      $form['actions']['submit']['#submit'][] = 'mon_module_form_redirect';
      break;
  }
}

function mon_module_form_redirect($form, FormStateInterface $form_state) {
  // ici renseignez la route vers laquelle vous voulez rediriger l'utilisateur
  $form_state->setRedirect('view.menus_patients.page');
}

 

Par Christophe MOLLET
Christophe Mollet

Drupal 10 : Informations et nouveautés

Afin de préparer la fin de vie de Drupal 9 et de faire face aux évolutions technologiques, Drupal 10 devrait voir le jour d'ici le courant de l'année 2022. À cette occasion, notre agence web ITSS vous dévoile, dans cet article, les nouveautés attendues par cette nouvelle version.

Par Christophe MOLLET
Christophe Mollet

Tout savoir sur Drupal 9.3

Afin d’offrir un socle technologique toujours plus stable et sécurisé à ses utilisateurs, Drupal met régulièrement à jour ses versions. Ce mois-ci, Drupal 9 bénéficie de sa troisième mise à jour, avec la sortie de Drupal 9.3. Mais qu'est-ce que cette version apporte de plus aux versions précédentes ? C'est ce que notre expert Drupal vous explique dans cet article.

Par Christophe MOLLET
Christophe Mollet

Wordpress vs Drupal : Quel CMS choisir ?

Si vous souhaitez créer un site internet, il est important de bien évaluer vos besoins et vos objectifs afin de choisir, par la suite, le CMS le plus adapté à votre projet. Afin de vous aider à y voir plus clair, notre agence web vous présente, dans cet article, les 2 CMS les plus populaires, à savoir : Wordpress et Drupal.

Par Christophe MOLLET
Christophe Mollet

10 modules Drupal 9 indispensables

Drupal possède déjà de nombreux modules de base, appelés module du cœur, extrêmement puissants et efficaces pour la construction de votre site web. Étant également un CMS très modulable et pouvant être personnalisé selon les besoins de chaque projet, il est important de connaître certains modules de la communauté, appelés module contrib, indispensables peu importe votre domaine. Un de nos experts Drupal vous présente, dans cet article, son TOP 10 des modules Drupal 9 les plus indispensables.

Par admin

Nouveau partenariat avec WorkAdventure !

WorkAdventure, c'est un bureau virtuel né lors des confinements. Dans des cartes façon jeux de GameBoy, chacun-e peut créer son avatar et partir explorer ! Rejoindre ses collègues dans des pièces de travail où une conversation Jitsi collective est en cours, lancer un appel vidéo en petit comité en se rapprochant d'un autre avatar, ou tout simplement se positionner dans une zone de silence où on ne pourra pas être dérangé : on peut émuler ici beaucoup des comportements d'un bureau, malgré la distance.

Capture de la carte WorkAdventure de l'association. Les dessins sont dans un style rétro rappelant les jeux sur GameBoy Color. On peut voir notamment un jardin avec un petit étang et des canards, une grande pièce avec des tables de 4 ou 6 places avec un ordinateur portable à chaque emplacement. Le logo de l'association est présent en haut sur un mur, et celui de Drupal est visible sur l'étang.

C'est un outil open source que nous apprécions particulièrement et que nous avons installé en auto-hébergement sur nos serveurs. Malheureusement, quelques problèmes techniques et le peu de disponibilité que nous avons pour les traiter ont un peu gâché notre plaisir et certains évènements ont dû repasser sur Meet.

Heureusement, WorkAdventure nous a offert un compte premium pendant un an gratuitement, ce qui nous décharge de la logistique liée à l'hébergement tout en nous donnant accès à des options supplémentaires essentielles pour nous, telles que la modération. Nous le savons, dans le monde de l'open source rien n'est gratuit et surtout pas l'hébergement, c'est pourquoi il est très important pour nous de les mettre en avant et peut-être de permettre à ce super produit de trouver des clients payants :)

Le sprint de traduction ainsi que les prochains Meetups auront normalement lieu sur cet outil, si ça vous plaît, n'hésitez pas à en parler dans vos entreprises, pour vos évènements en ligne, pour vos associations ! Nous sommes limités à 20 personnes simultanées sur la carte mais ça correspond à notre volume habituel lors de Meetups; de plus nous pourrons désormais diffuser en direct sur YouTube.

Pour en savoir plus sur WorkAdventure, vous pouvez regarder cette conférence donnée lors du Forum PHP 2021 ou encore visiter leur site.

Par admin

Changement de prestataire pour les emails de l'association

Si vous étiez là lors de l'Assemblé Générale Ordinaire du 24 mars, vous savez peut-être déjà que le changement de politique tarifaire de MailChimp nous a forcé à réévaluer notre "plan" chez eux. Les options gratuites ne convenant plus à notre volume de contacts, nous avons réalisé un benchmark avant de faire notre choix. Il a été finalisé le 29 janvier et à cette date nous avions une audience de 5926 contacts abonnés à la newsletter. Nous avons d'emblée décidé d'écarter les solutions "à la main" car nous ne voulons pas prendre le risque de voir nos emails bloqués ou considérés comme du spam. Voici le détail du comparatif, si ça vous intéresse :)

Nom Abonnement minimal Nb de contacts max Nb d'email max Avantages Inconvénients
MailChimp - abonnement Essentials : 78,84€ / mois 10 000 100 000 / mois On reste sur le même outil
Options avancées (landing pages etc)
PRIX !!!
MailChimp - pay as you go Pay as you go (achat de crédits sans abonnement)
  • 5 000 crédits : 158,58€
  • 10 000 crédits : 212,95€
  • 15 000 crédits : 317,16€
  • 50 000 crédits : 1 042,08€
  • 75 000 crédits : 1 563,12€
  • 200 000 crédits : 2084,16€
  • 2 000 000 crédits : 2627,86€
  • 5 000 000 crédits : 5210,41€
  • 25 000 000 crédits : 10 420,81€
/ / Pas d'abonnement Prix ! Compter 317€ pour 2 envois
SendinBlue - abonnement Lite : 19€ / mois
17,10€ en paiement annuel
illimité 10 000 / mois Aucune limite d'envoi quotidien
Abonnement sans engagement
Boîte française
SendinBlue - pay as you go Pay as you go
  • 5 000 crédits : 40$
  • 10 000 crédits : 75$
  • 20 000 crédits : 121$
  • 50 000 crédits : 201$
  • 100 000 crédits : 343$
  • 500 000 crédits : 1 189$
  • 1 000 000 crédits : 1 995$
/ /
  • Pas d'abonnement
  • Moins cher que MailChimp
  • Les crédits n'expirent jamais
  • Boîte française
Si les prix sont corrects, compter environ 210$ pour 3 envois
MailJet Essentiel : 14€ / mois
12,59€ en paiement annuel
illimité 15 000 / mois
Pas de limite quotidienne
Boîte française
Prix

Le choix de MailJet a été fait à l'unanimité auprès de l'ancien bureau donc, car c'est le meilleur rapport quantité/prix et c'est français (cocorico & RGPD) ! Maintenant qu'on paie pour le service, on va essayer d'avoir des newsletters plus fréquentes histoire de rentabiliser :)
Voilà vous savez tout ! La nouvelle newsletter va partir d'ici peu de temps, n'hésitez pas à nous faire vos retours au besoin :)

Par kgaut
Kevin Gautreau

Drupal 8 & Drupal 9 - supprimer de la config depuis le code

Voici comment, via un script où une fonction d'update, supprimer des clés de configuration dans la base de données :

<?php

$configsToDelete = [
  'mon_module.ma_config',
  'mon_module.mon_autre_config',
];

foreach ($configsToDelete as $configToDelete) {*
  Drupal::configFactory()->getEditable($configToDelete)->delete();
}

 

Par Artusamak
Julien Dubois

Évolution ou refonte complète de votre site internet ?

Évolution ou refonte complète de votre site internet ?
stephanie@happyculture.coop
lun 11/04/2022 - 16:06

Pour que votre site internet suive les changements dans les technologies web, les tendances en webdesign et propose la meilleure expérience utilisateur possible, vous pouvez soit le faire évoluer soit procéder à une refonte complète. Décryptons ensemble les solutions.







Corps

Entre l’évolution des technologies web et les tendances du webdesign, votre site internet doit rester à jour et suivre les évolutions techniques.
Une fois mis en ligne, vous l’enrichissez régulièrement de nouveaux contenus. Seulement voilà, au bout de quelques années, votre site peut ne plus correspondre exactement à l’image de votre structure, il vieillit, il peut devenir capricieux techniquement… sachant que l'obsolescence d’un site peut avoir un impact négatif sur votre image de marque et votre activité (design/ergonomie dépassée, baisse des performances économiques, mise à jour du contenu complexe).
Pas mal de raisons qui vous font dire que le changement, c’est maintenant !
Oui mais voilà, qu’est-ce qu’on change ?

Vous avez le choix entre 3 options selon l’étendue des “dégâts” :

  • l’évolution graphique 
  • l’évolution technique
  • la refonte complète

Avant de décider de l’option idéale, vous pouvez étudier les avis clients/prospects qui le visitent, prendre en compte le ressenti des équipes qui utilisent tous les jours le site et lister les problématiques rencontrées, voir ce que font les concurrents etc. Mais surtout vérifiez les statistiques et la performance de votre site. Et demandez-vous aussi ce que vous attendez de lui au regard des objectifs qui l’avaient vu naître.

1/ L’évolution graphique pour une modernisation

Si vous souhaitez remettre au goût du jour la perception visuelle de votre site, vous pouvez faire des petits pas ou décider de totalement repeindre le site.
Si votre site semble daté, vieillot, qu’il ne répond plus aux nouvelles tendances de design, il est temps de lui offrir un lifting. 
Dans une évolution graphique, vous n’allez pas changer la structure, l’architecture générale de votre site. Vous gardez le socle en l’état et modifiez la partie front end ou visible de votre site. 

Actualisez vos images et visuels

N'hésitez pas à remettre au goût du jour vos visuels et à remplacer les images par de nouvelles plus actuelles. Il existe de nombreuses plateformes d’images gratuites et libres de droits. Pensez à garder un œil sur le poids de vos pages pour limiter l’obésité croissante des pages web au fil du temps.

Votre identité visuelle évolue

Si votre identité visuelle évolue, vous allez alors mettre votre site aux couleurs de cette nouvelle charte graphique et intégrer votre nouveau logo, vos boutons et icônes, ou encore la typographie.
Vous pouvez également vouloir simplement moderniser ou rafraîchir la perception du site pour coller à l’air du temps sans tout changer.

Vous réorganisez le contenu

Le design doit être complètement repensé car la circulation dans les contenus n’est pas fluide ou les utilisateurs peinent à trouver ce qu’ils cherchent suite aux conclusions d’un audit en ergonomie par exemple.
Dans ce cas, les travaux seront plus imposants mais vous pourrez capitaliser sur le contenu existant, qui lui reste pertinent. Seule sa présentation et ce qui l’entoure sont retravaillés.

Un site responsive

Aujourd’hui tous les sites internet se doivent d’être responsive, accessible et lisible sur tous les supports, notamment sur les mobiles car 82% des Français se connectent à Internet depuis un mobile. Vous devez adapter votre contenu pour être lisible sur plusieurs largeurs d’écran différentes et optimiser l’espace disponible à l’écran.

2/ L’évolution technique pour augmenter l’impact

Une mise à jour technique peut être nécessaire dans plusieurs cas. 

Optimisez la vitesse de chargement de votre site web

Votre site est-il lent à charger ? Devenue essentiel dans la réussite d’un site internet, la vitesse de chargement a un impact sur la réussite de votre site. Les internautes patientent seulement 3 secondes pour arriver sur votre site. Si dans ce laps de temps, votre site ne s’affiche pas, ils surferont sur des sites concurrents. Travaillez sur le poids des pages, l’utilisation de différents niveaux de cache, le chargement progressif… Faites maigrir votre site.

Il existe des outils pour vérifier la vitesse de chargement de votre site. Notez que la vitesse de chargement sert également votre SEO et votre classement sur Google. 

Migration de votre CMS vers une version plus récente 

Votre CMS a peut-être besoin de passer la vitesse supérieure. On parle ici de montée de version. Jusqu’à novembre 2015, si vous passiez d’une version majeure à une autre de Drupal (exemple de Drupal 7 à Drupal 8), vous étiez obligés de passer par une refonte de votre site pour réappliquer votre code métier et votre contenu à la nouvelle version. Suite aux critiques des agences et autres acteurs de l’écosystème Drupal sur l’importance de l’effort à produire pour y arriver, la communauté a fait évoluer cette logique pour permettre de facilement passer d’une version majeure à une autre (ex : Drupal 8 à Drupal 9). 
Faire une montée de version vous permet de bénéficier des nouvelles fonctionnalités, APIs, optimisations de performances ou refontes d’interfaces.
Le fonctionnement de cette industrie fait que vous devriez toujours chercher à être au plus proche des dernières versions disponibles. 
Si votre site est dit headless, il est possible de modifier le backoffice sans faire évoluer le front-office.
> Notre article sur la fin de vie de Drupal 7 et 8 pourrait aussi vous intéresser.

3/ Repartir d’une feuille blanche

On parle de refonte complète lorsque des modifications en profondeur sont nécessaires et que faire évoluer l’existant sera plus complexe que tout reprendre.

Plusieurs raisons peuvent être à l’origine d’une refonte complète, comme par exemple : 

  • La structure de votre site ne répond pas aux attentes de vos utilisateurs et par conséquent ne répond pas à vos objectifs
  • Votre activité évolue et nécessite le développement de nouvelles fonctionnalités (Le rôle/positionnement du site doit changer suite à des décisions internes)
  • Vous n’avez pas la main sur votre contenu (un prestataire qui vous a enfermé dans un outil propriétaire ) ou 
  • Votre site est difficile à administrer (départ des personnes clés du projet et perte de la compréhension du fonctionnement)
  • Votre DSI vous impose un changement de technologie (changement de CMS ou framework). Changer de CMS implique souvent une refonte car il s'agit d’une migration du back et du front.

Une refonte offre alors plus de possibilités et vous permet d’avoir un site tout neuf et sur-mesure ! 

Mais attention, une refonte ne se fait pas à la légère. Elle doit être bien pensée en amont car elle demande un certain investissement. 

D’abord, c’est un investissement de temps : comptez plusieurs semaines voire plusieurs mois pour un projet de refonte. Ce projet transversal va aussi mobiliser les ressources internes des différents services impliqués dans le site web, ce qui augmentera les chances de réussite de la refonte. Et selon la taille du projet, une personne peut être entièrement dédiée en tant que Chef de projet de la refonte.

Et, c’est aussi un investissement financier : si vous faites appel à une société externe ou un indépendant pour vous aider dans votre chantier de refonte, cela impliquera un budget plus ou moins conséquent selon les travaux que vous souhaiterez leur confier. Selon la maturité de votre projet, une aide externe peut vous aider à capter les besoins de vos utilisateurs ou de votre équipe interne et vous aider dans la rédaction et le cadrage de votre expression des besoins (Happyculture, par exemple !)

Donc avant de vous lancer, vous allez devoir analyser votre site et ses performances, définir vos nouveaux objectifs, lister les problématiques rencontrées, etc, tout ce qui vous servira pour rédiger une bonne expression de besoins avant de contacter l’agence web qui réalisera cette prestation.
On profitera de l’occasion pour s’interroger sur les contenus existants. Faut-il tout conserver ? Tout réécrire ?

Vous devez avoir un cap clair avec vos objectifs de refonte car tout au long de ce projet, ils vous aideront à faire des arbitrages et juger à la fin de la mission si la refonte est une réussite.

Nous serons ravis de répondre à vos questions et de vous accompagner dans votre projet de refonte ou pour vous aider à définir vos objectifs. N’hésitez pas à nous contacter.

=> Et pour allez plus loin, consultez notre guide pour la refonte de votre site (bientôt en ligne).

Enfin, n'hésitez pas à jeter un œil à ce petit musée du web qui montre les changements parfois radicaux de sites web bien connus. 

Pour conclure, la mise en ligne de votre site n’est que le début d’une belle histoire. Pour que votre site reste en phase avec vos objectifs, gardez à l’esprit que la vie d’un site ne s’arrête pas à sa mise en ligne initiale, faites-le évoluer sur la durée et apportez-lui des améliorations régulières. Vous pérenniserez alors les développements effectués, optimiserez les performances de votre site et éviterez une refonte complète à court terme. L’écologie est au centre de bien des préoccupations. Nous devons aussi prendre notre part dans la création de sites. Rallonger la durée de vie des sites doit (re)devenir une préoccupation plutôt que d’entretenir la course au changement permanent. 
 

Crédits : photo de David Zieglgänsberger - Unsplash

Catégories
Développement
Drupal
Tags
refonte
évolution
webdesign
CMS
Par Artusamak
Julien Dubois

Performances des migrations Drupal avec une indexation des contenus pour Search API

Performances des migrations Drupal avec une indexation des contenus pour Search API
DuaelFr
lun 04/04/2022 - 09:00

Les migrations de grandes quantités de contenus dans Drupal et l'indexation dans un moteur de recherche Search API interne ne font pas bon ménage. Individuellement ce sont des opérations efficaces mais conjointement c'est une catastrophe. Que faire ?

Corps

Lors de la réalisation de nos projets nous sommes souvent amenés à réaliser la migration de grandes quantités de contenus afin d'éviter aux personnes en charge de l'administration du site un travail long et fastidieux de copie. De plus, la plupart des projets de cet ordre font le choix de donner accès à ces contenus par l'entremise d'un ou plusieurs moteurs de recherche aux fonctionnalités plus ou moins avancées. Nous sommes donc régulièrement confrontés à la cohabitation entre le module Migrate qui nous permet de gérer l'import massif de contenus depuis des sources variées et le module Search API qui sert de socle à l'indexation de ces contenus dans les moteurs de recherche.

Migrate comme Search API sont deux outils performants dans l'absolu. Cependant, lorsqu'ils sont utilisés ensemble, le temps nécessaire à l'import et l'indexation des contenus monte en flèche et devient rapidement improductif.

Mais qu'est-ce qui se passe ?


Extrait du sketch "Biouman" des Inconnus

Lorsque Migrate enregistre une entité, cela déclenche automatiquement un processus côté Search API qui peut s'avérer assez coûteux en ressources. En effet, ce dernier va charger tous ses index actifs un par un et vérifier s'il a quelque chose à faire puis procéder à l'indexation en elle-même.

La principale piste qui est généralement identifiée pour résorber ce problème est de désactiver l'option "Indexer les éléments immédiatement". Malheureusement, même si cela réduit effectivement le temps nécessaire aux migrations, ce n'est pas suffisant car Search API a tout de même besoin de consigner l'entité qui devra être indexée plus tard. Il charge donc tous les processeurs de tous ses index actifs pour déterminer si cette entité spécifique sera indexée immédiatement, ultérieurement ou pas du tout. Cette option ne permet donc d'économiser que l'étape d'indexation en elle-même ce qui est insuffisant dans le cas de grandes migrations.

Comment se sortir de cette galère ?


Bob l'éponge a l'air vraiment perdu. Il s'emmêle les bras en
montrant dans toutes les directions

Comme dit précédemment, Search API se préoccupe de tous ses index actifs pour déterminer quoi faire avec le contenu migré. La première solution que vous pouvez donc mettre en place est de désactiver les index avant d'exécuter vos migrations. Cela peut être réalisé avec Drush assez facilement via la commande search-api:disable-all (et sa petite sœur search-api:enable-all lorsque les migrations sont terminées) ou encore carrément en désactivant le serveur via la commande search-api:server-disable (et search-api:server-enable). Si vous ne lancez pas vos migrations via Drush, c'est également réalisable par le code mais nous ne couvrirons pas cela dans cet article.

L'avantage de cette méthode est qu'elle remplit l'objectif de performance en désactivant tout le processus de Search API et qu'elle est relativement simple à mettre en œuvre (une commande avant les migrations, une autre après). Elle présente cependant deux inconvénients importants :

  1. si le serveur ou les index sont désactivés, Search API ne sait pas qu'une entité a été créée ou modifiée et ne l'indexera donc jamais (il ne faisait pas tout ça pour rien quand il était activé avec l'option d'indexation immédiate désactivée) ;
  2. durant toute la durée des migrations, les pages du site s'appuyant sur l'un des index de recherche désactivés ne seront plus accessibles (erreur 500).

Forcer la reconstruction des trackers de Search API

Le premier inconvénient est majeur car cela n'a aucun intérêt d'essayer de résoudre les problèmes de performance de nos migrations si le contenu n'est plus jamais présenté dans les moteurs de recherche. Comme évoqué, cet inconvénient est dû au fait que Search API maintient une liste des contenus qu'il a indexé ou qu'il doit indexer dans la table search_api_item de la base de données. On appelle cette liste son tracker. Lorsque les index sont désactivés, le tracker n'est pas mis à jour et Search API ne sait donc pas qu'il a quelque chose à faire avec ces entités.

Une fois encore, Drush nous facilite la vie en exposant la commande search-api:rebuild-tracker qui, comme son nom l'indique, va imposer à Search API de reconstruire le tracker de ses index et donc de découvrir toutes les entités dont il n'avait pas connaissance auparavant.

Éviter l'indisponibilité des index Search API durant la migration

La seconde problématique n'en est une que si vous avez des migrations qui s'exécutent alors que le site est accessible. Si vos migrations ne font partie que d'un lot joué une seule fois avant la mise en production, cette section ne vous intéresse pas forcément… Cela dit, la solution est tellement simple que cela pourrait vous convaincre davantage que de désactiver les index.

En effet, pour que les index ne soient pas inaccessibles pendant la migration, il suffit de ne pas les désactiver !


Wait… What ?!

Si vous avez suivi depuis le début, vous vous rappelez que l'on a désactivé les index pour que Search API ne perde pas de temps à déterminer s'il faut oui ou non indexer chaque entité que vous importez. Donc si nous les réactivons nous retombons sur le problème de performances que l'on essaie de résoudre ! C'est sans compter une fonctionnalité non documentée de Search API : le paramètre search_api_skip_tracking !

Pour comprendre ce qu'il se passe dans le moteur de Search API, cherchons à comprendre à quel endroit il intervient dans le code. Connaissant un peu la façon dont Drupal est conçu nous pouvons imaginer deux méthodes : un hook ou un événement. Les événements liés à la modification d'une entité n'étant pas courants dans la version actuelle de Drupal, nous allons commencer par nous pencher sur les hooks. Étant donné que c'est une fonctionnalité de bas niveau de Search API, s'il y a un hook il sera situé dans le module search_api et pas l'un de ses sous-modules. De plus, Search API peut indexer tout type d'entité et il est donc peu probable qu'il s'agisse d'une implémentation d'un hook spécifique à un type d'entité donné. Nous allons donc pouvoir chercher dans la base de code une fonction dont le nom commence par search_api_entity_ ce qui nous laisse peu de choix : search_api_entity_delete, search_api_entity_insert, search_api_entity_update, search_api_entity_view et search_api_entity_extra_field_info. Bingo ! Les trois premiers concernent des opérations de création, modification et suppression d'entités génériques.

Lorsque l'on prend une de ces fonctions et que l'on regarde ce qu'il s'y passe en détail, on constate l'appel au service search_api.entity_datasource.tracking_manager et plus spécifiquement à ses méthodes entityInsert, entityUpdate et entityDelete. Chacune d'entre elles teste très rapidement la valeur d'un paramètre search_api_skip_tracking de l'entité et, s'il n'est pas vide, interrompt son exécution.

Sachant donc que cette valeur permet d'arrêter Search API avant qu'il ne consomme des ressources, nous pouvons l'ajouter à notre entité en cours de migration. C'est d'ailleurs très facile car Migrate ne se préoccupe pas vraiment de savoir si une donnée que l'on passe dans son mapping existe ou pas. Il est donc possible d'ajouter une entrée dans les process de nos migrations comme suit :

process:
  # ...
  search_api_skip_tracking:
    plugin: default_value
    default_value: 1
  # ...

Bien évidemment, ceci a pour effet de totalement contourner Search API et il est donc toujours nécessaire de reconstruire ses trackers après la migration comme vu au chapitre précédent.

Le plus simple pour la fin

Maintenant que les contenus ont été importés et que les trackers ont été reconstruits il ne reste plus qu'à indexer tout le contenu pour qu'il devienne disponible sur le site. Une fois encore Drush nous facilite la vie en nous permettant d'accéder à la commande search-api:index mais vous pouvez également passer par l'interface d'admin de Search API si vous préférez.

En résumé

  1. injectez la donnée search_api_skip_tracking dans vos entités via Migrate (ou désactivez vos index mais franchement ça vaut pas le coup) ;
  2. reconstruisez les trackers une fois vos migrations terminées via drush search-api:rebuild-tracker ;
  3. n'oubliez pas d'exécuter l'indexation manuellement une fois les trackers reconstruits via drush search-api:index ;
  4. attendez patiemment le prochain article en vous hydratant bien et en prenant soin de votre santé physique et mentale.

 

Voilà ! Maintenant vous savez comment faire lorsque vous avez à combiner migrations et indexation. Vous vous apercevrez lorsque vous y serez confrontés que l'indexation en elle-même peut prendre un très long temps et même ralentir au fil de son exécution. C'est une problématique a priori liée à la mémoire disponible mais cela fera l'objet d'un prochain article (plus court, c'est promis) !

Catégories
Développement
Drupal
Drupal 8
Drupal 9
Tags
migration
indexation
search_api
optimisation
Par kgaut
Kevin Gautreau

Installer DDEV sur ubuntu

DDEV est une couche d'abstraction à docker et docker-compose permettant de mettre en place une infrastructure pour développer simplement sur un projet drupal sans avoir à installer php, mysql... et autre sur sa machine.

Première étape, installer mkcert qui nous permettra d'avoir des certificats SSL autosignés et donc d'utiliser https sans avertissement :

sudo apt install libnss3-tools -y
wget https://github.com/FiloSottile/mkcert/releases/download/v1.4.3/mkcert-v1.4.3-linux-amd64
sudo cp mkcert-v1.4.3-linux-amd64 /usr/local/bin/mkcert
sudo chmod +x /usr/local/bin/mkcert
mkcert -install

 

Installation de ddev :

curl -LO https://raw.githubusercontent.com/drud/ddev/master/scripts/install_ddev.sh && bash install_ddev.sh

 

Et voila !

Vous pouvez maintenant utiliser ddev sur un projet existant via ddev start ou le configurer sur un nouveau projet via ddev config.

Voici un lien récapitulant les différentes commandes : https://ddev.readthedocs.io/en/stable/users/cli-usage/

Par kgaut
Kevin Gautreau

Drupal 8 & Drupal 9 - importer des fichiers de config dans le code

Voici comment importer via un script où une fonction d'update un ou plusieurs fichiers de config :

# Liste des fichiers de configuration à importer, sans l'extension .yml
$configsToImport = [
  'mon_module.ma_config',
];

# Chemin relative à la racine du drupal
$config_path = '../config/import';
$source = new \Drupal\Core\Config\FileStorage($config_path);

$config_storage = \Drupal::service('config.storage');
foreach ($configsToImport as $configToImport) {
  $config_storage->write($configToImport, $source->read($configToImport));
}

 

Pour rappel il est possible de retrouver automatiquement le dossier d'un module :

# Récupération du chemin du module « mon_module »
$config_path = drupal_get_path('module', 'mon_module') . '/config/install';

 

Par kgaut
Kevin Gautreau

Drupal 8 & Drupal 9 - ajouter une colonne à un champ personnalisé

Si vous avez créé un champ personnalisé dans drupal, associé en tant que champs, ou basefield à un type d'entité et que vous souhaitez le changer (ajouter un attribut par exemple cela peut s'avérer assez compliqué, surtout si votre champs à plusieurs instances dans différents types d'entités.

Je suis tombé sur un helper permettant de finalement réaliser ça simplement : https://gist.github.com/JPustkuchen/ce53d40303a51ca5f17ce7f48c363b9b#fi…

Comment s'y prendre ?

Commencez par modifier votre type de champ et principalement la méthode schema pour ajouter votre attribut

(dans mon cas le fichier est : web/modules/custom/mon_module/src/Plugin/Field/FieldType/ListedItem.php, j'ai ajouté la colonne shortcode.

<?php

namespace Drupal\mon_module\Plugin\Field\FieldType;

use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemBase;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\TypedData\DataDefinition;

/**
* Defines the 'listed' field type.
*
* @FieldType(
*   id = "listed",
*   label = @Translation("Listed"),
*   category = @Translation("General"),
*   default_widget = "listed",
* )
*/
class ListedItem extends FieldItemBase {

  /**
   * {@inheritdoc}
   */
  public function isEmpty() {
    return FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {

    $properties['menu'] = DataDefinition::create('boolean')
      ->setLabel(t('Menu'));
    $properties['pushs'] = DataDefinition::create('boolean')
      ->setLabel(t('Pushs'));
    $properties['listing'] = DataDefinition::create('boolean')
      ->setLabel(t('Listing'));
    $properties['shortcode'] = DataDefinition::create('boolean')
      ->setLabel(t('Shortcode'));

    return $properties;
  }


  /**
   * {@inheritdoc}
   */
  public static function schema(FieldStorageDefinitionInterface $field_definition) {

    $columns = [
      'menu' => [
        'type' => 'int',
        'size' => 'tiny',
        'default' => 0,
      ],
      'pushs' => [
        'type' => 'int',
        'size' => 'tiny',
        'default' => 0,
      ],
      'listing' => [
        'type' => 'int',
        'size' => 'tiny',
        'default' => 0,
      ],
      'shortcode' => [
        'type' => 'int',
        'size' => 'tiny',
        'default' => 0,
      ],
    ];

    $schema = [
      'columns' => $columns,
    ];

    return $schema;
  }

  /**
   * {@inheritdoc}
   */
  public static function generateSampleValue(FieldDefinitionInterface $field_definition) {

    $values['menu'] = (bool) mt_rand(0, 1);

    $values['pushs'] = (bool) mt_rand(0, 1);

    $values['listing'] = (bool) mt_rand(0, 1);

    $values['shortcode'] = (bool) mt_rand(0, 1);

    return $values;
  }

}

 

Puis copiez l'intégralité du helper dans votre fichier mon_module.install

function _field_type_schema_column_add_helper($field_type, array $columns_to_add = array()) {
  $processed_fields = [];
  $field_type_manager = \Drupal::service('plugin.manager.field.field_type');
  $field_definition = $field_type_manager->getDefinition($field_type);
  $field_item_class = $field_definition['class'];

  $schema = \Drupal::database()->schema();
  $entity_type_manager = \Drupal::entityTypeManager();
  $entity_field_manager = \Drupal::service('entity_field.manager');
  $entity_field_map = $entity_field_manager->getFieldMapByFieldType($field_type);
  // The key-value collection for tracking installed storage schema.
  $entity_storage_schema_sql = \Drupal::keyValue('entity.storage_schema.sql');
  $entity_definitions_installed = \Drupal::keyValue('entity.definitions.installed');

  foreach ($entity_field_map as $entity_type_id => $field_map) {
    $entity_storage = $entity_type_manager->getStorage($entity_type_id);

    // Only SQL storage based entities are supported / throw known exception.
    //    if (!($entity_storage instanceof SqlContentEntityStorage)) {
    //      continue;
    //    }

    $entity_type = $entity_type_manager->getDefinition($entity_type_id);
    $field_storage_definitions = $entity_field_manager->getFieldStorageDefinitions($entity_type_id);
    /** @var Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */
    $table_mapping = $entity_storage->getTableMapping($field_storage_definitions);
    // Only need field storage definitions of address fields.
    /** @var \Drupal\Core\Field\FieldStorageDefinitionInterface $field_storage_definition */
    foreach (array_intersect_key($field_storage_definitions, $field_map) as $field_storage_definition) {
      $field_name = $field_storage_definition->getName();
      try {
        $table = $table_mapping->getFieldTableName($field_name);
      } catch (SqlContentEntityStorageException $e) {
        // Custom storage? Broken site? No matter what, if there is no table
        // or column, there's little we can do.
        continue;
      }
      // See if the field has a revision table.
      $revision_table = NULL;
      if ($entity_type->isRevisionable() && $field_storage_definition->isRevisionable()) {
        if ($table_mapping->requiresDedicatedTableStorage($field_storage_definition)) {
          $revision_table = $table_mapping->getDedicatedRevisionTableName($field_storage_definition);
        }
        elseif ($table_mapping->allowsSharedTableStorage($field_storage_definition)) {
          $revision_table = $entity_type->getRevisionDataTable() ?: $entity_type->getRevisionTable();
        }
      }
      // Load the installed field schema so that it can be updated.
      $schema_key = "$entity_type_id.field_schema_data.$field_name";
      $field_schema_data = $entity_storage_schema_sql->get($schema_key);

      $processed_fields[] = [$entity_type_id, $field_name];
      // Loop over each new column and add it as a schema column change.
      foreach ($columns_to_add as $column_id) {
        $column = $table_mapping->getFieldColumnName($field_storage_definition, $column_id);
        // Add `initial_from_field` to the new spec, as this will copy over
        // the entire data.
        $field_schema = $field_item_class::schema($field_storage_definition);
        $spec = $field_schema['columns'][$column_id];

        // Add the new column.
        $schema->addField($table, $column, $spec);
        if ($revision_table) {
          $schema->addField($revision_table, $column, $spec);
        }

        // Add the new column to the installed field schema.
        if ($field_schema_data) {
          $field_schema_data[$table]['fields'][$column] = $field_schema['columns'][$column_id];
          $field_schema_data[$table]['fields'][$column]['not null'] = FALSE;
          if ($revision_table) {
            $field_schema_data[$revision_table]['fields'][$column] = $field_schema['columns'][$column_id];
            $field_schema_data[$revision_table]['fields'][$column]['not null'] = FALSE;
          }
        }
      }

      // Save changes to the installed field schema.
      if ($field_schema_data) {
        $recipient_column = $table_mapping->getFieldColumnName($field_storage_definition, 'recipient');
        unset($field_schema_data[$table]['fields'][$recipient_column]);
        if ($revision_table) {
          unset($field_schema_data[$revision_table]['fields'][$recipient_column]);
        }
        $entity_storage_schema_sql->set($schema_key, $field_schema_data);
      }
      if ($table_mapping->allowsSharedTableStorage($field_storage_definition)) {
        $key = "$entity_type_id.field_storage_definitions";
        if ($definitions = $entity_definitions_installed->get($key)) {
          $definitions[$field_name] = $field_storage_definition;
          $entity_definitions_installed->set($key, $definitions);
        }
      }
    }
  }
}

Ensuite créez une fonction d'update qui va modifier les schémas de l'ensemble des instance de votre champs en appelant le helper.

Dans mon cas, mon id de champs est listed et je souhaite ajouter la colonne shortcode :

/**
* node.listed add column shortcode
*/
function mon_module_update_8039() {
  _field_type_schema_column_add_helper('listed', ['shortcode']);
}

Et voila !

Sur le lien mentionné il y a d'autres exemples pour d'autres usages, que je n'ai pas tester.

Par admin

Statistiques 2022 de notre espace Slack

Mise à jour des statistiques d'utilisation de notre espace Slack, un an plus tard !

L'an dernier, nous avions listé quelques informations intéressantes sur notre usage de Slack : . Malheureusement la comparaison sera difficile, car les éléments auxquels nous avons accès ont changé depuis. Il n'est plus possible de voir certaines informations "toutes périodes confondues", il faudra donc se contenter des 30 derniers jours.

Membres actifs

Du 16/02/2022 au 18/03/2022

Souscription (par semaine) - du 16/02/2022 au 18/03/2022

Souscription (par jour) - du 16/02/2022 au 18/03/2022

Utilisation des canaux

Depuis leur création

Liste des canaux (1/3)  - Depuis leur création

Liste des canaux (2/3) - Depuis leur création

Liste des canaux (3/3) - Depuis leur création

Répartition des messages

Du 16/02/2022 au 18/03/2022

Répartition des messages (date d'envoi) - du 16/02/2022 au 18/03/2022

Répartition des messages (lecture) - du 16/02/2022 au 18/03/2022

Répartition des messages (écriture) - du 16/02/2022 au 18/03/2022

Je pense continuer à regarder ses statistiques chaque année, pour avoir des points de comparaison. Si vous avez des suggestions, n'oubliez pas que le canal #orga-slack est à votre disposition !

PS
Je n'ai pas eu le temps de récupérer les données autrement que via capture d'écran pour l'instant, j'ai bien conscience que ce n'est pas idéal. Si cela est bloquant, n'hésitez pas à nous demander des détails via bureau@drupal.fr.

Par Artusamak
Julien Dubois

Comprendre les tests automatisés

Comprendre les tests automatisés
DuaelFr
ven 25/02/2022 - 09:19

J'ai récemment eu à accompagner plusieurs équipes sur le sujet des tests automatisés ce qui m'a amené à rechercher des ressources sur Internet pour approfondir les notions que j'avais un peu acquises sur le tas au travers de mes contributions au cœur de Drupal. Le monde des tests est très vaste alors il me semble important d'avoir une bonne culture générale du sujet avant de se lancer étant donné que cela aura un impact sur la conception de votre stratégie de tests.

Vous trouverez donc ci-dessous quelques liens qui vous mèneront vers des articles du plus généraliste au plus spécifique.

Catégories
Par Artusamak
Julien Dubois

Améliorer son référencement pour prendre en charge les types d'entités personnalisés dans Pathauto

Améliorer son référencement pour prendre en charge les types d'entités personnalisés dans Pathauto
Artusamak
lun 21/02/2022 - 08:50

Lorsque vous créez un type d'entité personnalisé, vous voulez bénéficier des fonctionnalités tierces liées aux types de contenu (vues, révisions, traduction...). La génération des alias pour le référencement avec Pathauto est l'un des points incontournables.

Corps

Vous venez de créer un nouveau type d'entité. Vous contemplez votre œuvre, réfléchissant à la prochaine touche à ajouter pour la parfaire. Tout à coup, un éclair vous frappe : "Il nous faut prendre en charge les alias Pathauto". Évidemment ! Pourquoi se priver de la puissance de cet outil ? C'est l'un des modules indispensables de Drupal 9 qui vous permet d'améliorer la qualité de votre référencement (du SEO éthique et responsable bien sûr !). Comme il est 16h et que la fin de journée approche, vous espérez pouvoir boucler ça rapidement pour reprendre votre marathon visionnage de l'intégrale de Columbo au plus vite.

Scénario 1 : Je suis une personne chanceuse

L'accident heureux consiste à vous rendre dans l'interface de Pathauto et de voir si votre type d'entité n'est pas déjà visible. Si tel est le cas, vous aurez le temps de vous faire un épisode de plus car la journée est terminée, cela fonctionne. Pour les gens moins chanceux, voici ce qu'il faut faire pour que cela fonctionne.

Scénario 2 : J'ai besoin de débrider le chemin de chaque entité

L'intérêt d'un système de gestion de contenu est de maîtriser les URLs de ses contenus. Vous pouvez opter pour de la génération automatique mais vouloir débrider cette génération entité par entité (comme pour les nœuds).

Pathauto est pré-configuré pour prendre en charge n'importe quel type d'entité. La magie se passe dans \Drupal\pathauto\Plugin\Deriver\EntityAliasTypeDeriver::getDerivativeDefinitions(). Trois critères sont à respecter pour que cela fonctionne clé en main.

1. Votre type d'entité doit déclarer un template de lien "canonical".

# inspectors/src/Entity/Inspector.php

/**
* Defines the Inspector entity class.
*
* @ContentEntityType(
*   ...
*   links = {
*     "add-form" = "/admin/content/inspector/add/{inspector}",
*     "add-page" = "/admin/content/inspector/add",
*     "canonical" = "/inspector/{inspector}",
*     "edit-form" = "/admin/content/inspector/{inspector}/edit",
*     "delete-form" = "/admin/content/inspector/{inspector}/delete",
*     "collection" = "/admin/content/inspector"
*   },
*   ...
* )
*/
class Inspector extends RevisionableContentEntityBase {}

2. Votre type d'entité doit être fieldable.

3. Votre bundle doit avoir un champ "path" qui stockera le chemin vers l'entité pour les cas où vous voulez débrider.

Si vous remplissez ces trois critères, votre type d'entité est maintenant pleinement utilisable dans Pathauto. Vous pouvez fermer votre CMS préféré et retourner à votre visionnage, vous n'avez raté que la première moitié de l'épisode.

Scénario 3 : Je n'ai pas de champ "path" ou n'en ai pas besoin

Si le champ "path" peut être intéressant dans certains cas, il arrivera que vos types d'entités soient plus basiques. Dans ce cas, comment l'exposer à Pathauto ? Il y a un petit peu plus de travail mais rien d'insurmontable. La solution consiste à implémenter un plugin AliasType. (Si vous ne savez pas comment fonctionnent les plugins, je vous invite à relire notre article). N'oubliez pas de déclarer son annotation @AliasType.

# inspectors/src/Plugin/pathauto/AliasType/InspectorsAliasType.php

/**
* Defines the Inspector entity class.
*
* @AliasType(
*   id = "inspector_aliases",
*   label = @Translation("Inspectors"),
*   types = {"inspector"},
*   context_definitions = {
*     "inspector" = @ContextDefinition("entity:inspector")
*   }
* )
*/
class InspectorsAliasType extends EntityAliasTypeBase {}

Les deux attributs de l'annotation à regarder de près sont types qui liste des types d'entités pour lesquels votre plugin va s'appliquer et context_definitions qui permet d'accéder à la liste des tokens de votre type d'entité (et donc ses champs, ses métadonnées et toutes les fonctionnalités avancées de Pathauto).

Ne vous précipitez pas tout de suite sur la génération de vos alias car vous risquez d'être déçu(e). La configuration est possible mais il reste une étape importante pour pouvoir profiter de tout cela, déclencher la génération. Si l'on ne donne pas un coup de pouce au système, la génération de vos alias ne se fera jamais. Pathauto implémente les hook_entity_OP() pour déclencher la génération mais si vous n'avez pas de canonical ou de champ path comme évoqué précédemment, vous devrez déclencher vous même le générateur.

Les alias doivent se créer, se mettre à jour et se supprimer en fonction de l'opération effectuée sur votre entité, vous allez donc devoir implémenter vous aussi les hook_entity_OP(). Comme on cible un type d'entité précis, les hooks à implémenter sont hook_ENTITY_TYPE_OP(). Cela donne ce code :

# inspectors.module

/**
* Implements hook_ENTITY_TYPE_update().
*/
function inspectors_inspector_update(Drupal\Core\Entity\EntityInterface $entity) {
  $generator = \Drupal::service('pathauto.generator');
  try {
    $generator->createEntityAlias($entity, 'update');
  }
  catch (\InvalidArgumentException $e) {
    $generator->messenger()->addError($e->getMessage());
  }
}

/**
* Implements hook_ENTITY_TYPE_insert().
*/
function inspectors_inspector_insert(Drupal\Core\Entity\EntityInterface $entity) {
  $generator = \Drupal::service('pathauto.generator');
  try {
    $generator->createEntityAlias($entity, 'insert');
  }
  catch (\InvalidArgumentException $e) {
    $generator->messenger()->addError($e->getMessage());
  }
}

/**
* Implements hook_ENTITY_TYPE_delete().
*/
function inspectors_inspector_delete(EntityInterface $entity) {
  \Drupal::service('pathauto.alias_storage_helper')->deleteEntityPathAll($entity);
}

Une fois vos hooks implémentés, vous devriez pouvoir tester la génération des alias et si tout s'est bien passé, cela devrait fonctionner.

Bonus : générer en masse les alias

Il vous reste une dernière action à faire si voulez bénéficier de la génération en masse des alias. Il faut implémenter la méthode \Drupal\mobility_plans\Plugin\pathauto\AliasType\MobilityPlanAliasType::bulkUpdate() dans votre plugin d'alias.

# inspectors/src/Plugin/pathauto/AliasType/InspectorsAliasType.php

class InspectorsAliasType extends EntityAliasTypeBase {

  /**
   * Update the URL aliases for multiple entities.
   *
   * @param array $ids
   *   An array of entity IDs.
   * @param array $options
   *   An optional array of additional options.
   *
   * @return int
   *   The number of updated URL aliases.
   */
  protected function bulkUpdate(array $ids, array $options = []) {
    $options += ['message' => FALSE];
    $updates = 0;
    $generator = \Drupal::service('pathauto.generator');

    $entities = $this->entityTypeManager->getStorage($this->getEntityTypeId())->loadMultiple($ids);
    foreach ($entities as $entity) {
      // Update aliases for the entity's default language and its translations.
      foreach ($entity->getTranslationLanguages() as $langcode => $language) {
        $translated_entity = $entity->getTranslation($langcode);

        try {
          $result = $generator->createEntityAlias($translated_entity, 'update');
        }
        catch (\InvalidArgumentException $e) {
          $generator->messenger()->addError($e->getMessage());
        }

        if ($result) {
          $updates++;
        }
      }
    }

    if (!empty($options['message'])) {
      $this->messenger->addMessage($this->translationManager
        ->formatPlural(count($ids), 'Updated 1 %label URL alias.', 'Updated @count %label URL aliases.'), [
        '%label' => $this->getLabel(),
      ]);
    }

    return $updates;
  }

}

Et nous y voilà ! À vous les alias finement ciselés pour parfaire votre chef d'œuvre ! Vous pouvez commiter tout cela et retourner prendre des nouvelles de Peter Falk, il est sur le point d'attraper le meurtrier et résoudre l'enquête.

Catégories
Développement
Drupal
Drupal 8
Drupal 9
Tags
pathauto
type d'entité
seo
alias
Par Simon Georges
Simon Georges
Drupal depuis plus de 10 ans, SEO depuis 3 ans

Migration Drupal 7 à 9 et évolutions du site Tramil

Créé en 1982, Tramil est un programme de recherche appliquée à l'usage populaire des plantes médicinales dans la Caraïbe. L'objectif de ce programme est de valider scientifiquement les usages traditionnels des plantes médicinales pour les soins primaires.

Pages