WordPress hwk Blog ACF: Créer un Formulaire de Connexion, Inscription & Compte en AJAX grâce à ACF Form

ACF: Créer un Formulaire de Connexion, Inscription & Compte en AJAX grâce à ACF Form

18 March 2018
Ce tutoriel est déprécié et ne fonctionne qu’avec la version ACF Pro 5.7.10

ACF Form permet de créer des formulaires ACF à afficher en front-end. Avec le Contenu Flexible, c’est l’une des fonctionnalités les plus pratiques du plugin. Dans ce tutoriel nous allons utiliser toute la puissance de cette feature pour créer des fomulaires de connexion, d’inscription, de mot de passe perdu et de modification de compte. Cerise sur la gâteau, tous les formulaires sont optimisés pour Bootstrap 4.

Démarrage

Introduction à ACF Form

Pour comprendre les différentes étapes de ce tutoriel, il est nécessaire de saisir le fonctionnement natif de ACF Form. Comme la plupart des fonctions Advanced Custom Fields, acf_form() peut être appelé depuis n’importe quelle page de votre installation WordPress. Cette fonction va se charger d’afficher, valider et soumettre un formulaire de la même manière que dans le back-office, mais en front. Son champ d’action est vaste: Création/mise à jour de contenu, d’options, d’utilisateurs et bien plus!

Le premier paramètre de la fonction acf_form() est sans doute le plus important, il s’agit du post_id. Celui-ci va définir la “cible” de votre formulaire. ACF Form embarque déjà tout un tas de possibilité à ce niveau. Par exemple, post_id = 'new_post' va créer un formulaire de création de post. post_id = 'post_384' va automatiquement créer un formulaire de mise à jour de post, et il en va de même avec 'user_10' pour un utilisateur en particulier ou encore 'term_51' pour un terme de taxonomie.

Une fois le paramètre post_id défini, ACF Form va automatiquement afficher un formulaire avec tous les champs attribués à un post, un user ou un term. Les règles de validation entrées dans l’administration de ce groupe de champs seront aussi appliqués. Par exemple, un champ requis sera automatiquement requis via acf_form(), il en va de même pour les valeurs par défaut.

Tout comme dans le back-office, lorsque le formulaire est soumis par l’utilisateur, ACF lance une première requête AJAX afin de valider les champs. Si une erreur survient, alors la soumission est stoppé et un message s’affiche à côté du champ qui pose problème. Si la validation se termine correctement, alors le formulaire est définitivement soumis, la page se recharge et un message de succès apparait.

Créer un formulaire personnalisé

Il est aussi possible de choisir soit même les champs à afficher, afin de complètement contrôler les informations d’un formulaire. Pour cela nous avons à disposition deux arguments:

  • field_groups qui permet d’afficher tous les champs d’un groupe de champs en particulier.
  • fields qui permet d’afficher uniquement certains champs en particulier, peu importe son groupe de champs.

Ces deux paramètres priment sur les champs détectés automatiquement par post_id.

Créer sa propre logique de soumission

Il est tout à fait possible d’entrer un paramètre post_id arbitraire. Par exemple:

  • acf_form(array('post_id' => 'hwk', field_groups = array(5)))

Ce formulaire affichera tous les champs du groupe de champs 5. Une fois soumis, le formulaire sera validé et traité (champs requis, minimum/maximum etc..). Il n’y aura absolument aucune conséquence de mise à jour ou d’ajout en particulier. Et pour cause, ACF ne sait tout simplement pas à quoi correspond la cible 'hwk'.

Grâce aux différents hooks intégrés dans le plugin, il est possible de changer cela, et par conséquent, de créer notre propre logique de traitement et de soumission.

Traiter le formulaire en une seule requête Ajax

Afin d’optimiser la rapidité de traitement et d’améliorer l’expérience utilisateur, il est possible de ne passer que par une seule requête Ajax et de ne pas recharger la page. Nous allons pour cela créer notre propre action Ajax, vérifier la validation des champs avec acf_validate_save_post(), puis traiter nos données.

Compatibilité avec les champs d’upload

La fonction native Ajax de ACF Form n’utilise pas de formData, par conséquent les champs d’upload de fichiers ou d’images ne fonctionneront pas. C’est pourquoi nous allons ré-écrire la fonction Javascript acf.validation.fetch.

Création des groupes de champs

Pour bien commencer, nous allons créer plusieurs groupes de champs. Vous pouvez télécharger l’export des groupes de champs ACF en json ici.

Les groupes Connexion, Inscription, Mon Compte & Mot de passe oublié seront affichés à l’aide de acf_form(). Ces groupes de champs ne sont pas rattachés à un Post Type en particulier. Par conséquent, il est préférable de les désactiver pour les rendre invisible depuis le back-office.

Configuration générale

