Comment afficher le formulaire d’inscription de l’utilisateur sur le front-end du site web ?

Le processus implique 2 étapes :

  1. afficher le formulaire en front-end
  2. enregistrer les données lors de la soumission

Il y a 3 approches différentes qui me viennent à l’esprit pour afficher le front-end :

  • utiliser le formulaire d’inscription intégré, modifier les styles, etc pour le rendre plus « frontend like »
  • utiliser une page/poste WordPress, et afficher le formulaire à l’aide d’un shortcode
  • utiliser un template dédié qui n’est connecté à aucune page/poste, mais appelé par une url spécifique

Pour cette réponse, j’utiliserai la dernière. Les raisons sont:

  • utiliser le formulaire d’inscription intégré peut être une bonne idée, les personnalisations profondes peuvent être très difficiles en utilisant le formulaire intégré, et si on veut aussi personnaliser les champs du formulaire, la douleur augmente
  • utiliser une page WordPress en combinaison avec un shortcode, n’est pas si fiable, et aussi je pense que les shorcodes ne devraient pas être utilisés pour la fonctionnalité, juste pour le formatage et autres

1 : Construire l’url

Tout le monde sait que le formulaire d’inscription par défaut d’un site WordPress est souvent une cible pour les spammers. L’utilisation d’une url personnalisée est une aide pour résoudre ce problème. En outre, je veux aussi utiliser une url variable, c’est-à-dire que l’url du formulaire d’inscription ne doit pas être toujours la même, cela rend la vie des spammers plus difficile.L’astuce se fait en utilisant un nonce dans l’url:

/*** Generate dynamic registration url*/function custom_registration_url() { $nonce = urlencode( wp_create_nonce( 'registration_url' ) ); return home_url( $nonce );}/*** Generate dynamic registration link*/function custom_registration_link() { $format = '<a href="%s">%s</a>'; printf( $format, custom_registration_url(), __( 'Register', 'custom_reg_form' ) );}

Utiliser ces fonctions est facile pour afficher dans les templates un lien vers le formulaire d’inscription même s’il est dynamique.

2 : Reconnaître l’url, premier stub de la classe Custom_Reg\Custom_Reg

Maintenant nous devons reconnaître l’url. Pour la pourpose je vais commencer à écrire une classe, qui sera terminée plus tard dans la réponse:

<?php// don't save, just a stubnamespace Custom_Reg;class Custom_Reg { function checkUrl() { $url_part = $this->getUrl(); $nonce = urlencode( wp_create_nonce( 'registration_url' ) ); if ( ( $url_part === $nonce ) ) { // do nothing if registration is not allowed or user logged if ( is_user_logged_in() || ! get_option('users_can_register') ) { wp_safe_redirect( home_url() ); exit(); } return TRUE; } } protected function getUrl() { $home_path = trim( parse_url( home_url(), PHP_URL_PATH ), '/' ); $relative = trim(str_replace($home_path, '', esc_url(add_query_arg(array()))), '/'); $parts = explode( '/', $relative ); if ( ! empty( $parts ) && ! isset( $parts ) ) { return $parts; } }}

La fonction regarde la première partie de l’url après home_url(), et si elle correspond à notre nonce elle retourne TRUE. cette fonction sera utilisée pour vérifier notre requête et effectuer l’action nécessaire pour afficher notre formulaire.

3 : La classe Custom_Reg\Form

Je vais maintenant écrire une classe, qui sera responsable de générer le balisage du formulaire.Je l’utiliserai également pour stocker dans une propriété le chemin du fichier de template qui doit être utilisé pour afficher le formulaire.

