Planète

Par AlanT
Alan Tondelier

Les formulaires multi-étapes en ajax avec le module mforms

Les fomulaires multi-étapes permettent de récupérer de nombreuses informations sur l'utilisateur sans l'assomer d'un coup avec une grande quantité d'informations. Pour les besoins d'un projet, je me suis penché sur la réalisation d'un formulaire multi-étapes (ou multi-steps en anglais) sous Drupal.

Je recherchais une solution permettant d'afficher une barre de progression, un nombre variables d'étapes, un comportement en ajax, et la possibilité d'accéder facilement aux données utilisateur saisies. Des solutions envisagées j'ai retenu le module mforms. Le résultat de son implémentation est visible ici.

Retour sur expérience.

Un module complet

Installation & exemple

On commence par récupérer le module.

drush dl mforms -y

Une fois téléchargé, le module mforms est livré avec un module d'exemple complet que je vous recommande d'activer.

drush en mforms,mforms_example -y

Au moment de l'écriture de cet article, il existe un bug dans le module d'exemple concernant la gestion de plusieurs formulaires mforms sur la même page. Si vous rencontrez le soucis, utilisez le module developpement.

Un module, deux approches

Si vous avez activé le module d'exemple, vous pouvez vous rendre sur la page <votredrupal>/mforms pour une page d'exemple de mise en place de votre module.

Le module mforms propose deux façons pour gérer le contenu de vos formulaires multi-étapes :

  1. Un stockage dans la variable $form_state ( variable Drupal de stockage des champs de formulaires )
  2. Un stockage des données dans une varriable de SESSION.

Ces deux approches diffèrent principalement au niveau de la persistance des variables.
En effet les variables stockées avec $form_state seront rafraichies au chargement de la page, l'utilisation de $form_state est à privilégier pour les formulaires multi-étapes "simple" ou la deuxième étapes du fomulaire est une étape de validation de données (par exemple).

À contrario, l'utilisation d'une variable de SESSION va permettre de réutiliser les données saisies par l'utilisateur sur l'ensemble du site Internet mais également de revenir sur les saisies effectuées. C'est cette approche que j'ai retenu pour mon projet.

Construction d'un formulaire multi-étapes

Notre formulaire multi-étapes va faire l'objet d'un module personnalisé. Pour cet exemple je vais appeler le module multi_etapes. Comme nous allons traiter de la réalisation d'un module Drupal, le code sera commenté en anglais pour être en accord avec les bonnes pratiques Drupal. Si certains bouts de codes ne sont pas clairs, n'hésitez pas à poser vos questions en commentaire.

Les bases du module

Je ne reviens pas sur les bases d'un module Drupal, tout est bien expliqué sur la documentation officielle.

multi_etapes.info 

name = Multi Etapes
description = Add a multi-steps session based form. Built with the mforms module.
core = 7.x

; Package name 
package = Alan

; Module dependencies
dependencies[] = mforms

Ensuite le fichier .module :

multi_etapes.module

<?php

/**
 * @file
 * Add a multi-steps session based form. Built with the mforms module.
 */

/**
 * Implements hook_permission().
 */
function multi_etapes_permission() {
  return array(
    // 'administer my module' =>  array(
    // 'title' => t('Administer my module'),
    // 'description' => t('Perform administration tasks for my module.'),
    // ),
    'access multi-steps forms' => array(
      'title' => t('Access multi-steps forms'),
      'description' => t('Enable the user to fill-in custom multi-steps forms'),
    ),
  );
}

/**
 * Implements hook_menu().
 */
function multi_etapes_menu() {

  $items['myform'] = array(
    'title' => t('My multi-steps form'),
    'page callback' => 'multi_etapes_myform_page',
    'access arguments' => array('access multi-steps forms'),
    'type' => MENU_NORMAL_ITEM,
    'file' => 'inc/multi_etapes.pages.inc',
  );

  return $items;
}

/**
 * Implements hook_STORE_KEY_mforms_init().
 */
function multi_etapes_myform_mforms_init() {
  $store = MformsSessionStore::getInstance('myform');
  $steps = MformsSteps::getInstance($store);

  // If using mustistep controls you need to explicitly define form steps and
  // its attributes.
  $controls = MformsMultiStepControls::getInstance($store, $steps, array(
    '_myform_step1' => array('value' => t('Bio'), 'weight' => -103),
    '_myform_step2' => array('value' => t('Hobbies'), 'weight' => -102),
    '_myform_step3' => array('value' => t('Summary'), 'weight' => -101)
  ));
  // Ajaxify the form stepping process.
  $controls->ajaxify();

  // Init the mforms.
  mforms_init_module('multi_etapes', 'myform', $store, $controls);
}

Qu'avons-nous fait ?

  1. On créé un droit pour accéder au formulaire multi-étape,
  2. On défini une entrée de menu "classique" pour accéder à ce formulaire (protégée par le droit créé précédemment),
  3. On indique que la fonction de callback "multi_etapes_myform_page" se situe dans le fichier /inc/ du module,
  4. On initialise notre formulaire multi-étapes où myform est la clef via le hook hook_STORE_KEY_mforms_init(),
  5. On défini le nombre d'étapes à l'intérieur du hook et on leur donne un nom (attention au poids),
  6. On fini d'initialiser le formulaire.

inc/multi_etapes.pages.inc

<?php

/**
 * Entry page for the multi-step form.
 *
 * @return array
 *   Drupal renderable array.
 */
function multi_etapes_myform_page() {

  // Add user name to steps
  global $user;
  $uname = isset($user->name)?$user->name:'Guest';
  
  // Create parameters to be passed to the multi-step form
  $params = array('uname' => $uname);

  // Return Drupal renderable array.
  return array(
    'mform' => drupal_get_form('multi_etapes_myform', $params),
  );
}

/**
 * Callback function to generate form.
 *
 * @param array $form
 *   Drupal form array.
 * @param array $form_state
 *   Drupal form_state array.
 * @param array $params
 *   Optional params passed into form.
 *
 * @return array
 *   Drupal form array.
 */
function multi_etapes_myform($form, &$form_state, $params) {
  // pass defined parameters to mforms_build
  return mforms_build('myform', '_myform_step1', $form_state, $params);
}

/**
 * Callback function to validate form inputs
 *
 * @param array $form
 *   Drupal form array.
 * @param array $form_state
 *   Drupal form_state array.
 */
