Créer un module Drupal, types de contenu
Ecrit par Yoran, le sam, 24/05/2008 - 20:52

Dans le précédent tutoriel nous avons vu comment créer un module simple comprenant les notions de hooks et la méthode unifiée d'accès à la base de données. Il est maintenant temps de passer au stade au dessus avec un module prenant cette fois en charge un type de contenu totalement personnalisé.

Où allons nous ?

Dans le tutoriel précédent, nous avons crée notre premier module de gestion d'un liste de courses, pour l'instant capable d'afficher des informations statiques dans un bloc. Ce que nous allons voir maintenant c'est :

  • L'enregistrement, la modification et la suppression d'un produit. Un produit étant défini par son nom, sa quantité en réserve et la valeur seuil devant déclencher son rachat.
  • L'affichage de la liste des produits. Chaque produit devant être racheté sera surligné. Sur chaque ligne, seront disposées deux actions, l'une permettant de le modifier, l'autre de décrémenter la quantité de produit restant d'une unité.
  • Mettre les véritables informations dans le bloc du précédent tutoriel.

Avant de commencer, nous allons donc reprendre les sources du précédent tutoriel à fin d'en étendre les fonctionnalités.

Si vous avez suivi le précédent tutoriel et installé le module qui va avec, commencez par le desinstaller. En effet, nous allons ajouter une partie base de données qui ne sera pas créée le module est déjà installé.

Installation

Notre module courses a pour finalité de pouvoir saisir des produits et de les présenter sous la forme d'une liste. Nous pourrions pour cela utiliser un type de contenu simple comme story mais cette structure de stockage serait trop limitée car nous avons des informations plus précises à saisir que les seuls titre et corps. Il nous faut en effet spécifier le nombre de produits restants et le nombre minimum à avoir en réserve pour pouvoir générer notre liste de courses. Nous allons donc créer un nouveau type de node, produit, en cherchant à étendre le type de node simple.

Drupal stocke les données de base d'un node (titre, corps, etc.) dans la table node dont la clef primaire est le champ nid. Ce sont les valeurs de ce champ que vous retrouvez dans l'URL d'un node, sous la forme http://mon_site_drupal?q=/node/1234. Elle dispose aussi d'un champ type de type texte identifie le type du contenu. Dans notre cas ce type prendra la valeur produit.

Mais pour stocker nos informations supplémentaires, nous allons devoir créer une nouvelle table, traditionnellement nommée node_produit, contenant un champ quantité pour la quantité restante du produit et un champ seuil pour la valeur minimum à conserver en réserve. En outre elle disposera, elle aussi, d'un champ nid. Les informations sur un produit donné seront donc réparties sur les champs des deux tables à la fois (node et node_produit)reliées entre-elles par le même nid.

Du coup, pour obtenir toutes les informations disponibles sur un node de type produit, nous pouvons formuler la requête suivante :

SELECT n.*,i.* FROM node n, node_produit i WHERE n.nid=i.nid AND n.type='produit';

Alors nous pourrions évidemment créer cette nouvelle table à la main mais ce serait bien peu pratique lorsque viendra le moment de redistribuer ce module. La bonne pratique est donc de déléguer ce travail à Drupal lors de l'activation du module. De la même manière, nous allons lui indiquer comment la supprimer, lorsque le module est désactivé puis désinstallé.

Pour ce faire, il nous faut rajouter un troisième fichier à notre dossier module : courses.install qui va contenir deux hooks. Le premier, hook_install, est invoqué lors de la première activation du module (installation). Le second, hook_uninstall, est quant à lui invoqué lors de la désinstallation du module.

<?php

/**
 * Implementation of hook_install().
 */

function courses_install() {
  $result= array ();
  switch ($GLOBALS['db_type']) {
    // Installation de la table pour MySQL
    case 'mysql' :
    case 'mysqli' :
      $result[]= db_query("
      CREATE TABLE {node_produit} (
  nid int(10),
        quantite int(10),
        seuil int(10),
        PRIMARY KEY (nid)
        )"
);
      break;

    // Installation de la table pour PostgreSQL
    case 'pgsql' :
      error_log("install on Psql");
      $result[]=db_query("
    CREATE TABLE {node_produit} (
          nid INTEGER,
          quantite INTEGER,
          seuil INTEGER,
          PRIMARY KEY (nid)
         );"
);
      break;
  }
  return $result;
}

/**
 * Implementation of hook_uninstall().
 */

