Créer un bloc à l'aide d'un module

Drupal n'est pas seulement un Système de gestion de contenu (CMS) orienté vers la création d'articles, de pages ou d'autres contenus. Il permet aussi d'intégrer des blocs. Ceux-ci peuvent contenir des choses variées, telles qu'un menu, une vignette d'information, une présentation d'activité, une publicité, une liste de raccourcis hypertextes, une bannière, etc. Les blocs sont aussi important que les contenus qu'ils encadrent.

De plus Drupal permet une gestion précise des blocs. D'abord, il est possible de décider dans quelle région du site ou pour quel rôle sera affichée tel ou tel bloc. Il est également permis de déterminer si un bloc sera affiché dans toutes les pages du site ou sur certaines d'entre elles. La page de gestion des blocs se trouve dans la partie administration du site, à l'adresse relative admin/structure/block.

Dans cette présentation, nous allons créer un bloc personnalisé à l'aide d'un module.

Toutefois, pour limiter les redondances, je ne reviendrai pas sur les informations déjà données dans la présentation consacrée à Écrire un premier module pour Drupal 7. J'invite donc le lecteur débutant à consulter cet article avant de poursuivre la lecture de celui-ci.

Le nom du module et son emplacement

Comme nos premiers pas ont déjà été accomplit, le bloc que nous allons créer n'affichera pas bonjour le monde. Nous allons plutôt afficher les modules actifs de l'application Drupal. Et, pour nous amuser un peu, nous irons chercher nos informations dans la base de données du site.

Mais avant tout, il nous faut déterminer le nom de notre module et surtout créer nos fichiers sources.

Je propose que nous soyons d'une originalité folle et donc je suggère que notre module s'intitule First bloc. Quant au nom machine (interne) du module, aiyons l'incroyable audace de l'appeller first_bloc.

Maintenant que les noms du module sont choisis, il ne reste plus qu'à créer le répertoire ./sites/all/modules/custom/first_bloc.

Dans ce dossier, nous allons créer deux fichiers vides : first_bloc.info et first_bloc.module.

Le fichier first_bloc.info

La création d'un module pour Drupal implique toujours que nous commencions par le fichier .info. Je rappelle que c'est grâce à ce fichier que le module pourra être activé et que le fichier .module pourra être inclus dans Drupal.

Voici le code du fichier first_bloc.info :

name = First block
description = This module allows to create a block for my Drupal application.
package = Drupal 7 Development
version = 7.x-0.x-dev
core = 7.x
files[] = first_block.module

Déclarer l'existence d'un nouveau bloc à Drupal

C'est dans le fichier first_bloc.module que nous allons implémenter les deux fonctions dont nous aurons besoin pour afficher un bloc reprenant la liste des modules actifs du site.

La première fonction implémente un hook informant Drupal qu'un nouveau bloc est disponible. Comme notre hook s'appelle hook_block_info, notre fonction s'intitulera first_block_block_info().

D'une certaine manière, cette fonction a un rôle similaire au fichier .info puisqu'il fournit à Drupal les informations de base permettant d'activer, de positionner, de configurer ou de désactiver un bloc. Je rappelle que les blocs peuvent être gérés depuis la page d'administration des blocs (admin/structure/block).

Voici le code de cette fonction :

function first_block_block_info() {
  return array(
    'first_block' => array(
      'info' => t('First block - List of modules'),
      'cache' => DRUPAL_NO_CACHE,
    ),
  );
}

Comme nous pouvons le voir, cette fonction renvoie un tableau dont la clé principale est le nom machine (first_block) du bloc que nous souhaitons créer. Cette clé principale est associée à un tableau renvoyant une série d'informations. Parmi celles-ci, soulignons l'importance de la clé info dont le rôle est de fournir un nom humain qui permettra de gérer la position du bloc dans la page d'administration des blocs. Ceci étant dit, pour connaître la liste exhaustive et le rôle des informations renvoyées par cette fonction, je vous propose de consulter la documentation complète concernant ce hook à la page suivante : hook_block_info.

Notons que nous pourrions créer plusieurs blocs. Pour cela, il suffit de définir plusieurs clés principales. Par exemple, nous pourrions créer trois blocs en utilisant les clés first_block_1, first_block_2 et first_block_3.

Dans ce cas, le tableau renvoyé par le hook_block_info pourrait ressembler à ceci :

