Demande d'information
Alignement des images
Les balises audio et video foirent
Planète Libre et Micro-blogging
Mis à jour du flux RSS
Partages ?
®om
4LW
Admin-Linux
agatzebluz
Aldevar
Another Pinky Punky
AnTav
Antistress
Antoine Millet
Antonin Moulart
archi02
arNuméral
Artisan Numérique
Asher256
Aternatik
Aurélien Bompard
Bastnic
Benkemoun
Bilbo Planet
Biscotte
Blogmotion
bochecha
botchchikii
bouleetbil
Boutor
Breizh ardente
Cairo-Dock
Cameleon
Capof's Space
Captaine74
Carl Chenet
Cedynamix
champtoussel dominique
ChEza
Chicha
Chimrod
Christophe-Marie
Clapico
Corbier
Costalfy
Creasy
CSM 'illovae' Seldon
CyberSDF
Cyrille BORNE
dada
dahu_fou
Damien Cougar
Damocles
Daria
David Dup
David Larlet
Davromaniak
Ddmdllt
Des nouvelles de Wikilivres
Desidia
Devil505
Dhoko
DigitalSpirit
djibux
Dorian Dd
Duchatelet
E-PhasE
Eddy33
Edouard
Effraie
eMerzh
Emilien Macchi
Emilpoe
Emmanuel Gontcho
Emmanuel Kasper
Equinoxefr
Eric
Exceed
FACIL
Facilinux
Feilong
fgallaire
Finss
florentg
floruby
Fonctionerd
Framablog
François
Franck Archange
FredBezies
Full Circle Magazine
Fuse
Génération Linux
G3L
Gaëtan Tenshu
Gilir
Grégory Gutierez
Gregory Colpart
Guillaume Kulakowski
Hugues
Hyla project
Il Palazzo-sama
inalgnu
Jérémy Verda
Jeff
jeremy2491
jeromeg
jesuislibre
JJL
Johan Cwiklinski
Jonathan Ernst
Jonathan Le Lous
Jopa
Jp Fox
Juky
Julien
Julius
ka.da
Kagou
kamagatos
Kate
Kiddo
KissCoolMan
Labo-Linux
LeDucDuBleuet
Lemarinel
Lenezir
Liberez le tux
Libre Astux
Linalis
Littlewing
Louis Roché
lowje
Luc
Macsim
Manu Absolacom
Marco
Matao
Mathieu Comandon
Maxime Carron
McKey
meepix
Michael Zwyssig
Michauko
Mickaël
Minimumserious
Monitoring-FR
Morot
Motarion
mozillaZine-fr
Mr.Yann
MrTom
Nÿco
Naparuba
Nicofo
Nicolargo
Nicosmos
Nicoz
NiKo
nizarus
Noplay
Olivier Faurax
Olivier Prieur
Omega
Oncle Tom
Op'Aisne Source
openSyd
opossum1er
Osku
OxyRadio
Paquet Fedora du Jour
Pascal Chevrel
pc-kc
Peck
Phil
Pianopenguin
Pingax
PlayOnLinux
Ploum
Pokemon_JOJO
Poupoul2
Rémi Samier
Raphaël Hertzog
Ravomavain
Renaud Littolff
Renault
Respawner
Retouche Libre
Ricard
Robin Millette
Roland Mas
RollsRox
Rydgel
Saïmon
Samuel Martin
Sauthier
SckyzO
Scoffoni
Scurz
Shnoulle
Silvyn
Skhaen
Slobberbone
Splitsch
StandarT
StephZ
Sylvain
System Linux
Taltan
Tbellemb
Tchouvince
theClimber
TheGlu
TheLinuxFr
Thibaut
Thierry Andriamirado
Thom1
Thomas Bassetto
Tigrou Damien
TitaX
toitoinebzh
Toorop
TrouveTonGull.info
Tuxargon
Tuxicoman
U-Classroom
Uggy
Ulrich Diplodocus
Une goutte de blog
Uselink
Vanaryon
VELCS
Vetsel
Warren Dumortier
Wattazoum
Wavemaker
Weedfast
Yannig
yeKcim
Yellowiscool
Yoho
Yves Gesnel
Zanko
Zic
Zippy
ZitrouilleCeci étant dit, rien n'est encore fait, mais il semblerait que nous en prenions clairement le chemin.
Pour une part, il est clair que CVS peut être considéré à juste titre comme un gestionnaire de version pour le peu archaïque. Et il ne faut pas s'en étonner, ce système a tout de même plus de 20 ans !
Pour revenir à nos moutons, nous étions beaucoup à nous demander quand drupal.org se déciderait à basculer le dépôt des modules contribution (et du core) sur quelque chose de plus moderne, comme au hasard, subversion. Manque de bol, la grande mode des gestionnaires de version distribués est passée par là, et subversion nous est passé sous le nez au profit de GIT.
Alors qu'est-ce qu'un
La gestion décentralisée de version permet donc à chacun, y compris de petites équipes, de travailler sur les sources d'un projet tout en laissant à d'autre le choix d'intégrer ou pas les modifications au sein du projet central. C'est en quelque sorte la notion de fork généralisé. Ce concept est très intéressant pour permettre à toute une communauté de s'exprimer sur des projets d'envergure comme le noyau Linux, ou dans une moindre mesure, Drupal Core. C'est d'ailleurs Torvald, après une saga autour du gestionnaire de version propriétaire BitKeeper, qui a écrit les premières moutures de GIT. Mais
Entendons nous bien, je n'ai absolument rien contre ce type de gestionnaire, et encore moins contre GIT que j'envisage d'adopter pour mes propres projets. Mais autant je peux comprendre ce choix dans le cas du core de Drupal, autant pour la myriade de modules contribs, maintenus à grand peine par de petits développeurs dans leur coin (dont je fais parti) qui n'ont pas que cela à foutre de jongler d'un gestionnaire à l'autre. Et si l'on met ceci en perspective du fait que l'adoption massive de Subversion en entreprise est enfin concrétisée, GIT un choix que je trouve un peu... lourd.
D'autant plus lourd que ce produit, comme son concept de base, est très jeune. Cela veut dire apprendre un nouveau mode de fonctionnement pour ceux qui ont déjà eu du mal à passer à SVN, mais cela implique aussi une maturité plus faible sur des plate-formes comme
Tout drupalien sait ce qu'est un "élément de menu" : un machin qui se colle dans un... menu, et qui apparaît quelque part sur l'interface graphique pour permettre l'affichage d'une page. Ces éléments de menu sont généralement crées à la mano en passant par le backoffice, via la section Construction du site puis Menus. Là on peut ajouter des associations entre un chemin valide et un titre de lien.
Ce "chemin valide" est systématiquement fournit par l'un des modules activé (ex. user/login issu modules/user). Mais alors comment créer ses propres chemins liés à ses propres pages ou actions, sans passer par d'inutile (pour cela) usines à gaz comme Panel ou Views ?
Le but de ce tutoriel est de démystifier ce passage obligé de la vie d'un module.
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://www.arnumeral.fr/subversion/public/tutoriels_drupal/tutoriel_menus...gaston$
Lorsqu'apache reçoit une requête, par exemple http://mon_site/mon-super-article, il commence par ré-écrire la partie "chemin" de l'URL de sorte à la rendre assimilable par Drupal. C'est le fameux système d'URL simplifiées (clean UEL). L'URL devient alors http://mon_site/index.php?q=mon-super-article, index.php étant le point d'entrée de Drupal.
La partie ?q=mon-super-article est donc une variable $_GET['q'] que va recevoir drupal pour effectuer une action. La première étape pour résoudre ce chemin, est de déterminer si le contenu de $_GET['q'] ne serait pas hasard pas un alias en cherchant une correspondance dans la table url_alias. L'exemple est bien choisi, il en trouvera un qui sera node/666. mon-super-article est donc le chemin, et node/666 le chemin dit "interne", celui que va réellement prendre en charge un module.
Et c'est effectivement la seconde étape de Drupal, trouver quel module est en charge du chemin interne node/666. Plus exactement, et attention il va y avoir risque de confusion de vocabulaire, Drupal va chercher quel "menu" est associé à ce chemin. Pour ne pas entretenir la confusion trop longtemps, un "menu" pour les modules, est une association entre un chemin interne (ici node/666), et un module (ici, le module modules/node). Pour simplifier le discours, j'appellerais cela un menu handler pour le différencier des éléments de menu qui eux, associent un lien à un chemin interne.
Si aucun handler n'est trouvé, Drupal renvoie le fameux 404 fichier non trouve. Si le handler existe bien mais que l'utilisateur qui cherche à le déclencher n'a pas les droits pour cela, il renverra un 401 Access denied. Enfin si le handler existe et que l'utilisateur a les droits, Drupal va transférer le traitement à une des fonctions du module qui a déclaré ce handler.
Cette fonction, appelée callback a plusieurs possibilités. Si le chemin interne correspond à une page (c'est le cas de node/666), la fonction callback va renvoyer, par son return, une chaîne de caractère qui correspondra au coeur de page (région content). Drupal va alors récupérer cette chaîne, la faire passer dans le thème, et ainsi produire la page correspondant au chemin interne. C'est ainsi que l'article http://mon_site/mon-super-article sera affiché.
Autre cas de figure, le chemin interne n'est pas une page, mais une action comme par exemple node/666/delete (suppression d'un article). Dans ce cas, la fonction callback n'a aucune raison de renvoyer une chaîne de caractère. Elle va simplement supprimer l'article, et terminer, sans return par un drupal_goto pour rediriger une un chemin interne d'atterrissage.
Enfin, dernier cas un peu moins usité, le chemin interne doit renvoyer des données, par exemple du code XML pour une procédure AJAX. Dans ce cas le code sera imprimé (print ou echo) directement dans la fonction de callback, qui se terminera par un très brutal exit();, coupant ainsi la chique à Drupal.
Maintenant que le sujet est un peu dégrossi, voyons comment faire déclarer un handler par un module. Pour commencer, il nous faut un construire un module de base (voir ce tutoriel) dans lequel nous allons implémenter un hook_menu. Ce hook permet à Drupal de récupérer l'ensemble des handlers de menu publiés par un module donné et son implémentation ressemble à ceci :
function tutoriel_menus_menu() {
$items = array ();
$items['tutoriels/menus/simple'] = array (
'title' => 'Un menu simple',
'description' => "Ceci est un menu simple",
'page callback' => 'tutoriel_menus_simple_menu_callback',
'file' => 'tutoriel_menus.pages.inc',
'access arguments' => array (
'access content'
)
);
return $items;
}implémentation d'un hook_menu
Chaque handler déclaré par le hook_menu est une entrée pour un tableau qui sera renvoyé en retour de la fonction. La clef utilisée par ce tableau est le chemin interne pris en charge par le handler, et sa valeur, une structure le décrivant. Dans cette structure nous avons des éléments simples à comprendre comme title ou description qui permettrons à Drupal de créer un élément de menu à partir de ce handler. Notez que ces deux chaînes ne doivent pas utiliser la fonction de traduction t(...).
page callback est l'élément fondamental de ce handler. Il s'agit de la fonction qui va être appelée par Drupal pour effectuer l'action associée au chemin interne. Le paramètre file définit quant à lui dans quel fichier PHP cette fonction se trouve. Cette astuce a permis à Drupal 6 d'améliorer grandement ses performances par rapport à Drupal 5. En effet, file permet de ne charger que les fichiers PHP utiles au traitement d'un chemin, sans charger tout le reste.
Si le paramètre file est omis (ce qui n'est pas conseillé), Drupal cherchera la fonction parmi celles déjà chargée en mémoire. Si ce paramètre est présent, le fichier correspondant sera cherché dans le dossier du module (utilisez le paramètre file path si le fichier se trouve hors de ce dossier). D'une manière général, les callbacks correspondant à des pages sont rangées dans un fichier mon_module.pages.inc. Celles correspondant à l'administration dans mon_module.admin.inc, etc.
Le dernier paramètre, access argument, contient un tableau de permissions nécessaires à l'accès au menu. Ici access content désigne le droit d'accéder au contenu, ce que tout le monde peut généralement faire. Mais par exemple pour limiter aux seuls administrateur, nous aurions pu mettre administer site configuration.
Ce paramètre access argument est en réalité utilisé conjointement avec le paramètre access callback. Mais lorsque ce dernier est omis, Drupal utilise par défaut la valeur user_access. Ainsi vous comprenez que ces deux paramètres correspondent en réalité à un appel à la fonction user_access(array('access content')) qui renvoie vrai si l'utilisateur courant a les droits demandés. A titre d'exemple, vous auriez pu aussi utiliser pour access callback les fonctions is_anonymous_user ou user_is_logged_in, toute deux fournies par le module user. Comme elles ne prennent pas de paramètre, le paramètre access arguments peut dans ce cas être omis.
D'une manière générale, vous pouvez utiliser la fonction qui vous chante pour gérer les droits, pour peu qu'elle renvoie true si l'utilisateur est valide et false dans le cas contraire.
Il ne nous reste maintenant plus qu'à implémenter la callback, en commençant par créer un fichier tutoriel_menu.pages.inc et y placer une fonction comme celle-ci :
function tutoriel_menus_simple_menu_callback() {
$output="Coeur de page associé à ce modeste menu";
return $output;
}tutoriel_menus.pages.inc - callback générique
Bon, un peu simpliste comme rendu, mais c'est juste pour l'exemple. Vous pouvez mettre dans $output tout ce qui vous chante, de simple messages à des tableaux triables.
Il ne nous reste maintenant plus qu'à tester cela. Attention cependant, ce hook n'est, pour des raisons de performances, invoqué qu'à l'activation du module. C'est très bien pour la première fois, mais moins drôle lors des essais suivants. Pour régler ce problème, je vous conseille d'installer le module administration menu qui dans son menu déroulant de droite, dispose d'une action de vidange du cache des menus.
Une fois le module activé, vous devriez voir apparaître dans votre menu Navigation, le nouveau menu Un menu simple. En l'activant, dans le coeur de page, doit apparaître le retour de notre callback.
Il est possible de prolonger l'exemple précédent en créant une cascade de menu. Basiquement, une telle arborescence consiste simplement à créer un menu par niveau de chemin. Dans le chapitre précédent, le chemin était tutoriels/menus/simple. Pour rendre visible les deux niveaux précédents, il nous font donc créer deux menus respectivement pour tutoriels, puis tutoriels/menus.
$items['tutoriels'] = array (
'title' => 'Les tutoriels',
'page callback' => 'tutoriel_menus_tutoriels_callback',
'file' => 'tutoriel_menus.pages.inc',
'access arguments' => array (
'access content'
),
'expanded'=>true
);
$items['tutoriels/menus'] = array (
'title' => 'Les menus',
'description' => t("Tutoriel sur les menus"),
'page callback' => 'tutoriel_menus_tutoriels_menu_callback',
'file' => 'tutoriel_menus.pages.inc',
'access arguments' => array (
'access content'
),
'expanded'=>true
);Ajout de deux niveaux de menu
Notez la propriété expanded qui permettent d'auto-déployer les deux niveaux pour que tout soit visible par défaut. Enfin, comme nous utilisons deux nouvelles callback, il faut aussi les ajouter dans tutoriel_menu.pages.inc
function tutoriel_menus_tutoriels_callback() {
return "Les tutoriels";
}
function tutoriel_menus_tutoriels_menus_callback() {
return "Les tutoriels des menus";
}Ajout des deux callback
Il suffit maintenant de reconstruire les menus et d'observer le résultat.
Par défaut, nos éléments de menu sont intégrés par Drupal dans le menu administration si le chemin commence par admin/..., ou dans le menu navigation le cas échéant. Il est cependant possible d'ajouter nos handlers ailleurs en utilisant la propriété menu_name. Pour illustrer cette possibilité, nous allons ajouter aux liens primaires (un menu généralement affiché en haut à droite de chaque page), l'élément de menu A propos de nous :
$items['informations'] = array (
'title' => 'A propos de nous',
'page callback' => 'tutoriel_menus_a_propos_callback',
'file' => 'tutoriel_menus.pages.inc',
'access arguments' => array (
'access content'
),
'menu_name' => 'primary-links'
);Ajout d'un élément de menu dans le menu 'liens primaires'
Comme toujours, il nous faut une nouvelle callback :
function tutoriel_menus_a_propos_callback() {
return "Bla bla bla...";
}Callback pour l'élément de menu 'information'
Une fois le cache des menus reconstruit, si vous allez dans l'administration des menus, sur le menu Liens primaires, vous devriez voir apparaître le nouvel élément de menu. Et si votre thème l'affiche par défaut en haut à droite de la page, il y sera directement visible.
L'astuce ici consiste à savoir que le nom interne du menu liens primaires est primary-links. En utilisant ce nom pour la propriété menu_name, nous avons forcé l'ajout de ces menus aux liens primaires. Notez que c'est un ajout totalement dynamique, au sens où les menus ainsi créés disparaîtront d'eux même à la prochaine reconstruction des menus si vous les supprimez de votre hook. En somme une alternative intelligente pour déployer facilement des menus en production sans avoir à se refrapper une configuration manuelle, par exemple pour instancier toutes les sections d'un site sur le menu "liens secondaires".
Une autre manière de gérer les menus arborescents est de les afficher sous la forme d'onglet. Drupal permet en effet de gérer ainsi deux niveaux d'onglets (vous en avez un exemple dans la configuration d'un thème). Ces onglets sont très faciles à mettre en oeuvre pour peu d'en comprendre la logique.
$items['tutoriels/menus/simple/onglet-1'] = array (
'title' => 'Onglet 1',
'type' => MENU_DEFAULT_LOCAL_TASK,
'access arguments' => array (
'access content'
)
);
$items['tutoriels/menus/simple/onglet-2'] = array (
'type' => MENU_LOCAL_TASK,
'title' => 'Onglet 2',
'page callback' => 'tutoriel_menus_onglet2_callback',
'file' => 'tutoriel_menus.pages.inc',
'access arguments' => array (
'access content'
)
);
$items['tutoriels/menus/simple/onglet-2/onglet-2-1'] = array (
'title' => 'Onglet 2.1',
'access arguments' => array (
'access content'
),
'type' => MENU_DEFAULT_LOCAL_TASK
);
$items['tutoriels/menus/simple/onglet-2/onglet-2-2'] = array (
'title' => 'Onglet 2.2',
'page callback' => 'tutoriel_menus_onglet22_callback',
'file' => 'tutoriel_menus.pages.inc',
'access arguments' => array (
'access content'
),
'type' => MENU_LOCAL_TASK
);Ajout d'onglets
Comme vous le voyez, nous sommes ici proche des menus arborescents. La nouveauté est tout d'abord l'utilisation du paramètre type indiquant à Drupal que nous définissons ici des onglets (appelés Tasks, ou tâches). MENU_LOCAL_TASK marque un onglet normal et MENU_DEFAULT_LOCAL_TASK marque l'onglet par défaut pour un niveau. Notez l'absence de callback pour les handlers marqués MENU_DEFAULT_LOCAL_TASK. En effet, la règle est que chaque niveau d'onglet doit disposer d'un MENU_DEFAULT_LOCAL_TASK, et que ce handler est automatiquement au chemin interne de son handler parent. En d'autres termes, si vous cliquez sur Onglet 1, c'est le chemin de notre menu simple qui s'affiche et son handler qui est utilisé. De même en cliquant sur Onglet 2.1, c'est le handler et donc le chemin et la callback de l'onglet 2 qui est utilisé. Du coup, nous n'avons que deux callback à ajouter
function tutoriel_menus_onglet2_callback() {
return "Contenu de l'onglet 2";
}
function tutoriel_menus_onglet22_callback() {
return "Contenu de l'onglet 22";
}Callbacks pour les onglets
Comme nous l'avons vu plus haut, nous fabriquons dans notre module des handlers de menu, et non des éléments de menus. Si jusqu'à maintenant nous avions une création automatique d'éléments de menu pour chacun de nos handlers, c'est soit que nous ométions le paramétrage type, qui a pour valeur par défaut MENU_NORMAL_ITEM (comprendre "un handler pour lequel drupal doit créer un élément de menu"), ou que nous voulions afficher des onglets avec MENU_LOCAL_TASK et MENU_DEFAULT_LOCAL_TASK.
Maintenant, dans de nombreux cas nos modules n'ont aucun besoin d'éléments de menu mais juste une URL associée à une page et/ou une action. C'est par exemple le cas si nous voulons créer une action d'ajout, ou dans l'exemple qui suit, un simple Hello World.
$items['tutoriels/menus/hello'] = array (
'page callback' => 'tutoriel_menus_hello_callback',
'file' => 'tutoriel_menus.pages.inc',
'access arguments' => array (
'access content'
),
'type'=>MENU_CALLBACK
);Ajout d'un handler sans élément de menu
Et comme toujours, nous ajoutons la fonction callback associée
function tutoriel_menus_hello_callback() {
return "Hello World";
}Callback du handler sans élément de menu
Une fois le cache de menu reconstruit, vous constaterez que cette fois, aucun élément de menu n'a été rajouté par Drupal. Pour utiliser ce handler, il nous faut directement taper son URL (ou chemin interne) directement dans la zone d'adresse du navigateur, soit http://mon_site/tutoriels/menus/hello.
Les seules différences avec les handlers que nous avons créé jusqu'à maintenant sont que nous n'avons fournit à Drupal ni titre, ni description, et que nous avons en revanche spécifié MENU_CALLBACK comme paramètre type. Ce type permet juste de dire à Drupal qu'il n'est pas utile de cherche à créer un élément de menu.
Ce type de handler sans élément de menu est très utile pour créer des actions comme "ajouter", "supprimer", etc. Encore faut il pouvoir fournir des paramètres à ce menu de sorte à pouvoir indiquer, dans l'URL, la référence de l'objet à détruire.
Depuis la version 6, Drupal dispose d'un système très bien fait pour passer des paramètres au handler, basé sur le caractère %. Ainsi si nous ajoutons le menu suivant :
$items['tutoriels/menus/hello1/%'] = array (
'page callback' => 'tutoriel_menus_hello1_callback',
'page arguments' => array(3),
'file' => 'tutoriel_menus.pages.inc',
'access arguments' => array (
'access content'
),
'type'=>MENU_CALLBACK
);Ajout d'un handler paramétré
Et que nous ajoutons la callback suivante :
function tutoriel_menus_hello1_callback($message) {
return "Hello $message";
}Ajout d'une callback paramétrée
Après reconstruction du menu, nous constatons que l'URL http://mon_site/tutoriels/menus/hello1/gaston provoque l'affichage du message Hello gaston. Pour comprendre ce qui se passe, retournons sur la déclaration de notre handler.
D'abord, nous avons dans le chemin interne un symbole % qui indique à Drupal que cet élément de chemin est un paramètre et peut donc prendre n'importe quelle valeur (ici gaston). Ensuite nous avons un nouveau paramètre page arguments contenant un tableau. Le contenu de ce tableau sera transmis à la callback. Si un de ses éléments est un chiffre, il sera préalablement par l'élément de chemin de rang correspondant (qui commence à
. Si l'élément du tableau n'est pas un chiffre, il sera transmis tel-quel à la fonction.
Dans le cas de notre chemin, nous demandons à Drupal de placer en seul paramètre de la callback l'élément de chemin de rang 3 (c'est à dire le 4ième). C'est pour cela que notre callback dispose d'un paramètre $message qui recevra cette valeur.
Il est possible d'avoir plusieurs paramètres à la callback dont l'ordre et le type sera spécifié par page arguments. Mais plus intéressant encore, il est aussi possible de passer en paramètre des objets préchargés par Drupal. Pour tester cela, ajoutons encore un nouvel handler et sa callback :
$items['tutoriels/menus/hello2/%user'] = array (
'page callback' => 'tutoriel_menus_hello2_callback',
'page arguments' => array(3),
'file' => 'tutoriel_menus.pages.inc',
'access arguments' => array (
'access content'
),
'type'=>MENU_CALLBACK
);
// callback associée à mettre dans tutoriel_menus.pages.inc
function tutoriel_menus_hello2_callback($user) {
return "Hello {$user->name}";
}Ajout d'un handler paramétré avec objet
Reconstruisons le cache des menus et lançons l'URL http://mon_site/tutoriels/menus/hello2/1. Vous devriez alors voir apparaître un Hello administrateur (ou n'importe quel nom que vous aurez utilisé comme administrateur de votre site).
L'astuce ici tient à l'utilisation non plus du simple %, mais de %user. Ce dernier indique à Drupal de "charger en mémoire l'utilisateur aillant pour ID l'élément de chemin donné dans l'URL". C'est ainsi que notre callback hérite d'un objet $user chargé à partir de l'ID 1 (celui de l'administrateur).
Il est intéressant de comprendre comment le "magie" fonctionne ici. Lorsque Drupal rencontre un paramètre de handler de la forme %objet, il va chercher une fonction pré-existante de la forme objet_load. S'il la trouve (ici c'est le cas, il s'agit de user_load), il lui passe en paramètre l'élément de chemin (ici 1) correspondant. Les fonctions objet_load renvoient toujours l'objet chargé à partir du paramètre (ici $user=user_load(1)), et c'est cet objet qui est utilisé comme paramètre à la callback.
En standard, Drupal dispose de plusieurs fonctions de type objet_load. Citons par exemple node_load qui par un %node permet de passer un contenu en paramètre d'une callback.
Mais vous pouvez aussi créer la votre. En effet, si vous utilisez un élément de chemin %mon_objet comme ceci
Vous devez en outre ajouter une fonction de type object_load pour que Drupal puisse faire correspondre le paramètre de rang 3, à l'argument %mon_objet :$items['tutoriels/menus/hello3/%mon_objet'] = array (
'page callback' => 'tutoriel_menus_hello3_callback',
'page arguments' => array(3),
'file' => 'tutoriel_menus.pages.inc',
'access arguments' => array (
'access content'
),
'type'=>MENU_CALLBACK
);
// callback associée à mettre dans tutoriel_menus.pages.inc
function tutoriel_menus_hello3_callback($mon_objet) {
return "Hello {$mon_objet['name']}";
}Utilisation d'un chargeur custom
function mon_objet_load($id) {
return array('name'=>"Objet[$id]");
}Chargeur custom
Vous n'avez maintenant plus qu'à reconstruire les menus et tester http://mon_site/tutoriels/menus/hello3/12 pour voir apparaître "Hello Objet[12]".
Voilà, fin du "petit" tour d'horizon sur les menus. Comme vous l'avez vu, le sujet est pour le peu dense, à la mesure de la richesse du sujet. Ceci étant dit, nous n'avons ici qu'abordé l'essentiel, il reste encore beaucoup de choses à découvrir en explorant l'API des menus.
Tout drupalien sait ce qu'est un "élément de menu" : un machin qui se colle dans un... menu, et qui apparaît quelque part sur l'interface graphique pour permettre l'affichage d'une page. Ces éléments de menu sont généralement crées à la mano en passant par le backoffice, via la section Construction du site puis Menus. Là on peut ajouter des associations entre un chemin valide et un titre de lien.
Ce "chemin valide" est systématiquement fournit par l'un des modules activé (ex. user/login issu modules/user). Mais alors comment créer ses propres chemins liés à ses propres pages ou actions, sans passer par d'inutile (pour cela) usines à gaz comme Panel ou Views ?
Le but de ce tutoriel est de démystifier ce passage obligé de la vie d'un module.
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://www.arnumeral.fr/subversion/public/tutoriels_drupal/tutoriel_menus...gaston$
Lorsqu'apache reçoit une requête, par exemple http://mon_site/mon-super-article, il commence par ré-écrire la partie "chemin" de l'URL de sorte à la rendre assimilable par Drupal. C'est le fameux système d'URL simplifiées (clean UEL). L'URL devient alors http://mon_site/index.php?q=mon-super-article, index.php étant le point d'entrée de Drupal.
La partie ?q=mon-super-article est donc une variable $_GET['q'] que va recevoir drupal pour effectuer une action. La première étape pour résoudre ce chemin, est de déterminer si le contenu de $_GET['q'] ne serait pas hasard pas un alias en cherchant une correspondance dans la table url_alias. L'exemple est bien choisi, il en trouvera un qui sera node/666. mon-super-article est donc le chemin, et node/666 le chemin dit "interne", celui que va réellement prendre en charge un module.
Et c'est effectivement la seconde étape de Drupal, trouver quel module est en charge du chemin interne node/666. Plus exactement, et attention il va y avoir risque de confusion de vocabulaire, Drupal va chercher quel "menu" est associé à ce chemin. Pour ne pas entretenir la confusion trop longtemps, un "menu" pour les modules, est une association entre un chemin interne (ici node/666), et un module (ici, le module modules/node). Pour simplifier le discours, j'appellerais cela un menu handler pour le différencier des éléments de menu qui eux, associent un lien à un chemin interne.
Si aucun handler n'est trouvé, Drupal renvoie le fameux 404 fichier non trouve. Si le handler existe bien mais que l'utilisateur qui cherche à le déclencher n'a pas les droits pour cela, il renverra un 401 Access denied. Enfin si le handler existe et que l'utilisateur a les droits, Drupal va transférer le traitement à une des fonctions du module qui a déclaré ce handler.
Cette fonction, appelée callback a plusieurs possibilités. Si le chemin interne correspond à une page (c'est le cas de node/666), la fonction callback va renvoyer, par son return, une chaîne de caractère qui correspondra au coeur de page (région content). Drupal va alors récupérer cette chaîne, la faire passer dans le thème, et ainsi produire la page correspondant au chemin interne. C'est ainsi que l'article http://mon_site/mon-super-article sera affiché.
Autre cas de figure, le chemin interne n'est pas une page, mais une action comme par exemple node/666/delete (suppression d'un article). Dans ce cas, la fonction callback n'a aucune raison de renvoyer une chaîne de caractère. Elle va simplement supprimer l'article, et terminer, sans return par un drupal_goto pour rediriger une un chemin interne d'atterrissage.
Enfin, dernier cas un peu moins usité, le chemin interne doit renvoyer des données, par exemple du code XML pour une procédure AJAX. Dans ce cas le code sera imprimé (print ou echo) directement dans la fonction de callback, qui se terminera par un très brutal exit();, coupant ainsi la chique à Drupal.
Maintenant que le sujet est un peu dégrossi, voyons comment faire déclarer un handler par un module. Pour commencer, il nous faut un construire un module de base (voir ce tutoriel) dans lequel nous allons implémenter un hook_menu. Ce hook permet à Drupal de récupérer l'ensemble des handlers de menu publiés par un module donné et son implémentation ressemble à ceci :
function tutoriel_menus_menu() {
$items = array ();
$items['tutoriels/menus/simple'] = array (
'title' => 'Un menu simple',
'description' => "Ceci est un menu simple",
'page callback' => 'tutoriel_menus_simple_menu_callback',
'file' => 'tutoriel_menus.pages.inc',
'access arguments' => array (
'access content'
)
);
return $items;
}implémentation d'un hook_menu
Chaque handler déclaré par le hook_menu est une entrée pour un tableau qui sera renvoyé en retour de la fonction. La clef utilisée par ce tableau est le chemin interne pris en charge par le handler, et sa valeur, une structure le décrivant. Dans cette structure nous avons des éléments simples à comprendre comme title ou description qui permettrons à Drupal de créer un élément de menu à partir de ce handler. Notez que ces deux chaînes ne doivent pas utiliser la fonction de traduction t(...).
page callback est l'élément fondamental de ce handler. Il s'agit de la fonction qui va être appelée par Drupal pour effectuer l'action associée au chemin interne. Le paramètre file définit quant à lui dans quel fichier PHP cette fonction se trouve. Cette astuce a permis à Drupal 6 d'améliorer grandement ses performances par rapport à Drupal 5. En effet, file permet de ne charger que les fichiers PHP utiles au traitement d'un chemin, sans charger tout le reste.
Si le paramètre file est omis (ce qui n'est pas conseillé), Drupal cherchera la fonction parmi celles déjà chargée en mémoire. Si ce paramètre est présent, le fichier correspondant sera cherché dans le dossier du module (utilisez le paramètre file path si le fichier se trouve hors de ce dossier). D'une manière général, les callbacks correspondant à des pages sont rangées dans un fichier mon_module.pages.inc. Celles correspondant à l'administration dans mon_module.admin.inc, etc.
Le dernier paramètre, access argument, contient un tableau de permissions nécessaires à l'accès au menu. Ici access content désigne le droit d'accéder au contenu, ce que tout le monde peut généralement faire. Mais par exemple pour limiter aux seuls administrateur, nous aurions pu mettre administer site configuration.
Ce paramètre access argument est en réalité utilisé conjointement avec le paramètre access callback. Mais lorsque ce dernier est omis, Drupal utilise par défaut la valeur user_access. Ainsi vous comprenez que ces deux paramètres correspondent en réalité à un appel à la fonction user_access(array('access content')) qui renvoie vrai si l'utilisateur courant a les droits demandés. A titre d'exemple, vous auriez pu aussi utiliser pour access callback les fonctions is_anonymous_user ou user_is_logged_in, toute deux fournies par le module user. Comme elles ne prennent pas de paramètre, le paramètre access arguments peut dans ce cas être omis.
D'une manière générale, vous pouvez utiliser la fonction qui vous chante pour gérer les droits, pour peu qu'elle renvoie true si l'utilisateur est valide et false dans le cas contraire.
Il ne nous reste maintenant plus qu'à implémenter la callback, en commençant par créer un fichier tutoriel_menu.pages.inc et y placer une fonction comme celle-ci :
function tutoriel_menus_simple_menu_callback() {
$output="Coeur de page associé à ce modeste menu";
return $output;
}tutoriel_menus.pages.inc - callback générique
Bon, un peu simpliste comme rendu, mais c'est juste pour l'exemple. Vous pouvez mettre dans $output tout ce qui vous chante, de simple messages à des tableaux triables.
Il ne nous reste maintenant plus qu'à tester cela. Attention cependant, ce hook n'est, pour des raisons de performances, invoqué qu'à l'activation du module. C'est très bien pour la première fois, mais moins drôle lors des essais suivants. Pour régler ce problème, je vous conseille d'installer le module administration menu qui dans son menu déroulant de droite, dispose d'une action de vidange du cache des menus.
Une fois le module activé, vous devriez voir apparaître dans votre menu Navigation, le nouveau menu Un menu simple. En l'activant, dans le coeur de page, doit apparaître le retour de notre callback.
Il est possible de prolonger l'exemple précédent en créant une cascade de menu. Basiquement, une telle arborescence consiste simplement à créer un menu par niveau de chemin. Dans le chapitre précédent, le chemin était tutoriels/menus/simple. Pour rendre visible les deux niveaux précédents, il nous font donc créer deux menus respectivement pour tutoriels, puis tutoriels/menus.
$items['tutoriels'] = array (
'title' => 'Les tutoriels',
'page callback' => 'tutoriel_menus_tutoriels_callback',
'file' => 'tutoriel_menus.pages.inc',
'access arguments' => array (
'access content'
),
'expanded'=>true
);
$items['tutoriels/menus'] = array (
'title' => 'Les menus',
'description' => t("Tutoriel sur les menus"),
'page callback' => 'tutoriel_menus_tutoriels_menu_callback',
'file' => 'tutoriel_menus.pages.inc',
'access arguments' => array (
'access content'
),
'expanded'=>true
);Ajout de deux niveaux de menu
Notez la propriété expanded qui permettent d'auto-déployer les deux niveaux pour que tout soit visible par défaut. Enfin, comme nous utilisons deux nouvelles callback, il faut aussi les ajouter dans tutoriel_menu.pages.inc
function tutoriel_menus_tutoriels_callback() {
return "Les tutoriels";
}
function tutoriel_menus_tutoriels_menus_callback() {
return "Les tutoriels des menus";
}Ajout des deux callback
Il suffit maintenant de reconstruire les menus et d'observer le résultat.
Par défaut, nos éléments de menu sont intégrés par Drupal dans le menu administration si le chemin commence par admin/..., ou dans le menu navigation le cas échéant. Il est cependant possible d'ajouter nos handlers ailleurs en utilisant la propriété menu_name. Pour illustrer cette possibilité, nous allons ajouter aux liens primaires (un menu généralement affiché en haut à droite de chaque page), l'élément de menu A propos de nous :
$items['informations'] = array (
'title' => 'A propos de nous',
'page callback' => 'tutoriel_menus_a_propos_callback',
'file' => 'tutoriel_menus.pages.inc',
'access arguments' => array (
'access content'
),
'menu_name' => 'primary-links'
);Ajout d'un élément de menu dans le menu 'liens primaires'
Comme toujours, il nous faut une nouvelle callback :
function tutoriel_menus_a_propos_callback() {
return "Bla bla bla...";
}Callback pour l'élément de menu 'information'
Une fois le cache des menus reconstruit, si vous allez dans l'administration des menus, sur le menu Liens primaires, vous devriez voir apparaître le nouvel élément de menu. Et si votre thème l'affiche par défaut en haut à droite de la page, il y sera directement visible.
L'astuce ici consiste à savoir que le nom interne du menu liens primaires est primary-links. En utilisant ce nom pour la propriété menu_name, nous avons forcé l'ajout de ces menus aux liens primaires. Notez que c'est un ajout totalement dynamique, au sens où les menus ainsi créés disparaîtront d'eux même à la prochaine reconstruction des menus si vous les supprimez de votre hook. En somme une alternative intelligente pour déployer facilement des menus en production sans avoir à se refrapper une configuration manuelle, par exemple pour instancier toutes les sections d'un site sur le menu "liens secondaires".
Une autre manière de gérer les menus arborescents est de les afficher sous la forme d'onglet. Drupal permet en effet de gérer ainsi deux niveaux d'onglets (vous en avez un exemple dans la configuration d'un thème). Ces onglets sont très faciles à mettre en oeuvre pour peu d'en comprendre la logique.
$items['tutoriels/menus/simple/onglet-1'] = array (
'title' => 'Onglet 1',
'type' => MENU_DEFAULT_LOCAL_TASK,
'access arguments' => array (
'access content'
)
);
$items['tutoriels/menus/simple/onglet-2'] = array (
'type' => MENU_LOCAL_TASK,
'title' => 'Onglet 2',
'page callback' => 'tutoriel_menus_onglet2_callback',
'file' => 'tutoriel_menus.pages.inc',
'access arguments' => array (
'access content'
)
);
$items['tutoriels/menus/simple/onglet-2/onglet-2-1'] = array (
'title' => 'Onglet 2.1',
'access arguments' => array (
'access content'
),
'type' => MENU_DEFAULT_LOCAL_TASK
);
$items['tutoriels/menus/simple/onglet-2/onglet-2-2'] = array (
'title' => 'Onglet 2.2',
'page callback' => 'tutoriel_menus_onglet22_callback',
'file' => 'tutoriel_menus.pages.inc',
'access arguments' => array (
'access content'
),
'type' => MENU_LOCAL_TASK
);Ajout d'onglets
Comme vous le voyez, nous sommes ici proche des menus arborescents. La nouveauté est tout d'abord l'utilisation du paramètre type indiquant à Drupal que nous définissons ici des onglets (appelés Tasks, ou tâches). MENU_LOCAL_TASK marque un onglet normal et MENU_DEFAULT_LOCAL_TASK marque l'onglet par défaut pour un niveau. Notez l'absence de callback pour les handlers marqués MENU_DEFAULT_LOCAL_TASK. En effet, la règle est que chaque niveau d'onglet doit disposer d'un MENU_DEFAULT_LOCAL_TASK, et que ce handler est automatiquement au chemin interne de son handler parent. En d'autres termes, si vous cliquez sur Onglet 1, c'est le chemin de notre menu simple qui s'affiche et son handler qui est utilisé. De même en cliquant sur Onglet 2.1, c'est le handler et donc le chemin et la callback de l'onglet 2 qui est utilisé. Du coup, nous n'avons que deux callback à ajouter
function tutoriel_menus_onglet2_callback() {
return "Contenu de l'onglet 2";
}
function tutoriel_menus_onglet22_callback() {
return "Contenu de l'onglet 22";
}Callbacks pour les onglets
Comme nous l'avons vu plus haut, nous fabriquons dans notre module des handlers de menu, et non des éléments de menus. Si jusqu'à maintenant nous avions une création automatique d'éléments de menu pour chacun de nos handlers, c'est soit que nous ométions le paramétrage type, qui a pour valeur par défaut MENU_NORMAL_ITEM (comprendre "un handler pour lequel drupal doit créer un élément de menu"), ou que nous voulions afficher des onglets avec MENU_LOCAL_TASK et MENU_DEFAULT_LOCAL_TASK.
Maintenant, dans de nombreux cas nos modules n'ont aucun besoin d'éléments de menu mais juste une URL associée à une page et/ou une action. C'est par exemple le cas si nous voulons créer une action d'ajout, ou dans l'exemple qui suit, un simple Hello World.
$items['tutoriels/menus/hello'] = array (
'page callback' => 'tutoriel_menus_hello_callback',
'file' => 'tutoriel_menus.pages.inc',
'access arguments' => array (
'access content'
),
'type'=>MENU_CALLBACK
);Ajout d'un handler sans élément de menu
Et comme toujours, nous ajoutons la fonction callback associée
function tutoriel_menus_hello_callback() {
return "Hello World";
}Callback du handler sans élément de menu
Une fois le cache de menu reconstruit, vous constaterez que cette fois, aucun élément de menu n'a été rajouté par Drupal. Pour utiliser ce handler, il nous faut directement taper son URL (ou chemin interne) directement dans la zone d'adresse du navigateur, soit http://mon_site/tutoriels/menus/hello.
Les seules différences avec les handlers que nous avons créé jusqu'à maintenant sont que nous n'avons fournit à Drupal ni titre, ni description, et que nous avons en revanche spécifié MENU_CALLBACK comme paramètre type. Ce type permet juste de dire à Drupal qu'il n'est pas utile de cherche à créer un élément de menu.
Ce type de handler sans élément de menu est très utile pour créer des actions comme "ajouter", "supprimer", etc. Encore faut il pouvoir fournir des paramètres à ce menu de sorte à pouvoir indiquer, dans l'URL, la référence de l'objet à détruire.
Depuis la version 6, Drupal dispose d'un système très bien fait pour passer des paramètres au handler, basé sur le caractère %. Ainsi si nous ajoutons le menu suivant :
$items['tutoriels/menus/hello1/%'] = array (
'page callback' => 'tutoriel_menus_hello1_callback',
'page arguments' => array(3),
'file' => 'tutoriel_menus.pages.inc',
'access arguments' => array (
'access content'
),
'type'=>MENU_CALLBACK
);Ajout d'un handler paramétré
Et que nous ajoutons la callback suivante :
function tutoriel_menus_hello1_callback($message) {
return "Hello $message";
}Ajout d'une callback paramétrée
Après reconstruction du menu, nous constatons que l'URL http://mon_site/tutoriels/menus/hello1/gaston provoque l'affichage du message Hello gaston. Pour comprendre ce qui se passe, retournons sur la déclaration de notre handler.
D'abord, nous avons dans le chemin interne un symbole % qui indique à Drupal que cet élément de chemin est un paramètre et peut donc prendre n'importe quelle valeur (ici gaston). Ensuite nous avons un nouveau paramètre page arguments contenant un tableau. Le contenu de ce tableau sera transmis à la callback. Si un de ses éléments est un chiffre, il sera préalablement par l'élément de chemin de rang correspondant (qui commence à
. Si l'élément du tableau n'est pas un chiffre, il sera transmis tel-quel à la fonction.
Dans le cas de notre chemin, nous demandons à Drupal de placer en seul paramètre de la callback l'élément de chemin de rang 3 (c'est à dire le 4ième). C'est pour cela que notre callback dispose d'un paramètre $message qui recevra cette valeur.
Il est possible d'avoir plusieurs paramètres à la callback dont l'ordre et le type sera spécifié par page arguments. Mais plus intéressant encore, il est aussi possible de passer en paramètre des objets préchargés par Drupal. Pour tester cela, ajoutons encore un nouvel handler et sa callback :
$items['tutoriels/menus/hello2/%user'] = array (
'page callback' => 'tutoriel_menus_hello2_callback',
'page arguments' => array(3),
'file' => 'tutoriel_menus.pages.inc',
'access arguments' => array (
'access content'
),
'type'=>MENU_CALLBACK
);
// callback associée à mettre dans tutoriel_menus.pages.inc
function tutoriel_menus_hello2_callback($user) {
return "Hello {$user->name}";
}Ajout d'un handler paramétré avec objet
Reconstruisons le cache des menus et lançons l'URL http://mon_site/tutoriels/menus/hello2/1. Vous devriez alors voir apparaître un Hello administrateur (ou n'importe quel nom que vous aurez utilisé comme administrateur de votre site).
L'astuce ici tient à l'utilisation non plus du simple %, mais de %user. Ce dernier indique à Drupal de "charger en mémoire l'utilisateur aillant pour ID l'élément de chemin donné dans l'URL". C'est ainsi que notre callback hérite d'un objet $user chargé à partir de l'ID 1 (celui de l'administrateur).
Il est intéressant de comprendre comment le "magie" fonctionne ici. Lorsque Drupal rencontre un paramètre de handler de la forme %objet, il va chercher une fonction pré-existante de la forme objet_load. S'il la trouve (ici c'est le cas, il s'agit de user_load), il lui passe en paramètre l'élément de chemin (ici 1) correspondant. Les fonctions objet_load renvoient toujours l'objet chargé à partir du paramètre (ici $user=user_load(1)), et c'est cet objet qui est utilisé comme paramètre à la callback.
En standard, Drupal dispose de plusieurs fonctions de type objet_load. Citons par exemple node_load qui par un %node permet de passer un contenu en paramètre d'une callback.
Mais vous pouvez aussi créer la votre. En effet, si vous utilisez un élément de chemin %mon_objet comme ceci
Vous devez en outre ajouter une fonction de type object_load pour que Drupal puisse faire correspondre le paramètre de rang 3, à l'argument %mon_objet :$items['tutoriels/menus/hello3/%mon_objet'] = array (
'page callback' => 'tutoriel_menus_hello3_callback',
'page arguments' => array(3),
'file' => 'tutoriel_menus.pages.inc',
'access arguments' => array (
'access content'
),
'type'=>MENU_CALLBACK
);
// callback associée à mettre dans tutoriel_menus.pages.inc
function tutoriel_menus_hello3_callback($mon_objet) {
return "Hello {$mon_objet['name']}";
}Utilisation d'un chargeur custom
function mon_objet_load($id) {
return array('name'=>"Objet[$id]");
}Chargeur custom
Vous n'avez maintenant plus qu'à reconstruire les menus et tester http://mon_site/tutoriels/menus/hello3/12 pour voir apparaître "Hello Objet[12]".
Voilà, fin du "petit" tour d'horizon sur les menus. Comme vous l'avez vu, le sujet est pour le peu dense, à la mesure de la richesse du sujet. Ceci étant dit, nous n'avons ici qu'abordé l'essentiel, il reste encore beaucoup de choses à découvrir en explorant l'API des menus.
Petit alléluia, après des mois laborieux au possible, mon bouquin sur Drupal est enfin terminé, mis en page, parti sous presse et en est revenu tout pimpant. Avec du recul, je ne suis pas bien certain que je me serais lancé dans une telle aventure si j'avais eu pleinement conscience du temps que cela représenterait. Mais aujourd'hui, cela me fait juste bien plaisir de contempler le résultat final. Pour le reste, je ne suis sans doute pas le meilleur juge.
Ce livre n'est pas exactement un manuel mais plutôt la tentative d'un parcours (initiatique ?) à travers l'univers Drupalien. A ce titre il s'adresse à tout le monde ou presque, de la personne qui souhaite orchestrer la mise en œuvre Drupal dans un mode projet, au thèmeur qui désire comprendre comment le faire coller à ses besoins, en passant par celle ou celui qui veut simplement se laisser de la puissance en réserve pour construire son site personnel.

Tout au long de ce parcours, j'ai essayé d'égrener tous les astuces, bonnes pratiques, modules géniaux et autres chausse-trappes que j'ai pu rencontrer en quatre ans de projets Drupaliens. En gros, c'est le livre en français que j'aurais bien aimé avoir sous la main lorsque j'ai débuté, du moins c'est ce que j'ai cherché à faire.
Voilà, vous savez l'essentiel et je vous laisse juge du reste en espérant sincèrement que cela vous plaira :
Pour ce qui est de la sortie officielle en librairie, elle se fera le 10 septembre dans toutes les bonnes crémeries. Il y en aura aussi quelques exemplaires au DrupalCon auquel je n'ai malheureusement pas pu assister. Du coup, un grand merci à Daniel pour être allé chercher les livres chez Eyrolles et les avoir trimballé jusque là.
Petit alléluia, après des mois laborieux au possible, mon bouquin sur Drupal est enfin terminé, mis en page, parti sous presse et en est revenu tout pimpant. Avec du recul, je ne suis pas bien certain que je me serais lancé dans une telle aventure si j'avais eu pleinement conscience du temps que cela représenterait. Mais aujourd'hui, cela me fait juste bien plaisir de contempler le résultat final. Pour le reste, je ne suis sans doute pas le meilleur juge.
Ce livre n'est pas exactement un manuel mais plutôt la tentative d'un parcours (initiatique ?) à travers l'univers Drupalien. A ce titre il s'adresse à tout le monde ou presque, de la personne qui souhaite orchestrer la mise en œuvre Drupal dans un mode projet, au thèmeur qui désire comprendre comment le faire coller à ses besoins, en passant par celle ou celui qui veut simplement se laisser de la puissance en réserve pour construire son site personnel.