function courses_uninstall() {
  $result=array();
  $result[]=db_query('DROP TABLE {node_produit}');
  return $result;
}
courses/courses.install

Le code n'a rien de compliqué mais quelques points cependant méritent éclaircissement.

Tout d'abord, nous distinguons deux cas qui correspondent aux deux bases de données prises en charge par Drupal : mySQL et PostgreSQL. En effet, si les bases de données sont à peu près équivalentes s'agissant des requêtes INSERT,DELETE,UPDATE et SELECT, c'est un peu moins le cas pour les commandes CREATE et ALTER.

Ensuite concernant l'exécution des requêtes SQL, nous devons à tout prix éviter d'utiliser les fonctions base de données de PHP au profit de celles fournies par Drupal. En effet ces dernières présentent l'avantage de faire abstraction de la base de données sous-jacente.

Dans le cas spécifique de l'installation et de la désinstallation, nous utilisons update_sql mais pour le reste du code elle sera remplacée par db_query. Ces deux fonctions sont pratiquement identiques à la différence près qu'update_sql fournit plus d'informations sur le déroulement de la requête. Informations qui seront utilisées par l'assistant d'installation et de mise à jour de Drupal.

Dernier point : les accolades utilisées pour encadrer le nom des tables. Elles ne sont pas nécessaires mais permettent à Drupal certaines opérations de préparation des requêtes. C'est donc juste une bonne habitude à prendre.

Maintenant que notre installation est finalisée, il ne nous reste plus qu'à activer notre module dans le panneau d'administration de Drupal : http://mon_site_drupal?q=/admin/build/modules. Une fois cette opération achevée, vous pouvez vérifier à l'aide de votre explorateur de base de données préféré que la table node_produit a bel et bien été créée. Si vous désactivez et réactivez votre module, notez que le code d'installation n'est pas ré-exécuté. En effet, il vous faut d'abord désinstaller le module, pour que le hook courses_uninstall soit appelé et détruise la table. Ensuite, si vous réactivez à nouveau le module, la table sera recréée.

Type de contenu

Notre base est prête, notre module activé et pourtant si nous allons sur l'URL http://mon_site_drupal/?q=node/add, notre produit n'apparaît pas. En effet, il faut implémenter deux hooks supplémentaires dans courses.module pour indiquer à Drupal qu'un nouveau type de node est à prendre en charge. Le premier hook, hook_node_info, va décrire le nouveau type de node :

function courses_node_info() {
  return array(
    'produit' => array(
      'name' => t('Un produit'),
                'description' => t("Un produit dans la liste des courses."),
      'module' => 'courses'));
}
à ajouter au fichier courses.module

Ce hook permet de renvoyer à Drupal une liste de nouveaux types de contenu. Ici nous n'en indiquons qu'un seul, produit, avec sa description et son nom usuel. L'identifiant produit sera utilisé dans la colonne type de la table node comme vu au chapitre précédent.

Le dernier paramètre correspond au nom du module qui va gérer ce type de contenu, en l'occurrence notre module courses<.kbd>.

Formulaire de saisie

Une dernière étape est nécessaire à l'apparition dans la liste de notre nouveau type de contenu : mettre en place le formulaire de saisie. Mais avant cela, il est important de bien comprendre comment Drupal fait vivre un node.

Un node est représenté dans le code de Drupal par l'objet $node. A chaque étape du cycle de vie du node, différents hooks viennent enrichir cet objet jusqu'à y ajouter le code HTML qui sera inséré dans la page. C'est ainsi que dans le template node.tpl.php, l'affichage du titre passe par une référence à $node->title.

Il existe une relation étroite entre l'objet $node et sa représentation en base de donnée. Si le node existe déjà, Drupal transfère les valeurs correspondantes de la table node vers les champs de l'objet $node. Les identifiants de chaque colonne deviennent ainsi les noms d'attributs de l'objet. Lorsque le node n'existe pas encore, Drupal crée simplement un objet vide. C'est donc soit l'objet vide, soit l'objet chargé des valeurs en base, que nous allons récupérer dans le hook qui va créer notre formulaire, hook_form.

Ce hook est chargé de créer le formulaire mais aussi de l'initialiser avec les valeurs de l'objet $node. Ce formulaire sera soit utilisé sur un objet vide ou soit sur contenant les données d'un contenu existant.