return array(
  'first_block_1' => array(
    'info' => t('First block - Block one'),
    'cache' => DRUPAL_NO_CACHE,
  ),
  'first_block_2' => array(
    'info' => t('First block - Block two'),
    'cache' => DRUPAL_NO_CACHE,
  ),
  'first_block_3' => array(
    'info' => t('First block - Block three'),
    'cache' => DRUPAL_NO_CACHE,
  ),
);

Notons encore qu'il est préférable que le nom de la clé principale ou des clés principales commencent par le nom machine du module. En effet, les clés renvoyées par le hook_block_info sont les identifiants des blocs. Ils sont stockés dans une table de la base de données du site et ils constituent autant de clés uniques. Il importe donc d'éviter tout conflit avec une clé déjà présente dans la table idoine.

Retourner le contenu du bloc vers Drupal

Maintenant que nous avons fournit à Drupal les informations permettant d'activer, de positionner et de configurer le bloc, nous allons construire le contenu de notre bloc.

Pour ce faire, nous allons utiliser un autre hook dont le nom est hook_block_view. Dans notre module, ce hook sera implémenté sous le nom de fonction first_block_block_view.

Voici le code complet de notre fonction :

function first_block_block_view($delta = '') {
  if ($delta == 'first_block') {
    $output = NULL;
    if (($db_query_result = db_query("SELECT * FROM {system} WHERE type = 'module' AND status = 1 ORDER BY name ASC LIMIT 10")) !== FALSE) {
      while ($db_result = $db_query_result->fetchAssoc()) {
        $db_result['info'] = unserialize($db_result['info']);
        $output .= '<li>' . $db_result['info']['name'] . ' (' . $db_result['name'] . ')</li>';
      }
    }
    return array(
      'subject' => t('List of modules'),
      'content' => (!empty($output)) ? '<ul>' . $output . '</ul>' : '<p>' . t('For now, this list is empty!') . '</p>',
    );
  }
  return array();
}

Commençons par remarquer l'argument transmis par Drupal à notre fonction. Il s'agit d'une variable appelée delta et qui contient l'une des clés principales initialisée par le hook_block_info.