<?php // file: Form.phpnamespace Custom_Reg;class Form { protected $fields; protected $verb = 'POST'; protected $template; protected $form; public function __construct() { $this->fields = new \ArrayIterator(); } public function create() { do_action( 'custom_reg_form_create', $this ); $form = $this->open(); $it = $this->getFields(); $it->rewind(); while( $it->valid() ) { $field = $it->current(); if ( ! $field instanceof FieldInterface ) { throw new \DomainException( "Invalid field" ); } $form .= $field->create() . PHP_EOL; $it->next(); } do_action( 'custom_reg_form_after_fields', $this ); $form .= $this->close(); $this->form = $form; add_action( 'custom_registration_form', array( $this, 'output' ), 0 ); } public function output() { unset( $GLOBALS ); if ( ! empty( $this->form ) ) { echo $this->form; } } public function getTemplate() { return $this->template; } public function setTemplate( $template ) { if ( ! is_string( $template ) ) { throw new \InvalidArgumentException( "Invalid template" ); } $this->template = $template; } public function addField( FieldInterface $field ) { $hook = 'custom_reg_form_create'; if ( did_action( $hook ) && current_filter() !== $hook ) { throw new \BadMethodCallException( "Add fields before {$hook} is fired" ); } $this->getFields()->append( $field ); } public function getFields() { return $this->fields; } public function getVerb() { return $this->verb; } public function setVerb( $verb ) { if ( ! is_string( $verb) ) { throw new \InvalidArgumentException( "Invalid verb" ); } $verb = strtoupper($verb); if ( in_array($verb, array( 'GET', 'POST' ) ) ) $this->verb = $verb; } protected function open() { $out = sprintf( '<form method="%s">', $this->verb ) . PHP_EOL; $nonce = '<input type="hidden" name="_n" value="%s" />'; $out .= sprintf( $nonce, wp_create_nonce( 'custom_reg_form_nonce' ) ) . PHP_EOL; $identity = '<input type="hidden" name="custom_reg_form" value="%s" />'; $out .= sprintf( $identity, __CLASS__ ) . PHP_EOL; return $out; } protected function close() { $submit = __('Register', 'custom_reg_form'); $out = sprintf( '<input type="submit" value="%s" />', $submit ); $out .= '</form>'; return $out; }}

La classe génère le balisage du formulaire en bouclant tous les champs ajoutés en appelant la méthode create sur chacun d’eux.Chaque champ doit être une instance de Custom_Reg\FieldInterface.Un champ caché supplémentaire est ajouté pour la vérification du nonce. La méthode du formulaire est ‘POST’ par défaut, mais elle peut être paramétrée à ‘GET’ en utilisant la méthode setVerb.Une fois créé, le balisage est enregistré à l’intérieur de la propriété de l’objet $form qui est répercuté par la méthode output(), accrochée à 'custom_registration_form' : dans le modèle de formulaire, il suffit d’appeler do_action( 'custom_registration_form' ) pour sortir le formulaire.

4 : Le template par défaut

Comme je l’ai dit, le template du formulaire peut être facilement surchargé, cependant nous avons besoin d’un template de base comme solution de repli.Je vais écrire ici un template très grossier, plus une preuve de concept qu’un vrai template.

<?php// file: default_form_template.phpget_header();global $custom_reg_form_done, $custom_reg_form_error;if ( isset( $custom_reg_form_done ) && $custom_reg_form_done ) { echo '<p class="success">'; _e( 'Thank you, your registration was submitted, check your email.', 'custom_reg_form' ); echo '</p>';} else { if ( $custom_reg_form_error ) { echo '<p class="error">' . $custom_reg_form_error . '</p>'; } do_action( 'custom_registration_form' );}get_footer();

5 : L’interface Custom_Reg\FieldInterface

Chaque champ devrait être un objet qui implémente l’interface suivante

<?php // file: FieldInterface.phpnamespace Custom_Reg;interface FieldInterface { /** * Return the field id, used to name the request value and for the 'name' param of * html input field */ public function getId(); /** * Return the filter constant that must be used with * filter_input so get the value from request */ public function getFilter(); /** * Return true if the used value passed as argument should be accepted, false if not */ public function isValid( $value = NULL ); /** * Return true if field is required, false if not */ public function isRequired(); /** * Return the field input markup. The 'name' param must be output * according to getId() */ public function create( $value = '');}

Je pense que les commentaires expliquent ce que les classes implémentant cette interface devraient faire.

6 : Ajout de quelques champs

Maintenant, nous avons besoin de quelques champs. Nous pouvons créer un fichier appelé ‘fields.php’ où nous définissons les classes de champs :