function courses_form(& $node)
{
  $form['title']= array (
    '#type' => 'textfield',
    '#title' => t("Nom du produit"),
    '#default_value' => $node->title,
    '#required' => TRUE
  );
  $form['body_filter']['body']= array (
    '#type' => 'textarea',
    '#title' => t('Notes'),
    '#description'=>t("Notes sur le produit"),
    '#default_value' => $node->body,
    '#rows' => 10,
    '#required' => FALSE);
  $form['body_filter']['format']= filter_form($node->format);

  $form['seuil'] = array(
    '#type' => 'textfield',
    '#title' => t("Seuil d'achat"),
    '#description'=>t("Quantité minimum en dessous de laquelle il faut racheter le produit"),
    '#size' => 4,
    '#required' => TRUE,
    '#default_value' => $node->seuil
  );


  $form['quantite'] = array(
    '#title' => t("Quantité"),
    '#description'=>t("Quantité de produit restant en réserve"),
    '#type' => 'textfield',
    '#size' => 4,
    '#required' => TRUE,
    '#default_value' => $node->quantite
  );

  return $form;
}
Corps de la fonction courses_form

Ici, rien de nouveau par rapport au formulaire que nous avions définit pour le module statistique. Il est juste un peu plus complet mais fonctionne exactement sur le même principe.

Comme nous l'avons vu plus haut, l'objet $node est vide à sa création. Si nous l'utilisons tel quel, les champs quantité et seuil seront vides (et non égal à Innocent. Il est donc nécessaire d'implémenter un nouveau hook, hook_prepare, qui a pour rôle de préparer l'objet $node avant utilisation dans le formulaire.

function courses_prepare(& $node) {
  $node->quantite=$node->quantite?$node->quantite:0;
  $node->seuil=$node->seuil?$node->seuil:0;
}
à ajouter au fichier courses.module

Grâce à cette fonction, nous initialisons notre objet $node avec les bonnes valeurs par défaut. De la même manière nous pouvons valider ce que renvoie le formulaire en implémentant cette fois hook_validate

function courses_validate($form_id, $form_values) {
  if (!is_numeric($form_values['quantite']) || $form_values['quantite'] < 0) {
    form_set_error('quantite', t('Le champ quantité doit être une valeur numérique et positive'));
  }
  if (!is_numeric($form_values['seuil']) || $form_values['seuil'] < 0) {
    form_set_error('seuil', t('Le champ seuil doit être une valeur numérique et positive'));
  }
}
à ajouter au fichier courses.module

Ici nous cherchons juste à nous assurer que les valeurs saisies sont numériques et positives. Maintenant notre formulaire est complet. Nous pouvons l'essayer en utilisant l'URL http://mon_site_drupal?q=/node/add/produit.

Base de données

Nous avons maintenant un objet $node correctement renseigné par notre formulaire. Il ne nous reste donc plus qu'à coder sa sauvegarde en base de données. Pour ce faire, nous allons devoir implémenter quatre hooks, un par commande SQL.

Commençons par le plus simple : la lecture. Elle est prise en charge par le hook hook_load :

function courses_load($node) {
  return db_fetch_object(db_query('SELECT * FROM {node_produit} WHERE nid = %d', $node->nid));
}
à ajouter à courses.module

hook_load reçoit en paramètre un objet $node contenant déjà des informations extraites de la table node. Cela inclut le nid que nous utilisons dans notre requête pour lire en base nos valeurs supplémentaires. L'objet ainsi formé est renvoyé à Drupal qui l'agrégera à l'objet $node. Les fonctions de base de donnée utilisées ici sont les mêmes que celles abordées dans le précédent tutoriel.

Voyons maintenant comment insérer, mettre à jour ou encore détruire nos données supplémentaires en implémentant respectivement hook_insert, hook_update et hook_delete :

function courses_insert($node) {
  db_query("INSERT INTO {node_produit} (nid, quantite, seuil) VALUES (%d, %d, %d)", $node->nid, $node->quantite, $node->seuil);
}

function courses_update($node) {
  db_query("UPDATE {node_produit} set quantite=%d, seuil=%d WHERE nid=%d", $node->quantite, $node->seuil, $node->nid);
}


function courses_delete(&$node) {
  db_query('DELETE FROM {node_produit} WHERE nid = %d', $node->nid);
}
à ajouter à courses.module

Notre nouveau type de contenu implémente maintenant tous les hooks nécessaires à son stockage en base de données. Nous pouvons maintenant créer un nouveau produit, saisir ses valeurs, le sauvegarder, le modifier, et le détruire.

