Drupal et les triggers personnalisés

  • warning: array_map(): Argument #2 should be an array in /var/www/titouille.ch/www/modules/system/system.module on line 1050.
  • warning: array_keys() expects parameter 1 to be array, null given in /var/www/titouille.ch/www/includes/theme.inc on line 1845.
  • warning: Invalid argument supplied for foreach() in /var/www/titouille.ch/www/includes/theme.inc on line 1845.
Portrait de titouille

Voilà un petit outil bien intéressant dans Drupal (6) :

Les triggers, ou si vous préférez "déclencheurs"...
Drupal est vraiment bien fait, si on passe outre certains points comme les performances lors d'une installation par défaut (oui... 300 requêtes pour afficher une page, c'est pas des plus performants, mais la aussi, il y a des systèmes de mise en cache dans le noyau principal, et des modules supplémentaires tel que Boost pour pouvoir optimiser un peu ce comportement...)

Bref... C'est le module "Trigger" (dans le noyau Drupal) qui gère les déclencheurs. Les déclencheurs sont liés aux "actions". Par défaut, il existe différentes actions et différents déclencheurs, comme par exemple :

- action : publier un noeud
- déclencheur : à l'enregistrement d'un nouveau contenu.

Si vous avez bien suivi, on peut donc rendre un noeud "publié" dès lors qu'il est enregistré.

allons un peu plus loin :

- action: dépublier un noeud si il contient certains mots-clés
- déclencheur : à l'enregistrement ou à la mise à jour d'un contenu

On peut ainsi rendre un noeud "invisible" si il contient des mots-clés précis (à caractère sexuel, homophobe, etc...) et ces noeuds devront être soumis à l'approbation de l'administrateur avant de pouvoir être rendu publics.

Vous voyez que ça peut vite devenir intéressant d'utiliser des actions associées à des déclencheurs.

Les déclencheurs sont limités, et les actions également... Généralement, les déclencheurs sont sur les manipulations de contenus (insert, update, delete pour les noeuds, les commentaires, la taxonomie ou encore les utilisateurs) et les actions permettent des choses tel que publier / dépublier un noeud, promouvoir un contenu en page d'accueil, supprimer un contenu, bloquer un utilisateur, etc...

Maintenant, on aimerai certainement pouvoir créer nos propres actions, et peut-être même nos propres déclencheurs. C'est ce que nous allons voir dans la suite de ce billet.

L'exemple est le suivant : J'ai un type de contenu particulier (content-type) qui se nomme "news". Lorsqu'un utilisateur le crée, il n'est pas publié. Il est soumis à l'approbation de l'administrateur. Lorsque l'administrateur va publier le noeud, nous aimerions exécuter une action telle qu'une insertion dans une table de la base de données.

La première chose est de créer un nouveau module, que j'appellerai "mytriggers". Je crée un répertoire "mytriggers" dans sites/all/modules, et j'y rajoute 3 fichiers : mytriggers.info, mytriggers.install et mytriggers.module.

Le fichier mytriggers.info va ressembler à ça :

 ; $Id: mytriggers.info,v 1.1 2010/03/09 11:22:11 titouille Exp $
name = My triggers
description = "my own triggers"
dependencies[] = trigger
core = 6.x

Le point important ici est la ligne "dependencies[]" qui va indiquer que mon module dépend du module "trigger" inclut dans le noyau Drupal.

le fichier mytriggers.install va contenir 2 fonctions vides, que nous pourrons toujours implémenter si nécessaire :

<?php
// $Id: mytrigger.install,v 1.1 2010/03/09 11:24:11 titouille Exp $
 
/**
 * Implementation of hook_install().
 */
function mytriggers_install() {
 
 
}
 
function mytriggers_uninstall() {
 
 
}

Ces 2 méthodes sont appelée par le noyau lors de l'activation du module et lors de sa désinstallation. On peut y ajouter ce que nous désirons, par exemple si nous avons besoin de rajouter des variables dans la table "variable" nous pourrions ajouter les lignes "variable_set( ... )" nécessaires dans la fonction hook_install et la suppression des mêmes variables dans hook_uninstall.
Vous vous demandez peut-être pourquoi je parle de "hook" ? C'est ainsi que sont nommées les fonctions pouvant être appelées par le noyau Drupal. Chaque module peut déclarer différentes fonctions de "hook" et ces fonctions seront appelées par le noyau pour exécuter différentes actions... Par exemple hook_menu permet de définir le menu à afficher pour un module particulier.