Nous allons créer trois fonctions d’aide et appliquer plusieurs hooks afin de créer une configuration générale pour nos formulaires ACF Form Ajax. Voici leur description:

  • Deregister ACF Style
    acf_form() importe plusieurs feuilles de styles dès qu’il est appelé sur une page. Nous allons les dé-enregistrer afin de laisser Bootstrap prendre la main sur le style.
  • Elements Class
    Nous ajoutons col-12 form-group sur chaque wrapper de champs et form-control sur les champs. Il est possible d’ajouter ses propres class dans le wrapper des champs depuis l’administration ACF, via le paramètre class du champ.
  • Default Form Args
    Nous définissons des paramètres par défaut pour chaque appel de acf_form(). Cela nous évitera, par exemple, d’ajouter à chaque fois <div class="row"> en début de formulaire.
  • Elements Required Class
    Nous ajoutons ici une class text-danger sur les astérisques de champs requis, pour coller avec la style de Bootstrap.
  • Get Form
    Fonction d’aide, qui nous permet de décrypter le $_POST du formulaire ACF. Nous aurons ainsi accès à tous les paramètres du acf_form() lors de notre traitement Ajax.
  • Get Data
    Par défaut ACF Form envoi les champs dans des variables $_POST['field_xyz'] (via leur key). Cette fonction nous permettra de les traiter à l’aide de leur name.
  • Get Return
    Une fonction simple mais efficace. Celle-ci va automatiser le traitement du retour json: redirection et/ou message de succès.
<?php
/**
* ACF Form: Deregister ACF style
*/
add_action('wp_print_styles', 'hwk_acf_form_deregister_styles', 99);
function hwk_acf_form_deregister_styles(){
wp_deregister_style('acf');
wp_deregister_style('acf-field-group');
wp_deregister_style('acf-global');
wp_deregister_style('acf-input');
wp_deregister_style('acf-datepicker');
}
/**
* ACF Form: Elements Class
*/
add_filter('acf/load_field', 'hwk_acf_form_field_class');
function hwk_acf_form_field_class($field){
if(is_admin() || wp_doing_ajax())
return $field;
$field['wrapper']['class'] .= ' col-12 form-group';
if($field['type'] != 'checkbox')
$field['class'] .= ' form-control';
return $field;
}
/**
* ACF Form: Default form args
*/
add_filter('acf/validate_form', 'hwk_acf_form_args');
function hwk_acf_form_args($args){
if(is_admin() || wp_doing_ajax())
return $args;
if(!$args['html_before_fields'])
$args['html_before_fields'] = '<div class="row">';
if(!$args['html_after_fields'])
$args['html_after_fields'] = '</div>';
if($args['html_updated_message'] == '<div id="message" class="updated"><p>%s</p></div>')
$args['html_updated_message'] = '<div class="alert alert-success">%s</div>';
if($args['html_submit_button'] == '<input type="submit" class="acf-button button button-primary button-large" value="%s" />')
$args['html_submit_button'] = '<input type="submit" class="acf-button button btn btn-primary btn-lg" value="%s" />';
// Set default Uploader to default
$args['uploader'] = 'basic';
return $args;
}
/**
* ACF Form: Elements Required Class
*/
add_filter('acf/get_field_label', 'hwk_acf_form_label', 10, 2);
function hwk_acf_form_label($label, $field){
if(is_admin() || wp_doing_ajax())
return $label;
$label = str_replace('class="acf-required"', 'class="acf-required text-danger"', $label);
return $label;
}
/**
* ACF Form: Helper – Get Form
*/
function hwk_acf_form_get_form($form = false){
if(!$form)
$form = $_POST['_acf_form'];
if(!$form)
return false;
$form = json_decode(acf_decrypt($form), true);
if(!$form || empty($form) || !is_array($form))
return false;
return $form;
}
/**
* ACF Form: Helper – Get Data
*/
function hwk_acf_form_get_data($acf = false){
if(!$acf)
$acf = $_POST['acf'];
if(!$acf)
return false;
$data = array();
foreach($acf as $key => $value){
$input = acf_get_field($key);
if(!isset($input['name']))
continue;
$data['input'][$input['name']] = $value;
$data['key'][$input['name']] = $key;
}
if(empty($data))
return false;
return $data;
}
/**
* ACF Form: Helper – Get Return
*/
function hwk_acf_form_get_return($form){
if(empty($form) || !is_array($form))
return false;
$return = array();
if(isset($form['return']) && !empty($form['return']))
$return['redirect'] = $form['return'];
if(isset($_POST['redirect']) && ($redirect = esc_url(urldecode($_POST['redirect']))))
$return['redirect'] = $redirect;
if(isset($form['updated_message']) && !empty($form['updated_message']))
$return['message'] = $form['updated_message'];
return $return;
}

Configuration des formulaires