Un des gros avantage de Drupal est l'abstraction de la couche base de données qu'il propose. Elle est certes simple mais très efficace. Et du point de vue du module, peu importe que l'on utilise PostrgeSQL ou MySQL, Drupal lui fournit un jeu de fonctions permettant d'oublier ce "détail".

La fonction db_query a pour rôle de lancer la requête et de renvoyer un curseur sur le résultat. Elle permet l'utilisation des motifs %d et %s comme la fonction PHP sprintf.

Notez que db_query ne va pas automatiquement insérer de guillemets autour des chaînes. C'est donc à vous de mettre des '%s'. En revanche, elle se charge de l'échappement des caractères spéciaux vous protégeant des injections de code SQL malicieux, ce qui n'est déjà pas si mal.

La fonction db_fetch_object va lire en base un enregistrement à la position courante du curseur (celui renvoyé par db_query) et transformer le résultat en un objet PHP. L'objet reçoit ainsi deux champs type et count contenant les résultats de la requête.

La fonction db_result est une version simplifiée de db_fetch_object qui est utile lorsque la requête ne renvoie qu'une seule valeur.

Affichage

La première chose que l'on constate une fois que l'on a saisi et validé notre premier produit est que les informations affichées n'incluent ni la quantité, ni le seuil de rachat. Ne sont présentes que les données d'un node standard. Pour changer cela, nous allons implémenter un nouveau hook, hook_view, et une fonction de thème.

function courses_view($node, $teaser = FALSE, $page = FALSE) {
  $node= node_prepare($node, $teaser);
  if ($page) {
    $node->content['Informations'] = array(
        '#value' => theme('produit_view', $node),
        '#weight' => 1,
    );
  }
  return $node;
}
à ajouter à courses.module

Notre première tâche est de préparer le node à l'affichage. C'est le rôle de la fonction node_prepare qui va appliquer les filtres d'entrée au corps et fabriquer le résumé (teaser).

Une fois le node préparé, nous vérifions dans quel contexte nous sommes appelé pour n'inclure nos informations supplémentaire que lorsque Drupal a besoin du node en entier (mode page) et pas seulement du résumé (mode teaser).

Si nous sommes bien en mode page, nous pouvons ajouter à l'objet $node des sections $node->content[]. Ces sections seront affichée par Drupal après le titre et le corps du node. Chacune d'entre elles est composée d'un contenu HTML (propriété #value) et d'un ordre d'apparition (propriété #weight).

Pour renseigner #value nous avons rajouté une petite finesse : l'utilisation d'une fonction de thème theme('courses_view', $node). Cela consiste à demander à Drupal d'habiller l'objet $node avec le thème courses_view. La fonction en interne va procéder de la manière suivante :

  • Si votre site a un thème, par exemple mon_theme, Drupal vérifie s'il existe une fonction qui s'appelle mon_theme_courses_view. Si tel est le cas, c'est elle qui sera utilisée.
  • Si votre site utilise un theme engine, par exemple phptemplate, Drupal cherche une fonction nommée phptemplate_courses_view.
  • Le cas échéant, Drupal cherche une fonction nommée theme_courses_view. Si elle existe, c'est elle qui sera utilisée.

Une bonne pratique est donc de toujours fournir un thème par défaut en utilisant ce dernier cas de figure. Ainsi, Drupal pourra toujours trouver de quoi habiller notre node tout en laissant à vos utilisateurs la possibilité d'en changer le style. Là aussi c'est une bonne pratique à garder à l'esprit.

function theme_produit_view($node)
{
  $result = "<h2>Détail du produit</h2>";
  $result.= "<div class='quantite'>Quantité restante :".$node->quantite."</div>";
  $result.= "<div class='seuil'>Seuil avant rachat : ".$node->seuil."</div>";
  return $result;
}
à ajouter à courses.module

Pour des raisons de performances, ce type de fonction thème n'est pas cherché par Drupal de manière automatique. Il est donc nécessaire d'implémenter un hook supplémentaire, hook_theme, pour enregistrer notre fonction theme_produit_view dans les tablettes de Drupal.

function courses_theme() {
  return array(
    'produit_view' => array(
      'arguments' => array('node' => NULL)
    )
  );
}
A ajouter à la suite du module pour Drupal 6