Commençons les choses sérieuses avec nos déclencheurs. Tout d'abord, nous allons déclarer une méthode hook_hook_info() qui va définir un déclencheur :

/**
 * Implementation of hook_info().
 */
function mytriggers_hook_info() {
  return array(
    'mytriggers' => array(
      'mytriggers' => array(
        'update_news' => array(
          'runs when' => t('After existing news is updated'),
        ),
      ),
    ),
  );
}

Soyez bien attentifs : la clé "update_news" correspond à l'action du déclencheur. Nous pourrions modifier son nom comme nous le désirons, par exemple "update" ou "after_news_update". Cette "action" est réutilisée par la suite dans les actions définies, il faudra donc faire attention à bien faire référence à la clé que nous avons inscrite ici.

la clé "run when" défini simplement le nom du déclencheur, ici "After existing news is updated" ou si vous préférez "Après qu'une nouvelle existante est mise à jour. La fonction t( ... ) est utilisée pour la localisation. Tout ce qui passe par cette fonction est soumis aux traductions dans les langages activés sur le site. Si vous faites une recherche sur cette description dans le panneau de recherches des traductions (module locale) vous devriez trouver la chaîne de caractères et vous pourrez inscrire la traduction en français par exemple.

Voilà pour ce qui est de la déclaration de notre déclencheur. Déclarons maintenant la fonction hook_nodeapi() qui va servir à intercepter la mise à jour des noeuds de type news :

/**
 * Implementation of hook_nodeapi().
 */
function mytriggers_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
  switch ($op) {
    case 'update':
      if ($node->type == 'news') {
        module_invoke_all('script', 'update_news', $node);
      }
      break;
  }
}

La méthode hook_nodeapi prend plusieurs paramètres, et contient un switch sur l'opération effectuée. L'opération qui nous intéresse est "update", et nous allons tester si le type du noeud est bien "news". C'est uniquement dans le cas d'un type de contenu "news" que nous désirons effectuer une opération spéciale. Si le type est correct, alors nous allons appeler la méthode "module_invoke_all" en lui passant le type de hook (hook-type) à déclencher, sur quelle opération et le noeud concerné. Comme vous pouvez le voir, ici l'opération n'est pas "update" mais bien "update_news", telle que dans la déclaration que nous avons faites dans la méthode hook_hook_info(). Il est nécessaire de faire bien attention à l'opération passée, qui doit correspondre à l'opération de notre déclencheur.

Notre fonction module_invoke_all va déclencher une méthode hook_hook-type dans tous les modules ou cette méthode est implémentée. Ici, tous les modules implémentant hook_script vont être appelé et leur méthode hook_script va être appelée en leur passant l'opération "update" et le noeud $node.

La suite des opérations, vous devez vous en douter, est de créer cette méthode hook_script. Voici à quoi elle va ressembler :

/**
 * Implementation of hook_script().
 */
function mytriggers_script($op, $node) {
  $aids = _trigger_get_hook_aids('mytriggers', $op);
 
  $context = array(
    'hook' => 'mytriggers',
    'op' => $op,
    'node' => $node,
  );
  actions_do(array_keys($aids), $node, $context);
}

Ici, la fonction _trigger_get_hook_aids( ... ) va permettre de récupérer toutes les actions associées à notre déclencheur. Notre méthode hook_script va chercher quels sont les actions liées aux déclencheurs de "mytriggers" et les exécuter.

Enfin, il est nécessaire de déclarer une ou plusieurs actions dans la méthode hook_action_info() :

/**
 * Implementation of hook_action_info().
 */
function mytriggers_action_info() {
  return array(
    'mytriggers_write_into_db_action' => array(
       'type' => 'site',
       'description' => t( 'Write a row in mysql db' ),
       'configurable' => FALSE,
       'hooks' => array(
  		 'mytriggers' => array('update_news'),
       )
    )
  );
}