Pour comprendre l'importance de cette clé, il est nécessaire de comprendre le fonctionnement du hook_block_view. Il faut savoir que pour chaque clé principale d'un bloc (contenue dans l'argument delta), correspond autant d'appels à tous les hook_block_view implémentés dans les modules actifs. Notre fonction first_block_block_view est donc appellée autant de fois qu'il y a de clés principales de bloc (bien entendu, cette affirmation doit être modulée en fonction du statut, de la position ou de la configuration de chaque bloc). Ceci explique pourquoi nous commençons notre fonction en évaluant le contenu de la variable delta. Ainsi, si elle correspond à la clé principale que nous avons renseignée dans first_block_block_info, nous pouvons retourner le contenu du bloc dans un tableau associatif, sinon nous devons retourner un tableau vide.

Ceci nous amène à dire quelques mots sur la variable retournée par notre fonction. En effet, comme le code nous le laisse entendre, le contenu du bloc créé par la fonction first_block_block_view est stockée dans un tableau associatif. Ce tableau est constituée de deux clés : subject et content.

La clé subject contient le titre du bloc. Ce contenu est de type string et doit respecter les normes de codage html. Une chaîne de caractère vide peut être affecté à cette variable.

Comme son nom l'indique, la clé content inclut le contenu du bloc. Ce contenu est de type string et doit respecter les normes de codage html. Une chaîne de caractère vide peut être affecté à cette variable.

Lorsque la fonction first_block_block_view est appelée par Drupal et que vous ne souhaitez pas crée de contenu, il suffit de retourner un tableau vide.

Construire le contenu du bloc

Dans l'exemple que nous sommes en train de construire, nous créons un bloc contenant la liste des modules actifs du site.

Pour construire cette liste, nous allons chercher nos informations dans la base de données du site. Plus concrètement, nous allons interroger la table system et lui demander de nous renvoyer les lignes correspondant aux modules actifs.

Dans notre requête, nous allons poser deux conditions. D'abord, puisque la table system contient aussi des informations sur les themes, nous devons sélectionner les lignes relatives à des modules et donc la valeur du champ type doit être un module. D'autre part, pour pouvoir sélectionner les modules actifs, il faut que la valeur du champ status soit égale à 1.

Par ailleurs, nous allons classer les modules par ordre alphabétique et nous ne sélectionnerons que les 10 premiers modules retourné par la requête.

Au final, voici la forme de notre requête SQL :

SELECT * FROM {system} WHERE type = 'module' AND status = 1 ORDER BY name ASC LIMIT 10

Il est à noter que dans cette requête, le nom de la table est entourée d'accolades. Cette convention d'écriture est requise par Drupal. Cette astuce permet de créer un certain niveau d'abstraction entre le nom par défaut de la table et son nom réel.

Maintenant que notre requête est définie, nous pouvons la transmettre à Drupal en utilisant la fonction db_query de la manière suivante :

db_query("SELECT * FROM {system} WHERE type = 'module' AND status = 1 ORDER BY name ASC LIMIT 10")

Cette fonction retourne une variable objet (implémentant l'interface DatabaseStatementInterface). Grâce à cette variable de type objet, nous pourrons accéder à la fonction public fetchAssoc. Cette méthode nous permettra de parcourir ligne par ligne les résultats de la requête effectuée par db_query . En effet, à chaque appel de la méthode fetchArray, celle-ci renvoit une ligne de champs sous la forme d'un tableau associatif. Lorsque toutes les lignes ont été parcourues, la fonction fetchArray retourne la valeur FALSE.

Voici le code permettant de lire les résultats de la requête SELECT et de construire le contenu de notre bloc à l'aide de la variable $output :

$output = NULL;
if (($db_query_result = db_query("SELECT * FROM {system} WHERE type = 'module' AND status = 1 ORDER BY name ASC LIMIT 10")) !== FALSE) {
  while ($db_result = $db_query_result->fetchAssoc()) {
    $db_result['info'] = unserialize($db_result['info']);
    $output .= '<li>' . $db_result['info']['name'] . ' (' . $db_result['name'] . ')</li>';
  }
}

Signalons que chaque ligne de la table system contient un champ info contenant une table qui a été préalablement sérialisée. Donc, avant de pouvoir lire les clés et les valeurs de cette table associative, il faut désérialiser la variable $db_result['info'] à l'aide de la function PHP unserialize. Nous pourrons ensuite accéder à la variable $db_result['info']['name'] dans laquelle se trouve le nom humain du module. Quant au nom machine,  il est "simplement" stocké dans la variable $db_result['name'].

Une fois que nous disposons de ces deux variables, nous pouvons construire une ligne de texte respectant les normes de codage html. Chaque ligne étant ajoutée dans la variable $output.

Le code complet du fichier first_block.module

<?php
/**
 *  @file
 *  A module exemplifying Drupal coding practices and APIs.
 *
 *  This module allows to create a block for my Drupal application.
 *  It illustrates coding standards, practices, and API use for Drupal 7.
 */

/**
 * Implements hook_block_info()
 */
function first_block_block_info() {
  return array(
    'first_block' => array(
      'info' => t('First block - List of modules'),
      'cache' => DRUPAL_NO_CACHE,
    ),
  );
}

/**
 * Implements hook_block_view()
 */
function first_block_block_view($delta = '') {
  if ($delta == 'first_block') {
    $output = NULL;
    if (($db_query_result = db_query("SELECT * FROM {system} WHERE type = 'module' AND status = 1 ORDER BY name ASC LIMIT 10")) !== FALSE) {
      while ($db_result = $db_query_result->fetchAssoc()) {
        $db_result['info'] = unserialize($db_result['info']);
        $output .= '<li>' . $db_result['info']['name'] . ' (' . $db_result['name'] . ')</li>';
      }
    }
    return array(
      'subject' => t('List of modules'),
      'content' => (!empty($output)) ? '<ul>' . $output . '</ul>' : '<p>' . t('For now, this list is empty!') . '</p>',
    );
  }
  return array();
}

Problèmes pouvant surgir

Si vous constater que Drupal ne tient pas compte du module first_block, c'est peut être parce que ce module n'a pas été activé dans la page d'administration des modules (admin/modules).

Si vous implémentez un nouveau hook et qu'il n'est pas pris en compte par Drupal, c'est parce que vous devez préalablement vider les caches avant de tester votre code (admin/config/development/performance). Cette opération n'est généralement pas nécessaire lorsque vous modifiez le code d'un hook déjà implémenté et déjà pris en compte par Drupal.

Si le bloc créé n'apparait pas sur la page de votre site de développement, il faut vérifier s'il a été correctement positionné et configuré (admin/structure/block).