Attention cependant, ce hook n'est lu qu'à l'activation du node. Si l'on veut pour tester avoir une prise en charge dynamique, il est nécessaire d'ajouter, par exemple juste avant l'implémentation ci-dessus, un appel à drupal_rebuild_theme_registry();. Mais pensez à enlever cette ligne lorsque vous avez fini de tester sous peine de plomber les performances.

Actions du node

Vous aurez sans doute remarqué que chaque node disposent, à la fin du résumé ou de la page, d'une série de liens qui offrent des actions qui lui sont propres. Ces liens sont définis grâce à l'implémentation de hook_view. Nous allons donc utiliser cette fonctionnalité pour ajouter à chaque node un lien vers notre future liste de courses.

.
function courses_link($type, $node = NULL, $teaser = FALSE) {
  $links = array();
  if ($type == 'node' && $node->type='produit') {
      $links['liste_des_courses'] = array(
        'title'=>t('Liste des courses'),
        'href'=> "courses/liste",
        'attributes'=>array('title' => t('Afficher la liste complète des courses.')));
  }
  return $links;
}
à ajouter à courses.module

Attention cependant, contrairement à tous les hooks que nous avons étudié jusqu'à maintenant, ce hook n'est pas spécifique au type produit. Il faut donc vérifier avant d'ajouter un lien que l'on est bien dans le contexte d'un node (et pas d'un commentaire), et que le type du node est bien produit.

Maintenant que nous avons notre lien vers la liste des courses, il ne nous reste plus qu'à la mettre en oeuvre.

Liste de courses

Avant de rentrer dans le vif du sujet, arrêtons nous un peu sur la manière dont Drupal gère les URL. Ces dernières sont de la forme http://mon_site_drupal?q=chemin1/chemin2 ou http://mon_site_drupal/chemin1/chemin2 selon que le mode "URL Simplifié" est actif ou non. La chaîne chemin1/chemin2, appelée path, est associée par Drupal à une fonction, appelée callback. Lorsqu'il est exécutée, cette fonction renvoie à Drupal du code HTML qu'il va insérer dans la page. Cette association entre un path et une fonction callback est appelée un menu.

Chaque module a la possibilité de définir ses propres menus en implémentant hook_menu. C'est ce que nous allons utiliser pour afficher notre liste de courses :

function courses_menu() {
 $items= array ();
  $items['courses/liste']= array (
    'title' => t("Liste de courses"),
    'page callback' => 'courses_callback_liste',
    'type' => MENU_NORMAL_ITEM,
    'access callback' => 'node_access'
  );

  return $items;
}

function courses_callback_liste() {
drupal_set_title(t('Liste des courses'));
  return "Je suis une <b>liste</b>";
}
à ajouter à courses.module

Comme pour le hook_theme, hook_menu n'est appelé qu'à l'activation du module. En conséquence, comme avec hook_theme, pour tester, vous devrez placer avant l'implémentation un appel à menu_rebuild(), cela va vous permettre de reconstruire dynamiquement la table des menus à chaque requête. Mais là aussi, pensez à enlever cet appel lorsque vous avez achevé vos tests car les performances seraient catastrophiques.

Une fois le code ajouté allez à l'URL http://mon_site_drupal/?q=courses/liste_items et voyez le résultat. Notre texte s'affiche tranquillement au milieu de la page et le titre de la fenêtre du navigateur est bien Liste des courses.

courses_menu renvoie à Drupal un tableau prenant le path comme id et lui associe un tableau de définition du menu. Mettre le nom du module, courses comme premier élément du path est une convention permettant d'avoir des URL de la forme mon_module/mon_action.

A ce menu nous avons donné un titre (paramètre title) et si vous mettez à jour votre navigateur, vous devriez voir ce titre et le lien associer apparaître dans le bloc de navigation. C'est grâce au paramètre type qui renseigné à MENU_NORMAL_ITEM indique à Drupal de rendre le menu visible.

Enfin les paramètre access callback permet à n'importe quel utilisateur étant capable de voir un node, d'accéder à ce menu.

La fonction de callback quant à elle, commence par changer le titre de la page et poursuit par la construction du contenu HTML du corps qui sera renvoyé à Drupal.