<?php// file: fields.phpnamespace Custom_Reg;abstract class BaseField implements FieldInterface { protected function getType() { return isset( $this->type ) ? $this->type : 'text'; } protected function getClass() { $type = $this->getType(); if ( ! empty($type) ) return "{$type}-field"; } public function getFilter() { return FILTER_SANITIZE_STRING; } public function isRequired() { return isset( $this->required ) ? $this->required : FALSE; } public function isValid( $value = NULL ) { if ( $this->isRequired() ) { return $value != ''; } return TRUE; } public function create( $value = '' ) { $label = '<p><label>' . $this->getLabel() . '</label>'; $format = '<input type="%s" name="%s" value="%s" class="%s"%s /></p>'; $required = $this->isRequired() ? ' required' : ''; return $label . sprintf( $format, $this->getType(), $this->getId(), $value, $this->getClass(), $required ); } abstract function getLabel();}class FullName extends BaseField { protected $required = TRUE; public function getID() { return 'fullname'; } public function getLabel() { return __( 'Full Name', 'custom_reg_form' ); }}class Login extends BaseField { protected $required = TRUE; public function getID() { return 'login'; } public function getLabel() { return __( 'Username', 'custom_reg_form' ); }}class Email extends BaseField { protected $type = 'email'; public function getID() { return 'email'; } public function getLabel() { return __( 'Email', 'custom_reg_form' ); } public function isValid( $value = NULL ) { return ! empty( $value ) && filter_var( $value, FILTER_VALIDATE_EMAIL ); }}class Country extends BaseField { protected $required = FALSE; public function getID() { return 'country'; } public function getLabel() { return __( 'Country', 'custom_reg_form' ); }}

J’ai utilisé une classe de base pour définir l’implémentation de l’interface par défaut, cependant, on peut ajouter des champs très personnalisés en implémentant directement l’interface ou en étendant la classe de base et en surchargeant certaines méthodes.

À ce stade, nous avons tout pour afficher le formulaire, maintenant nous avons besoin de quelque chose pour valider et enregistrer les champs.

7 : La classe Custom_Reg\Saver

<?php// file: Saver.phpnamespace Custom_Reg;class Saver { protected $fields; protected $user = array( 'user_login' => NULL, 'user_email' => NULL ); protected $meta = array(); protected $error; public function setFields( \ArrayIterator $fields ) { $this->fields = $fields; } /** * validate all the fields */ public function validate() { // if registration is not allowed return false if ( ! get_option('users_can_register') ) return FALSE; // if no fields are setted return FALSE if ( ! $this->getFields() instanceof \ArrayIterator ) return FALSE; // first check nonce $nonce = $this->getValue( '_n' ); if ( $nonce !== wp_create_nonce( 'custom_reg_form_nonce' ) ) return FALSE; // then check all fields $it = $this->getFields(); while( $it->valid() ) { $field = $it->current(); $key = $field->getID(); if ( ! $field instanceof FieldInterface ) { throw new \DomainException( "Invalid field" ); } $value = $this->getValue( $key, $field->getFilter() ); if ( $field->isRequired() && empty($value) ) { $this->error = sprintf( __('%s is required', 'custom_reg_form' ), $key ); return FALSE; } if ( ! $field->isValid( $value ) ) { $this->error = sprintf( __('%s is not valid', 'custom_reg_form' ), $key ); return FALSE; } if ( in_array( "user_{$key}", array_keys($this->user) ) ) { $this->user = $value; } else { $this->meta = $value; } $it->next(); } return TRUE; } /** * Save the user using core register_new_user that handle username and email check * and also sending email to new user * in addition save all other custom data in user meta * * @see register_new_user() */ public function save() { // if registration is not allowed return false if ( ! get_option('users_can_register') ) return FALSE; // check mandatory fields if ( ! isset($this->user) || ! isset($this->user) ) { return false; } $user = register_new_user( $this->user, $this->user ); if ( is_numeric($user) ) { if ( ! update_user_meta( $user, 'custom_data', $this->meta ) ) { wp_delete_user($user); return FALSE; } return TRUE; } elseif ( is_wp_error( $user ) ) { $this->error = $user->get_error_message(); } return FALSE; } public function getValue( $var, $filter = FILTER_SANITIZE_STRING ) { if ( ! is_string($var) ) { throw new \InvalidArgumentException( "Invalid value" ); } $method = strtoupper( filter_input( INPUT_SERVER, 'REQUEST_METHOD' ) ); $type = $method === 'GET' ? INPUT_GET : INPUT_POST; $val = filter_input( $type, $var, $filter ); return $val; } public function getFields() { return $this->fields; } public function getErrorMessage() { return $this->error; }}