function multi_etapes_myform_validate($form, &$form_state) {
  mforms_validate('myform', $form, $form_state);
}

/**
 * Callback function to process the form inputs.
 *
 * @param array $form
 *   Drupal form array.
 * @param array $form_state
 *   Drupal form_state array.
 */
function multi_etapes_myform_submit($form, &$form_state) {
  mforms_submit('myform', $form, $form_state);
}

Encore beaucoup d'initialisations dans ce fichier :

  1. On défini la fonction appelée par notre menu,
  2. On récupère le nom de l'utilisateur courant pour montrer le passage de variable à notre formulaire,
  3. On appelle le formulaire via drupal_get_form,
  4. Notre formulaire multi-étapes est défini de façon traditionnelle avec les hook_form , hook_validate et hook_submit.

Il nous reste à mettre en place le code structurant chacune des étapes de notre formulaire.
Le module mform va chercher les étapes ( définies par _myform_stepX() ) dans un fichier qui doit être nommé de la façon suivante : nom_du_module.cleformulaire.inc et placé dans un dossier mforms à la racine de votre module.

Le coeur du fomulaire multi-étapes

Nous allons faire un formulaire d'exemple en 3 étapes :

  1. Informations sur l'utilisateur avec son login récupéré automatiquement, 
  2. Ses activités (Films, livres et sports),
  3. Un résumé des valeurs saisies, un champ pour envoyer un message à l'équipe du site et une case a cocher pour valider et terminer le formulaire.

Ce formulaire reprend les éléments du formulaire d'exemple fourni par le module mforms, en ajoutant des variations qui me semblent intéressantes.

mforms/multi_etapes.myform.inc - Première étape

/**
 * First step called by the Mforms state machine.
 *
 * @param array $form_state
 *   Drupal form_state array.
 * @param string $next_step
 *   Mforms next step callback name.
 * @param mixed $params
 *   Optional params passed into form.
 *
 * @return array
 *   Drupal form array.
 */
function _myform_step1(&$form_state, &$next_step, $params) {
  // Define following step callback. If none set, that implies it is
  // the last step.
  $next_step = '_myform_step2';

  // Retrieve submitted values. This comes in handy when back action
  // occured and we need to display values that were originaly submitted.
  $data = mforms_get_vals('myform');

  // If we have the data it means we arrived here from back action, so show
  // them in form as default vals.
  if (!empty($data)) {
    $vals = $data;
  }
  elseif (isset($form_state['values'])) {
    $vals = $form_state['values'];
  }

  // Define form array and return it.
  $form = array();

  $form['login'] = array(
    '#type' => 'textfield',
    '#disabled'=>true,
    '#title' => t('Login'),
    '#default_value' => isset($vals['loginv']) ? $vals['loginv'] : $params['uname'], // get login name from previous page (not submited but for design purpose).
  );

  $form['name'] = array(
    '#type' => 'textfield',
    '#title' => t('Name'),
    '#default_value' => isset($vals['name']) ? $vals['name'] : NULL,
  );
  $form['email'] = array(
    '#type' => 'textfield',
    '#title' => t('Email'),
    '#default_value' => isset($vals['email']) ? $vals['email'] : NULL,
    '#required' => TRUE,
  );
  $form['www'] = array(
    '#type' => 'textfield',
    '#title' => t('Your web site'),
    '#default_value' => isset($vals['www']) ? $vals['www'] : NULL,
  );

  // store the login in a hidden field so that the value is submited

  $form['loginv'] = array(
    '#type' => 'hidden',
    '#default_value' => isset($vals['loginv']) ? $vals['loginv'] : $params['uname'],
  );

  return $form;
}

/**
 * Validate callback - validates email address.
 *
 * @param array $form
 *   Drupal form array.
 * @param array $form_state
 *   Drupal form_state array.
 */
function _myform_step1_validate($form, &$form_state) {
  if (!valid_email_address($form_state['values']['email'])) {
    form_set_error('email', t('Invalid email.'));
  }
}
  1. On défini la fonction de première étape,
  2. On indique l'étape suivante,
  3. On défini les champs à afficher tout en peuplant les valeurs présaisies si elles existent,
    Pour cet exemple, le champ "Login" est récupéré automatiquement. Si l'utilisateur est anonyme, "Guest" est affiché,
  4. On soumet la première étape du fomulaire à la fonction de validation.

mforms/multi_etapes.myform.inc - Deuxième étape

/**
 * Step two.
 *
 * @param array $form_state
 *   Drupal form_state array.
 * @param string $next_step
 *   Mforms next step callback name.
 * @param mixed $params
 *   Optional params passed into form.
 *
 * @return array
 *   Drupal form array.
 */
function _myform_step2(&$form_state, &$next_step, $params) {
  $next_step = '_myform_step3';
  $form = array();

  $data = mforms_get_vals('myform');

  if (!empty($data)) {
    $vals = $data;
  }
  elseif (isset($form_state['values'])) {
    $vals = $form_state['values'];
  }

  $form['movies'] = array(
    '#type' => 'textarea',
    '#title' => t('Movies'),
    '#default_value' => isset($vals['movies']) ? $vals['movies'] : NULL,
  );
  $form['books'] = array(
    '#type' => 'textarea',
    '#title' => t('Books'),
    '#default_value' => isset($vals['books']) ? $vals['books'] : NULL,
  );
  $form['sports'] = array(
    '#type' => 'textarea',
    '#title' => t('Sports'),
    '#default_value' => isset($vals['sports']) ? $vals['sports'] : NULL,
  );

  return $form;
}

Rien de plus par rapport à l'étape 1, on passe à la dernière étape de ce formulaire.

mforms/multi_etapes.myform.inc - Troisième étape

/**
 * Third step.
 *
 * @param array $form_state
 *   Drupal form_state array.
 * @param string $next_step
 *   Mforms next step callback name.
 * @param mixed $params
 *   Optional params passed into form.
 *
 * @return array
 *   Drupal form array.
 */
function _myform_step3(&$form_state, &$next_step, $params) {
  $form = array();

  // Get the collected values submited at each step.
  // Here is one difference - the second parameter that defines the step
  // from which we want to retrieve the data.
  $vals1 = mforms_get_vals('myform', '_myform_step1');
  $vals2 = mforms_get_vals('myform', '_myform_step2');

  $form['summary'] = array(
    '#type' => 'fieldset',
    '#title' => t('Summary'),
  );

  $form['summary']['sum_bio'] = array(
    '#markup'=>
    "<h3>".t('Bio')."</h3>
    <ul>
      <li><em>".t('Login')." :</em> ${vals1['loginv']}</li>
      <li><em>".t('Name')." :</em> ${vals1['name']}</li>
      <li><em>".t('e-mail')." :</em> ${vals1['email']}</li>
      <li><em>".t('Website')." :</em> ${vals1['www']}</li>
     </ul>",
  );

  $form['summary']['sum_hobbies'] = array(
    '#markup'=>
    "<h3>".t('Hobbies')."</h3>
    <ul>
      <li><em>".t('Movies')." :</em> ${vals2['movies']}</li>
      <li><em>".t('Books')." :</em> ${vals2['books']}</li>
      <li><em>".t('Sports')." :</em> ${vals2['sports']}</li>
     </ul>",
  );



  $data = mforms_get_vals('myform');

  if (!empty($data)) {
    $vals = $data;
  }
  elseif (isset($form_state['values'])) {
    $vals = $form_state['values'];
  }

  $form['message_team'] = array(
    '#type' => 'textarea',
    '#title' => t('A message for the team'),
    '#default_value' => isset($vals['message_team']) ? $vals['message_team'] : NULL,
  );

  $form['confirm']=array(
    '#type' => 'checkbox',
    '#title' => t('Validate and end the survey'),
  );

  return $form;
}

/**
 * Validate callback.
 *
 * @param array $form
 *   Drupal form array.
 * @param array $form_state
 *   Drupal form_state array.
 */
function _myform_step3_validate($form, &$form_state) {
  if (!$form_state['values']['confirm']) {
    form_set_error('confirm', t('You have to validate the survey in order to continue !'));
  }
}

/**
 * Implement submit callback for the last step to process all data submitted.
 *
 * @param array $form
 *   Drupal form array.
 * @param array $form_state
 *   Drupal form_state array.
 */
function _myform_step3_submit($form, &$form_state) {
  // Here do what you want with the data

   multi_etapes_save_data() // exemple function

  // Send a mail to the team if there is a custom message

  if($form_state['values']['message_team'] && !empty($form_state['values']['message_team'])){
    // add your own mail function
    if(!_multi_etapes_drupal_mail('contact@votresite.com','webmaster@votresite.com',t('Answer to the survey'),$form_state['values']['message_team'])){

      drupal_set_message(t('Something went wrong'),'error');

    }

  }

  // Call mforms_clean(); 
  // Clear all data from the session variable

  mforms_clean('myform');

  drupal_set_message(t('Thank you for your time.'));
}
  1. Cette fois on affiche un résumé des information saisies précédemment, on utilise la fonction mforms_get_vals avec un second paramètre pour récupérer les valeurs d'une étape en particulier,
  2. On affiche les résultats des deux étapes précédentes,
  3. On affiche un champ pour que l'utilisateur puisse envoyer un message à l'équipe du site,
  4. On met en place une case à cocher pour valider nos résultats et envoyer le message (si saisi),
  5. Une fonction de validation vérifie que la case a bien été cochée lors de l'envoi du formulaire,
  6. La fonction d'envoi traite les données (je ne développe pas ce point, mais on peut imaginer que les données saisies viennent peupler une node ou une entitée personnalisée),
  7. Si l'utilisateur a entré un message à destination de l'équipe on envoi ce message par mail (par exemple),
  8. On vide le contenu de notre formulaire (effacement de la variable de SESSION) et on affiche un message à l'utilisateur.

Et voilà ! Notre module est en place et fonctionnel. Les possibilités sont nombreuses et le système robuste permet de créer n'importe quel type de formulaire multi-étape.

Pour aller plus loin

Quelques astuces complémentaires.

Execution de javascript

Lors de la réalisation du formulaire multi-étape pour le site applicatif de l'eGrid, j'ai eu besoin d'executer du code javascript pour réaliser un carrousel (étape Évaluation).

Le problème avec l'execution de javascript dans les appels en ajax Drupal, c'est qu'il est toujours délicat de savoir à quel moment se "brancher" pour executer son code. Le morceau de javascript suivant permet d'executer le code souhaité à l'étape désirée. Attention cependant le code est réinterprété a chaque reconstruction du DOM (et donc à chaque affichage de message d'erreur de validation).

J'ai placé le code dans js/multi_etapes.js et modifié multi_etapes.info pour déclarer le fichier js ( scripts[] = js/multi_etapes.js ).

(function ($, Drupal, window, document, undefined) {

  Drupal.behaviors.multi_etapes = {
    attach: function(context, settings) {

      if(context === document || context[0].nodeName == 'FORM'){
          var step = $('[id^="edit-steps-btns-wrapper"] .current-step').attr('id');

          var regex = /step(\d)/;
          var match = regex.exec(step);
          step = parseInt(match[1]);

          alert('Etape ' + step);
          // on peut envisager un switch pour gérer chaque étape.
      }
    }
  }
})(jQuery, Drupal, this, this.document);

Si vous vous aventurez dans la réalisation d'un tel formulaire avec javascript, je serai très interessé par vos retours et vos solutions pour bien gérer l'execution JS.

Accéder aux données de la session

Le fait de travailler avec les sessions permet d'accéder à tout moment aux valeurs du formulaire. Ceci m'a été particulièrement utile lors de la génération d'une fiche de résumé des saisies en PDF. Pour accéder aux valeurs stockées :

$valeurs = $_SESSION['clefduformulaire']

Ce sera tout pour aujourd'hui concernant les formulaires multi-étapes sous Drupal, il existe d'autres méthodes utilisant le module webform ou en construisant directement avec l'API form de Drupal, avez-vous essayé ces autres méthodes ? Qu'en pensez-vous ?

Par AlanT
Alan Tondelier

Lancement du Blog !

Lancement du Blog !

Enfin !
Après plusieurs semaines de questionnement personnel ponctuées principalement par des "faut-il ?", le Blog est finalement en ligne.

Qu'attendre de ce blog ?

Ce blog est né de ma volonté de partager des connaissances acquises au cours des 3 dernières années sur Drupal.
En effet, au fil des semaines passées a approfondir le CMS Drupal, j'ai été à plusieurs reprises surpris de ne pas lire plus d'articles sur des problèmes récurrents, rencontrés par de nombreuses personnes de la communauté et qui ne trouvent que des réponses partielles, incomplètes ou "alambiquées".

Sans avoir la prétention d'apporter des réponses "ultimes" ou parfaites, je veux avant tout partager avec ceux qui me liront (coucou !) des pistes cohérentes, fonctionnelles et permettant, je l'espère, d'apporter une brique de qualité à l'édifice Drupal.

Le blog d'un autodidacte sur la scène Drupal & Web

Qui suis-je ?
Pour faire court, je suis un ingénieur diplômé de Grenoble-INP - Pagora filière communication imprimée. Ayant toujours côtoyé de près les sites Internet, depuis l'époque où couinaient les modems 56K, j'ai en 2011 entrepris de tenter l'aventure web, d'abord à Paris puis à Rouen en Haute Normandie.
Après près de 3 années en agence, j'ai finalement décidé de prendre mon indépendance.

Je ponctuerai ce blog de quelques articles plus personnels afin de donner quelques retours d'expérience sur cette aventure numérique.

Une promesse avec moi-même

Il est très facile de ne pas tenir la rédaction d'un blog, aussi j'essaierai dans la mesure du possible de poster un article au minimum par semaine afin de faire vivre cet espace :)

Blog ... GO !

 

Par AlanT
Alan Tondelier

Fournir un flux XML et RSS valide à partir d&#039;une vue Drupal

Il vous est peut-être arrivé de rencontrer des erreurs de validation de vos flux RSS, ou de tout flux XML générés avec des vues Drupal. Cet article propose des pistes de réponses pour assurer leur validation. A noter que je m'intéresse ici à une solution Drupal, cependant cette démarche est tout à fait adaptable à d'autres CMS/technologies.

Caractères interdits et sections CDATA

Une histoire de norme Unicode

Les flux XML doivent se contraindre à des standards établis par le w3c, parmis ces standards il existe une norme qui va nous intéresser tout particulièrement pour nos sites Drupal : celle concernant l'encodage des caractères de nos flux. Celle-ci précise que tous les caractères à l'intérieur de vos balises xml doivent se plier à la norme ISO/IEC 10646.

Il arrive que certain caractères saisis dans vos contenus, ne valident pas la norme ISO/IEC 10646. Ceci arrive notemment lorsque l'on créé des contenus à partir de copier/coller sauvages à partir d'un logiciel tier. Conséquence directe, notre flux ne passe pas la validation et une belle erreur est listée sur le validateur w3c :

Line 79, Column 68: non SGML character number 31

Nous allons voir comment chasser ces caractères indésirables.

Utilisation des sections CDATA pour assurer la compatibilité de vos flux

Une erreur classique dans la génération d'un flux xml est la présence de markup html dans les balises XML.
Pour éviter ce problème, Drupal converti en entitées HTML le contenu des champs lors de la génération de l'affichage de votre vue (et donc de votre flux). 

Cependant certaines visionneuses de flux ou logiciels d'importation peuvent avoir du mal à gérer les entitées HTML et se comportent mieux avec du code HTML "brut". Pour permettre l'inclusion de balises HTML dans notre XML, une solution toute simple est d'utiliser les sections CDATA :

  <item>
    <title>Titre de mon article</title>
    <link>http://blog.alantondelier.net/lien</link>
    <description>
      <![CDATA[ <h1>Ceci est un titre</h1> <img src="http://blog.alantondelier.net/uneimage.jpg" alt="Une image dans mon flux" /> <p>Blabla</p> ]]>
    </description>
  </item>

Couplées à la fonction html_entity_decode pour retrouver notre markup, les sections CDATA permettent d'assurer la compatibilité de nos flux XML tout en laissant le code valide.

Mise en place de la solution

Intéressons-nous à la mise en place d'une solution pour filtrer les mauvais caractères et pour ajouter les sections <[CDATA[]]>.

Nous allons intervenir au niveau du theming Drupal, et plus particulièrement au niveau du template de views générant le code pour chaque élément du flux.
Prenons l'exmple le plus courant : celui d'un flux RSS basique généré par views. Pour les autres cas la méthode présentée est à adapter selon vos besoins. 

Pour les flux RSS, le template appelé sera views-view-row-rss.tpl.php. Placez une copie de ce template dans votre theme et ouvrez le :

<?php

/**
 * @file
 * Default view template to display a item in an RSS feed.
 *
 * @ingroup views_templates
 */
?>
  <item>
    <title><?php print $title; ?></title>
    <link><?php print $link; ?></link>
    <description><?php print $description; ?></description>
    <?php print $item_elements; ?>
  </item>

Nous allons commencer par modifier le template pour décoder l'HTML et ajouter une section CDATA dans notre balise <description> (il est possible d'effectuer la même opération sur <title> si vous y affichez de l'html "brut").

    <description>
      <![CDATA[ <?php print html_entity_decode($description); ?> ]]>
    </description>

Il faut également retirer tous les caractères interdits par la norme XML. On utilise une fonction PHP de traitement de chaîne, empruntée ici, que l'on ajoute au template.php de notre theme :

/**
 * Removes invalid XML
 *
 * @access public
 * @param string $value
 * @return string
 */

function montheme_xmlcleaner($value)
{

    $ret = "";
    $current;
    if (empty($value))
    {
        return $ret;
    }

    $length = strlen($value);
    for ($i=0; $i < $length; $i++)
    {
        $current = ord($value{$i});
        if (($current == 0x9) ||
            ($current == 0xA) ||
            ($current == 0xD) ||
            (($current >= 0x20) && ($current <= 0xD7FF)) ||
            (($current >= 0xE000) && ($current <= 0xFFFD)) ||
            (($current >= 0x10000) && ($current <= 0x10FFFF)))
        {
            $ret .= chr($current);
        }
        else
        {
            $ret .= " ";
        }
    }
    return $ret;
}

On termine par ajouter l'appel à cette fonction dans notre template views-view-row-rss.tpl.php :

<?php

/**
 * @file
 * Default view template to display a item in an RSS feed.
 *
 * @ingroup views_templates
 */

?>
  <item>
    <title><?php print montheme_xmlcleaner($title); ?></title>
    <link><?php print $link; ?></link>
    <description>
      <![CDATA[ <?php print montheme_xmlcleaner(html_entity_decode($description)); ?> ]]>
    </description>
    <?php print $item_elements; ?>
  </item>

La magie opère : votre flux xml est valide. 

N'hésitez pas à partager vos expériences sur les flux xml & Drupal en commentaire ou si vous avez rencontré un problème différent sur le sujet.

Par GoZ
Fabien CLEMENT

Soumettre un formulaire généré avec la formapi en GET sans les valeurs form_id et form_build_id

Il serait dommage de générer un formulaire "à la main" sous Drupal alors qu'il y a la [formapi](https://api.drupal.org/api/drupal/developer!topics!forms_api_reference.html/7) pour nous faciliter la vie.

Le problème à résoudre ici est d'utiliser la formapi pour générer une formulaire d'envoi en GET mais sans être pollué par les variables de drupal en URL (form_id, form_build_id, op etc).

en lire plus

Par admin

Assemblée générale extraordinaire du 18 mars 2014

Fichier attaché Taille
dff-ag-2014.pdf 82.41 Ko

Chers adhérents,

J’ai le plaisir de vous convier à une assemblée général extraordinaire de notre association le mardi 18 mars prochain, conformément à l’article 8 des statuts. Cette assemblée extraordinaire sera immédiatement suivie d’une assemblée générale ordinaire.

Cette réunion se tiendra le mardi 18 mars 2014 à partir de 19h à la Maison des Association du 3ème, 5 rue Perrée, 75003 Paris, dans la salle 1.

L’assemblée générale extraordinaire aura pour objet la soumission au vote de modifications de nos statuts, notamment sur la constitution du conseil d’administration et sur son mode de scrutin. L’objectif de cette AGE est également de recaler dans le temps les assemblées générale afin de respecter les statuts (dans les 3 mois suivant la fin de l’exercice). Nous vous transmettrons dans les prochains jours la nouvelle version proposée des statuts.

L’assemblée générale ordinaire qui suivra aura à l’ordre du jour :
Rapport moral présenté par le président
Rapport financier présenté par le trésorier
Élection du nouveau conseil d’administration
Questions ouvertes
Les membres désirant présenter leur candidature au conseil d’administration et ceux qui souhaitent inclure des points complémentaires à l’ordre du jour devront se manifester au plus tôt au bureau via email à bureau@listes.drupalfr.org

Si vous ne pouvez être physiquement présent lors du vote, vous pouvez vous faire représenter par un autre membre de l’association muni d‘un pouvoir régulier (ou vous pouvez l’envoyer à l’adresse de l’association au minimum 4 jours avant l’assemblée générale). Le pouvoir est disponible dans le document joint.

Vous pouvez également participer à l'assemblée générale par un moyen de communication électronique permettant de vous identifier formellement. Dans ce cas, si vous souhaitez participer aux votes vous devrez renoncer à l'anonymat des votes afin de les transmettre.

Je vous rappelle également que seuls les membres à jour de leur cotisation peuvent participer à l’assemblée générale et participer au vote, n'hésitez pas à adhérer dés à présent grâce au bulletin d'adhésion en ligne http://drupalfr.org/sites/default/files/formulaire-adherent-drupalfr.pdf ou sur place le jour même.

Cordialement
Olivier Friesse
Président Drupal France et Francophonie

En page d'accueil : 
Par benftwc

Drupal : utiliser, c’est bien, contribuer, c’est mieux !

Sous Drupal, nous le savons, il existe une myriade de modules, tous plus intéressant que les autres. Seulement, savez-vous qui se cache derrière ? Toute une communauté !

Un module, avant d’être utilisé par des milliers de sites, doit passer par quelques étapes, à commencer par la sandbox. Cette Sandbox permet aux développeurs d’envoyer leur modules à la communauté afin d’obtenir des retours, des tests, et des patchs.

Je vais vous parler un peu plus en détail des patchs.

Un « patch », kézako ?

Un patch est tout « simplement » un fichier texte, contenant un différentiel produit par GIT. Pour avoir une idée, vous pouvez exécuter cette ligne :

$ git diff

Vous devriez voir apparaître toutes les modifications apportées sur tout les fichiers de la branche courante sous cette forme :

diff --git a/currency_converter.admin.inc b/currency_converter.admin.inc
index e644435..487862f 100644
--- a/currency_converter.admin.inc
+++ b/currency_converter.admin.inc
@@ -11,7 +11,7 @@
  */
 function currency_converter_admin_settings() {

-  $default_weight = array('BYR' => 0, 'RUB' => 1, 'EUR' => 2, 'USD' => 3, 'CAD' => 4, 'PLN' => 5, 'UAH' => 6, 'CNY' => 7, 'LTL' => 8, 'LVL' => 9);
+  $default_weight = array('BYR' => 0, 'RUB' => 1, 'EUR' => 2, 'USD' => 3, 'CAD' => 4, 'PLN' => 5, 'UAH' => 6, 'CNY' => 7, 'LTL' => 8, 'LVL' => 9, 'JPY' => 10, 'GBP' => 11, 'CHF' => 12, 'AUD' => 13, 'HKD' => 14, 'INR' => 15);
   $weight = variable_get('currency_converter_weight', $default_weight);
   asort($weight);

Comme vous pouvez le remarquer, certaines lignes débutent par un « - », d’autres par un « + ». Simplement, les lignes avec « - » ont étés supprimées, celles avec un « + » ajoutées.

Comment créer un patch

Pour créer un patch, c’est très simple, peu de connaissances GIT sont requises.

Une fois vos modifications effectuées, testées puis validées, vous n’avez qu’une commande à entrer pour générer le patch :

 $ git diff > fichier.patch

Et c’est tout ! Il vous restera ensuite de suivre les règles de nommage, puis d’envoyer votre patch sur Drupal.

Contribuer au module, de A à Z

Bien, voici donc les étapes détaillées à suivre afin de maintenir un module


## Première étape : On récupère notre projet, sous la bonne version
$ git clone --branch X.x-X.x http://git.drupal.org/project/projectname.git projectname
$ cd projectname

## Nous sommes donc dans le répertoire crée par GIT, contenant les sources du module

A présent que le projet est copié, nous pouvons travailler dedans et effectuer nos modifications

## A présent que toutes nos modifications sont faites, nous pouvons passer à la création du patch
$ git diff > [description de ce que vous avez modifié][numéro du ticket][numéro du commentaire].patch

A présent, vous savez créer vos patch et les envoyer. Regardons comment en utiliser un.

Appliquer un patch

Une autre façon de contribuer est d’appliquer les patchs pour les tester et faire un retour au développeur.

Encore une fois, très peu de connaissances GIT sont requises

$ git apply -v fichier.patch

Vous êtes fins prêt.

Partez à la conquete de nouveaux modules à débugger ! Drupal.org vous propose également ces 2 liens :

Patch Bingo et Bug Bingo

Vous y trouverez de manière aléatoire un module / thème qui nécessite une intervention.

Vous trouverez d’autres infos sur les slides de @simongeorges et @Artusamak, disponibles ici

The post Drupal : utiliser, c’est bien, contribuer, c’est mieux ! appeared first on Benftwc.

Manipuler les menus de Drupal 8







Drupal propose une API nous permettant de manipuler et modifier les menus de façon dynamique très facilement. Si l'approche pour altérer un menu sous Drupal 8 ne diffère que très peu de Drupal 7, découvrons quelques subtilités introduites par la nouvelle version majeure de Drupal.

Thème 
Drupal 8
Theming
Menu

Par admin

AGE et AGO de l'Association Drupal France et francophonie - 18 mars

Association Drupal France & Francophonie
21 Place de la république
75003 Paris
www.drupalfr.org

Annonce

Assemblée Générale Extraordinaire et Assemblée Générale Ordinaire

Date :
Mardi 18 mars 2014

Lieu :
Maison des Association du 3ème
Salle 1
5 rue Perrée,
75003 Paris

Station métro :
GoogleMaps

Déroulement :
Ouverture des portes (19h00)
Ordre du Jour AGE (19h15)

  • Présentation du mode de scrutin pour l’élection du conseil d’administration

Ordre du Jour AGO (19h30) :

  • Rapport moral présenté par le président
  • Rapport financier présenté par le trésorier
  • Élection du nouveau bureau
  • Questions ouvertes

Voter
Pourront participer à ce vote tous les adhérents à jour de cotisation.

Vous aurez la possibilité d'adhérer à l'association le jour du vote. Le montant de l'adhésion et de 20€ par personne ou 30€ par salarié Drupal pour les personnes morales, mais vous pouvez utiliser le formulaire de demande d'adhésion (http://drupalfr.org/sites/default/files/formulaire-adherent-drupalfr.pdf).

Les adhérents qui ne peuvent venir peuvent donner procuration à l'adhérent de leur choix et l'adresser par mail : bureau [at] listes.drupalfr.org
La procuration est disponible pour téléchargement dans ce billet.

Fichier attaché Taille
Délégation du pouvoir 61.62 Ko
Tags : 
Par cyprien

Drupal + AngularJS : l'ultime solution de theming pour Drupal ?

Customiser le code HTML créé par Drupal et ses modules est sans doute l'un des points les plus difficiles à comprendre. Les solutions proposées pour tenter de faciliter cette tâche sont nombreuses : Starter thèmes, modules type Panels ou  Display Suite, modules d'intégration de bibliothèques JavaScript, la liste est infinie. On passe beaucoup de temps dans la création d'un site sous Drupal à tenter de livrer un code HTML qui conviendra à l'intégrateur, qui de toute façon s'arrachera les cheveux à un moment ou un autre.

AngularJS est un framework Javascript qui adapte et étend le HTML grâce à des directives qu'on y inclut. Mes premiers pas avec AngularJS m'ont donné à penser qu'il pourrait bien être complémentaire de Drupal. Parlons de ces premiers pas justement, la communauté AngularJS propose un tutoriel pour débuter qui permet de créer une application Phonecat qui affiche des informations techniques à propos de téléphones portables.

Je vous propose donc de modifier cette application pour qu'elle récupère ses informations depuis Drupal via des web services. Drupal servirait alors uniquement de backoffice à l'application AngularJS, chose que ce CMS fait très bien.

Fonctionnement

L'idée est donc la suivante :

A partir de Drupal je vais créer un nouveau type de contenu classiquement qui générera des nodes. La liste de ces nodes sera créée via le module Views. Ensuite, la vue et les nodes seront exposés via des webservices REST grâce au module Services.

Côté client, j'utiliserai $resource d'AngularJS pour les accès à ces services REST, et les données pourront être exploitées par les contrôleurs et présentées par les templates  de l'application.

Installation de Drupal et de Phonecat AngularJS

J'ai décidé de positionner l'application AngularJS à la racine de mon répertoire et d'avoir un sous-dossier Drupal dans ce répertoire. Voici l'arborescence que j'ai obtenue après installation :

Créer un virtualhost ng-drupal

J'ai commencé par créer un nouveau virtual host dans Apache qui mène à mon répertoire Drupal/Angular. Voici mon ng-drupal.conf :

&lt;VirtualHost *:80&gt; <br />&nbsp; DocumentRoot /home/desktop/s/ng-drupal <br />&nbsp; ServerName ng-drupal.tld <br />&nbsp; &lt;Directory /home/desktop/s/ng-drupal&gt; <br />&nbsp;&nbsp;&nbsp; AllowOverride all <br />&nbsp; &lt;/Directory&gt; <br />&lt;/VirtualHost&gt;

Cloner le tutorial Phonecat / Angular

Je suis parti du tutorial officiel proposé sur le site officiel AngularJS. Si vous ne connaissez pas ce framework, je vous encourage à le lire dans son ensemble.

Placez-vous à la racine du répertoire précédemment créé et exécutez les deux commandes suivantes. La première permet de copier le code source du tutoriel et la seconde de se placer à la dernière étape.

git clone https://github.com/angular/angular-phonecat.git .<br />git checkout -f step-12

A l'adresse suivante, j'ai maintenant le tutoriel qui s'affiche :
http://ng-drupal.tld/app/

Installation de Drupal

J'ai ensuite installé Drupal 7 dans le sous-répertoire /drupal du dossier principal. Si vous ne connaissez pas ce CMS, vous pouvez utiliser mon livre librement téléchargeable chez Framabook.

Pour accéder à l'installateur, j'ai lancé cette url : http://ng-drupal.tld/drupal

Après quelques minutes, Drupal était installé :)

Installation de la Feature angular-phonecat

Pour vous éviter de longues configurations et saisies, j'ai créé une « Feature » (module) qui contient les éléments Drupal qui permettent de créer les webservices pour l'application Phonecat. Elle contient :

  • un type de contenu phone avec tous les champs des téléphones du tutorial
  • du contenu qui utilise le type de contenu Phone
  • une vue qui liste tous les téléphones du site
  • un service qui permettent d'exporter sous forme de JSON la vue ou un élément du contenu

J'ai donc installé cette Feature dans le répertoire drupal/sites/default/modules (à créer si il n'existe pas).
Vous pouvez télécharger cette feature ici.

Installation des dépendances

Ce module a des dépendances manquantes, je les ai installées

  • Ctools : boîte à outils utilisée par un grand nombre de modules Drupal
  • Features : permet de créer des fonctionnalités sous forme de module
  • Field_group : utilisé pour regrouper les nombreux champs du contenu phone
  • Node_export_features : permet d'inclure du contenu dans une feature
  • Universally Unique ID : module permettant d'attribuer un id unique à chaque contenu
  • Services : permet à Drupal de créer des webservices
  • Views : permet d'extraire différents types de données de Drupal et de les présenter
  • Libraries : permet aux modules Drupal d'utiliser des bibliothèques externes
  • REST Server : permet de créer un serveur REST
  • Services views : permet à Services d'exposer le résultat des vues

Pour plus de rapidité, j'ai utilisé la commande Drush suivante :

drush dl ctools features views services field_group node_export uuid libraries services_views

J'ai ensuite pu activer les modules et ma feature. J'ai dû m'y reprendre à deux fois car mes contenus ont été importés avant mon type de contenu.

L'installation coté Drupal est terminée !

Les contenus dans Drupal

Vous avez maintenant la possibilité d'ajouter de nouveaux téléphones graphiquement avec Drupal :
Menu Content – Lien Add content – Phone

Voici l'interface que j'ai utilisée pour ajouter les quatre téléphones de la Feature que j'ai créée.

Pour ceux qui connaissent mal Drupal, sachez que vous pouvez créer ce type de formulaire graphiquement dans Drupal en choisissant les champs qui les composent.

Vous pouvez modifier le type de contenu Phone (Menu Structure – Content types, puis lien manage fields de Phone) :

La page de liste

J'ai commencé par créer la page de liste des téléphones.

La vue dans Drupal

Cette liste utilise 4 champs pour chaque téléphone :

  • Le titre
  • La description
  • Le nid (pour créer un lien vers le détail d'un téléphone)
  • L'image

J'ai donc créé une vue Phone-list dans Drupal (menu Structure – Views, puis lien Edit de la vue Phone-list)

Notez que j'ai utilisé le redimensionnement en 100x100 pour l'image, mais j'aurais pu en créer une autre facilement.

Le webservice dans Drupal

Ensuite, pour exporter cette vue en JSON, j'ai utilisé le module services (menu Structure – Services, puis lien Edit Resources du service phones).

J'ai indiqué que le chemin à utiliser pour accéder au service sera phones (onglet Edit) et que ca sera un serveur REST. J'ai autorisé le webservice à accéder à la lecture des nodes pour le détail des téléphones et aux vues pour accéder à celle que je viens de créer : Phone-list (onglet Ressources).

Voici le chemin complet à utiliser pour accéder à une vue via un webservice :
http://example.com/<endpoint path>/views/<view name>

Ce qui m'a donné :
http://ng-drupal.tld/drupal/phones/views/phone_list.json

Récupération dans Angularjs de phone_list.json

Au terme de ce tutoriel, j'aurai donc deux services : un basé sur Views qui me permettra de récupérer la liste des téléphones, un autre sur Nodes qui me permettra de récupérer un seul téléphone.

J'ai donc commencé par créer le premier service, dans le fichier services.js de l'application Phonecat :

phonecatServices.factory('Phones', ['$resource',<br />&nbsp; function($resource){<br />&nbsp;&nbsp;&nbsp; return $resource('http://ng-drupal.tld/drupal/phones/views/phone_list.json', {}, {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; query: {method:'GET', isArray:true}<br />&nbsp;&nbsp;&nbsp; });<br />&nbsp; }]);

J'ai utilisé ce service avec le controller PhoneListCtrl dans le fichier controllers.js :

phonecatControllers.controller('PhoneListCtrl', ['$scope', 'Phones',<br />&nbsp; function($scope, Phones) {<br />&nbsp;&nbsp;&nbsp; $scope.phones = Phones.query();<br />&nbsp;&nbsp;&nbsp; $scope.orderProp = 'age';<br />&nbsp; }]);

Grâce à Batarang, je vois que le JSON est bien chargé.

Affichage dans le template

Il ne reste donc qu'à charger les bons éléments dans le template  partials/phone-list.html

Titre et description :

Les champs s'appellent maintenant node_title, nid et description :

&lt;a href="#/phones/{{phone.nid}}"&gt;{{phone.node_title}}&lt;/a&gt;<br />&lt;p&gt;{{phone.description}}&lt;/p&gt;

Image :

Pour le champ image, j'injecte directement le html provenant du JSON grâce à la directive ng-bind-html. Pour qu'elle fonctionne, il me faut ajouter la dépendance ngSanitize à mon module phonecatService dans services.js :

var phonecatServices = angular.module('phonecatServices', ['ngResource', 'ngSanitize']);

Je dois également ajouter le script angular-sanitize.js dans index.html :

&lt;script src="lib/angular/angular-sanitize.js"&gt;&lt;/script&gt;

puis, je complète mon fichier phone-list.html pour l'affichage des images

&lt;a href="#/phones/{{phone.nid}}" class="thumb" ng-bind-html="phone.images"&gt;&lt;/a&gt;

Filtres

Tout est bien chargé, il ne me reste plus qu'à faire fonctionner les filtres en changeant les noms des champs.

Dans partials/pḧone-list.html :

&lt;option value="node_title"&gt;Alphabetical&lt;/option&gt; <br />&lt;option value="nid"&gt;Newest&lt;/option&gt;

Dans controller.js , indiquer la valeur par défaut (nid) :

$scope.orderProp = 'node_title';

La liste des téléphones fonctionne maintenant ! Finalement, je n'ai eu à changer que les noms de champs !

Page détail

Création du service dans Drupal

Rien à faire ou presque pour cette page détail coté Drupal puisque le module Service fournit par défaut un moyen de récupérer le JSON d'un node. Il m'a suffit pour cela de cocher le service node/retrieve pour que mon webservice fonctionne avec l'adresse :
http://ng-drupal.tld/drupal/phones/node/1.json

Modification du service dans AngularJS

Coté AngularJS, il faut modifier l'url de la ressource Phone :

phonecatServices.factory('Phone', ['$resource',<br />&nbsp; function($resource){<br />&nbsp;&nbsp;&nbsp; return $resource('http://ng-drupal.tld/drupal/phones/node/:phoneId.json', {}, {<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; query: {method:'GET', params:{phoneId:'phones'}, isArray:true}<br />&nbsp;&nbsp;&nbsp; });<br />&nbsp; }]);

L'argument n'est donc plus le nom du téléphone, mais le nid drupal du node.

Toutes les données sont chargées, il faut maintenant les afficher !

Modification du phone-detail.html avec le json de Drupal

J'ai eu un travail assez long pour récupérer les valeurs intéressantes dans le JSON généré par Drupal avec l'application Angular. Il a fallu gérer quatre types de données  :

Type texte simple :

Par exemple, le champ RAM est affiché comme cela à l'origine :

{{phone.storage.ram}}

Il devient alors :

{{phone.field_ram.und[0].value}}

« und » est bien connu des drupaliens et veut dire undefined, c'est à dire que la langue n'est pas définie pour ce champ. 0 est l'indice du champ en cas de valeur multiple

Type texte multiple :

Il est possible d'avoir un champ qui contient un nombre non défini de valeurs sous Drupal. C'est par exemple le cas du champ dimensions.

J'ai donc dû boucler sur le tableau phone.field_dimensions.und pour récupérer et afficher chaque valeur :

&lt;dd ng-repeat="dim in phone.field_dimensions.und"&gt;{{dim.value}}&lt;/dd&gt;

Type booléen :

A l'étape n°9 du tutoriel Angular, un filtre checkmark est ajouté. Il évalue si la valeur est à true ou false alors que Drupal retourne 0 ou 1. J'ai donc dû changer la règle du filtre :

return input == 1 ? '\u2713' : '\u2718';

Et afficher le résultat ainsi :

{{phone.field_infrared.und[0].value | checkmark}}

Les images :

J'ai rencontré un petit souci pour afficher les images car les données du json ne fournissaient qu'un lien vers une url Drupal (public://).

Conclusion

Après la rédaction de ce tutoriel, je n'ai pas de réponse pour savoir si la cohabitation entre Drupal et AngularJS est possible. J'ai trouvé des solutions à tous les problèmes qui se sont posés à moi et j'ai pu entrevoir le potentiel d'AngularJS . Cela m'a donné l'envie d'aller plus loin :

  • Comment fonctionnerait une authentification ?
  • Peut-être aurait-il été encore plus facile et versatile de créer un petit module Drupal utilisant la fonction drupal_json_encode (lien?)
  • L'initiative développée pour Drupal 8 (https://groups.drupal.org/wscci) facilitera-t-elle ce type de développement ?

N'hésitez pas à partager vos réflexions sur le sujet. Si vous avez un projet à développer et que vous aimeriez utiliser Drupal et AngularJS, n'hésitez pas à me contacter !

Téléchargez le dossier app de l'application modifiée.

Par admin

Promouvoir et défendre le logiciel libre

L'APRIL, l'association pour la promotion et la défense des logiciels libres lance une nouvelle campagne d'adhésion en ce début 2014.

Priorité au Logiciel Libre! Je soutiens l'April.

Cet appel permet à l'association de sensibiliser les responsables politiques à l'utilisation des logiciels libres à travers différentes actions et communications autour du libre, dont Drupal France et Francophonie est membre.

Si vous aussi, vous utilisez des logiciels libres, et que vous pensez que c'est important d'avoir du code ouvert pour éviter les dérives, c'est le moment d'adhérer.

Les raisons pour adhérer sont nombreuses et détaillées dans la page de campagne. Les avantages sont regroupés dans différentes rubriques, comme :

  • Augmenter la capacité d'action
  • Mieux se faire entendre
  • Financer les actions, campagnes...
  • Améliorer la qualité du monde numérique

Pour adhérer, rendez-vous ici : http://april.org/campagne/

Relayer les appels sur Twitter : #JeSoutiensApril et #prioriteLL


Priorité au Logiciel Libre! Je soutiens l'April.
Tags : 
Par hellosct1

Opération Livres Gratuits aux Editions ENI

l'éditeur "Editions ENI', qui ont publié le dernier livre appelé : "Construisez votre application avec PHP & MySQL - MySQLi - PDO", lance une grande campagne de communication destinée à faire connaître l'ensemble de nos livres au public le plus large possible.

Cette campagne se déroule du 11 au 13 février 2014, sur tous les livres publiés aux Editions ENI. L'accès va vous permettre de consulter les nombreux ouvrages à travers leur outil pour vous faire une idée de leurs contenus.

L'opération commence ce soir à minuit (lundi 10 février) et vous pouvez consulter l'ensemble des livres qui parlent de

   CMS : Drupal, Wordpress, Joomla
   Framework : Zend...
   Langage : PHP, Python, Perl, ...
   Base de bonnées : MySQL, PostgreSQL...
   Web
   Accessibilité
   Projet
   etc

Alors n'attendez pas pour consulter tous les livres des Editions ENI en accès gratuit pendant 3 jours !
Découvrez-les vite sur : www.editions-eni.fr/livresgratuits

Donner un accès direct au tunnel d'achat avec Drupal Commerce







Donner un accès direct au tunnel d'achat de votre site e-commerce au moyen d'un lien peut s'avérer nécessaire lorsque vous souhaitez diffusez des informations sur les produits de votre boutique en ligne sur d'autres supports comme une newsletter par exemple, ou encore un site internet partenaire ou satellite.

Thème 
Drupal 7
Commerce
Développement

Par cutesquirrel
Etienne

Utiliser xhprof avec Drupal et Devel

Rencontrant de gros soucis de performances depuis quelques temps sur un site, j’ai utilisé xhprof pour trouver le souci, et le problème m’a sauté aux yeux grâce à cet outil ! sudo apt-get install php5-dev sudo pecl install xhprof-beta # notez juste apres, l'emplacement du fichier xhprof.so, ca aide. cd /etc/php5/apache2/conf.d sudo vi 20-xhprof.ini Coller […]

Pages