La première valeur, "mytriggers_write_into_db_action" va correspondre à une méthode que nous allons implémenter, qui sera la méthode déclenchée par l'action.
Le type, "news", est le type auquel l'action va être associable. Le type par défaut serait "node", mais en mettant "news" je pourrais voir mon action sous "news" dans la liste des actions ( /admin/settings/actions).
La description sera inscrite dans la liste des actions.
La valeur "configurable" permet de dire si une action nécessite des informations supplémentaires. Exemple : l'action de dépublier un noeud si il contient certains mots-clés ne peut pas agir toute seule. Elle doit forcément connaître les mots-clés à supprimer pour pouvoir faire son test. Lorsqu'on crée une action configurable, on doit également implémenter une méthode qui va permettre de fournir les arguments nécessaires à l'action. Si vous désirez des informations supplémentaires, jetez un oeil ici. L'auteur explique ce cas particulier (en anglais).

Enfin, les "hooks" sont les actions qui vont être liées. Nous pourrions par exemple avoir :

'hooks' => array( 
  'nodeapi' => array( 'presave', 'insert', 'update', 'view' ),
  'comment' => array( 'insert', 'update' ),
)

Qui impliquerai que notre action pourrait être associée aux déclencheurs sur les contenus (nodeapi) pour les actions => sauvegarde, insertion, mise à jour et vue; ainsi que sur les commentaires (comment) pour les actions => insertion, mise à jour.
Remplir ces informations de "hooks" indique à quel(Drunk déclencheur(Drunk notre action peut être associée.
C'est justement ici que je vais indiquer que mon action va pouvoir être associée à l'action "update_news" du déclencheur "mytriggers" que j'avais inséré dans hook_hook_info() (première méthode du module). Si dans hook_hook_info j'avais remplacé "update_news" par "after_news_update" alors j'aurai du mettre :
"hooks" => array(
  'mytriggers' => array( 'after_news_update' )
);

Pour que mon action puisse être associée au déclencheur que j'ai créé.

Il ne reste plus qu'à implémenter la méthode mytriggers_write_into_db_action( &$node, $context ) pour lui faire exécuter ce qu'on désire :

function mytriggers_write_into_db_action( &$node, $context ) {
 
  // test pour voir si l'action est bien déclenchée
  drupal_set_message( "mytriggers_write_into_db_action called..." );
  db_query( "insert into ..." );
}

En activant mon module "mytriggers", je vais activer une nouvelle action que je pourrais voir dans /admin/settings/actions.

Un nouvel onglet va également apparaître dans /admin/build/trigger qui sera nommé du nom de mon module dans le fichier .info, dans mon cas "my triggers".

Sous "my triggers", je vais voir un nouveau déclencheur "After existing news is updated" auquel je pourrais associer une action : "Write a row in mysql db".
Vous pourrez voir que cette action n'est associée qu'à ce déclencheur et pas aux autres déclencheurs disponibles. Ceci grâce à la méthode hook_action_info qui défini que mon action n'est associable qu'au déclencheur "mytrigger" et à l'action "update" (After existing news is updated).

Pour créer de nouveaux déclencheurs, il suffira de rajouter de nouvelles actions de déclencheur dans hook_hook_info :

function mytriggers_hook_info() {
  return array(
    'mytriggers' => array(
      'mytriggers' => array(
  	'insert_news' => array(
  	  'runs when' => 'After new news is added',
  		),
        'update_news' => array(
          'runs when' => 'After existing news is updated',
        ),
      ),
    ),
  );
}

Il faudra bien entendu modifier les méthodes de hook telles que hook_nodeapi() et hook_action_info() pour que tout soit en règle.

Si vos actions ne sont pas disponibles sous vos déclencheurs, allez sur la page /admin/settings/actions et repassez sur la page /admin/build/trigger pour contrôler. Il est parfois nécessaire d'aller sur la page des actions pour qu'ils apparaissent ensuite sous les déclencheurs...

Voilà. J'ai tenté de rendre ce billet le plus intéressant possible, même si il y a bcp de blabla. Je pense qu'utiliser des noms d'actions autre que des actions basiques telles que "update" ou "insert" permet également de savoir ou ces valeurs sont réutilisées car ce n'est pas simple à comprendre le fonctionnement des actions et déclencheurs.

J'espère que ça sera compréhensible et que ces explications vous permettront d'aller encore plus loin dans votre utilisation de Drupal Wink

références :
How to create custom trigger for "publish node"
Creating Custom Triggers in Drupal
How to write custom actions and triggers in Drupal
Action And Trigger support in my modules
Drupal Actions and Triggers
Writing an action for Drupal 6