Cette classe, a 2 méthodes principales, l’une (validate) qui boucle les champs, les valide et sauvegarde les bonnes données dans un tableau, la seconde (save) sauvegarde toutes les données dans la base de données et envoie le mot de passe par email au nouvel utilisateur.

8 : Utiliser des classes définies : finir la classe Custom_Reg

Maintenant nous pouvons travailler à nouveau sur la classe Custom_Reg, en ajoutant les méthodes qui « collent » l’objet défini et les font fonctionner

<?php // file Custom_Reg.phpnamespace Custom_Reg;class Custom_Reg { protected $form; protected $saver; function __construct( Form $form, Saver $saver ) { $this->form = $form; $this->saver = $saver; } /** * Check if the url to recognize is the one for the registration form page */ function checkUrl() { $url_part = $this->getUrl(); $nonce = urlencode( wp_create_nonce( 'registration_url' ) ); if ( ( $url_part === $nonce ) ) { // do nothing if registration is not allowed or user logged if ( is_user_logged_in() || ! get_option('users_can_register') ) { wp_safe_redirect( home_url() ); exit(); } return TRUE; } } /** * Init the form, if submitted validate and save, if not just display it */ function init() { if ( $this->checkUrl() !== TRUE ) return; do_action( 'custom_reg_form_init', $this->form ); if ( $this->isSubmitted() ) { $this->save(); } // don't need to create form if already saved if ( ! isset( $custom_reg_form_done ) || ! $custom_reg_form_done ) { $this->form->create(); } load_template( $this->getTemplate() ); exit(); } protected function save() { global $custom_reg_form_error; $this->saver->setFields( $this->form->getFields() ); if ( $this->saver->validate() === TRUE ) { // validate? if ( $this->saver->save() ) { // saved? global $custom_reg_form_done; $custom_reg_form_done = TRUE; } else { // saving error $err = $this->saver->getErrorMessage(); $custom_reg_form_error = $err ? : __( 'Error on save.', 'custom_reg_form' ); } } else { // validation error $custom_reg_form_error = $this->saver->getErrorMessage(); } } protected function isSubmitted() { $type = $this->form->getVerb() === 'GET' ? INPUT_GET : INPUT_POST; $sub = filter_input( $type, 'custom_reg_form', FILTER_SANITIZE_STRING ); return ( ! empty( $sub ) && $sub === get_class( $this->form ) ); } protected function getTemplate() { $base = $this->form->getTemplate() ? : FALSE; $template = FALSE; $default = dirname( __FILE__ ) . '/default_form_template.php'; if ( ! empty( $base ) ) { $template = locate_template( $base ); } return $template ? : $default; } protected function getUrl() { $home_path = trim( parse_url( home_url(), PHP_URL_PATH ), '/' ); $relative = trim( str_replace( $home_path, '', add_query_arg( array() ) ), '/' ); $parts = explode( '/', $relative ); if ( ! empty( $parts ) && ! isset( $parts ) ) { return $parts; } }}

Le constructeur de la classe accepte une instance de Form et une de Saver.

init() méthode (utilisant checkUrl()) regarde la première partie de l’url après home_url(), et si elle correspond avec le bon nonce, il vérifie si le formulaire a déjà été soumis, si c’est le cas en utilisant l’objet Saver, il valide et sauvegarde les données de l’utilisateur, sinon il imprime simplement le formulaire.