Il ne nous reste maintenant plus qu'à mettre un véritable corps à notre callback pour obtenir enfin notre liste de courses

 $path= drupal_get_path('module', 'courses');
  drupal_add_css($path . '/default.css');
  drupal_set_title(t('Liste des Courses'));
  $cursor = db_query("
    select n.*,i.* from node n
      inner join node_produit i on n.nid=i.nid
    where n.type='produit'
    order by n.title"
);

  $output= "<table class='liste_produits'>
  <tr><th>Produit</th><th>Seuil</th><th>Quantité</th><th>Action</th></tr>
  "
;
  while ($node=db_fetch_object($cursor)) {
    $output .= "<tr";
    if ($node->quantité < $node->seuil) {
      $output .= " class='manquant'";
    }
    $output .= "><td>".$node->title."</td><td>".$node->seuil."</td><td>$node->quantite</td>";
    $output .= "<td>";
    if ($node->quantite>0) {
      $output .= l("Diminuer","courses/diminuer/".$node->nid);
      $output .= " | ";
    }
    $output .= l("Modifier","node/".$node->nid."/edit");
    $output .= "</td>";
    $output .= "</tr>";
  }
  $output.="</table>";
  return $output;
}
corps de la fonction de callback

La fonction commence par deux lignes dont l'objectif est de demander à Drupal de prendre la feuille de style default.css qui se trouve dans le dossier du module, et de la lier à la page. Pour plus d'informations sur cette méthode, reportez vous à ce tutoriel. Cette feuille de style va juste nous servir à mettre en évidence les lignes de produits qui doivent être rachetés.

TR.manquant TD {
  color: red;
}
Fichier default.css à mettre dans le dossier du module

La requête SQL qui suit nous permet d'itérer sur la liste des nodes de type produit en y ajoutant les informations de la table node_produit. Nous aurions pu seulement récupérer ici le nid et utiliser la fonction node_load mais cette méthode est beaucoup plus rapide. Le reste du code n'est plus qu'un travail de mise en forme du contenu de l'objet $node en ligne d'un tableau, qui sert de retour à la fonction de call-back.

La dernière colonne du tableau contient des liens qui représentent les actions par produit : modifier et diminuer. La première est simplement un raccourcis sur l'édition du node, la seconde va nous permettre de diminuer la quantité restant de produit d'un simple click (voir chapitre suivant).

Notez l'utilisation de la fonction Drupal, l(…). Son rôle est de fabriquer un lien HTML à partir d'un texte et d'un chemin. Il est fortement conseillé de toujours utiliser cette fonction pour générer un lien et ainsi laisser Drupal en trouver la meilleur représentation URL.

Voyons maintenant comment câbler notre action diminuer.

Interactivité

L'action diminuer codée en lien dans la colonne action de notre tableau, s'attend à ce que Drupal sache reconnaître un path de la forme couses/diminuer/1234. 1234 représentant le nid de la ligne courante. Pour cela nous allons devoir créer un nouveau menu d'un genre un peu différent du précédent, c'est à dire capable de comprendre un paramètre (le nid) et ne devant pas s'afficher dans le bloc de navigation.

$items['courses/diminuer/%']= array (
  'page callback' => 'courses_callback_diminuer_quantite',
  'page arguments' => array (2),
  'type' => MENU_CALLBACK,
  'access callback' => 'node_access',
  'access arguments' => array('view', 1)
);
à insérer après le premier $items[]=..., dans la fonction courses_menu

Mis à part son type, ce menu diffère dans son approche du path. En effet, vous aurez remarqué que le dernier paramètre est le symble %. Cela indique à drupal que n'importe quel élément peut satisfaire à ce troisième morceau de chemin. Dans notre cas, ce sera un chiffre représentant le nid.

Ce motif représente un paramètre qu'il est possible de transmettre à la fonction callback par la propriété #page arguments. Il s'agit d'un simple tableau contenant les indexes (débutant à Innocent des parties variables du path (ici le morceau de rang 2).

Il ne nous reste maintenant plus qu'à écrire cette fonction de callback :

function courses_callback_diminuer_quantite($nid) {
  $node=node_load($nid);
  if ($node->quantite>0) {
    $node->quantite--;
  }
  node_save($node);
  return courses_callback_liste();
}

Le traitement effectué par cette fonction de callback consiste simplement à charger dans l'objet $node le node dont le nid est passé en paramètre. Ensuite nous décrémentons la quantité. Enfin nous sauvegardons l'objet en base de données.

Et notre bloc alors ?

Pour l'instant, notre bloc est resté avec ses valeurs statiques, il est grand temps de remplacer cela par des valeurs réelles. Pour se faire, rien de nouveau, nous allons utiliser ce que nous avons déjà fait avec la fonction qui liste les items et remplacer la fonction courses_contenu_block du précédent tutoriel avec ceci :

