Le 09/01/18

Un développement toujours à l'écoute de vos besoins : exemple de la création d'un module Drupal

L’utilisation d’un système de gestion de contenu performant, est devenu une condition essentielle à la réussite et l’aboutissement d’un projet Web réussi. Dès lors, le choix du CMS devient un point sur lequel il vaut la peine que les preneurs de décisions se penchent et qu’ils prennent du temps. La grande force de Drupal ? Sa communauté : active et regroupant de plus en plus de membres, elle offre continuellement de nouveaux modules, permettant à chacun d’ajouter de nouvelles fonctionnalités à son application web.

Ces apports sans cesse renouvelés de la communauté ont toutefois leurs limites lorsqu'un besoin bien spécifique est exprimé, il est alors parfois nécessaire de créer de nouveaux modules. Afin de mieux comprendre comment s'articule un module Drupal, et l'architecture logicielle derrière, prenons ici l'exemple d'un module développé récemment au sein de Smart/Origin, permettant une identification en deux temps, soit via nom d'utilisateur, soit via adresse email. L'objectif est ici de demander un nom d'utilisateur ou une adresse mail, de regarder si la chaîne de caractères saisie correspond à un nom d'utilisateur ou à une adresse email associé(e) à un compte existant pour ensuite, dans un second temps, demander un mot de passe avec lequel lancer la procédure de connexion.

Une fonctionnalité d'identification est bien évidemment déjà présente dans ce que peut proposer Drupal : un formulaire demande un nom d'utilisateur et un mot de passe, interroge la base de données avec les informations récupérées et procède, ou non, à l'identification. Vous le savez, un des principes fondamentaux du développement informatique est de ne pas chercher à réinventer la roue, c'est à dire qu'ici il sera bien sûr préférable de se baser sur l'existant, que de partir sur la programmation d'un module de connexion à part entière !

La première étape sera alors d'intervenir dans la génération du formulaire de connexion proposé par Drupal. Le système de hooks nous permet justement de venir modifier le comportement d'un module, à différentes étapes du process, que ce soit lors de la génération des blocks, des menus ou du rendu de la vue par exemple.

Représentation du système de hooks utilisé par Drupal (source : https://www.slideshare.net/tanoshimi/creating-custom-drupal-modules)

Commençons donc par la modification du formulaire :

function two_step_module_form_user_login_alter(&$form, &$form_state){
    $form["#attached"]["js"][] = drupal_get_path('module', 'two_step_module') . '/js/two_step_module.js';
    $form["#attached"]["css"][] = drupal_get_path('module', 'two_step_module') . '/css/two_step_module.css';
    $form["#attributes"]["class"][] = "first-step";
    $form["actions"]["submit"]["#suffix"] = "<div id='bouton_next_step' class='bouton-form-connexion' onclick='next_step()'>Étape suivante</div>
    <div id='bouton_login' class='bouton-form-connexion' onclick='login()'>Se connecter</div>
    <div id='retour-page-login' onclick='retour()'>Retour</div>";
}

Pour mieux comprendre le fonctionnement de ce hook, rappelons que Drupal est très précis sur l'appellation des fonctions car ce nom spécifie directement leur utilisation. Ici, notre module "two_step_module" vient altérer ("alter") le formulaire user_login("form_user_login"). Notons qu'ici, la visibilité des boutons et des champs du formulaire sera gérée par les classes CSS "first-step" et "second-step" associées au formulaire.

 

Exemple de formulaire de connexion avec la classe "first-step" associée, masquant notamment le champ de mot de passe.

Suivons le fil, avec la fonction JS "next_step()" appelée par "Etape suivante" :

function next_step(){
    //Récupération de la valeur du formulaire
    var user_mail = jQuery("#text-input-user").val();
    //appel ajax
    jQuery.ajax({
        url : Drupal.settings.basePath + 'check_user_mail',
        type : 'POST',
        data : 'user_mail=' + user_mail,
        dataType : 'json',
        success : function(result, statut){
            if(result["status"] === "failure"){
                jQuery("#messages .section").html('<div class="error messages">' + result["message"] + '</div>');
            }
            else{
                jQuery("#text-input-user").val(result["username"]);
                jQuery("#user-login").attr("class","second-step");
            }
        },
        error : function(res, statut, erreur){
            alert(erreur);
        }
  });
}

Nous récupérons alors dans une variable la valeur du champ "user" du formulaire. Pour le moment nous ne savons pas si cette valeur est un nom d'utilisateur ou une adresse mail. C'est pour cela que celle ci est envoyée à l'API "/checkuser_mail" qui fait le travail de vérification et qui nous retourne un résultat au format JSON.

Ce résultat est soit un statut "failure" pour spécifier que la recherche a échouée, soit un nom d'utilisateur valide. Dans ce cas le formulaire passe en mode "second-step", qui masque le champ « user », et affiche le champ du mot de passe ainsi que le bouton submit.

function two_step_module_menu(){
    $items = array();
    $items["check_user_mail"] = array(
        'page callback' => '_two_step_module_check_user_mail',
        'access callback' => true,
        'type' => MENU_CALLBACK,
    );
    return $items;
}

N'oubliez pas d'indiquer à Drupal comment il doit réagir lorsqu'un appel est effectué sur l'url appelée en ajax, "check_user_mail", qui ici nous renvoie vers la fonction « _two_step_module_check_user_mail" (ci-dessus).

function _two_step_module_check_user_mail(){
    $user_mail = $_POST["user_mail"];
    $status = "success";
    $username = "";
    $message = "";
    //on regarde la base de données Drupal
    $result = _two_step_module_check_user_mail_drupal($user_mail);
    if (!$result){
        $status = "failure";
        $message = t("Nom d'utilisateur ou mail non trouvé, merci de vérifier votre saisie");
    }
    else{
        $username = $result;
    }
    header('Content-type:application/json;charset=utf-8');
    print json_encode(array("status" => $status, "username" => $username, "message" => $message));
    return null;
}

La fonction ci-dessus fait ici office de contrôleur, qui récupère la donnée passée en POST lors de notre appel ajax, appelle la fonction qui s'occupera de la vérification ("_two_step_module_check_user_mail_drupal()") et transmet ensuite le résultat.

function _two_step_module_check_user_mail_drupal($user_mail){
    //construction de la requête
    $user_name_requete = db_select("users", "u");
    $or_request = db_or()
        ->condition("u.name", $user_mail)
        ->condition("u.mail", $user_mail);
    $user_name_requete->condition($or_request);
    $user_name_requete->fields("u", array("name"));
    //execution de la requête
    $user_name_requete_result = $user_name_requete->execute();
    //traitement du résultat
    foreach($user_name_requete_result as $single_item){
        $user_name = $single_item->name;
        return $user_name;
    }
    return null;
}

Ce qui nous amène à l'interrogation de la base de données en tant que telle, où l'on cherche ici à savoir si un "user" ayant comme paramètre "name" ou "mail" égal à notre variable "$user_mail" existe, et que pour le résultat, seul le champ "name" nous intéresse. Si nous avons un résultat, c'est gagné ! Nous pouvons maintenant retourner ce résultat en tant que "$user_name", car nous sommes alors sûrs que ce que nous avons, c'est le nom d’utilisateur.

Retour donc sur la fonction javascript qui, comme nous l'avons vu, mettra ce nom d'utilisateur en tant que valeur du champ "user", avant de masquer celui-ci et de demander un mot de passe.

Formulaire de connexion avec la classe "second-step" associée : nom d'utilisateur validé, en attente de mot de passe.

Notre travail de modification est alors terminé car, lors de la validation du formulaire par l'utilisateur, nous retrouvons l'état attendu par Drupal :

• Un nom d'utilisateur dans le champ "user", que celui-ci ait originellement été renseigné avec un nom d'utilisateur ou une adresse mail.

• Le mot de passe associé à ce nom d'utilisateur dans le champ « password ».

Comme vous pouvez le constater, il est possible grâce à ce système que chacun puisse se baser sur une fonctionnalité existante afin de l'adapter à ses desiderata particuliers. 

Chez Smart/Origin, nous pensons que l’équilibre d’un tel modèle repose alors sur la participation bi-latérale de chacun. Nous avons donc rendu ce module disponible sur le site de partage communautaire de Drupal, à l’adresse ci-dessous :

https://www.drupal.org/project/ldap_two_steps_login

 

Le module développé est compatible avec une identification LDAP (https://www.drupal.org/project/ldap), chaque LDAP configuré sera ainsi vérifié si aucun résultat ne ressort de la requête sur la base de données Drupal.

 

Article rédigé par Robin Gueneley.