Si vous avez un besoin de formulaire un tant soit peu complexe dans Drupal, vous utilisez très certainement le module Webform. C'est le cas du site de l'un de mes clients, dont l'ensemble des formulaires sont créés via ce module. Ceux-ci sont multiples sur le site : formulaire de contact, formulaire d'inscription newsletter ou encore formulaire de candidature. Le besoin s'est fait sentir de remonter les informations de ces formulaires vers Sugar CRM, ou plus exactement "de créer des leads dans Sugar Market grâce aux informations issus des webforms".
Il nous faut donc une manière de récupérer les informations soumises dans un formulaire du module Webform et de les transmettre à l'API de Sugar CRM dans le format souhaité. Egalement, puisque plusieurs formulaires vont renvoyer leurs informations, nous devrons être le plus générique possible, et offrir une configuration permettant de faire le lien entre le champ webform et la donnée dans l'API.
La méthode est alors toute trouvée : la création d'un webform handler : une classe capable d'effectuer un traitement sur les données envoyées dans le formulaire.
A nouveau, c'est le système de plugin de Drupal qui vient à notre rescousse. Dans la configuration de votre formulaire webform ; dans l'onglet handler, un système nommé plugin manager va découvrir l'ensemble des plugins de type @WebformHandler déclarés dans le code et vous proposer de les affecter au webform. Ainsi, lorsque celui-ci sera validé, l'ensemble des gestionnaires (handlers) en question seront appelés un à un pour effectuer leurs actions spécifiques sur les données soumises. L'un peut sauvegarder les résultats, l'autre envoyer un email de confirmation à l'utilisateur ou encore un email de notification à l'administrateur. Un autre enfin, celui que nous allons créer, va envoyer les données à une API externe.
Création d'un handler minimaliste
La première chose à faire est de créer un plugin de type @WebformHandler. Pour cela :
- Nous utiliserons l'annotation
@WebformHandler
sur une nouvelle classe d'un module custom - Cette classe se trouve placée dans le dossier
src/Plugin/WebformHandler
de notre module - La classe étendra
WebformHandlerBase
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <?php namespace Drupal\custommodule\Plugin\WebformHandler; use Drupal\webform\Annotation\WebformHandler; use Drupal\webform\Plugin\WebformHandlerBase; /** * Webform submission remote post handler. * * @WebformHandler( * id = "handler_sugar_market", * label = @Translation("SugarMarket new Contact connector"), * category = @Translation("External"), * description = @Translation("Inserts webform submission as a new SugarMarket Contact."), * cardinality = \Drupal\webform\Plugin\WebformHandlerInterface::CARDINALITY_SINGLE, * results = \Drupal\webform\Plugin\WebformHandlerInterface::RESULTS_PROCESSED, * submission = \Drupal\webform\Plugin\WebformHandlerInterface::SUBMISSION_OPTIONAL, * tokens = TRUE, * ) */ class SugarMarketWebformHandler extends WebformHandlerBase { }
Drupal\webform\Annotation\WebformHandler
Nous remarquons dans cette définition les quelques propriétés suivantes :
- id : l'ID du handler, c'est assez évident
- label : là aussi, pas vraiment besoin d'explication
- category : permet de ranger les gestionnaires par catégorie dans la UI d'admin. Un peu comme pour les sections de la page module finalement
- description : assez évident aussi !
- cardinality : combien de handler de ce type vous pouvez attacher à un même webform. Suivant la nature de votre gestionnaire, il peut faire sens de limiter à un seul, comme ici.
- results : indique si les résultats vont être utilisés par le handler. En l'occurrence ici oui puisque nous allons envoyer nos résultats à une API externe.
- submission : indique si le handler a besoin que les résultats soient sauvegardés en base de données pour fonctionner. Ici évidemment non. Il serait même envisageable de ne se servir du webform que comme d'un formulaire pour l'envoi vers un système externe, sans rien sauvegarder en interne sur notre site
- token : si le support des tokens s'avère nécessaire
A partir de là, notre webform devient disponible dans la UI.
La classe WebformHandlerBase
que nous étendons comprend un grand nombre de méthodes que nous pouvons surcharger. En particulier, postSave()
nous permettra d'agir sur la soumission après sa sauvegarde (mais avant les hook insert et update). Cela se fait par l'usage de la méthode au sein de la classe de notre gestionnaire.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 /** * {@inheritdoc} */ public function postSave(WebformSubmissionInterface $webform_submission, $update = TRUE) { // Get the submitted data as an array. $submission = $webform_submission->toArray(TRUE); // Build data from the submissions. $data = [ 'crm_type' => 'Contact', 'first_name' => $submission['data']['firstname'], 'last_name' => $submission['data']['lastname'], 'email' => $submission['data']['email'], 'country' => 'France', ]; // Send via an appropriate service. $this->sugarMarketService->postContact($data); }
Et c'est tout !
Lors de la soumission du formulaire, si bien entendu le handler est utilisé dans le webform, cette méthode va être appelée automatiquement. Les données de soumission du formulaire sont récupérées et transmises ici à un service que nous avons créé pour maintenant la lisibilité du code.
Quelques configurations supplémentaires
Dans le paragraphe précédent, nous avons découvert le code minimal pour le fonctionnement d'un gestionnaire de soumission de formulaire. Cela dit, il est évidemment possible d'aller plus loin en offrant à ce gestionnaire la possibilité d'être configuré.
Plus clairement, en reprenant notre exemple précédent, nous avons hardcodé la correspondance entre le champ de notre formulaire et le champ correspondant de l'API SugarMarket. Cela fonctionne parfaitement pour notre projet et c'est suffisant ! Pour autant, si le projet devait être plus générique, nous pourrions proposer une configuration permettant à l'administrateur de choisir quelle donnée, parmi l'ensemble des champs du webform, associer à chaque champ de l'API.
WebformHandlerBase
que nous avons étendu contient des tas de méthodes pour un grand nombre de cas d'usage. Je ne peux que vous conseiller de jeter un œil à son interface pour découvrir l'étendue des possibilités.Nous allons pour cela devoir utiliser plusieurs méthodes différentes :
defaultConfiguration()
: cette méthode permet de définir l'ensemble des configurations utilisées par notre gestionnaire ainsi que lui attribuer une valeur par défaut1 2 3 4 5 6 7 8 9 10 11 12 13 14
/** * {@inheritdoc} */ public function defaultConfiguration() { return [ // Options 'contact_firstname' => '', 'contact_lastname' => '', 'contact_email' => '', 'contact_country' => '', 'contact_language' => '', 'contact_type' => 'Contact', ]; }
buildConfigurationForm()
: cette méthode permet la construction d'un formulaire de configuration qui sera disponible en admin.1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
/** * {@inheritdoc} */ public function buildConfigurationForm(array $form, FormStateInterface $form_state) { // Retrieve a list of all fields configured in the webform build. $options = $this->buildWebformFieldListAsSelectOptions(); $form['submission_data'] = [ '#type' => 'details', '#title' => $this->t('Submission data mapping'), '#description' => $this->t('Map the webform field to SugarMarket Contacts field data'), ]; $form['submission_data']['contact_firstname'] = [ '#type' => 'select', '#title' => $this->t('Contact firstname'), '#default_value' => $this->configuration['contact_firstname'], '#options' => $options, '#empty_value' => '', '#empty_option' => $this->t('- Do not send -') ]; // Continue with all necessary fields. // .... return $this->setSettingsParents($form); }
submitConfigurationForm()
: sauvegarde de la configuration. Heureusement, il n'y a quasiment rien à faire grâce aux méthodes d'aide de Webform.1 2 3 4 5 6 7 8
/** * {@inheritdoc} */ public function submitConfigurationForm(array &$form, FormStateInterface $form_state) { parent::submitConfigurationForm($form, $form_state); // Helper method made by Webform for us: saves the configurations. $this->applyFormStateToConfiguration($form_state); }
getSummary()
: construit une prévisualisation des configurations enregistrées à afficher sur la page de listing des handlers associés à un webform.1 2 3 4 5 6 7 8 9 10 11
/** * {@inheritdoc} */ public function getSummary() { $configuration = $this->getConfiguration(); $settings = $configuration['settings']; return [ '#settings' => $settings, ] + parent::getSummary(); }
postSave()
: nous pouvons désormais modifier notre méthode d'envoi des données pour prendre en compte nos modifications.1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
/** * {@inheritdoc} */ public function postSave(WebformSubmissionInterface $webform_submission, $update = TRUE) { $submission = $webform_submission->toArray(TRUE); $data = [ 'crm_type' => $this->configuration['contact_type'] ]; if (!empty($this->configuration['contact_firstname'])) { $data['first_name'] = $this->getSubmissionValue($submission['data'], $this->configuration['contact_firstname']); } // Continue with other fields.... $this->sugarMarketService->postContact($data); }
buildWebformFieldListAsSelectOptions()
: notre premier helper. Nous l'avons utilisé dans la méthode buildConfigurationForm. Il permet de créer la liste des champs comme options pour nos select.1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
/** * Builds a key => name list of the fields in the webform. * * This is made to be used as #options in a select list to choose an * element among the webform configured fields. * * @return array */ protected function buildWebformFieldListAsSelectOptions() { $webform = $this->getWebform(); $elements = $webform->getElementsInitializedFlattenedAndHasValue('view'); foreach ($elements as $key => $element) { $title = $element['#admin_title'] ?: $element['#title'] ?: $key; if (!empty($element['#webform_composite_elements'])) { foreach ($element['#webform_composite_elements'] as $subkey => $subelement) { if (!isset($subelement['#access']) || $subelement['#access']) { $subtitle = $subelement['#admin_title'] ?: $subelement['#title'] ?: $subkey; $options["$key::$subkey"] = "$title - $subtitle ($key)"; } } } else { $options[$key] = "$title ($key)"; } } return $options; }
getSubmissionValue()
: un second helper utilisé dans le nouveaupostSave
pour récupérer la valeur d'un champ depuis la clef du champ. Cette méthode permet d'aller récupérer en profondeur une valeur, par exemple extraire le pays en particulier depuis un champ complexe type adresse.1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
/** * Get the submission value for a given complex key set. * @param $submissions * @param $keys * * @return mixed */ protected function getSubmissionValue($submissions, $keys) { $split = explode('::', $keys); $value = $submissions; $index = 0; while (is_array($value)) { $value = $value[$split[$index]]; $index++; } return $value; }
Voilà qui est fait !!
C'était un peu long, mais nous avons montré comment créer une classe capable de traiter les données soumises via un webform. Nous avons aussi montré comment créer une configuration pour notre handler, notamment dépendant des champs définis au sein de notre formulaire, ce qui était la partie la plus tricky de ce post.
Commentaires
Bonjour Dominique !
Super pour ton article, il y en a assez peu sur ce type d'usage de Webform !
Dans mon cas, on souhaite utiliser Webform comme première étape d'un "tunnel d'achat". Webform envoie à un module de paiement externe les nom, prénom, adresse e-mail et somme à payer de l'utilisateur. Nous hésitons sur la marche à suivre, on aimerait sauvegarder la soumission du form et être redirigé vers le module externe.
Les 2 solutions envisagées sont :
• remote_post et redirection à la main car il transmet bien les données à mon module externe mais ne redirige pas vers la suite du process d'achat.
• confirmation url et enregistrement de la soumission avant d'être redirigé sur la page du module externe (données envoyées en GET)
Selon toi quel serait le meilleur système ?
Merci 🙂
Bonjour,
Difficile de décider en réfléchissant deux minutes comme ça, mais instinctivement envoyer des données en GET me parait bizarre et le remote handler est là pour ça. Donc j'aurais tendance à proposer la solution 1: l'usage du remote_post handler de webform pour l'envoi de la donnée et une redirection ensuite.
hook_webform_handler_invoke_post_save_alter
$form_state->setRedirect(...)
Qu'en pensez-vous ?
Oui c'est bien ce que je comptais faire (solution numéro 1 avec le hook_webform_handler_invoke_post_save_alter)
Merci pour la réponse rapide et précise !
Ajouter un commentaire