init() méthode déclenche également le hook d’action 'custom_reg_form_init' en passant l’instance du formulaire comme argument : ce hook doit être utilisé pour ajouter des champs, pour configurer le modèle personnalisé et aussi pour personnaliser la méthode du formulaire.

9 : Mettre les choses ensemble

Maintenant, nous devons écrire le fichier principal du plugin, où nous pouvons

  • requérir tous les fichiers
  • charger le domaine textuel
  • démarrer l’ensemble du processus en utilisant l’instanciation de la classe Custom_Reg et l’appel de la classe sur celle-ci en utilisant un hook raisonnablement précoce
  • utiliser le ‘custom_reg_form_init’ pour ajouter les champs à la classe de formulaire

Donc :

<?php /** * Plugin Name: Custom Registration Form * Description: Just a rough plugin example to answer a WPSE question * Plugin URI: https://wordpress.stackexchange.com/questions/10309/ * Author: G. M. * Author URI: https://wordpress.stackexchange.com/users/35541/g-m * */if ( is_admin() ) return; // this plugin is all about frontendload_plugin_textdomain( 'custom_reg_form', FALSE, plugin_dir_path( __FILE__ ) . 'langs'); require_once plugin_dir_path( __FILE__ ) . 'FieldInterface.php';require_once plugin_dir_path( __FILE__ ) . 'fields.php';require_once plugin_dir_path( __FILE__ ) . 'Form.php';require_once plugin_dir_path( __FILE__ ) . 'Saver.php';require_once plugin_dir_path( __FILE__ ) . 'CustomReg.php';/*** Generate dynamic registration url*/function custom_registration_url() { $nonce = urlencode( wp_create_nonce( 'registration_url' ) ); return home_url( $nonce );}/*** Generate dynamic registration link*/function custom_registration_link() { $format = '<a href="%s">%s</a>'; printf( $format, custom_registration_url(), __( 'Register', 'custom_reg_form' ) );}/*** Setup, show and save the form*/add_action( 'wp_loaded', function() { try { $form = new Custom_Reg\Form; $saver = new Custom_Reg\Saver; $custom_reg = new Custom_Reg\Custom_Reg( $form, $saver ); $custom_reg->init(); } catch ( Exception $e ) { if ( defined('WP_DEBUG') && WP_DEBUG ) { $msg = 'Exception on ' . __FUNCTION__; $msg .= ', Type: ' . get_class( $e ) . ', Message: '; $msg .= $e->getMessage() ? : 'Unknown error'; error_log( $msg ); } wp_safe_redirect( home_url() ); }}, 0 );/*** Add fields to form*/add_action( 'custom_reg_form_init', function( $form ) { $classes = array( 'Custom_Reg\FullName', 'Custom_Reg\Login', 'Custom_Reg\Email', 'Custom_Reg\Country' ); foreach ( $classes as $class ) { $form->addField( new $class ); }}, 1 );

10 : Tâches manquantes

Maintenant tout est à peu près fait. Il ne nous reste plus qu’à personnaliser le template, probablement en ajoutant un fichier de template personnalisé dans notre thème.

Nous pouvons ajouter des styles et des scripts spécifiques uniquement à la page d’inscription personnalisée de cette façon

add_action( 'wp_enqueue_scripts', function() { // if not on custom registration form do nothing if ( did_action('custom_reg_form_init') ) { wp_enqueue_style( ... ); wp_enqueue_script( ... ); }});

En utilisant cette méthode, nous pouvons mettre en file d’attente certains scripts js pour gérer la validation côté client, par exemple celui-ci. Le balisage nécessaire pour faire fonctionner ce script peut être facilement géré en éditant la classe Custom_Reg\BaseField.

Si nous voulons personnaliser l’email d’inscription, nous pouvons utiliser la méthode standard et ayant des données personnalisées enregistrées sur les méta, nous pouvons les utiliser dans l’email.

La dernière tâche que nous voulons probablement mettre en œuvre est d’empêcher la demande au formulaire d’inscription par défaut, aussi simple que :

add_action( 'login_form_register', function() { exit(); } );

Tous les fichiers peuvent être trouvés dans un Gist ici.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *