Comment affecter un "node" un sous-menu ?

Information importante

En raison d'un grand nombre d'inscriptions de spammers sur notre site, polluant sans relache notre forum, nous suspendons la création de compte via le formulaire de "sign up".

Il est néanmoins toujours possible de devenir adhérent•e en faisant la demande sur cette page, rubrique "Inscription" : https://www.drupal.fr/contact


De plus, le forum est désormais "interdit en écriture". Il n'est plus autorisé d'y écrire un sujet/billet/commentaire.

Pour contacter la communauté, merci de rejoindre le slack "drupalfrance".

Si vous voulez contacter le bureau de l'association, utilisez le formulaire disponible ici, ou envoyez-nous un DM sur twitter.

Bonjour,

J'ai créé un nouveau "node" depuis la page d'administration "content page" (node/20 par exemple)... et maintenant, j'aurais besoin d'affecter ce node au sous-menu déclaré ci-dessous.
Pourriez-vous me suggérer comme faire ça svp ?

Ce qui me gêne, c'est que j'ai besoin de récupérer le lien attribué à mon node :
http://127.0.0.1/mysite/?q=node/20

Or, en déclarant mon sous-menu tel que je l'ai fait ci-dessous, l'URL devient :
http://127.0.0.1/mysite/?q=menu1/subitem1

J'ai aussi pensé à définir un callback "test", puis charger le node pour le "themer" ... mais je pense qu'il y a beuaocup plus simple non ? (genre en surchargeant le path ou le link...)

function list_projects_menu() {

$items['menu1'] = array(
'title' => t('my infos'),
'page callback' => 'show_page',
'access callback' => TRUE,
'type' => MENU_NORMAL_ITEM,
);

$items['menu1/subitem1'] = array(
'title' => t('my infos'),
'page callback' => 'test', // node/20 ?
'access callback' => TRUE,
);
}

Cédric

Forum : 
Version de Drupal : 

Je ne suis pas bien sur de tout comprendre alors en vrac :)

1/ lorsque tu définis un chemin pour un node, c'est un alias qui coexiste avec le chemin d'origine. Donc les deux URLS fonctionnent node/20 et menu1/subitem1. Le premier lien peut être vu comme le permalink du node (le truc qui ne changera jamais) et le second comme le lien joli. Tu peux au passage ajouté autant d'alias que tu veux à n'importe quel chemin en allant dans l'administration des alias.

2/ Si tu veux créer un lien vers un noeud, pour avoir la version "jolie" à partir de la version "permalink", il faut passer par la fonction

<?php
 l
('mon lien', 'node/20')
?>

ou

<?php
 url
('node/20')
?>

pour l'url sans le A/HREF. Il faut systématiquement utiliser ces fonctions pour créer des liens et jamais créer des URLS ou des A/HREF à la main.

3/ lorsque tu es dans ton code et que le chemin en cours est menu1/subitem1, tu peux savoir quel est le noeud d'origine en passant par la fonction

<?php
 $node
=menu_get_object()
?>

Si elle renvoie null, c'est pas un noeud, sinon elle revoit un objet node dont tu peux tirer le nid par $node->nid.

4/ Si tu veux connaître le permalink associé à un chemin "joli", tu utilises simple

<?php
 $_GET
['q']
?>

, drupal l'a en effet nettoyer (il me semble, à vérifier) pour n'avoir que le permalink. Si tu veux ce que l'utilisateur a réellement utilisé comme chemin, utilise

<?php
 request_uri
()
?>

.

Voilà, en espérant que dans le tas tu trouves ton bonheur :)

Merci beaucoup c'est super clair.

Par contre, est-ce possible d'appeler mon 'node/20' directement depuis la fonction ci-dessous (sans passer par les hook list_projects_menu_alter() ou list_projects_menu_link_alter() ni via l'intermédiaire d'un callback) ?


function list_projects_menu() {

  $items['menu1'] = array(
    'title' => t('my infos'),
    'page callback' => 'show_page',
    'access callback' => TRUE,
    'type' => MENU_NORMAL_ITEM,
  );

  $items['menu1/subitem1'] = array(
    'title' => t('my infos'),
    'type' => MENU_NORMAL_ITEM,
  );
}

car si je remplace 'menu1/subitem1' par 'node/20'... comme ci-dessous, ma page est bien affichée lorsque je clique sur le sous-menu... mais celui-ci est déplacé à la racine et n'appartient donc plus au menu parent "menu1"... et au niveau de l'IHM ça change tout...


function list_projects_menu() {

  $items['menu1'] = array(
    'title' => t('my infos'),
    'page callback' => 'show_page',
    'access callback' => TRUE,
    'type' => MENU_NORMAL_ITEM,
  );

  $items['node/20'] = array(
    'title' => t('my infos'),
    'type' => MENU_NORMAL_ITEM,
  );
}

Ouh là, tu mélanges deux concepts là (3 en fait :-)

1/ Tu es les "handler de chemins" qui sont déclarés par les hooks menus. Leur rôle est d'associer un chemin interne (toto/tutu/titi) à une fonction PHP. Il ne faut pas se laisser berner par le _menu du hook_menu car cela n'a rien à voir. Le seul lien possible est qu'un handler de menu qui a dans son type le flag MENU_VISIBLE_IN_TREE (c'est le cas si tu utilises MENU_NORMAL_ITEM) va être en plus dupliqué en tant que menu. Pour bien comprendre, node/20 est en réalité déjà déclaré par le module mofules/node.module (tu peux vérifier ;-) sous la forme 'node/%'. Donc en déclarant ton propre handler de menu node/20, je ne te garanti pas la non explosivité du résultat :)

2/ Les chemins internes peuvent être aliasés en associant un menu interne existant (c'est à dire déclaré par un module comme indiqué en 1/) et un chemin "virtuel".

3/ Tu as menus que sont ces choses que tu peux créer dans l'interface d'administration (admin/build/menu) et qui contiennent des éléments de menu (dans le vrai sens du terme cette fois) qui associe un... menu, à un chemin interne déclaré par un hook_menu :)

Si tu veux une vision "base de données" de ces trois choses. Les hook_menu sont rangés dans la table menu_router. Les alias dans la table url_alias. Et enfin les menus et éléments de menus sont dans menu_links. 3 notions différentes, trois stockage différents.

Si tu veux j'ai fais un tuto sur le sujet car c'est loin d'être évident :
http://arnumeral.fr/creer-des-handlers-de-menu

Donc en somme je n'ai toujours pas bien compris ce que tu cherchais à faire mais à priori c'est clairement pas la bonne méthode. Tu veux quoi, que ton node/20 soit utilisable par l'URL menu1/subitem1 ?

Ah Jquery sait faire ça, c'est bon à savoir ! :-)

Donc en somme je n'ai toujours pas bien compris ce que tu cherchais à faire
mais à priori c'est clairement pas la bonne méthode. Tu veux quoi, que ton
node/20 soit utilisable par l'URL menu1/subitem1 ?

Oups.. je suis un peu confus, d'être aussi peu clair...
En fait, oui je crois que tu as bien défini ce que je voulais faire :
que mon node/20 soit utilisable par l'URL menu1/subitem1.

Mais je suppose que tu vas me répondre que je dois obligatoirement passer par un Callback ?

Euh non, je vais te dire que tu n'as rien d'autre à faire qu'à activer le module 'path', à aller dans l'édition de ton node/20 et dans la zone 'Paramètres du chemin d'URL' et tu tapes 'menu1/subitem1', tu valides et tu profites :)

En fait c'est le coup de l'url_alias dont je parlais plus haut, ce module permet de l'alimenter via le formulaire du node.

Euh sauf que mon menu est entièrement dynamique (les sous-items - chacun associé à un Node - sont générés dynamiquement).
Je ne veux surtout pas obliger le futur administrateur du site à devoir renseigner la zone "Paramètres du chemin d'URL" pour tout nouveau node qui sera créé...
(chaque nouveau Node créé, donne lieu à l'ajout dynamique d'un subitem dans mon menu)

Il faudrait que ce soit fait dynamiquement...

Mon code actuel est le suivant (sans le savoir, ça semble rejoindre ce que tu me conseillais) :

function afficher_node($str_node) {
  $path = drupal_get_path_alias($str_node);
  drupal_goto($path);
}

mes sous-items appellent le callback afficher_node() en leur passant en paramètre le node à afficher...
jusque là tout va bien... mais le problème c'est que lorsque l'utilisateur clique sur le lien du sous-menu... bien que la page s'affiche parfaitement, le menu parent se referme (collapse) comme si le node affiché ne faisait pas partie du menu.
En gros, c'est ça mon problème initial...

Pour tester le fonctionnement de base des menus, je suis retourné dans la console d'admininistation des Menus, et j'ai ajouté manuellement un sous-item associé à un de mes nodes. Lorsque je teste l'affichage de ma page correspondant au node du sous-item, le menu reste bien ouvert.

En ce cas les modules 'pathauto' et 'token' sont ton ami. Le premier va créer les alias en fonction des données fournies par le second. Et si le second ne te fournis pas les données qui t'intéresse, tu peux injecter les tiennes très simplement (l'exemple est fourni avec le module token).

Bonjour,

Comme je suis curieux, je me demandais ce que cache de tels composants...
Ce que je veux faire semble tellement simple (enfin c'est l'impression que j'en ai), que je pensais qu'un coup de SQL dans les tables menu_rooter et menu_links suffirait, ce n'est pas le cas ?

Si je devais mettre mes liens et path à jour directement en Php, il faudrait faire cela comment ? (c'est aussi pour mieux apprendre le coeur de Drupal)...

Je suis à 100% d'accord sur ton approche minimaliste :) Mais pour cela, il faut comprendre la "mécanique".

  1. Lorsque tu actives un module, drupal inspecte son hook_menu et fabrique les entrées de menu_router. Cette table n'est donc clairement à ne pas toucher !

  2. Lorsque pendant son inspection drupal trouve des chemins internes dans ce hook_menu qui peuvent apparaître dans un menu utilisateur (typiquement le menu navigation), il va ajouter des entrées dans menu_links. C'est aussi le cas lorsque tu utilises admin/build/menu. La différence entre les deux cas est le champ 'customized' de cette table qui est à 1 dans le second cas. Donc cette table est elle non plus pas à toucher à la main.

Après il faut bien comprendre que les entrées de menu_links n'ont qu'un seul objectif, générer des liens, elles ne sont en rien responsable des urls auxquelles elle font appel. Ces URL sont uniquement et strictement générées par les hook_menu et donc la table menu_router.

  1. Reste heureusement la troisième table dont je te parlais plus haut, url_alias, qui elle va te permettre de jouer sur ces urls en déclarant des sorts de synonymes. Cette table a principalement deux colonnes, src (le chemin physique issu de hook_menu/menu_router) et dst pour placer tes chemins arbitraire.

Et c'est juste cela que fant pathauto/token de manière automatiser. Pathauto s'accrochent aux évenements de création/modification de contenu et insère/modifie une entrée dans path_auto en correspondance. C'est aussi ce que fait plus simplement le module "path" (dont dépend pathauto), en te permettant d'éditer cette table en passant admin/build/path.

Maintenant pour mettre cela en "musique", lorsque Drupal reçoit une requête avec un chemin à traiter, il commence par chercher une correspondance dans la colonne dst de la table url_alias. S'il en trouve, il replace ce chemin par le champ src. Le chemin d'origine est du coup devenu un chemin interne compatible avec menu_router. Drupal va ainsi pouvoir trouver le module et la callback à exécuter.

Tout ce blabla pour dire que des trois tables menu_router, menu_links et url_alias, seule la dernière est modifiable facilement et sans risques. Tu peux donc dans ton cas y insérer src:node/20 et dst:menu1/submenu1.

Ensuite si tu veux mimer le comportement de pathauto et insérer toi-même tes éléments dans url_alias à chaque ajout/modification/suppression de contenu, jette un oeil à hook_nodeapi ( http://api.drupal.org/api/function/hook_nodeapi ) qui va te permettre de t'accrocher sur ces évenements et ainsi faire ta petite toutouille.

En fait, voici mon code... mais comme l'ajout suivant dans la table URL_ALIAS n'a rien changé... je me demande si je n'ai pas oublié
quelque chose (src : node/20 et dst : menu1/subitem1)

function list_projects_menu() {
  $items['menu1'] = array(
    'title' => t('Mon titre'),
    'page callback' => 'show_projects_list',
    'access callback' => TRUE,
    'type' => MENU_DYNAMIC_ITEM,
  );

  $items['menu1/subitem1'] = array(
    'title' => t('Mon titre1'),
    'page callback' => 'display_node',
    'page arguments' => array('node/20'),
    'access callback' => TRUE,
  );
}

function show_projects_list() {
    return "Hello world";
}

function display_node($str_node) {
  $path = drupal_get_path_alias($str_node);
  drupal_goto($path);
}

La seule chose qui change, c'est l'URL affichée dans le navigateur lorsque je clique sur le sous-menu.
- Sans la modif dans URL_ALIAS :
URL = http://127.0.0.1/mon_site/?q=node/20

Jusque là tout est logique... sauf qu'il me reste mon erreur de menu qui se referme alors que j'ai cliqué dans un sous-élément du menu...
Je n'arrive pas à comprendre pourquoi ce menu se referme ?

comme si la translation : menu1/subitem1' -> 'node/20'
déclarée dans mon hook_menu ci-dessous générait des soucis au niveau de la gestion du menu :

$items['menu1/subitem1'] = array(
    'title' => t('Mon titre1'),
    'page callback' => 'display_node',
    'page arguments' => array('node/20'),
    'access callback' => TRUE,
  );

Mais si je retire tout mon code, je n'ai plus rien pour tester (puisque mon menu et les sous-menu sont générés dynamiquement depuis mon module).
D'ailleurs je ne l'ai pas indiqué mais mes sous-menus sont générés avec une boucle directement dans le hook_menu :

foreach ($titles as $count => $title) {
    $items['menu1/subitem' . $count] = array(
      'title' => $title,
      'page callback' => 'display_node',
      'page arguments' => array('node/' . $nodes[$count]),
      'access callback' => TRUE,
    );
  }

Ahh ok, ça commence à prendre forme (c'est bien au bout de 20 posts ;-)

essaye un truc comme cela :

<?php
      
...
     
'page callback' => 'mon_module_display_node',
     
'page arguments' => array($nodes[$count]),
      ...
?>

et une fonction mon_module_display_node comme ceci

<?php
function mon_module_display_node($nid) {
  
$node=node_load($nid);
   return
node_view($node, FALSE, TRUE, TRUE);
}
?>

Maintenant avec cette approche, il faut avoir conscience de deux chose. Tout d'abord tes chemins (et les menus associés puisque tu utilises 'type'=>MENU_NORMAL_ITEM), seront bien créés dynamiquement mais uniquement lorsque le module est activé pour la première fois. Sinon il faut que tu forces la reconstruction du registres par un menu_rebuild() (ou via drush cc all).

Ensuite, dans la mesure où ton URL menu1/submenu1 renvoie le contenu d'un noeud mais sans passer par le chemin d'origine, et sans url_alias, tu n'auras donc pas les onglets 'éditer', 'voir', etc... que tu trouves classiquement dans un noeud.

Si tu veux avoir ces onglets, tu dois passer comme je le disais plus haut par des insertions dans url_alias. Et si tu veux avoir aussi des menus associé à ces alias, il faut simplement créer pogramatiquement un menu et les sous-éléments qui vont avec (utilise pour cela la fonction menu_link_save()).

Ouah, effectivement ça prend forme :-)

Alors, voila où j'en suis... si j'utilise ton code (sans toucher à la table URL_ALIAS)...
j'ai enfin effectivement enfin mon menu qui fonctionne parfaitement, il reste ouvert car j'ai cliqué sur l'un de ses sous-items...

Par contre, comme j'ai effectivement besoin des onglets VIEW et EDIT pour que l'administrateur puisse éditer la page, j'ai inséré un enregistrement dans la table URL_ALIAS. Mais là, problème... les onglets apparaissent bien... mais c'est le menu qui ne fonctionne plus...
Donc au final, j'ai juste besoin d'utiliser menu_link_save() c'est ça ?

Je l'utilise comment ? je dois rajouter tous les éléments (root + subitems) comme ceci ?

function list_projects_menu() {
  $items['menu1'] = array(
    'title' => t('Mon titre'),
    'page callback' => 'show_projects_list',
    'access callback' => TRUE,
    'type' => MENU_DYNAMIC_ITEM,
  );
  menu_link_save($items);

  $items['menu1/subitem1'] = array(
    'title' => t('Mon titre1'),
    'page callback' => 'list_projects_display_node',
    'page arguments' => array('node/20'),
    'access callback' => TRUE,
  );
  menu_link_save($items);
}

url_alias/menu_link_save et hook_menu sont deux approches distinctes à ne pas mixer. Le code que je t'ai modifié convient à la second mais pour la première, tu ne dois pas utiliser de hook_menu car tu ne crées aucun chemin. En plus la fonction menu_link_save crée des liens (c'est à dire des des éléments de menus) et pas des chemins !! donc tu ne peux pas sauver tes items avec, ce sont d'autres objet.

Pour utiliser cette seconde approche tu dois virer ton hook_menu et créer à ton module un mon_module.install (au même niveau que ton mon_module.module maintenant vide). Dans ce nouveau fichier tu vas créer un hook_install comme ceci :

<?php
function mon_module_install() {
     ...
ta soupe pour récupérer tes nids

    
// Création du menu racine
    
$parent_link['link_path']='<front>';     // Il faut un chemin dans tous les cas...
    
$parent_link['link_title']="menu1";
     
$parent_link['module']= 'mon_module'; // important pour pouvoir retrouver plus tard 'ton' lien
    
menu_link_save($parent_link);

    
// Création des subitems
    
$i=1;
     foreach (
$nids as $nid) {
       
// création du sous-menu
   
$link['link_path']='node/$nid';
   
$link['link_title']="subitem$nid";
       
$link['plid']=parent_link['mlid'];
       
$link['module']= 'mon_module';
       
menu_link_save($link];

       
// création de l'alias sur qui va lier node/X à menu1/subitemY
       
path_set_alias("node/$nid", "menu1/subitem$i");
       
$i++;
     }
  }

  function
mon_module_uninstall() {
   
// un peu sauvage, certes :)
   
db_query("DELETE FROM {menu_links} WHERE module='mon_module'");
   
db_query("DELETE FROM {url_alias} WHERE dst like 'menu1/%'");
  }
?>

Je tape cela à l'arrache, j'ai pas testé le code mais tout le principe est là. Pour que tout cela fonction il te faut donc désactiver/désinstaller/activer ton module de sorte à ce que le hook_install se déclenche.

Mais si je créé le menu dynamique dans mon_module_install(), ça signifie que le menu sera généré une fois pour toutes qu'au moment de l'installation (pas très dynamique pour un menu dynamique).
Or, dans mon cas... l'administrateur doit pouvoir créer des nodes (et donc les sous-menus correspondants avec) à tout moment.
Aujourd'hui, mon code fonctionnait pas trop mal, car à chaque nouveau node créé, l'administrateur n'avait besoin que de faire un menu_Rebuild() mais dans le cas présent, cela l'obligerait à désactiver/activer le module...

Je ne suis pas certain que ce soit adapté à ce que je veux faire...
ou alors peut-être y a t-il une manière de re-déplacer ce code dans mon_module.module ?

(désolé d'être un peu chiant, hein :-) )

Attends, on va reprendre le problème un peu à la base. A quoi te sert ton menu exactement ? Avec ton système j'imagine que jusqu'à maintenant il échouait dans le bloc "navigation", c'est bien cela ?

En fait, oui j'ai un menu dans le bloc navigation qui ressemble à ceci :

Se déconnecter
Les projets
  - Créer un nouveau projet
  - Projet 1
  - Projet 2
  - ...
Admin

A chaque fois que je clique sur "Creer un nouveau projet", j'appelle en fait : http://127.0.0.1/mon_site/?q=node/add/project.
Ou Project est un Type de Contenu que j'ai créé via mon module Project.
Ainsi, l'administrateur peut créer un nouveau projet "Projet 3" par exemple.
Dés que le nouveau projet est créé, Drupal doit peupler le menu "Les projets" et ajouter ce nouveau projet parmi les sous-menus.
Pour modifier ou supprimer un projet, l'administrateur a juste à cliquer sur son projet (clic sur le sous-menu) puis comme il accède aux boutons VIEW et EDIT (/DELETE), il peut éditer (ou supprimer) ce qu'il veut assez rapidement.

J'ai pensé à ce mode de fonctionnement car il y a peu de projets (2 / an maxi) donc le menu ne sera probablement jamais en surpopulation. et d'autre part, parce que j'aime l'idée que l'administrateur ou l'utilisateur puisse visualiser les projets ainsi.
Actuellement, en cas d'ajout d'un nouveau projet, l'appel à la fonction menu_rebuild() est requis... c'est la seule petite contrainte que je vois...

Mais sinon, il me vient une idée... puisque la 1ère approche fonctionne presque exactement comme je veux... ne puis-je pas tout simplement ajouter manuellement mes 2 boutons VIEW et EDIT ? si oui, y a t-il une manière propre et simple ?

Concernant la 2nde approche, ne peut-on pas tout simplement déporter le code que tu m'as indiqué, et le placer dans la fonction mon_module_init() du fichier mon_module.module ?

(aller jusqu'au bout des 2 approches m'intéresse... pour ma culture Drupalesque ;-) )

Pour ta question, oui c'est possible mais ce serait désastreux en terme de performances car ce hook est appelé à chaque construction de page. Si tu veux faire une telle chose, utilise plutôt le hook_nodeapi pour te connecter à la création/suppression d'un noeud.

Sinon, si tu ne fais qu'utiliser le bloc "navigation", pourquoi ne fais tu simplement pas ton propre bloc 'Mes projets' en ne te cassant du coup plus les pieds avec les menus ??? Là du coup tu peux faire un truc totalement dynamique avec un bloc qui lirait les noeuds de type 'projet' à chaque construction. Désolé, une troisième approche donc ;-)