Créer un module Drupal
Une des grandes forces de Drupal réside en son architecture à base de modules. Que ce soit pour la gestion des blogs ou celle d’un forum, chaque fonction fondamentale est en réalité un simple module interagissant avec le cœur de Drupal. Et si les modules fournis en standard ne suffisent pas, des centaines d’autres sont disponibles couvrant à peu prés tous les usages.
Mais malgré cette richesse, il arrive parfois que l’on ne trouve pas LE module « qui va bien ». Alors pourquoi ne pas le fabriquer soi-même et ainsi découvrir à quel point Drupal s'adapte facilement à des besoins spécifiques.
Pré-requis
Les seuls pré-requis pour développer un module sont une bonne connaissance des concepts clés de Drupal (blocks, node, type de contenu, taxinomie, etc.) ainsi qu'une relative maîtrise de PHP et des bases de données.
Armé de tout cela, nous allons commencer en douceur avec la création d'un module dont le seul but est de modifier à l'affichage tous nos nodes pour y ajouter un petit message.
Les sources
L'ensemble des sources de ce tutoriel est disponible ici. Il s'agit d'un serveur Subversion, donc vous pouvez aussi directement récupérer les sources dans votre dossier site/all/modules par la commande suivante :
gaston$cd /var/www/drupal/site/all/modulesgaston$mkdir tutorielsgaston$cd tutorielsgaston$svn co http://svn.arnumeral.fr/subversion/public/tutoriels_drupal/tutoriel_modules...gaston$
Pourquoi faire son module ?
Contrairement à d'autres CMS du marché, Drupal est modulaire "à la racine" et ce depuis ses débuts. Nous sommes loin d'un système qui s'apparenterait à des greffons mais plus d'une architecture de type micro-kernel. Comme un système d'exploitation de ce type, Drupal va, lors de sa phase de démarrage, charger l'ensemble des modules activés en mémoire et les laisser communiquer entre eux pour effectuer une tâche donnée.
Ainsi la grande majorité des fonctions CMS de Drupal sont à la charge des modules, y compris les fondamentaux comme la gestion des nodes, des commentaires, des flux rss, etc. Et comme un système d'exploitation standard, le noyau et les modules disposent d'un ensemble de fonctions spécialisées et groupées par famille (cache, courriels, transactions HTTP, images, etc.), permettant tant d'accélérer le développement en évitant la duplication de code que de réduire le nombre de failles.
Une fois le travail achevé, généralement lorsque la page HTML est produite, le noyau Drupal va éteindre puis décharger les modules, pour finalement s'arrêter. A ce stade, la différence avec un système d'exploitation classique est que Drupal effectue ce cycle démarrage/chargement/traitements/déchargement/arrêt à chaque requête d'un navigateur WEB.
Dans ce type d'architecture, la nature distribuée des traitement entraîne un fort volume d'échange entre entre modules. Ce point constitue généralement le goulot de performance des micro-noyaux. Avec Drupal le problème est réglé assez brutalement par l'utilisation de simples appels de fonctions. Il n'y a donc pas de pénalités particulières à cette approche et Drupal n'est pas pour rien un des CMS les plus véloce de sa catégorie.
Toujours est-il que cette approche "modulaire" depuis l'origine a eu pour résultat une extrême richesse des fonctions que les modules peuvent exploiter. Contrairement aux CMS qui utilisent une vision "greffons" et qui prévoient les points d'attache possibles, Drupal n'est au fond que points d'attache. Il est ainsi très rare de ne pas pouvoir modifier son comportement par un module correctement développé.
Tout cela pour dire que les modules étant un des grands atouts de ce CMS, il serait dommage de se limiter à ceux fabriqués par les autres tant il est si facile de fabriquer les siens, totalement adaptés à ses besoins. Et plus loin encore, tous les modules de Drupal étant sous licence GPL, savoir en fabriquer c'est aussi savoir en modifier pour récupérer par exemple une fonction voulue plutôt que d'embarquer l'usine à gaz au grand complet...
Des modules et des hooks
Un module Drupal n'est rien d'autre qu'un bout de code PHP que l'on peut activer ou désactiver à volonté et dont le seul rôle est d'interagir avec les autres modules de Drupal.
Les interactions entre modules se font sur un mode participatif. En effet l'idée fondatrice est qu'au cours d'un de ses traitements, un module peut demander leur contribution à d'autres modules.
Pour mieux comprendre, prenons un exemple. Le module qui gère les nodes s'appelles en toute originalité node. Un de ses rôle est de transformer un contenu stocké dans la base de donnée en un contenu affichable en HTML. Une des étapes de ce traitement consiste à ajouter les liens qui se trouvent sous le contenu (répondre au commentaire, afficher la version imprimable, etc.). Le module node n'ayant en soit aucun lien qui lui soit propre à rajouter, va demander aux autres modules si l'un d'eux est intéressé. Un des modules qui va répondre présent est le module comments qui gère les commentaires et qui va ajouter le lien "Ajouter un commentaire". De même, et s'il est activé, le module print va lui aussi répondre pour rajouter cette fois le lien "version PDF", "version imprimable" et "envoyer à un ami". Et ainsi de suite.
Pour réussir à mettre en oeuvre cette collaboration, Drupal a introduit le concept de "hooks" ou "crochets". Chaque module peut ainsi déclarer un nombre arbitraire de hooks disponibles pour les autres modules qui sont généralement décrits dans sa documentation. Dans notre exemple, le hook proposé par le module node s'appelle "link" et tous les modules qui veulent répondre à ce hook vont déclarer une fonction dont le nom sera composé du nom du module suivi du nom du hook, link, et séparés par un caractère souligné. Ainsi le module comments et le module print vont tout deux implémenter une fonction PHP de ce type avec comments_links d'un côté, et print_links de l'autre. Une telle implémentation ressemblera à ceci :
Lorsque le module node va avoir besoin de la participation des modules pour ajouter ses liens, il va simplement demander à Drupal de prendre chaque nom de module activé pour regarder s'il existe une fonction composée de ce nom suivi de "_link". Ensuite Drupal va simplement appeler ces fonctions les unes après les autres. Du côté du module node, cet appel va ressembler à ceci :
$node->links = invoke_all('link', $node, $teaser);
invoke_all est la fonction drupal qui va chercher les fonctions hooks de type 'link' dans tous les modules activés et appeler cette fonction si elle existe avec les paramètres qui sont donné. Chaque module va ainsi fournir son tableau de liens que drupal va agréger pour fournir au module appelant, node, un seul tableau contenant tous les liens qui ont été ajoutés par tous ces modules.
Maintenant qui dit appel de fonction, dit paramètres. Il ne suffit donc pas de connaître le nom du hook pour pouvoir l'implémenter, encore faut-il savoir quels paramètres le module appelant va nous transmettre. Pour cela, il faut potasser la documentation de drupal. Ou alors, de manière beaucoup plus direct, il suffit de taper dans google hook_ suivi du nom du hook, par exemple hook_block. Çà marche à tout les coups. Faites cependant attention à bien sélectionner l'onglet correspondant à votre version de Drupal, car d'une version à l'autres les paramètres et les hooks eux-mêmes changent.
Structure d'un module
Organisation générale
Un module est donc un fournisseur de hooks. En conséquence, la majeure partie de son code va s'insérer dans les traitements de Drupal et étendre son comportement. Maintenant de manière plus prosaïque, un module est simplement un dossier qui contient au minimum deux fichiers.
Ce dossier doit porter le nom du module et doit être placé dans l'arborescence de Drupal, à un endroit qui soit approprié pour que ce dernier puisse le voir. Techniquement il est possible de les mettre dans modules/ mais vous seriez vite embêtés lorsque viendra le temps de mettre à jour votre Drupal. Il est donc préconisé de mettre cela dans un dossier sites/all/modules/mes_modules que vous créerez si nécessaire. Ainsi lorsque vous changez de version, il suffit de déplacer le dossier sites à sa place dans la nouvelle arborescence.
Dans ce dossier mes_modules, vous allez donc créer le sous-dossier du module lui-même, dans notre cas tutoriel-modules. A l'intérieur de celui-ci, vous devez avoir au minimum deux fichiers, portant le même nom de base que le dossier parent : tutoriel-modules.info et tutoriel-modules.module. Comme nous allons le voir plus loin, le premier fichier contient des informations sur le module, le second son code PHP. Tout ceci nous donne l'arborescence suivante :
modules
... etc ...
site
all
modules
contributions
...etc...
mes_modules
tutoriel-modules
tutoriel-modules.info
tutoriel-modules.module
...etc...
themes
...etc...
Fichier .info
Le fichier tutoriel-modules.info est un simple fichier texte contenant des informations sur le module comme son nom, sa description, la version de Drupal avec lequel il est compatible, etc. Ce qui nous donne pour notre exemple, le contenu suivant :
Authentication realm: <http://svn.arnumeral.fr:80> Subversion
Password for 'apache': Authentication realm: <http://svn.arnumeral.fr:80> Subversion
Username: svn: OPTIONS of 'http://svn.arnumeral.fr/subversion/public/tutoriels_drupal/tutoriel_modules/tutoriel_modules.info': authorization failed (http://svn.arnumeral.fr)tutoriel_modules.info (Sources)
package indique dans quelle catégorie le module doit être affiché. name, description sont des informations textuelles qui seront affichées dans le panneau d'administration des modules. name reprend le nom du module (qui est aussi le nom du dossier). version indique quant à lui la version du module. C'est d'ailleurs plus une norme qu'autre chose qui se lit "Module version 0.1 pour Drupal 6.x". Enfin core prévient Drupal que le module est seulement compatible avec sa version 6.x. Tout autre version de Drupal empêchera le module de s'activer.
Si nous avions eu besoin de définir une dépendance avec un autre module, nous aurions ajouté une clause de la forme dependencies[] = autre_module. Enfin, si vous désirez ajouter des commentaires, il suffit d'utiliser le symbole ; suivi du texte du commentaire.
Fichier .module
Le second fichier est le code PHP du module. C'est lui qui va héberger les hooks que nous souhaitons implémenter. Pour l'instant, nous allons faire simple et n'en mettre aucun. Notre fichier va donc contenir une simple balise de démarrage de code PHP :
tutoriel_modules.module
Nous avons maintenant un module complet et inutile mais que Drupal est cependant capable de voir et d'activer. Pour s'en convaincre, il suffit d'aller faire un tour en /admin/build/modules pour le voir apparaître dans la liste. Vous constatez que les informations affichées sont les champs name et description de notre fichier tutoriel_modules.info.
A ce stade nous pourrions donc activer notre module, mais ne le faites pas tout de suite, nous avons encore quelque chose à rajouter.
Implémentation d'un hook
Ce module n'a pas d'autre but que de nous permettre de comprendre comment tout cela fonctionne, aussi ne vous attendez pas à un comportement extraordinaire. Nous allons simplement utiliser le hook link du module node.modules dont nous parlions plus haut pour ajouter à chaque node un nouveau lien "modifier" qui permettra d'éditer le contenu du node courrant.
Comme nous l'avons vu, le hook link est défini de la manière suivante dans la documentation drupal :
hook 'link'
Ce qui veut dire que nous allons devoir ajouter à notre fichier tutoriel_modules.module une fonction définie comme ceci :
fonction hook_link
Notre implémentation du hook est bien nommée {nom_du_module}_{nom_du_hook}, Drupal le reconnaîtra donc lorsque le module node aura besoin de l'appeler.
Même si ce n'est pas directement le sujet de cet article, ce hook est trés pratique pour ajouter de nouveaux liens sous les nodes, mais aussi sous les commentaires. En effet, le paramètre $type permet de faire la distinction entre le type de lien demandé par $type=="comment" ou $type=="node". Le second paramètre $object sera donc soit un node, soit un commentaire selon la valeur de $type, mais ici c'est le type "node" qui nous intéresse.
Le dernier paramètre permet quant à lui de savoir si le lien est désiré sous le node dans la forme contractée (teaser), comme par exemple sur la page de garde du site, ou sous sa forme complète. Nous avons maintenant tout ce qu'il nous faut pour ajouter un lien à tous les nodes en mode "teaser" comme "complet".
Authentication realm: <http://svn.arnumeral.fr:80> Subversion
Password for 'apache': Authentication realm: <http://svn.arnumeral.fr:80> Subversiontutoriel_modules.module (Sources)
La première chose que nous faisons ici est de vérifier que l'utilisateur a bien le droit de modifier le contenu. Si nous ne le faisons pas le lien serait visible y compris pour les visiteurs anonymes. Même si ces derniers aboutiraient sur une erreur 403 ce ne serait pas très élégant.
Ensuite, une fois que nous nous somme assuré que la demande vise bien un node, il suffit de créer un tableau associatif contenant une série de tableaux représentant chacun un lien. Ici nous n'en avons qu'un mais rien n'empêche d'en mettre plusieurs. Dans tous les cas c'est Drupal qui se charge de transformer ce tableau en la balise <A href="..." qui va bien. Ce tableau reprend peu ou prou les arguments de la fonction Drupal l(...) que vous pouvez étudier ici.
Notez que l'on ne met pas de slash au début de l'attribut href. Ceci permet à la fonction Drupal l(...) de choisir toute seule la meilleur représentation pour l'URL. Du coup si vous utilisez le module Alias ou l'internationalisation, l'URL sera modifiée pour correspondre à vos réglages.
Pour finir, un dernier regard sur la fonction t(...) qui est la formule magique permettant à Drupal de traduire le mot anglais edit dans la langue courante de l'interface. Une bonne habitude à prendre dés le début.
Il ne nous reste maintenant plus qu'à activer notre module et à regarder son impact sur un node quelconque.
Conclusion
Voilà, nous avons terminé notre premier module fonctionnel en espérant que vous avoir montré à quel point, une fois quelques notions de base assimilées Drupal, construire ce genre de brique est rapide et facile.