Tout au long de ce parcours, j'ai essayé d'égrener tous les astuces, bonnes pratiques, modules géniaux et autres chausse-trappes que j'ai pu rencontrer en quatre ans de projets Drupaliens. En gros, c'est le livre en français que j'aurais bien aimé avoir sous la main lorsque j'ai débuté, du moins c'est ce que j'ai cherché à faire.
Voilà, vous savez l'essentiel et je vous laisse juge du reste en espérant sincèrement que cela vous plaira :
Pour ce qui est de la sortie officielle en librairie, elle se fera le 10 septembre dans toutes les bonnes crémeries. Il y en aura aussi quelques exemplaires au DrupalCon auquel je n'ai malheureusement pas pu assister. Du coup, un grand merci à Daniel pour être allé chercher les livres chez Eyrolles et les avoir trimballé jusque là.
| Fichier attaché | Taille |
|---|---|
| Table des matières | 80.68 Ko |
| Le module d'exemple | 1.6 Ko |
| Le thème d'exemple | 39.5 Ko |
Petit alléluia, après des mois laborieux au possible, mon bouquin sur Drupal est enfin terminé, mis en page, parti sous presse et en est revenu tout pimpant. Avec du recul, je ne suis pas bien certain que je me serais lancé dans une telle aventure si j'avais eu pleinement conscience du temps que cela représenterait. Mais aujourd'hui, cela me fait juste bien plaisir de contempler le résultat final. Pour le reste, je ne suis sans doute pas le meilleur juge.
Ce livre n'est pas exactement un manuel mais plutôt la tentative d'un parcours (initiatique ?) à travers l'univers Drupalien. A ce titre il s'adresse à tout le monde ou presque, de la personne qui souhaite orchestrer la mise en œuvre Drupal dans un mode projet, au thèmeur qui désire comprendre comment le faire coller à ses besoins, en passant par celle ou celui qui veut simplement se laisser de la puissance en réserve pour construire son site personnel.

Tout au long de ce parcours, j'ai essayé d'égrener tous les astuces, bonnes pratiques, modules géniaux et autres chausse-trappes que j'ai pu rencontrer en quatre ans de projets Drupaliens. En gros, c'est le livre en français que j'aurais bien aimé avoir sous la main lorsque j'ai débuté, du moins c'est ce que j'ai cherché à faire.
Voilà, vous savez l'essentiel et je vous laisse juge du reste en espérant sincèrement que cela vous plaira :
Pour ce qui est de la sortie officielle en librairie, elle se fera le 10 septembre dans toutes les bonnes crémeries. Il y en aura aussi quelques exemplaires au DrupalCon auquel je n'ai malheureusement pas pu assister. Du coup, un grand merci à Daniel pour être allé chercher les livres chez Eyrolles et les avoir trimballé jusque là.
La question posée ici est : "comment peut-on mettre le contenu d'un noeud, ou d'une liste de noeuds, dans un noeud principal"... Le cas typique serait une page, avec un texte d'introduction (le noeud maître), suivi d'une liste de noeuds (dits "esclaves", résumés ou complets).

Le stockage du noeud est assuré conjointement par la table node et node_revisions. La première contient les informations fondamentales du noeud (nid, statut, langue, et surtout type), et la seconde prend en charge les données du noeud par révision (titre, corps, etc.). La valeur du champ vid (pour version ID) de la table node pointe sur la révision en cours dans la table node_revision.

Ainsi comme un type de contenu peut potentiellement se ramifier sur de nombreuses tables, il n'est pas possible de créer une requêtes SQL universelle qui remonterait toutes les données d'un coup. Heureusement Drupal nous fournit la fonction node_load qui effectue ce travail sans douleur à partir du nid du noeud.

Nous allons donc utiliser cette fameuse fonction node_load pour charger le noeud de nid 41 et toutes ses éventuelles dépendances :
$noeud_esclave=node_load(41);
Pas bien sorcier n'est-ce pas ? La fonction va ainsi charger toutes les information de la dernière révision d'un noeud, et en faire un objet PHP que nous stockons dans $noeud_esclave. Ainsi $noeud_esclave->title nous renvoie le titre du noeud, $noeud_esclave->body sont corps (tel qu'il a été saisi, et donc sans format d'entrée appliqué), etc.
$node n'est donc pour l'instant pas formaté, il va nous falloir un peu plus pour le transformer en une version XHTML exploitable. C'est cette fois le rôle de la fonction node_view
print node_view($noeud_esclave, true, false, true);
Le premier paramètre est l'objet $noeud_esclave que nous avons obtenu de la fonction node_load. Le second paramètre true indique que nous désirons obtenir la version résumée du noeud (le "teaser"). Le troisième paramètre à false prévient la fonction que nous ne voulons pas afficher ce contenu en tant que page. Enfin, le dernier paramètre à true demande à ce que les liens additionnels soient ajoutés au contenu.
Nous avons maintenant la théorie, reste à voir comment concrètement intégrer cela au noeud "maître".
La meilleur des approches reste (évidemment
celle du module. Il vous suffit pour cela de créer un module basique et d'exploiter le hook_nodeapi. Comme son nom le suggère, ce hook permet d'intercepter chacune des étapes de la vie d'un noeud, y compris son affichage.

Comme nous le disions, le hook node_api est invoqué à chaque opération (ajout, mise à jour, suppression, impression, etc.) sur un noeud. Le noeud en question est passé comme premier paramètre du hook ($node). Notez au passage qu'il s'agit là d'un passage par référence induisant que nous pouvons modifier le contenu de noeud. Le second paramètre, $op indique l'opération effectuée. Ici nous nous intéressons uniquement à $op=='view' correspondant à l'étape finale de visualisation du noeud.function mon_module_nodeapi(&$node, $op, $teaser, $page) {
switch ($op) {
case 'view' : {
if ($node->nid == 42 && !$teaser) {
...
}
}
}
}Implémentation de hook_nodeapi
Le paramètre $teaser nous indique quant à lui si l'opération vise un affichage résumé ou complet. Nous ciblons donc notre action sur un noeud de nid 42 en affichage complet (!$teaser).
Pour bien comprendre ce qui suit, il faut savoir que lorsque Drupal prépare un noeud pour l'affichage, il lui ajoute un champ content qui est un tableau associatif. Ce champ va contenir des portions de code XHTML qui à eux tous vont constituer le contenu à afficher.
De manière standard, ce champ ne contient qu'une clef body (ou teaser) avec comme valeur un tableau indexé. Ce tableau a deux clefs, #value contenant le résultat formaté XHTML (en fonction du format d'entrée) de $node->body (ou node->teaser), et #weight qui contient la position de ce morceau de XHTML par rapport à d'éventuels autres.
node : Object (
'title' => 'NID:41 - Un noeud esclave',
'body' => 'consectetur adipiscing elit.
Aliquam vel tristique sapien.',
...
'content' => array(
'body' => array (
'#value'=>'consectetur adipiscing elit.Aliquam vel tristique sapien.',
'#weight'=>0))Vue interne de l'objet $node
Pour créer le $content du modèle node.tpl.php, Drupal n'a alors plus qu'à agréger tous les éléments #value dans l'ordre des #weight.
C'est donc cette caractéristique que nous allons exploiter pour ajouter notre propre fragment de XHTML et utilisant le résultat du node_view.
function mon_module_nodeapi(&$node, $op, $teaser, $page) {
switch ($op) {
case 'view' : {
if ($node->nid == 42 && !$teaser) {
$noeud_esclave = node_load(41);
$node->content['noeud_esclave'] = array (
'#value' => node_view($noeud_esclave, true, false, true),
'#weight' => 1);
}
}
}
}Implémentation finale de hook_nodeapi
Le champ #weight prend pour valeur 1, ce qui le placera au dessous du contenu du noeud maître. Une valeur négative inverserait simplement ce comportement.


Je n'en fais pas un mystère, je ne suis pas un Viewsonados mais il faut tout de même être honnête, la modulo-mania appliquée peut amener à des trucs assez géants avec Views...
Je suis ainsi tombé (j'ai encore un peu mal
, sur un exemple assez magnifique pondu par une graaaande WebAgency spécialisée Drupal, tout ça... Pour répondre à une partie de notre besoin, leur idée a été d'utiliser le module Panels et de créer un "Page Panel". De là, ils collent une vue "Views" qui ne pond qu'un seul noeud sur la partie haute, et pour celle du bas, une seconde vue "Views" qui cette fois sort une liste de noeuds... Je vous laisse imaginer la simplicité de debuggage et les performances d'un tel assemblage...
Il existe plusieurs autres "techniques" que j'ai croisées à droite à gauche (y compris chez moi, à mes débuts sur Drupal) qui ne valent pas, loin de là, celle du nodeapi. La plus courante et bien évidement la pire, consiste à utiliser l'horrible format d'entrée code PHP :
insérer du code dans un noeud":Retourner au sommaire
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam vel tristique sapien.
<?php
$node=node_load(41);
print node_view($node, true, false, true);
?>
Alors ça, c'est comme croiser les effluves, c'est MAL. A chaque fois que vous vient l'envie de faire un chose pareil, ayez une pensée compatissante pour le pauvre bougre qui passera dans 10 mois sur votre projet en se demandant d'où vient le comportement extraordinaire de ce contenu. En plus, mettre du code dans une base de donnée (car cela revient à cela), au delà d'être très sale, est une calamité lorsque viendra le temps de debugger.
Dans un domaine connexe mais issues ici aussi de la fameuse WebAgency dont je parlais plus haut (mais j'imagine qu'ils ne sont pas les seuls), le pompon de l'utilisation de cette "technique" a été de coller 200 lignes de code PHP dans un noeud, et d'associer le chemin de ce noeud (node/XXX) à un alias (chemin/vers/ma/fonction). Tout cela pour créer une page de fonctionnalités complexe alors qu'il aurait été si simple de mettre ce code dans une fonction, cette fonction dans un module, et d'ajouter le chemin dans un hook_menu...
Du fonctionnel dans la présentation...
L'autre (mauvaise) possibilité consiste à utiliser la fonction phptemplate_preprocess_node d'un thème (généralement dans template.php) et à ajouter le contenu XHTML du noeud esclave à la variable $vars['content'] juste avant qu'elle ne devienne le $content de node.tpl.php.
Alors ça marche mais ce n'est pas terrible. En effet, la beauté du système de thème de Drupal tient justement à ce qu'il permet une stricte séparation entre les fonctionalités d'un côté et leur présentation de l'autre. Ce que nous faisons ici relève du domaine du fonctionnel et n'a donc rien à faire dans le thème. Une bonne manière de savoir si telle ou telle chose va dans un thème ou dans un module, est de ce demander si cette chose doit survivre à un changement brutale de thème. Si c'est le cas, vous n'avez d'autre choix que le module.
De manière plus dégradée, évitez aussi de coller ce type de code, ou tout autre code d'ailleurs, directement dans node.tpl.php. En faisant cela vous commettez la même erreur que plus haut avec en prime le risque de rendre vos modèles totalement illisibles.
D'une manière générale, une petite règle qui vaut la peine d'être rappelée : Drupal est suffisamment bien conçu pour qu'un modèle ne doive contenir que les fonctions PHP if/else et print. Tout le reste doit être dans un module, ou, le cas échéant, dans template.php.
Conclusion
Nous avons vu ici qu'il est relativement simple d'intégrer proprement un noeud dans un autre, avec en prime la possibilité d'exploiter sans soucis le module Printer pour générer une version imprimable ou PDF de l'ensemble.
La question posée ici est : "comment peut-on mettre le contenu d'un noeud, ou d'une liste de noeuds, dans un noeud principal"... Le cas typique serait une page, avec un texte d'introduction (le noeud maître), suivi d'une liste de noeuds (dits "esclaves", résumés ou complets).

Le stockage du noeud est assuré conjointement par la table node et node_revisions. La première contient les informations fondamentales du noeud (nid, statut, langue, et surtout type), et la seconde prend en charge les données du noeud par révision (titre, corps, etc.). La valeur du champ vid (pour version ID) de la table node pointe sur la révision en cours dans la table node_revision.

Ainsi comme un type de contenu peut potentiellement se ramifier sur de nombreuses tables, il n'est pas possible de créer une requêtes SQL universelle qui remonterait toutes les données d'un coup. Heureusement Drupal nous fournit la fonction node_load qui effectue ce travail sans douleur à partir du nid du noeud.

Nous allons donc utiliser cette fameuse fonction node_load pour charger le noeud de nid 41 et toutes ses éventuelles dépendances :
$noeud_esclave=node_load(41);
Pas bien sorcier n'est-ce pas ? La fonction va ainsi charger toutes les information de la dernière révision d'un noeud, et en faire un objet PHP que nous stockons dans $noeud_esclave. Ainsi $noeud_esclave->title nous renvoie le titre du noeud, $noeud_esclave->body sont corps (tel qu'il a été saisi, et donc sans format d'entrée appliqué), etc.
$node n'est donc pour l'instant pas formaté, il va nous falloir un peu plus pour le transformer en une version XHTML exploitable. C'est cette fois le rôle de la fonction node_view
print node_view($noeud_esclave, true, false, true);
Le premier paramètre est l'objet $noeud_esclave que nous avons obtenu de la fonction node_load. Le second paramètre true indique que nous désirons obtenir la version résumée du noeud (le "teaser"). Le troisième paramètre à false prévient la fonction que nous ne voulons pas afficher ce contenu en tant que page. Enfin, le dernier paramètre à true demande à ce que les liens additionnels soient ajoutés au contenu.
Nous avons maintenant la théorie, reste à voir comment concrètement intégrer cela au noeud "maître".
La meilleur des approches reste (évidemment
celle du module. Il vous suffit pour cela de créer un module basique et d'exploiter le hook_nodeapi. Comme son nom le suggère, ce hook permet d'intercepter chacune des étapes de la vie d'un noeud, y compris son affichage.

Comme nous le disions, le hook node_api est invoqué à chaque opération (ajout, mise à jour, suppression, impression, etc.) sur un noeud. Le noeud en question est passé comme premier paramètre du hook ($node). Notez au passage qu'il s'agit là d'un passage par référence induisant que nous pouvons modifier le contenu de noeud. Le second paramètre, $op indique l'opération effectuée. Ici nous nous intéressons uniquement à $op=='view' correspondant à l'étape finale de visualisation du noeud.function mon_module_nodeapi(&$node, $op, $teaser, $page) {
switch ($op) {
case 'view' : {
if ($node->nid == 42 && !$teaser) {
...
}
}
}
}Implémentation de hook_nodeapi
Le paramètre $teaser nous indique quant à lui si l'opération vise un affichage résumé ou complet. Nous ciblons donc notre action sur un noeud de nid 42 en affichage complet (!$teaser).
Pour bien comprendre ce qui suit, il faut savoir que lorsque Drupal prépare un noeud pour l'affichage, il lui ajoute un champ content qui est un tableau associatif. Ce champ va contenir des portions de code XHTML qui à eux tous vont constituer le contenu à afficher.
De manière standard, ce champ ne contient qu'une clef body (ou teaser) avec comme valeur un tableau indexé. Ce tableau a deux clefs, #value contenant le résultat formaté XHTML (en fonction du format d'entrée) de $node->body (ou node->teaser), et #weight qui contient la position de ce morceau de XHTML par rapport à d'éventuels autres.
node : Object (
'title' => 'NID:41 - Un noeud esclave',
'body' => 'consectetur adipiscing elit.
Aliquam vel tristique sapien.',
...
'content' => array(
'body' => array (
'#value'=>'consectetur adipiscing elit.Aliquam vel tristique sapien.',
'#weight'=>0))Vue interne de l'objet $node
Pour créer le $content du modèle node.tpl.php, Drupal n'a alors plus qu'à agréger tous les éléments #value dans l'ordre des #weight.
C'est donc cette caractéristique que nous allons exploiter pour ajouter notre propre fragment de XHTML et utilisant le résultat du node_view.
function mon_module_nodeapi(&$node, $op, $teaser, $page) {
switch ($op) {
case 'view' : {
if ($node->nid == 42 && !$teaser) {
$noeud_esclave = node_load(41);
$node->content['noeud_esclave'] = array (
'#value' => node_view($noeud_esclave, true, false, true),
'#weight' => 1);
}
}
}
}Implémentation finale de hook_nodeapi
Le champ #weight prend pour valeur 1, ce qui le placera au dessous du contenu du noeud maître. Une valeur négative inverserait simplement ce comportement.


Je n'en fais pas un mystère, je ne suis pas un Viewsonados mais il faut tout de même être honnête, la modulo-mania appliquée peut amener à des trucs assez géants avec Views...
Je suis ainsi tombé (j'ai encore un peu mal
, sur un exemple assez magnifique pondu par une graaaande WebAgency spécialisée Drupal, tout ça... Pour répondre à une partie de notre besoin, leur idée a été d'utiliser le module Panels et de créer un "Page Panel". De là, ils collent une vue "Views" qui ne pond qu'un seul noeud sur la partie haute, et pour celle du bas, une seconde vue "Views" qui cette fois sort une liste de noeuds... Je vous laisse imaginer la simplicité de debuggage et les performances d'un tel assemblage...
Il existe plusieurs autres "techniques" que j'ai croisées à droite à gauche (y compris chez moi, à mes débuts sur Drupal) qui ne valent pas, loin de là, celle du nodeapi. La plus courante et bien évidement la pire, consiste à utiliser l'horrible format d'entrée code PHP :
insérer du code dans un noeud":Retourner au sommaire
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam vel tristique sapien.
<?php
$node=node_load(41);
print node_view($node, true, false, true);
?>
Alors ça, c'est comme croiser les effluves, c'est MAL. A chaque fois que vous vient l'envie de faire un chose pareil, ayez une pensée compatissante pour le pauvre bougre qui passera dans 10 mois sur votre projet en se demandant d'où vient le comportement extraordinaire de ce contenu. En plus, mettre du code dans une base de donnée (car cela revient à cela), au delà d'être très sale, est une calamité lorsque viendra le temps de debugger.
Dans un domaine connexe mais issues ici aussi de la fameuse WebAgency dont je parlais plus haut (mais j'imagine qu'ils ne sont pas les seuls), le pompon de l'utilisation de cette "technique" a été de coller 200 lignes de code PHP dans un noeud, et d'associer le chemin de ce noeud (node/XXX) à un alias (chemin/vers/ma/fonction). Tout cela pour créer une page de fonctionnalités complexe alors qu'il aurait été si simple de mettre ce code dans une fonction, cette fonction dans un module, et d'ajouter le chemin dans un hook_menu...
Du fonctionnel dans la présentation...
L'autre (mauvaise) possibilité consiste à utiliser la fonction phptemplate_preprocess_node d'un thème (généralement dans template.php) et à ajouter le contenu XHTML du noeud esclave à la variable $vars['content'] juste avant qu'elle ne devienne le $content de node.tpl.php.
Alors ça marche mais ce n'est pas terrible. En effet, la beauté du système de thème de Drupal tient justement à ce qu'il permet une stricte séparation entre les fonctionalités d'un côté et leur présentation de l'autre. Ce que nous faisons ici relève du domaine du fonctionnel et n'a donc rien à faire dans le thème. Une bonne manière de savoir si telle ou telle chose va dans un thème ou dans un module, est de ce demander si cette chose doit survivre à un changement brutale de thème. Si c'est le cas, vous n'avez d'autre choix que le module.
De manière plus dégradée, évitez aussi de coller ce type de code, ou tout autre code d'ailleurs, directement dans node.tpl.php. En faisant cela vous commettez la même erreur que plus haut avec en prime le risque de rendre vos modèles totalement illisibles.
D'une manière générale, une petite règle qui vaut la peine d'être rappelée : Drupal est suffisamment bien conçu pour qu'un modèle ne doive contenir que les fonctions PHP if/else et print. Tout le reste doit être dans un module, ou, le cas échéant, dans template.php.
Conclusion
Nous avons vu ici qu'il est relativement simple d'intégrer proprement un noeud dans un autre, avec en prime la possibilité d'exploiter sans soucis le module Printer pour générer une version imprimable ou PDF de l'ensemble.
La question posée ici est : "comment peut-on mettre le contenu d'un noeud, ou d'une liste de noeuds, dans un noeud principal"... Le cas typique serait une page, avec un texte d'introduction (le noeud maître), suivi d'une liste de noeuds (dits "esclaves", résumés ou complets).

Le stockage du noeud est assuré conjointement par la table node et node_revisions. La première contient les informations fondamentales du noeud (nid, statut, langue, et surtout type), et la seconde prend en charge les données du noeud par révision (titre, corps, etc.). La valeur du champ vid (pour version ID) de la table node pointe sur la révision en cours dans la table node_revision.

Ainsi comme un type de contenu peut potentiellement se ramifier sur de nombreuses tables, il n'est pas possible de créer une requêtes SQL universelle qui remonterait toutes les données d'un coup. Heureusement Drupal nous fournit la fonction node_load qui effectue ce travail sans douleur à partir du nid du noeud.

Nous allons donc utiliser cette fameuse fonction node_load pour charger le noeud de nid 41 et toutes ses éventuelles dépendances :
$noeud_esclave=node_load(41);
Pas bien sorcier n'est-ce pas ? La fonction va ainsi charger toutes les information de la dernière révision d'un noeud, et en faire un objet PHP que nous stockons dans $noeud_esclave. Ainsi $noeud_esclave->title nous renvoie le titre du noeud, $noeud_esclave->body sont corps (tel qu'il a été saisi, et donc sans format d'entrée appliqué), etc.
$node n'est donc pour l'instant pas formaté, il va nous falloir un peu plus pour le transformer en une version XHTML exploitable. C'est cette fois le rôle de la fonction node_view
print node_view($noeud_esclave, true, false, true);
Le premier paramètre est l'objet $noeud_esclave que nous avons obtenu de la fonction node_load. Le second paramètre true indique que nous désirons obtenir la version résumée du noeud (le "teaser"). Le troisième paramètre à false prévient la fonction que nous ne voulons pas afficher ce contenu en tant que page. Enfin, le dernier paramètre à true demande à ce que les liens additionnels soient ajoutés au contenu.
Nous avons maintenant la théorie, reste à voir comment concrètement intégrer cela au noeud "maître".
La meilleur des approches reste (évidemment
celle du module. Il vous suffit pour cela de créer un module basique et d'exploiter le hook_nodeapi. Comme son nom le suggère, ce hook permet d'intercepter chacune des étapes de la vie d'un noeud, y compris son affichage.

Comme nous le disions, le hook node_api est invoqué à chaque opération (ajout, mise à jour, suppression, impression, etc.) sur un noeud. Le noeud en question est passé comme premier paramètre du hook ($node). Notez au passage qu'il s'agit là d'un passage par référence induisant que nous pouvons modifier le contenu de noeud. Le second paramètre, $op indique l'opération effectuée. Ici nous nous intéressons uniquement à $op=='view' correspondant à l'étape finale de visualisation du noeud.function mon_module_nodeapi(&$node, $op, $teaser, $page) {
switch ($op) {
case 'view' : {
if ($node->nid == 42 && !$teaser) {
...
}
}
}
}Implémentation de hook_nodeapi
Le paramètre $teaser nous indique quant à lui si l'opération vise un affichage résumé ou complet. Nous ciblons donc notre action sur un noeud de nid 42 en affichage complet (!$teaser).
Pour bien comprendre ce qui suit, il faut savoir que lorsque Drupal prépare un noeud pour l'affichage, il lui ajoute un champ content qui est un tableau associatif. Ce champ va contenir des portions de code XHTML qui à eux tous vont constituer le contenu à afficher.
De manière standard, ce champ ne contient qu'une clef body (ou teaser) avec comme valeur un tableau indexé. Ce tableau a deux clefs, #value contenant le résultat formaté XHTML (en fonction du format d'entrée) de $node->body (ou node->teaser), et #weight qui contient la position de ce morceau de XHTML par rapport à d'éventuels autres.
node : Object (
'title' => 'NID:41 - Un noeud esclave',
'body' => 'consectetur adipiscing elit.
Aliquam vel tristique sapien.',
...
'content' => array(
'body' => array (
'#value'=>'consectetur adipiscing elit.Aliquam vel tristique sapien.',
'#weight'=>0))Vue interne de l'objet $node
Pour créer le $content du modèle node.tpl.php, Drupal n'a alors plus qu'à agréger tous les éléments #value dans l'ordre des #weight.
C'est donc cette caractéristique que nous allons exploiter pour ajouter notre propre fragment de XHTML et utilisant le résultat du node_view.
function mon_module_nodeapi(&$node, $op, $teaser, $page) {
switch ($op) {
case 'view' : {
if ($node->nid == 42 && !$teaser) {
$noeud_esclave = node_load(41);
$node->content['noeud_esclave'] = array (
'#value' => node_view($noeud_esclave, true, false, true),
'#weight' => 1);
}
}
}
}Implémentation finale de hook_nodeapi
Le champ #weight prend pour valeur 1, ce qui le placera au dessous du contenu du noeud maître. Une valeur négative inverserait simplement ce comportement.


Je n'en fais pas un mystère, je ne suis pas un Viewsonados mais il faut tout de même être honnête, la modulo-mania appliquée peut amener à des trucs assez géants avec Views...
Je suis ainsi tombé (j'ai encore un peu mal
, sur un exemple assez magnifique pondu par une graaaande WebAgency spécialisée Drupal, tout ça... Pour répondre à une partie de notre besoin, leur idée a été d'utiliser le module Panels et de créer un "Page Panel". De là, ils collent une vue "Views" qui ne pond qu'un seul noeud sur la partie haute, et pour celle du bas, une seconde vue "Views" qui cette fois sort une liste de noeuds... Je vous laisse imaginer la simplicité de debuggage et les performances d'un tel assemblage...
Il existe plusieurs autres "techniques" que j'ai croisées à droite à gauche (y compris chez moi, à mes débuts sur Drupal) qui ne valent pas, loin de là, celle du nodeapi. La plus courante et bien évidement la pire, consiste à utiliser l'horrible format d'entrée code PHP :
insérer du code dans un noeud":Retourner au sommaire
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam vel tristique sapien.
<?php
$node=node_load(41);
print node_view($node, true, false, true);
?>
Alors ça, c'est comme croiser les effluves, c'est MAL. A chaque fois que vous vient l'envie de faire un chose pareil, ayez une pensée compatissante pour le pauvre bougre qui passera dans 10 mois sur votre projet en se demandant d'où vient le comportement extraordinaire de ce contenu. En plus, mettre du code dans une base de donnée (car cela revient à cela), au delà d'être très sale, est une calamité lorsque viendra le temps de debugger.
Dans un domaine connexe mais issues ici aussi de la fameuse WebAgency dont je parlais plus haut (mais j'imagine qu'ils ne sont pas les seuls), le pompon de l'utilisation de cette "technique" a été de coller 200 lignes de code PHP dans un noeud, et d'associer le chemin de ce noeud (node/XXX) à un alias (chemin/vers/ma/fonction). Tout cela pour créer une page de fonctionnalités complexe alors qu'il aurait été si simple de mettre ce code dans une fonction, cette fonction dans un module, et d'ajouter le chemin dans un hook_menu...
Du fonctionnel dans la présentation...
L'autre (mauvaise) possibilité consiste à utiliser la fonction phptemplate_preprocess_node d'un thème (généralement dans template.php) et à ajouter le contenu XHTML du noeud esclave à la variable $vars['content'] juste avant qu'elle ne devienne le $content de node.tpl.php.
Alors ça marche mais ce n'est pas terrible. En effet, la beauté du système de thème de Drupal tient justement à ce qu'il permet une stricte séparation entre les fonctionalités d'un côté et leur présentation de l'autre. Ce que nous faisons ici relève du domaine du fonctionnel et n'a donc rien à faire dans le thème. Une bonne manière de savoir si telle ou telle chose va dans un thème ou dans un module, est de ce demander si cette chose doit survivre à un changement brutale de thème. Si c'est le cas, vous n'avez d'autre choix que le module.
De manière plus dégradée, évitez aussi de coller ce type de code, ou tout autre code d'ailleurs, directement dans node.tpl.php. En faisant cela vous commettez la même erreur que plus haut avec en prime le risque de rendre vos modèles totalement illisibles.
D'une manière générale, une petite règle qui vaut la peine d'être rappelée : Drupal est suffisamment bien conçu pour qu'un modèle ne doive contenir que les fonctions PHP if/else et print. Tout le reste doit être dans un module, ou, le cas échéant, dans template.php.
Conclusion
Nous avons vu ici qu'il est relativement simple d'intégrer proprement un noeud dans un autre, avec en prime la possibilité d'exploiter sans soucis le module Printer pour générer une version imprimable ou PDF de l'ensemble.
La question posée ici est : "comment peut-on mettre le contenu d'un noeud, ou d'une liste de noeuds, dans un noeud principal"... Le cas typique serait une page, avec un texte d'introduction (le noeud maître), suivi d'une liste de noeuds (dits "esclaves", résumés ou complets).

Le stockage du noeud est assuré conjointement par la table node et node_revisions. La première contient les informations fondamentales du noeud (nid, statut, langue, et surtout type), et la seconde prend en charge les données du noeud par révision (titre, corps, etc.). La valeur du champ vid (pour version ID) de la table node pointe sur la révision en cours dans la table node_revision.

Ainsi comme un type de contenu peut potentiellement se ramifier sur de nombreuses tables, il n'est pas possible de créer une requêtes SQL universelle qui remonterait toutes les données d'un coup. Heureusement Drupal nous fournit la fonction node_load qui effectue ce travail sans douleur à partir du nid du noeud.

Nous allons donc utiliser cette fameuse fonction node_load pour charger le noeud de nid 41 et toutes ses éventuelles dépendances :
$noeud_esclave=node_load(41);
Pas bien sorcier n'est-ce pas ? La fonction va ainsi charger toutes les information de la dernière révision d'un noeud, et en faire un objet PHP que nous stockons dans $noeud_esclave. Ainsi $noeud_esclave->title nous renvoie le titre du noeud, $noeud_esclave->body sont corps (tel qu'il a été saisi, et donc sans format d'entrée appliqué), etc.
$node n'est donc pour l'instant pas formaté, il va nous falloir un peu plus pour le transformer en une version XHTML exploitable. C'est cette fois le rôle de la fonction node_view
print node_view($noeud_esclave, true, false, true);
Le premier paramètre est l'objet $noeud_esclave que nous avons obtenu de la fonction node_load. Le second paramètre true indique que nous désirons obtenir la version résumée du noeud (le "teaser"). Le troisième paramètre à false prévient la fonction que nous ne voulons pas afficher ce contenu en tant que page. Enfin, le dernier paramètre à true demande à ce que les liens additionnels soient ajoutés au contenu.
Nous avons maintenant la théorie, reste à voir comment concrètement intégrer cela au noeud "maître".
La meilleur des approches reste (évidemment
celle du module. Il vous suffit pour cela de créer un module basique et d'exploiter le hook_nodeapi. Comme son nom le suggère, ce hook permet d'intercepter chacune des étapes de la vie d'un noeud, y compris son affichage.

Comme nous le disions, le hook node_api est invoqué à chaque opération (ajout, mise à jour, suppression, impression, etc.) sur un noeud. Le noeud en question est passé comme premier paramètre du hook ($node). Notez au passage qu'il s'agit là d'un passage par référence induisant que nous pouvons modifier le contenu de noeud. Le second paramètre, $op indique l'opération effectuée. Ici nous nous intéressons uniquement à $op=='view' correspondant à l'étape finale de visualisation du noeud.function mon_module_nodeapi(&$node, $op, $teaser, $page) {
switch ($op) {
case 'view' : {
if ($node->nid == 42 && !$teaser) {
...
}
}
}
}Implémentation de hook_nodeapi
Le paramètre $teaser nous indique quant à lui si l'opération vise un affichage résumé ou complet. Nous ciblons donc notre action sur un noeud de nid 42 en affichage complet (!$teaser).
Pour bien comprendre ce qui suit, il faut savoir que lorsque Drupal prépare un noeud pour l'affichage, il lui ajoute un champ content qui est un tableau associatif. Ce champ va contenir des portions de code XHTML qui à eux tous vont constituer le contenu à afficher.
De manière standard, ce champ ne contient qu'une clef body (ou teaser) avec comme valeur un tableau indexé. Ce tableau a deux clefs, #value contenant le résultat formaté XHTML (en fonction du format d'entrée) de $node->body (ou node->teaser), et #weight qui contient la position de ce morceau de XHTML par rapport à d'éventuels autres.
node : Object (
'title' => 'NID:41 - Un noeud esclave',
'body' => 'consectetur adipiscing elit.
Aliquam vel tristique sapien.',
...
'content' => array(
'body' => array (
'#value'=>'consectetur adipiscing elit.Aliquam vel tristique sapien.',
'#weight'=>0))Vue interne de l'objet $node
Pour créer le $content du modèle node.tpl.php, Drupal n'a alors plus qu'à agréger tous les éléments #value dans l'ordre des #weight.
C'est donc cette caractéristique que nous allons exploiter pour ajouter notre propre fragment de XHTML et utilisant le résultat du node_view.
function mon_module_nodeapi(&$node, $op, $teaser, $page) {
switch ($op) {
case 'view' : {
if ($node->nid == 42 && !$teaser) {
$noeud_esclave = node_load(41);
$node->content['noeud_esclave'] = array (
'#value' => node_view($noeud_esclave, true, false, true),
'#weight' => 1);
}
}
}
}Implémentation finale de hook_nodeapi
Le champ #weight prend pour valeur 1, ce qui le placera au dessous du contenu du noeud maître. Une valeur négative inverserait simplement ce comportement.


Je n'en fais pas un mystère, je ne suis pas un Viewsonados mais il faut tout de même être honnête, la modulo-mania appliquée peut amener à des trucs assez géants avec Views...
Je suis ainsi tombé (j'ai encore un peu mal
, sur un exemple assez magnifique pondu par une graaaande WebAgency spécialisée Drupal, tout ça... Pour répondre à une partie de notre besoin, leur idée a été d'utiliser le module Panels et de créer un "Page Panel". De là, ils collent une vue "Views" qui ne pond qu'un seul noeud sur la partie haute, et pour celle du bas, une seconde vue "Views" qui cette fois sort une liste de noeuds... Je vous laisse imaginer la simplicité de debuggage et les performances d'un tel assemblage...
Il existe plusieurs autres "techniques" que j'ai croisées à droite à gauche (y compris chez moi, à mes débuts sur Drupal) qui ne valent pas, loin de là, celle du nodeapi. La plus courante et bien évidement la pire, consiste à utiliser l'horrible format d'entrée code PHP :
insérer du code dans un noeud":Retourner au sommaire
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam vel tristique sapien.
<?php
$node=node_load(41);
print node_view($node, true, false, true);
?>
Alors ça, c'est comme croiser les effluves, c'est MAL. A chaque fois que vous vient l'envie de faire un chose pareil, ayez une pensée compatissante pour le pauvre bougre qui passera dans 10 mois sur votre projet en se demandant d'où vient le comportement extraordinaire de ce contenu. En plus, mettre du code dans une base de donnée (car cela revient à cela), au delà d'être très sale, est une calamité lorsque viendra le temps de debugger.
Dans un domaine connexe mais issues ici aussi de la fameuse WebAgency dont je parlais plus haut (mais j'imagine qu'ils ne sont pas les seuls), le pompon de l'utilisation de cette "technique" a été de coller 200 lignes de code PHP dans un noeud, et d'associer le chemin de ce noeud (node/XXX) à un alias (chemin/vers/ma/fonction). Tout cela pour créer une page de fonctionnalités complexe alors qu'il aurait été si simple de mettre ce code dans une fonction, cette fonction dans un module, et d'ajouter le chemin dans un hook_menu...
Du fonctionnel dans la présentation...
L'autre (mauvaise) possibilité consiste à utiliser la fonction phptemplate_preprocess_node d'un thème (généralement dans template.php) et à ajouter le contenu XHTML du noeud esclave à la variable $vars['content'] juste avant qu'elle ne devienne le $content de node.tpl.php.
Alors ça marche mais ce n'est pas terrible. En effet, la beauté du système de thème de Drupal tient justement à ce qu'il permet une stricte séparation entre les fonctionalités d'un côté et leur présentation de l'autre. Ce que nous faisons ici relève du domaine du fonctionnel et n'a donc rien à faire dans le thème. Une bonne manière de savoir si telle ou telle chose va dans un thème ou dans un module, est de ce demander si cette chose doit survivre à un changement brutale de thème. Si c'est le cas, vous n'avez d'autre choix que le module.
De manière plus dégradée, évitez aussi de coller ce type de code, ou tout autre code d'ailleurs, directement dans node.tpl.php. En faisant cela vous commettez la même erreur que plus haut avec en prime le risque de rendre vos modèles totalement illisibles.
D'une manière générale, une petite règle qui vaut la peine d'être rappelée : Drupal est suffisamment bien conçu pour qu'un modèle ne doive contenir que les fonctions PHP if/else et print. Tout le reste doit être dans un module, ou, le cas échéant, dans template.php.
Conclusion
Nous avons vu ici qu'il est relativement simple d'intégrer proprement un noeud dans un autre, avec en prime la possibilité d'exploiter sans soucis le module Printer pour générer une version imprimable ou PDF de l'ensemble.