Le fonctionnement des différents formulaires est assez similaire, néanmoins chacun d’entre eux aura sa propre configuration. Voici les principaux hooks utilisés:

  • ACF Register Form
    Chaque formulaire aura sa propre déclaration via la fonction acf_register_form(), afin de pré-enregistrer nos arguments acf_form(). Nous pourrons par la suite directement appeler acf_form() avec son id. Par exemple, dans notre Template de page Login, nous appellerons: acf_form('hwk_ajax_acf_login').
  • ACF Ajax Validation
    L’action acf/validate_save_post intervient juste avant le début du traitement des erreurs de ACF Form (/includes/validation.php:271). Nous allons utiliser ce hook afin de nous glisser dans la requête Ajax et ajouter notre propre logique. Les retours json seront assurés par la fonction wp_send_json_success() afin de garantir que le reste de la soumission native de ACF Form sera annulée.

    • Validation de la requête
      La variable $_POST['_acf_post_id'] nous servira à vérifier que le formulaire correspond bien à celui qui nous intéresse. Dans l’exemple du formulaire de Connexion, $_POST['_acf_post_id']devra être égal à 'hwk_ajax_acf_login'.
    • Récupération des données
      Nous récupérons le formulaire via hwk_acf_form_get_form() et les données via hwk_acf_form_get_data(). Ces deux fonctions doivent obligatoirement retourner un tableau, sinon quelques chose cloche.
    • Gestion des erreurs
      acf_form() utilise les fonctions inhérentes à ACF pour ajouter et récupérer les erreurs de traitement de formulaire. Respectivement acf_add_validation_error()acf_get_validation_errors(). Si la récupération des erreurs retourne des valeurs, alors ACF stop la soumission et retourne un json avec pour argument: 'valid' => false, 'errors' => array('champ' => 'Message d'erreur').
      Nous utiliserons aussi cette terminologie pour nos propres erreurs de champs. Néanmoins, dans le cas où la validation des champs est terminé mais qu’une erreur générale se produit (ex: un e-mail n’a pas pu être envoyé), nous utiliserons les arguments 'valid' => true, 'data' => array('error' => 'Message d'erreur générale'). Ce dernier sera traité de manière personnalisé par notre script javascript.
    • Validation des champs
      Ici, nous validons les champs. Comme nous avons accès à toutes les données du formulaire, nous pouvons valider les champs les uns après les autres, ou les uns dépendants des autres. Si un champ ne répond pas à notre logique, nous ajoutons une erreur ciblé à l’aide de la fonction acf_add_validation_error('acf[field_xyz]', 'Message d'erreur'). Notez que nos propres règles s’additionnent ici à celles définies dans le Back-office d’ACF: Champ requis, maximum/minimum etc… Pratique!
  • ACF Ajax Submit
    C’est ici le noyau du traitement de nos données. Cette fonction Ajax sera appelée lorsqu’un formulaire est soumis.

    • Afin de valider l’authenticité de la requête, nous allons utiliser la fonction acf_verify_nonce().
    • Nous validons les données à l’aide de acf_validate_save_post() qui appel l’action acf/validate_save_post.
    • Lorsque les champs sont validés et que nous avons terminé notre traitement (connexion, création de compte, mise à jour), alors nous envoyons une réponse json positive pour faire savoir que tout s’est bien passé.

Formulaire Connexion

Comme son nom l’indique, le formulaire de Connexion va identifier l’utilisateur en se baant sur son login et son password. Sa particularité est d’ajouter un champ caché redirect. Ce champ sera pré-rempli par la variable $_GET['redirect'], si celle-ci existe.

Ainsi nous pourrons rediriger l’utilisateur identifié vers une adresse dynamiquement définie. Typiquement l’adresse de la page que l’utilisateur visitait avant de s’identifier. Afin d’ajouter manuellement un champ <input type="hidden" /> nous allons passer par l’action acf/input/form_data.

Configuration de groupe de champs ACF Connexion:

  • Label: Login / Nom: login / Type: Texte
  • Label: Mot de passe / Nom: password / Type: Mot de passe
  • Label: <vide> / Nom: remember / Type: Select / Valeur: remember_me : Se souvenir de moi
<?php
/**
* ACF Form Login: Register Form
*/
acf_register_form(array(
'id' => 'hwk_ajax_acf_login',
'post_id' => 'hwk_ajax_acf_login',
'form_attributes' => array(
'class' => 'acf-form acf-form-ajax',
),
'field_groups' => array(14), // ACF Fields Group ID
'updated_message' => __('Logged in!'),
'return' => home_url('account'),
'submit_value' => __('Login'),
'html_submit_button' => '<input type="submit" class="acf-button button btn btn-primary btn-lg" value="%s" /> &nbsp; <a href="/connexion/mot-de-passe-oublie">' . __('Lost password') . '</a>',
));
/**
* ACF Form Login: Input Hidden
*/
add_action('acf/input/form_data', 'hwk_ajax_acf_login_input');
function hwk_ajax_acf_login_input($args){
if(is_admin() || (isset($args['post_id']) && $args['post_id'] != 'hwk_ajax_acf_login'))
return;
echo '<input type="hidden" name="action" value="hwk_ajax_acf_login_ajax_submit" />';
if(isset($_GET['redirect']) && ($redirect = esc_url(urldecode($_GET['redirect']))))
echo '<input type="hidden" name="redirect" value="' . $redirect . '" />';
}
/**
* ACF Form Login: Ajax Validation
*/
add_action('acf/validate_save_post', 'hwk_ajax_acf_login_ajax_validation');
function hwk_ajax_acf_login_ajax_validation(){
if(!isset($_POST['_acf_post_id']) || $_POST['_acf_post_id'] != 'hwk_ajax_acf_login')
return;
if((!$form = hwk_acf_form_get_form()) || (!$data = hwk_acf_form_get_data()))
wp_send_json_success(array(
'valid' => 1,
'data' => array(
'error' => __('An error occured. Please try again later.')
)
));
$input = $data['input'];
$key = $data['key'];
// Login
$input['login'] = sanitize_user($input['login']);
if(empty($input['login']) || !username_exists($input['login']))
acf_add_validation_error('acf['.$key['login'].']', __('Invalid username'));
// Password
$input['password'] = sanitize_text_field($input['password']);
$user = get_user_by('login', $input['login']);
if(empty($input['password']) || !$user || !wp_check_password($input['password'], $user->data->user_pass, $user->ID))
acf_add_validation_error('acf['.$key['password'].']', __('Invalid password'));
}
/**
* ACF Form Login: Ajax Submit
*/
add_action('wp_ajax_hwk_ajax_acf_login_ajax_submit', 'hwk_ajax_acf_login_ajax_submit');
add_action('wp_ajax_nopriv_hwk_ajax_acf_login_ajax_submit', 'hwk_ajax_acf_login_ajax_submit');
function hwk_ajax_acf_login_ajax_submit(){
if(!acf_verify_nonce('acf_form') || !isset($_POST['_acf_post_id']) || $_POST['_acf_post_id'] != 'hwk_ajax_acf_login')
return;
// Errors
if(!acf_validate_save_post())
wp_send_json_success(array(
'valid' => 0,
'errors' => acf_get_validation_errors(),
));
// Form data check
if((!$form = hwk_acf_form_get_form()) || (!$data = hwk_acf_form_get_data()))
wp_send_json_success(array(
'valid' => 1,
'data' => array(
'error' => __('An error occured. Please try again later.')
)
));
$input = $data['input'];
$key = $data['key'];
// Login user
$user = wp_signon(array(
'user_login' => $input['login'],
'user_password' => $input['password'],
'remember' => (($input['remember_me']) ? true : false),
), false);
if(is_wp_error($user))
wp_send_json_success(array(
'valid' => 1,
'data' => array(
'error' => $user
)
));
wp_send_json_success(array(
'valid' => 1,
'data' => hwk_acf_form_get_return($form),
));
}

Formulaire Mot de passe oublié

Ce formulaire est assez similaire au formulaire de Connexion. Au lieu d’identifier l’utilisateur, il lui envoie un e-mail de ré-initialisation de mot de passe. Malheureusement, il n’y a aucun moyen d’appeler la fonction native de WordPress, car celle-ci se situe dans le fichier wp-login.php.

Pour pallier à ce problème, nous allons copier/coller la partie qui nous intéresse et notamment les filtres retrieve_password_title & retrieve_password_message.  Ainsi nous sommes sûr que notre script reste compatible avec les autres plugins.

Configuration du groupe de champs ACF Mot de passe oublié:

  • Label: E-mail / Nom: email / Type: E-mail
<?php
/**
* ACF Form Lost Password: Register Form
*/
acf_register_form(array(
'id' => 'hwk_ajax_acf_lost_password',
'post_id' => 'hwk_ajax_acf_lost_password',
'form_attributes' => array(
'class' => 'acf-form acf-form-ajax',
),
'field_groups' => array(41), // ACF Fields Group ID
'updated_message' => __('Password reset was sent.'),
'return' => '',
'submit_value' => __('Submit'),
'html_submit_button' => '<input type="submit" class="acf-button button btn btn-secondary btn-lg" value="%s" /> &nbsp; <a href="/connexion">' . __('Login') . '</a>',
));
/**
* ACF Form Lost Password: Input Hidden
*/
add_action('acf/input/form_data', 'hwk_ajax_acf_lost_password_input');
function hwk_ajax_acf_lost_password_input($args){
if(is_admin() || !isset($args['post_id']) || $args['post_id'] != 'hwk_ajax_acf_lost_password')
return;
echo '<input type="hidden" name="action" value="hwk_ajax_acf_lost_password_ajax_submit" />';
}
/**
* ACF Form Lost Password: Ajax Validation
*/
add_action('acf/validate_save_post', 'hwk_ajax_acf_lost_password_ajax_validation');
function hwk_ajax_acf_lost_password_ajax_validation(){
if(!isset($_POST['_acf_post_id']) || $_POST['_acf_post_id'] != 'hwk_ajax_acf_lost_password')
return;
// Form data check
if((!$form = hwk_acf_form_get_form()) || (!$data = hwk_acf_form_get_data()))
wp_send_json_success(array(
'valid' => 1,
'data' => array(
'error' => __('An error occured. Please try again later.')
)
));
$input = $data['input'];
$key = $data['key'];
// Email
$input['email'] = sanitize_email($input['email']);
if(empty($input['email']))
acf_add_validation_error('acf['.$key['email'].']', __('Invalid e-mail'));
$user = get_user_by('email', $input['email']);
if(!$user)
acf_add_validation_error('acf['.$key['email'].']', __('Invalid e-mail'));
}
/**
* ACF Form Lost Password: Ajax Submit
*/
add_action('wp_ajax_hwk_ajax_acf_lost_password_ajax_submit', 'hwk_ajax_acf_lost_password_ajax_submit');
add_action('wp_ajax_nopriv_hwk_ajax_acf_lost_password_ajax_submit', 'hwk_ajax_acf_lost_password_ajax_submit');
function hwk_ajax_acf_lost_password_ajax_submit(){
if(!acf_verify_nonce('acf_form') || !isset($_POST['_acf_post_id']) || $_POST['_acf_post_id'] != 'hwk_ajax_acf_lost_password')
return;
// Errors
if(!acf_validate_save_post())
wp_send_json_success(array(
'valid' => 0,
'errors' => acf_get_validation_errors(),
));
// Form data check
if((!$form = hwk_acf_form_get_form()) || (!$data = hwk_acf_form_get_data()))
wp_send_json_success(array(
'valid' => 1,
'data' => array(
'error' => __('An error occured. Please try again later.')
)
));
$input = $data['input'];
$key = $data['key'];
// Setting user_data for wp-login.php script
$user_data = get_user_by('email', $input['email']);
// Start wp-login.php (line: 342)
$user_login = $user_data->user_login;
$user_email = $user_data->user_email;
$key = get_password_reset_key( $user_data );
if ( is_wp_error( $key ) ) {
wp_send_json(array('error' => $key));
}
if ( is_multisite() ) {
$site_name = get_network()->site_name;
} else {
/*
* The blogname option is escaped with esc_html on the way into the database
* in sanitize_option we want to reverse this for the plain text arena of emails.
*/
$site_name = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES );
}
$message = __( 'Someone has requested a password reset for the following account:' ) . "\r\r";
/* translators: %s: site name */
$message .= sprintf( __( 'Site Name: %s'), $site_name ) . "\r\r";
/* translators: %s: user login */
$message .= sprintf( __( 'Username: %s'), $user_login ) . "\r\r";
$message .= __( 'If this was a mistake, just ignore this email and nothing will happen.' ) . "\r\r";
$message .= __( 'To reset your password, visit the following address:' ) . "\r\r";
$message .= '<' . network_site_url( "wp-login.php?action=rp&key=$key&login=" . rawurlencode( $user_login ), 'login' ) . ">\r";
/* translators: Password reset email subject. %s: Site name */
$title = sprintf( __( '[%s] Password Reset' ), $site_name );
/**
* Filters the subject of the password reset email.
*
* @since 2.8.0
* @since 4.4.0 Added the `$user_login` and `$user_data` parameters.
*
* @param string $title Default email title.
* @param string $user_login The username for the user.
* @param WP_User $user_data WP_User object.
*/
$title = apply_filters( 'retrieve_password_title', $title, $user_login, $user_data );
/**
* Filters the message body of the password reset mail.
*
* If the filtered message is empty, the password reset email will not be sent.
*
* @since 2.8.0
* @since 4.1.0 Added `$user_login` and `$user_data` parameters.
*
* @param string $message Default mail message.
* @param string $key The activation key.
* @param string $user_login The username for the user.
* @param WP_User $user_data WP_User object.
*/
$message = apply_filters( 'retrieve_password_message', $message, $key, $user_login, $user_data );
if ( $message && !wp_mail( $user_email, wp_specialchars_decode( $title ), $message ) )
wp_send_json_success(array(
'valid' => 1,
'data' => array(
'error' => __('The email could not be sent.') . "<br />" . __('Possible reason: your host may have disabled the mail() function.')
),
));
// End wp-login.php
wp_send_json_success(array(
'valid' => 1,
'data' => hwk_acf_form_get_return($form),
));
}

Formulaire d’Inscription

Le formulaire d’inscription est probablement le formulaire le plus complexe. En plus d’enregistrer et d’identifier un utilisateur à la volée, celui-ci doit aussi exécuter le reste de la soumission native de acf_form() afin d’enregistrer les autres champs de type user_meta. Dans notre exemple, ce processus est symbolisé par le champ Commentaire, à la fois présent dans le groupe de champs Inscription et dans le groupe de champs Utilisateur.

Pour laisser ACF Form reprendre en main lors de la seconde partie du processus d’enregistrement, nous allons utiliser toutes les données du formulaire soumis, mais en modifiant le post_id et le return. Ainsi, le post_id deviendra 'user_5' (ID de l’utilisateur fraichement crée), et return passera à null pour éviter toute redirection dans notre requête Ajax.

Nous utiliserons une nouvelle instance de class new acf_form_front() et appellerons la méthode acf()->form_front->submit_form($form) afin que ACF mette à jour l’utilisateur, si besoin. Notez qu’il est important que le champ qui sert de symbolique à l’user_meta soit nommé de la même façon dans le groupe de champs Inscription & Utilisateur.

Configuration du groupe de champs ACF Inscription:

  • Label: Login / Nom: login / Type: Texte
  • Label: E-mail / Nom: email / Type: E-mail
  • Label: Mot de passe / Nom: password / Type: Mot de passe
  • Label: Mot de passe (vérification) / Nom: password_check / Type: Mot de passe
  • Label: Commentaire / Nom: comment / Type: Texte
<?php
/**
* ACF Form Register: Register Form
*/
acf_register_form(array(
'id' => 'hwk_ajax_acf_register',
'post_id' => 'hwk_ajax_acf_register',
'form_attributes' => array(
'class' => 'acf-form acf-form-ajax',
),
'field_groups' => array(18), // ACF Fields Group ID
'updated_message' => '',
'return' => home_url('account'),
'submit_value' => __('Register'),
'html_submit_button' => '<input type="submit" class="acf-button button btn btn-secondary btn-lg" value="%s" />',
));
/**
* ACF Form Register: Input Hidden
*/
add_action('acf/input/form_data', 'hwk_ajax_acf_register_input');
function hwk_ajax_acf_register_input($args){
if(is_admin() || !isset($args['post_id']) || $args['post_id'] != 'hwk_ajax_acf_register')
return;
echo '<input type="hidden" name="action" value="hwk_ajax_acf_register_ajax_submit" />';
}
/**
* ACF Form Register: Ajax Validation
*/
add_action('acf/validate_save_post', 'hwk_ajax_acf_register_ajax_validation');
function hwk_ajax_acf_register_ajax_validation(){
if(!isset($_POST['_acf_post_id']) || $_POST['_acf_post_id'] != 'hwk_ajax_acf_register')
return;
// Form data check
if((!$form = hwk_acf_form_get_form()) || (!$data = hwk_acf_form_get_data()))
wp_send_json_success(array(
'valid' => 1,
'data' => array(
'error' => __('An error occured. Please try again later.')
)
));
$input = $data['input'];
$key = $data['key'];
// Login
$input['login'] = sanitize_user($input['login']);
if(empty($input['login']) || username_exists($input['login']))
acf_add_validation_error('acf['.$key['login'].']', __('Invalid username'));
// Email
$input['email'] = sanitize_email($input['email']);
if(empty($input['email']) || email_exists($input['email']))
acf_add_validation_error('acf['.$key['email'].']', __('Invalid e-mail'));
// Password
if(empty($input['password']) || sanitize_text_field($input['password']) !== sanitize_text_field($input['password_check'])){
acf_add_validation_error('acf['.$key['password'].']', __('Passwords don\'t match'));
acf_add_validation_error('acf['.$key['password_check'].']', __('Passwords don\'t match'));
}
}
/**
* ACF Form Register: Ajax Submit
*/
add_action('wp_ajax_hwk_ajax_acf_register_ajax_submit', 'hwk_ajax_acf_register_ajax_submit');
add_action('wp_ajax_nopriv_hwk_ajax_acf_register_ajax_submit', 'hwk_ajax_acf_register_ajax_submit');
function hwk_ajax_acf_register_ajax_submit(){
if(!acf_verify_nonce('acf_form') || !isset($_POST['_acf_post_id']) || $_POST['_acf_post_id'] != 'hwk_ajax_acf_register')
return;
// Errors
if(!acf_validate_save_post())
wp_send_json_success(array(
'valid' => 0,
'errors' => acf_get_validation_errors(),
));
// Form data check
if((!$form = hwk_acf_form_get_form()) || (!$data = hwk_acf_form_get_data()))
wp_send_json_success(array(
'valid' => 1,
'data' => array(
'error' => __('An error occured. Please try again later.')
)
));
$input = $data['input'];
$key = $data['key'];
// Create user
$user = wp_insert_user(array(
'user_login' => $input['login'],
'user_email' => $input['email'],
'user_pass' => $input['password']
));
if(is_wp_error($user))
wp_send_json_success(array(
'valid' => 1,
'data' => array(
'error' => $user
)
));
// Legacy ACF Form fields saving
$proxy = $form;
$proxy['post_id'] = 'user_' . $user;
$proxy['return'] = '';
acf()->form_front = new acf_form_front();
acf()->form_front->submit_form($proxy);
// Login user
$user = wp_signon(array(
'user_login' => $input['login'],
'user_password' => $input['password'],
'remember' => true,
), false);
if(is_wp_error($user))
wp_send_json_success(array(
'valid' => 1,
'data' => array(
'error' => $user
)
));
wp_send_json_success(array(
'valid' => 1,
'data' => hwk_acf_form_get_return($form),
));
}

Formulaire Mon Compte

Le formulaire Mon Compte permet a un utilisateur identifié de modifier son profil. Il est assez proche du formulaire Inscription. Néanmoins il nécessite de pré-remplir les champs du formulaire avec les données de l’utilisateur. Pour y parvenir pour allons appliquer un filtre sur acf/load_value en utilisant les données renvoyées par wp_get_current_user().

Configuration du groupe de champs ACF Mon compte:

  • Label: E-mail / Nom: email / Type: E-mail
  • Label: Nouveau mot de passe / Nom: password / Type: Mot de passe
  • Label: Nouveau mot de passe (vérification) / Nom: password_check / Type: Mot de passe
  • Label: Commentaire / Nom: comment / Type: Texte
<?php
/**
* ACF Form Account: Register Form
*/
acf_register_form(array(
'id' => 'hwk_ajax_acf_account',
'post_id' => 'hwk_ajax_acf_account',
'form_attributes' => array(
'class' => 'acf-form acf-form-ajax',
),
'field_groups' => array(53), // ACF Fields Group ID
'updated_message' => __('Account updated.'),
'return' => '',
'submit_value' => __('Update'),
'html_submit_button' => '<input type="submit" class="acf-button button btn btn-primary btn-lg" value="%s" />',
));
/**
* ACF Form Register: Input Hidden
*/
add_action('acf/input/form_data', 'hwk_ajax_acf_account_input');
function hwk_ajax_acf_account_input($args){
if(is_admin() || !isset($args['post_id']) || $args['post_id'] != 'hwk_ajax_acf_account')
return;
echo '<input type="hidden" name="action" value="hwk_ajax_acf_account_ajax_submit" />';
}
/**
* ACF Form Account: Default Values
*/
add_filter('acf/load_value', 'hwk_ajax_acf_account_values', 10, 3);
function hwk_ajax_acf_account_values($value, $post_id, $field){
if(is_admin() || $post_id != 'hwk_ajax_acf_account')
return $value;
if(!$current_user = wp_get_current_user())
return $value;
if($field['name'] == 'email')
return $current_user->user_email;
if($field['name'] == 'password')
return '';
if($field['name'] == 'password_check')
return '';
return get_field($field['name'], 'user_' . $current_user->ID);
}
/**
* ACF Form Account: Ajax Validation
*/
add_action('acf/validate_save_post', 'hwk_ajax_acf_account_ajax_validation');
function hwk_ajax_acf_account_ajax_validation(){
if(!isset($_POST['_acf_post_id']) || $_POST['_acf_post_id'] != 'hwk_ajax_acf_account')
return;
if((!$form = hwk_acf_form_get_form()) || (!$data = hwk_acf_form_get_data()) || (!$current_user = wp_get_current_user()))
wp_send_json_success(array(
'valid' => 1,
'data' => array(
'error' => 'Une erreur est survenue. Veuillez réessayer ultérieurement.'
)
));
$input = $data['input'];
$key = $data['key'];
// Email
$input['email'] = sanitize_email($input['email']);
if(empty($input['email']))
acf_add_validation_error('acf['.$key['email'].']', 'Adresse e-mail invalide');
if($input['email'] != $current_user->user_email && email_exists($input['email']))
acf_add_validation_error('acf['.$key['email'].']', 'Cette adresse e-mail existe déjà');
// Password
if(!empty(sanitize_text_field($input['password'])) && !empty(sanitize_text_field($input['password_check'])) && sanitize_text_field($input['password']) !== sanitize_text_field($input['password_check'])){
acf_add_validation_error('acf['.$key['password'].']', 'Les mots de passe ne correspondent pas');
acf_add_validation_error('acf['.$key['password_check'].']', 'Les mots de passe ne correspondent pas');
}
}
/**
* ACF Form Account: Ajax Submit
*/
add_action('wp_ajax_hwk_ajax_acf_account_ajax_submit', 'hwk_ajax_acf_account_ajax_submit');
function hwk_ajax_acf_account_ajax_submit(){
if(!acf_verify_nonce('acf_form') || !isset($_POST['_acf_post_id']) || $_POST['_acf_post_id'] != 'hwk_ajax_acf_account')
return;
// Errors
if(!acf_validate_save_post())
wp_send_json_success(array(
'valid' => 0,
'errors' => acf_get_validation_errors(),
));
// Form data check
if((!$form = hwk_acf_form_get_form()) || (!$data = hwk_acf_form_get_data()) || (!$current_user = wp_get_current_user()))
wp_send_json_success(array(
'valid' => 1,
'data' => array(
'error' => 'Une erreur est survenue. Veuillez réessayer ultérieurement.'
)
));
$input = $data['input'];
$key = $data['key'];
$args = array(
'ID' => $current_user->ID,
'user_login' => $current_user->user_login,
'user_email' => $input['email']
);
$relog = false;
if(!empty($input['password'])){
$args['user_pass'] = wp_hash_password($input['password']);
$relog = true;
}
// Update user
$user = wp_insert_user($args);
if(is_wp_error($user))
wp_send_json_success(array(
'valid' => 1,
'data' => array(
'error' => $user->get_error_messages()
)
));
// Legacy ACF Form fields saving
$proxy = $form;
$proxy['post_id'] = 'user_' . $user;
$proxy['return'] = '';
acf()->form_front = new acf_form_front();
acf()->form_front->submit_form($proxy);
// No Relog: Early success
if(!$relog)
wp_send_json_success(array(
'valid' => 1,
'data' => hwk_acf_form_get_return($form),
));
// Relog: Log user again
$user = wp_signon(array(
'user_login' => $current_user->user_login,
'user_password' => $input['password'],
'remember' => true,
), false);
if(is_wp_error($user))
wp_send_json_success(array(
'valid' => 1,
'data' => array(
'error' => $user
)
));
wp_send_json_success(array(
'valid' => 1,
'data' => hwk_acf_form_get_return($form),
));
}

Les Templates de Pages

Maintenant que tous formulaires sont configurés, nous pouvons appeler les formulaires via acf_form('id') dans nos pages. Voici deux exemples, en respectant la syntaxe Bootstrap 4.

Page de connexion

<?php
acf_enqueue_scripts();
get_header();
?>
<?php if(have_posts()): ?>
<?php while(have_posts()): the_post(); ?>
<div class="col-md-4">
<div class="h-100 bg-light">
<div class="card-body">
<?php if(!get_query_var('hwk_page_lost_password')): ?>
<h2 class="mt-1">Connexion</h3>
<?php acf_form('hwk_ajax_acf_login'); ?>
<?php else: ?>
<h2 class="mt-1">Mot de passe perdu</h3>
<?php acf_form('hwk_ajax_acf_lost_password'); ?>
<?php endif; ?>
</div>
</div>
</div>
<div class="col-md-8">
<h2>Créer un compte</h3>
<?php acf_form('hwk_ajax_acf_register'); ?>
</div>
<?php endwhile; ?>
<?php endif; ?>
<?php get_footer(); ?>

Page Mon Compte

<?php
$current_user = wp_get_current_user();
acf_enqueue_scripts();
get_header();
?>
<?php if(have_posts()): ?>
<?php while(have_posts()): the_post(); ?>
<div class="col-md-12">
<h2>Bonjour, <?php echo $current_user->user_login; ?></h3>
<?php acf_form('hwk_ajax_acf_account'); ?>
</div>
<?php endwhile; ?>
<?php endif; ?>
<?php get_footer(); ?>

Les scripts Javascript

Nous commençons par ré-écrire la fonction acf.validation.fetch afin d’utiliser un formData pour être compatible avec les champs d’upload. Afin de gérer les retours json envoyés par nos requêtes Ajax, nous avons besoin de définir différents hooks Javascripts: validation_begin, add_field_error & validation_complete.

jQuery(document).ready(function($){
if(typeof acf === 'undefined')
return;
acf.do_action('ready', $('body'));
if($('.acf-form.acf-form-ajax').length){
try{
// ACF Form: Replacing Default Ajax Query Args
// Adds support of legacy browser uploader (via FormData)
// acf/ssets/js/input.js:13366
var acf_validation_fetch = acf.validation.fetch;
acf.validation.fetch = function($form){
if(this.busy)
return false;
var self = this;
acf.do_action('validation_begin');
this.busy = 1;
// Create formData
var formdata = new FormData($form[0]);
// New Ajax arguments
$.ajax({
url: acf.get('ajaxurl'),
data: formdata,
type: 'post',
cache: false,
processData: false,
contentType: false,
success: function(json){
if(!acf.is_ajax_success(json))
return;
self.fetch_success( $form, json.data );
},
complete: function(){
self.fetch_complete( $form );
}
});
return;
}
acf.validation.error_class = 'has-error';
acf.validation.message_class = 'error text-danger small';
acf.validation.disable_submit = function(){return};
acf.validation.show_spinner = function(){return};
}catch(e){}
// ACF Form: Setting $form
$('.acf-form .acf-button').click(function(e){
$form = $(this).closest('form');
});
// ACF Form: Default Submit
$('.acf-form.acf-form-ajax').on('submit', function(e){
e.preventDefault();
});
// ACF Form: Validation Begin
acf.add_action('validation_begin', function(){
$form.find('.alert').remove();
$form.find('.acf-error-message').remove();
$form.find('.has-error').removeClass('has-error');
$form.find('.form-control').removeClass('is-invalid');
$form.find('.error').remove();
});
// ACF Form: Field error
acf.add_action('add_field_error', function($field){
$field.find('.form-control').addClass('is-invalid');
$field.find('.error').insertAfter($field.find('.acf-input'));
});
// ACF Form: Validation Complete
acf.add_filter('validation_complete', function(json, $form){
if(!json.valid || json.errors)
return json;
// Global Error
if(json.data.error){
$form.prepend('<div class="alert alert-danger">' + json.data.error + '</div>');
$('html, body').animate({
scrollTop: $form.offset().top 150
}, 500);
return;
}
// Success
if(json.data.message){
// Redirect
if(json.data.redirect){
window.location = json.data.redirect;
return;
}
$form.prepend('<div class="alert alert-success">' + json.data.message + '</div>');
$('html, body').animate({
scrollTop: $form.offset().top 150
}, 500);
return;
}
return json;
});
}
});

La feuille de Style CSS

.acf-form.acf-form-ajax .acf-error-message{
display:none;
}
.acf-form.acf-form-ajax .error.text-danger.small p{
margin:0;
margin-top:5px;
}
.acf-form.acf-form-ajax [data-name="remember_me"] ul{
list-style:none;
padding:0;
}
.acf-form.acf-form-ajax [data-name="remember_me"] input{
margin-right:7px;
}
.acf-hidden-wp-editor{
display:none;
visibility:hidden;
}

Les hooks de Templating & Redirections

Pour parfaire l’expérience utilisateur de nos pages, nous allons définir des redirections. Nous utilisons pour cela l’action template_redirect. Afin d’éviter de créer une nouvelle page pour le formulaire Mot de passe oublié, nous réutiliserons la page de Connexion en attribuant une query_var. Enfin, nous ajouterons une logique sur les menu pour afficher des éléments logiques par rapport au statut du visiteur (identifié/non-identifié).

<?php
add_action('template_redirect', 'hwk_acf_form_redirect');
function hwk_acf_form_redirect(){
// Logged in
if(is_user_logged_in() && (is_page('connexion') || is_page('mot-de-passe-oublie') || is_page('inscription')))
wp_redirect(home_url('mon-compte'));
// Not logged in
if(!is_user_logged_in() && is_page('mon-compte'))
wp_redirect(home_url());
}
add_filter('template_include', 'hwk_acf_form_lost_password_page');
function hwk_acf_form_lost_password_page($template){
if(is_page('mot-de-passe-oublie') && $new_template = locate_template('page-connexion.php')){
set_query_var('hwk_page_lost_password', true);
return $new_template;
}
return $template;
}
add_filter('wp_nav_menu_objects', 'hwk_acf_form_menu_items', 10, 2);
function hwk_acf_form_menu_items($items, $args){
foreach($items as $key => $item){
if(get_field('logged_in', $item) && !is_user_logged_in())
unset($items[$key]);
if(get_field('logged_out', $item) && is_user_logged_in())
unset($items[$key]);
}
return $items;
}