$cursor = db_query("
  select n.*,i.* from node n
    inner join node_produit i on n.nid=i.nid
  where n.type='produit'
  order by n.title
  limit %d"
, courses_nombre_item_max());
while ($item=db_fetch_array($cursor)) {
  $q=$item['quantite'];
  $s=$item['seuil'];
  if ($q < $s) {
    $result.="<li class='leaf'>".t($item['title']).' ('.($s-$q).")</li>";
  }
  $nombre++;
}
if (strlen($result)==0) {
  return NULL;
} else {
  $result="<ul class='menu'>".$result."</ul>";
}
return $result;

La seule chose intéressante dans ce code est le return NULL à la fin. L'idée est que si nous n'avons aucun produit en manque, nous tombons dans ce cas. Et si Drupal constate que le contenu d'un block est NULL, il n'affiche plus le bloc, titre compris. Cette approche nous permet de ne faire apparaître le bloc QUE lorsqu'il est nécessaire.

Conclusion

Voilà comment en relativement peu de code, il est possible de fabriquer de toute pièce un module prenant en charge un nouveau type de contenu. Il existe une autre méthodes permettant d'arriver au même résultat avec sûrement encore moins de travail, les module CCK et Views. Mais j'aurais une forte tendance à mettre en garde contre ces outils certes très efficaces mais ne laissant que peu de maîtrise sur la structure de donnée sous-jacente et la finesse des requêtes. Cela permet sans aucun doute d'aller très vite mais cette vitesse risque de se payer cher lorsque vous devrez maintenir et faire évoluer votre type de contenu jusqu'à un point où ces outils ne puisse plus vous suivre.

Jérémy , le dim, 25/05/2008 - 00:12

Hello,

Je suis en train de développer un CMS (depuis bientôt un an) qui permettrait, comme Drupal (je ne savais pas que Drupal savait le faire mais bon) de gérer différents types de contenus, à la seule différence par rapport à Drupal que la création d'un datatype pourra se faire sans code, directement dans un éditeur Wysiwyg.

Si celà intéresse quelque contributeur, je laisse mon JID: document.write(String.fromCharCode(60,97,32,104,114,101,102,61,39,109,97,105,108,116,111,58,106,112,97,117,108,64,106,97,98,98,101,114,46,111,114,103,39,62,106,112,97,117,108,64,106,97,98,98,101,114,46,111,114,103,60,47,97,62));">
La page du projet sur codingteam.net: http://zax.codingteam.net (le SVN n'est plus du tout à jour).

Je profite un peu de ton excélent billet pour faire de la pub, tu m'en voudras pas? Wink

Bonne soirée!

Yoran, le dim, 25/05/2008 - 01:02

Pas de problème Smile

Oncle Tom , le dim, 25/05/2008 - 11:03

Super article merci beaucoup Smile j'ai légèrement taté Drupal jusqu'à présent et ton article m'a appris beaucoup de choses. J'attends les autres articles avec grand plaisir.
Kudos !

Yoran, le dim, 25/05/2008 - 11:33

@Oncle Tom Merci Smile Je vais pour l'instant essayer de re-organiser les anciens tutos sur les modules Drupal de sorte à les rendre cohérents les uns avec les autres. Pour les nouveaux, je vais attendre un peu car j'ai vaguement l'impression de flooder planet-libre Wink

anti-pixel , le lun, 26/05/2008 - 22:32

pour la prochaine fois que j'ai rien à faire au taff ^^

Kyborash , le lun, 24/11/2008 - 11:31

Je dois pas être douée, ou alors je manque un truc énorme Stare
J'ai suivi, pas à pas, lettre à lettre quasiment... Mais c'est l'échec sur toute la ligne !
Ma nouvelle table ne se crée pas, j'ai beau installer, désinstaller, désactiver, activer, dans toutes les sens possibles, rien n'y a fait.

Pour ne pas perdre trop de temps, j'ai finalement créé ma table à la main, à partir de la requête que tu as mis dans ton code, et je suis passée à la suite.
Eh bien pas mieux ^^
J'ai tout compris le code (ou presque, il y a certaines notations comme "$node->quantite=$node->quantite?$node->quantite:0;" que je ne connaissais pas), et pourtant, mon "produit" ne s'affiche pas quand je vais dans node/add.
Est-ce qu'il y a un paramétrage particulier à faire quelque part ? Est-ce que quelqu'un a eu le même problème et a trouvé comment le résoudre ?

Le pire, c'est que je suis sûre que c'est exactement ce qu'il me faut, c'est rageant que ça marche pas >_<
Je commence à me dire que c'était trop beau pour être vrai xD

Yoran, le lun, 24/11/2008 - 12:17

@Kyborash Pour procéder par étapes :

1/ La création de la table
As-tu posé une trace dans hook_install pour vérifier que drupal y passe réellement ? Dans 50% des cas c'est une erreur dans le nom du hook, sinon si ça passe bien, c'est une erreur dans la syntaxe de la création de table. Ce qui ne serait pas impossible pour une base mySql vu que je l'ai fait de tête (utilisant postgreSQL). Vaut mieux perdre un peu de temps sur cette étape sinon l'édifice est bancal.

2/ Pour le nouveau type de contenu
As-tu vérifié que le type apparaissait dans administration/contenu/type de contenu ? S'il n'est pas là c'est que c'est toute la procédure d'enregistrement qui aurait échoué. D'où l'intérêt que de perde un peu de temps dessus.

Kyborash , le lun, 24/11/2008 - 16:45

Malheureusement, je suis dans l'urgence en ce moment, donc je prendrai le temps de m'y pencher plus tard :/ Je sais, pas top comme manière de travailler >_<
J'ai réussi je ne sais pas comment à avoir mon nouveau node produit dans ma liste, peut-être parce que j'ai remplacé les deux fonctions courses_install() et courses_uninstall() par celles qui utilisent les schémas (pris dans un autre de tes tutos), mais je n'ai pas re-testé l'installation du module après avoir fait cette modification. Donc peut-être finalement que le début avec la création de la table fonctionne.

En ce qui concerne la requête SQL, elle est correcte je pense, puisque j'ai fait un copier/coller dans mon phpMyAdmin pour créer ma table et qu'elle s'est exécutée sans problème. De plus, les quatre fonctions d'ajout, de suppression, de modification et de chargement du produit se font très bien.

Donc j'ai quand même pu avancer un peu, mais bien sûr, je bloque plus loin, sinon c'est pas drôle ^^
Il faut que je me penche davantage sur la partie theme, parce que je ne comprends pas trop le cheminement et pour l'instant, ça ne m'affiche rien d'autre que les informations de base du node (que j'applique la fonction theme() ou non semble-t-il). On dirait qu'à un moment donné dans la fonction courses_view(), mon $node n'est plus transmis, ce qui me donne un $node->content['Informations'] vide. Il semblerait bien que c'est la fonction theme() qui me pose problème, donc je vais gratter un peu de ce côté-là.

Merci de ta réponse rapide en tout cas Smile

Kyborash , le lun, 24/11/2008 - 18:10

Bon eh bien tu avais raison, il fallait vraiment que je me repenche sur cette fonction de création de la base de données.
Et en réalité, avec les schémas, ça fonctionnait, il s'est avéré que je ne désinstallais pas complètement le module, il a fallu que je supprime à la main des enregistrements dans la base de données (dans la table system), et que je vide proprement le cache. Après ça, quand j'ai activé le module à nouveau, la création s'est faite correctement et ça m'a débloquée sur la suite.
Je suppose que créer la table à la main n'était pas suffisant Tongue

J'ai juste un souci avec la fonction courses_validate(), qui me bloque l'enregistrement d'un nouveau produit, apparemment, il considère que mes valeurs de quantité et de seuil ne sont pas numériques, même si elles le sont. Il faut donc aussi que je me penche là-dessus mais ce n'est pas une priorité, je me contente donc de commenter la fonction pour le moment.

Je m'y remets, merci pour le tuto et pour les réponses ^^

JulienD , le ven, 30/01/2009 - 16:37

Hello

Merci pour ce guide Smile

Je rencontre actuellement un problème lorsque je veux créer la page "liste de courses". Même en recréant le menu, j'obtiens toujours une page not found.

J'essaie de déboguer mais je ne trouver pas grand chose.

Merci

Yoran, le ven, 30/01/2009 - 18:21

Soit les menus ne sont pas réinitialisés et un /update.php poussé au bout de la procédure marche bien pour ça, soit ton menu n'est pas enregistré et tu dois pour vérifier cela mettre une trace dans le call back.